Components, DateTime and AR

I’m doing some work with date/time and getting frustrated (as usual) of how fractured and non-standard this stuff is - and also trailing off into an AR issue.

First off, we have a bunch of types that deal with date/time in Yii: CTimestamp, CDateTimeParser and CDateFormatter - I realize this is an attempt to break down areas of responsibility, but it seems pretty scattered. And it seems there’s quite a bit of duplication in there too - custom implementations of things that PHP (5.3) already does out of the box.

In Yii 2.0, how about extending the built-in DateTime class into CDateTime, and getting rid of all these other helper-classes?

I also would suggest setting the PHP default timezone to UTC - with no option to set it to anything else. This may seem radical, but ideally, nobody should store date/time in databases (at least not MySQL) as anything other than UTC, unless they’re actually using a column-type that stores the timezone, or unless the application serves only a single specific timezone. Any ordinary application that serves North America or Canada needs to support at least two timezones, and if you’re using both date and datetime column-types, you can get yourself into serious trouble.

Which brings me to my next issue: no real support for date/time in AR… Ideally, you should be able to just use DateTime (or CDateTime, if it existed) and just deal with date/time as objects rather than strings - it’s a cruel and unusual stumbling block, and most people resort to using calls like date(‘Y-m-d H:i:s’, $mytime) which unfortunately not only formats things in an SQL-compatible format, but also converts the time to the PHP default timezone; many users do not understand this.

Regardless of whether you agree with that particular point of view or not, it’s clear there’s something missing as far as handling datatypes in general in AR - not just datetime, but certain other types could be handled better as well: booleans, enums, flags, etc. have no proper support in terms of O/R mapping.

Poking through the AR codebase, looking for a way to add support for the DateTime type, I’m thinking there’s a bigger conceptual issue with AR in general: the one-to-one mapping and coupling between attributes and columns. This precludes mapping anything other than simple value-types directly to database, e.g. user-defined types - for example, in a customer database, a common requirement is to have an Address type, and mapping two instances (e.g. mailing and billing addresses) to a customer-record. Another example would be an Audit type (e.g. created, created_by, updated, updated_by) which could be added as a single attribute to any model-type, rather than having to define the 4 attributes individually on each type and applying a behavior.

My immediate train of thought is along the lines of an O/R service-component (attached to Yii::app) which would define default mapper-types for attribute-types… of course, to implement something like that, you would need to define attribute-types which PHP doesn’t currently support, so this might involve yet another callback on CActiveRecord that defines the attribute-types - unless you’re willing to consider adding support for metadata in general, e.g. via doc-blocks, which just brings my trail even further from where I started:

If you had attribute-types defined as metadata (regardless of whether it’s provided by a callback or doc-blocks) it would seem natural to use the same information for other things, e.g. making it a general feature of CModel, so that CFormModel and eventually HTML-helpers could use the type-information to establish various defaults…

Which would then lead me think about having a general metadata-facility, so we could define even more metadata that would be useful to HTML-helpers for establishing even more defaults, and so we didn’t need a growing number of callbacks for various types of metadata…

And so on along those lines…

I guess in my mind perhaps I’m turning Yii into something entirely different than what it is :wink:

Anyway, these are just some thoughts that bumped around in my head this afternoon and I thought I’d share…

I agree that date/time handling in Yii2 can use 5.3 features.

Also I somewhat agree about UTC. Not sure about now allowing to change it but it’s a good thing to have it as default along with explaination about why it is set to UTC.

Your idea about metadata is definitely interesting but goes away from AR as we see it in Yii1 to something else.

Agree. Especially about UTC - I do this exclusively. It’s especially important when working against servers in different timezones, dealing with localization, etc.

Or does it? Yes, I suppose it does :slight_smile:

But I’ve really never thought the AR implementation was one of Yii’s strongest suits. It’s definitely easy to learn, but it leaves you in the dark on a number of issues, even basic things like date/time, as discussed.

I think at the very least, Yii 2.0 needs proper type-mapping - simply mapping everything as strings seems naive.

With regards to metadata - I’m not sure this really diverts from the course already set forth by Yii 1? You are defining metadata, and the framework (AR in particular, but also the HTML helper) is consuming that metadata and using it to drive various decisions and establish sensible defaults.

The trouble is the metadata "format", or the means by which metadata is defined:

  • Using callback methods is not a very scalable (in terms of complexity) or organized approach, as you’ll be adding another callback for every new kind of information, and there are already quite a few kinds of information “out of the box”.

  • All this information is scattered across multiple callbacks, so you can’t get a clear overview of precisely what types of metadata are associated with one specific property.

  • Inheritance in the model is cumbersome, since you have to remember to call the parent metadata-method, and manually merge the parent metadata in subclasses - in every callback.

  • It doesn’t refactor well, since every callback needs to repeat all the property-names - for example, it gets increasingly cumbersome to rename a property. Even more so in cases with inheritance.

  • You’re polluting your model-API with an increasing number of seemingly instance-methods, but they are actually being treated as static, e.g. the returned metadata is stored statically and shared between many instances of a model-type.

