This post is to highlight some changes coming to the EE Datetime system that we want to highlight to developers who might be building things using EE. There is potential that interactions with the current EE Datetime system will break when these changes are rolled out.
[notification type=”alert-info” close=”false” ]The datetime changes mentioned in this post are currently available on this branch of the EE repository. Currently there is no set release date for these changes but they are coming soon.[/notification]
In most cases, modifying code to work with the old system will only need some minor updates to enable it to work with the new system so it shouldn’t take long to update any of your custom code and/or plugins implementing EE Datetime system methods and logic (we’ve already done that for any of our official add-ons).
Since Event Espresso is a product that is primarily concerned with events, it is assumed then that dates and times are a critical component of our product. Therefore, one of the important considerations when designing EE4 is having a robust date and time (or datetime) system. There are a few end goals we have with this system:
Dates and times become a complex subject when you throw timezones and all the different formats they can have into the mix.
- Developers need a simple way to interact with dates and times.
- End Users need a simple way to modify how dates and times are displayed on their site.
2. Clear and Consistent
It should be easy to point to a date and/or time and know what timezone it is in, and what format it is in.
- Developers need to have a clear understanding of what timezone a given date and time string is in, what format it is in, and have a clear way to interact with the system where things are not ambiguous.
- End users shouldn’t require the date and time interface be explained to them. They should also be able to have the dates and times in their language and display to their site visitors in their language.
Dates and Times can be displayed in many different formats, timezones and languages (both human and machine).
- Developers should be able to interact with our system using a variety of formats, and timezones and via different machine languages (php, js, mysql, json etc).
- Users should be able change the way dates and times are displayed on the front end of their site and in their administrative pages. End users should have flexibility in displaying dates and times in creative ways (i.e. calendar type boxes) The visitor to users sites, should (eventually, still on the roadmap) be able to control how dates and times are formatted and modify them so they are in their own timezone.
How Dates and Times Work in WordPress
Most WordPress developers will be familiar with how dates and times work in WordPress, however it is worthwhile summarizing this for the purpose of this document:
1. WordPress sets the offset for all php date related processing to UTC +0 (or GMT).
This means that when you use a php related date function like
date() it returns the result in UTC+0.
2. In the General Settings page for WordPress, users are able to set the format for how dates and times are displayed on their site.
This means whenever dates and times are displayed on their WordPress site, they will be in the given format and given timezone.
Very important: WordPress makes the assumption that once people set their timezone on this page, they will never be changing it (even though you CAN change it). How do we know this assumption is made? See the next point…
3. WordPress saves dates in the database for posts in four columns.
Two for the date in the current timezone (
post_modified_date) and two for the date in gmt (utc+0) (
In BOTH cases, the date is saved in the mysql timestamp format (
When post dates are retrieved by default from the WP database using their date functions. The default date retrieved on the frontend is
post_modified_date, unless wp is doing internal date and time calculations in which case it uses the gmt values (eg publishing a post that was set on a schedule for being published) or unless the others are explicitly requested.
The advantages to doing things this way, is because wp can then use php date functions to just transform and display the save “post_date” and it will always be in the correct set timezone for the end user. Then they still have GMT for falling back on when doing date and time calculations.
However remember WP is assuming people will never change their timezone (which in reality is actually a fairly safe assumption to make in most cases for simple blogs). Why is this a gotcha? Because IF someone changes their timezone then all of their existing posts will have the date displayed for those posts as originally saved (which is for the date in the old timezone). Which for all intents and purposes is accurate, but to the end user it may appear incorrect because its not displaying it in the date and time for the current timezone. If the end user wants to update things, then they need to go into each post and modify the publish date (or write a script to automatically convert existing
post_modified_date to the current timezone values using the saved gmt values.
My guess is that WP did this because it saves a having to convert things to and from GMT time and thus makes things efficient (while still being able to query against that gmt time). The huge downside to this approach in my opinion, is the assumption that people will rarely change the timezone for their website.
Frankly, I don’t think that’s an assumption we can make with Event Espresso, especially if we are ever going to offer the feature where people can list event dates and times for specific timezones (separate from the set site timezone).
current_time() is a function in WordPress that is intended to make it easier to quickly get a correct date and time string for NOW on this website, without having to worry about timezone stuff.
It returns two possible formats mysql and unixtimestamp. It has a flag that allows you to indicate you want the date returned in.
Here’s the incredible gotcha with WordPress.
If you do
current_time( 'timestamp' ), WordPress will return a unixtimestamp. But its actually NOT equivalent to what you’d get with
time(). In fact, what WordPress does, is they ADD the offset for the set timezone on the site TO that unixtimestamp. The reason they do this, is so if you do something like
date( 'Y-m-d H:i:s', current_time('timestamp' ) ), it will just output the date as is because remember internally, WP has set all php date functions to UTC. Since UTC+0 has no offset, it will take an incoming unixtimestamp and not apply any offset. So WordPress APPLIES the offset ahead of time. If one was to do
date( 'Y-m-d H:i:s', time() ) that returns the date for UTC+0. This is a HUGE gotcha, because developers not familiar with what WP is doing here will think it is returning an actual unixtimestamp, its not.
You CAN get the actual unixtimestamp by doing
current_time( 'timestamp', true ).
WordPress has a neat system in place for localizing Date and time display via the function
date_i18n(). The way it works is you send it a format you want the result in a “unixtimestamp”, and whether it should be in GMT (UTC+0) or not, then it spits out that format localized in the set language for the site.
Here’s the gotcha, when you send it a “unixtimestamp” (regardless of what you set the gmt flag to), WordPress considers that unixtimestamp to be their specially computed timestamp with the offset applied NOT an actual unixtimestamp. The GMT flag just indicates whether you want the returned string to be converted to GMT before returning. However, if you send in an actual unixtimestamp, then that will get “converted” to GMT and thus will be returned with what appears to be a double offset from the actual time in the set timezone for the current site.
There’s more, but the reason I wanted to very briefly go over how Dates and Times work in WordPress is to illustrate that as long as you operate on the assumptions WordPress makes, and are aware of how things work internally, everything works out great.
However, the way WordPress does things, conflicts with the needs of EE.
The Initial Implementation of the Datetime System
Keep in mind, in the initial implementation of the datetime system, we still supported PHP5.2 . This meant a lot of useful php date functions that would have made our lives easier at that time were not available to us because they required PHP5.3. In the early development of EE4, we did not think that was a big deal and we thought we’d be able to work around most deficiencies with PHP5.2.
1. In effort to keep it simple.
- Allow them to use a familiar interface for setting how their dates and times display (the WordPress general settings) and the timezone for how everything displays. So really for users with existing sites this would “just work”.
EE_Datetime_Field handles the complex conversions necessary to make sure data gets to and from the database as expected. Throw any format at it for incoming dates and it should “just work”.
- we discovered that we couldn’t just let people set the format to whatever they wanted (even one of the suggested default formats
d/m/Y provided by WordPress), because for our internal conversions, we had to use
strtotime() has a limited number of formats it can convert correctly. So as a “fix” we didn’t allow people to set their date/time formats to something that EE could not convert.
- the ticking BOMB with this, is that our “preventative” measures does not catch users who install EE on a pre-existing WordPress site that is using one of those formats. They may have had their site up for a while using one of those formats with no problems and all of a sudden they install EE and it doesn’t work correctly. The user could file an issue about this our response to them would be, “Sorry, you can’t use that format”.
- we also discovered that WordPress allows users to set their sites to a UTC offset that PHP doesn’t recognize as valid. Our workaround was to behind the scenes set that offset to the closest valid one and use it instead (this doesn’t change even with the refactor).
2. Keep things Clear and Consistent
- for the most part, the format for all our date-pickers in the backend was fairly consistent (although users are not able to change that format).
- For the most part anywhere dates and times were just displayed (and not interacted with), they followed the format that was set by them in their WordPress settings, and in the timezone they expected.
- For the most part, what was made accessible to developers was useful. They had a clear set of methods to get dates and times from the database, and a clear set of methods to send dates and time into the database.
- Clear zones describing how dates and times “live” in those zones.
- DB – mysql timestamp UTC+0
- Models – unixtimestamp (no offset applied).
- Client Code – whatever WordPress was set at.
- localization? What localization? In the initial iteration of the datetime system I completely forgot about setting things up so dates and times would be localized when displayed.
- As fixes were implemented to make localization possible, all the gotchas I mentioned above regarding how WordPress does “unixtimestamp” came into play.
- Date calculations by client code were not easy to do and a lot of conversions became necessary to accommodate the flexibility we wanted to retain.
EE_Datetime_Field had a TON of code handling conversions to cover different scenarios that would get thrown at it and to account for the deficiencies with PHP5.2. Flaws in these conversions were found and bandaid fixes applied. There started to be a multitude of different functions and methods for developers to use as a result of fixing some of these deficiencies. It became ambigous to the user not familiar with the system how queries should be setup in the different zones for datetime related items… should I use
current_time('timestamp', true)? Should I use
current_time('timestamp')? Should I use
date('Y-m-d H:i:s', strtotime( $date_string ) )? Will this query still work when we build a feature for people to change the timezone for how things are displayed? Or when they can have events saved in multiple timezones?
- things have become increasingly unclear and inconsistent.
- Set a format, and it works. (well actually, only certain formats work…)
- Can I change how the datepicker’s display time and dates? No ( see this feature request in our support forums )
- Still some flexibility if you are willing to ignore using our system and just build your own tools for interacting with dates and times (which also applies to any add-ons we build that use dates and times)
- Be very careful what you send via queries otherwise you will get unintended results.
Enter the Refactor
Over time we realized, that there were a number of problems with the current date time system that were increasingly creating headaches in terms of unintended bugs and in the long run not supporting our goals for this critical system.
Keep in mind that while we wanted to try to preserve backward compatibility as much as possible with existing code that might be in the wild, we realized that to ‘do things properly’ would require some breakage. However, we were able to keep the breakage limited to the querying and display of date and time information in cases where old code is not updated to use the new paradigms of the refactored date time system.
High Level overview of changes
1. Dates continue to be saved as mysql timestamps (
Y-m-d H:i:s ) in the database in UTC +0.
This is something that has not changed from the original system. This is why there is no data destruction with this update.
2. Dates live within the models and model objects as PHP DateTime objects.
When dates are retrieved from the database they are stored as PHP
DateTime objects in the timezone set internally on the
EE_Base_Class object for the values of
EE_Datetime_Fields. This allows for less conversion related code.
3. Any unix timestamps coming into
EE_Datetime_Field::prepare_for_set() methods are considered to have no offset on them.
This is a big change from the previous system. In the previous system incoming unixtimestamps were considered to have an offset already applied to them via
current_time(). We felt that it was much clearer to treat unixtimestamps as actual timestamps.
4. Formats are now required for when instantiating
EE_Base_Class objects with date time strings that are not a unixtimestamp.
The method signature for
EE_Base_Class::new_instance() has changed so it now not only allows for passing the properties and their values as an array, the timezone string for any dates and times included in the first argument, but also now a third argument for date formats.
The date formats argument is expected to be an array where the first value is the date format and the second value is the time format.
The reason for this change is so that developers now can use any date and time strings they want in instantiating a
EE_Base_Class that has date string values. However, to make sure there is no ambiguity regarding the format sent in ( d/m/Y vs m/d/Y for example ), the format is sent in.
If developers don’t want to be bothered with a format, you can instantiate just using a unixtimestamp (with no offset applied) and EE will convert it to the appropriate DateTime object internally. However if you do not send in a unixtimestamp you must send in the formats to ensure the date string is converted properly internally.
5. New helpers are available for setting up strings for date related queries.
We’ve added some helpers accessible to
EEM_Base child classes that make it easier to setup queries using the EE Models system involving date and time strings.
EE_Base_Class helper for displaying localized date.
EE_Base_Class::get_i18n_datetime( $field, $format ) is available for easily retrieving the localized date.
7. Extensive unit test coverage
We have covered the new system fairly extensively with unit tests to ensure that any future modifications do not break functionality.
More extensive examples/documentation of usage will be provided in the code documentation on this site (and will be linked to from this post as it becomes available).
//assuming we're sending in strings that are in the set timezone for the site (which does not require sending in a timezone string.
$datetime = new EE_Datetime(
'EVT_ID' => 2,
'DTT_name' => 'Test Datetime',
'DTT_EVT_start' => 'March 5th, 2015 12:00',
'DTT_EVT_end' => 'March 5th, 2015 23:00'
array( 'F jS, Y', 'H:i' )
Example model query helper method usages:
//get all EE_Datetime objects with an EVT_start_date greater than now
$where_args = array(
'DTT_EVT_start' => EEM_Datetime::instance()->current_time_for_query( 'DTT_EVT_start' )
$datetimes = EEM_Datetime::instance()->get_all( array( $where_args ) );
//get all EE_Datetime objects where the end date is before a specific date in the future.
$where_args = array(
'DTT_EVT_end' => EEM_Datetime::instance()->convert_datetime_for_query( 'DTT_EVT_end', 'March 5th, 2015, 23:00', 'F jS, Y H:i' )
$datetimes = EEM_Datetime::instance()->get_all( array( $where_args ) );