Maybe I should go ahead and integrate my annotation-library with AR, so you can see what I’m talking about - I haven’t been doing Yii for almost 2 years and just recently returned, so maybe now is the time…

I’m all in favour of using the DateTime classes in Yii 2. That’s including DateInterval and DatePeriod. I’m also content with UTC as default timezone. However, I’d want to oppose the idea of enforcing it. I’ve always experienced Yii as a liberal framework: If you don’t like something, re-configure the offending component or replace it by your own implementation. Locking users to a timezone doesn’t really blend in.

The metadata aspect sounds promissing. This could potentially give way for more db-agnostic code. Turning DATETIME and TIMESTAMP fields into DateTime objects and vice-versa were already a great leap forward. I find your ideas on the apping of complex types a bit scary, though. Is there any ORM implementation that already does this?

As mentioned before: the idea of using PHP’s DateTime class in Yii2 and using UTC as the default (but not mandatory) timezone is good.

I disagree on the metadata stuff as I want to maintain the flexibility to interpret data myself. E.g. (to stay with the datetime subject) the datetime can be stored as a UTC or as a text. Many, if not all, functions to manipulate date and time store this datetime as the number of milliseconds since 1/1/1970. This has not always fit my needs as I stored dates which lay before the beginning of the calendar, i.e. the date 1/1/-200 (= the year 200 BC). This does not work correctly with the datetime used by PHP.

Also numbers can be stored as strings and depending on the platform/programming language used they are either converted to a number or handled as a string.

Therefore I’d like to handle the conversion myself and not leave it to the metadata stored in the database.

Many major ORMs allow component-mappings - e.g. mapping an Address-class to a number of properties.

Most major ORMs have defaults for mapping things like DateTime, e.g. for most common uses it’s not a problem that you can’t map dates before Unix Epoch, so that would make a sensible default; by adding metadata specifically to a property that needs a special mapping, you’d be able to deviate from the default.

On my current project, I’ve added this to my main config file:


'timezone' => 'UTC',

As expected, date() now works like time(), in the sense that they work with UTC time, without converting anything - so all date/time values now travel to/from the database unmodified.

To display and parse date/time in the user’s local timezone, I added these methods to my WebUser component:


  /**

   * @return string the current User's timezone-name

   * @todo this is currently a hard-coded value

   */

  public function getUserTimezone()

  {

    return 'America/New_York';

  }


  /**

   * Format a date/time from a timestamp in the User's local timezone

   *

   * @param string $format date() compatible string-format

   * @param int $timestamp Unix timestamp

   * @return string

   * @see date()

   */

  public function formatTime($format=null, $timestamp=null)

  {

    $tz = date_default_timezone_get();

    date_default_timezone_set($this->getUserTimezone());


    $str = date($format, $timestamp===null ? time() : $timestamp);


    date_default_timezone_set($tz);


    return $str;

  }


  /**

   * Parse a date/time from a string in the User's local timezone

   *

   * @param string $str date/time in strtotime() compatible format

   * @return int

   */

  public function parseTime($str)

  {

    $tz = date_default_timezone_get();

    date_default_timezone_set($this->getUserTimezone());


    $time = strtotime($str);


    date_default_timezone_set($tz);


    return $time;

  }



These are ugly, but it appears there is no date-formatting function in PHP that lets you specify the timezone at call-time. I guess it’s good PHP isn’t multi-threaded, or this would cause some serious havoc :wink:

After thinking about this, I would suggest you let CApplication manage the PHP default timezone, same as it does now - but make "UTC" the default timezone. As long as people understand the consequence of changing it, there is no reason to make this read-only.

Now here’s what occurred to me: we should distinguish between application timezone and user timezone.

In other words, the PHP default timezone would be considered the application timezone, and this would be the timezone that PHP, Yii (and your application) uses to format and parse date/time, internally. (such as when talking to databases, web-services and other APIs)

Formatting and parsing date/time in the user’s timezone, on the other hand, is something that is specific to the current user - in my WebUser-extension above, this is currently hard-coded, but it will eventually rely on CUserIdentity to provide the active user’s timezone choice. (establishing a default based on browser-settings would be a plus)

Another option would be an user-time service application-component like Yii::app()->time, which would work together with the i18n-framework, e.g. to integrate support for user-locales with support for user-time…

+1. But I think it would be better to leave the actual implementation of user timezones to the application author. It might be difficult to provide a "one size fits all"-solution for that through a framework.