In version 4.9.59p of Event Espresso Core, there will be a workaround fix for a PHP DateTime object bug we discovered while troubleshooting an issue on our EventSmart platform. I just thought I’d write a few words about this bug because it’s entirely possible other developers will encounter it in the wild as well.
On Event Smart we had a customer start reporting that outgoing message notifications from our messages system were displaying times for the related events that did not match the dates setup for the event in the event editor. For instance, the time configured for the event was 4am-1pm but the time in the message was 12:00am-9am. The site was set with a timezone of UTC-4, so on first glance this appeared to be something wrong with our timezone conversion logic because the difference between those times was 4! However, with dates and times being such a critical part of our application, we have pretty significant automated test coverage of all our logic related to that including coverage of the code used in our messages system. The other puzzling thing was that the messages system was using the same internal methods as elsewhere in the app for displaying those dates and times and those places were displaying things fine. Hmmm… puzzling. Further, we noticed that we were only able to reproduce this on sites that were using a UTC offset instead of a timezone string for their timezone.
Then I remembered that all outgoing messages and notifications are being processed by separate workers via a gearman job system so it’s possible there’s something in that environment that is subtly different than what is happening in a normal web request. So I began adding a bunch of logging to capture time output within the environment of the gearman workers themselves when messages were being generated. At first I thought, maybe there’s some caching that is not getting cleared correctly but that was ruled out. And then I started seeing some weird behaviour where right after changing a timezone on a DateTime the internal unix timestamp in the DateTime object was affected ! That’s significant because the unix timestamp should not change. It’s the primary reference for that DateTime object! Then I thought I’d see if I could reproduce this with just some standalone code, something like this:
//beginning timestamp for the provided time is 1457694000
$d1 = new DateTime('2016-03-11 11:00:00', new DateTimeZone('UTC'));
$t1 = $d1->getTimestamp();
I ran this code on 3v4l.org (super nifty tool for running php code across multiple php versions) and lo and behold look at the results!
This means that there’s version specific behaviour for PHP’s DateTime object! Once I realized this was going on it immediately clicked that on EventSmart it’s possible we might have different versions of php being used in different contexts and sure enough we discovered that our host had forgot to have our gearman workers use the latest version of PHP7 and they were still using PHP5.6 where web requests were using the latest version of Php 7. Once we changed the PHP version for our workers boom, things started working again.
So I realized, this would affect all our Event Espresso customers as well (since Event Smart is built using Event Espresso), and I began to dig more. In the process I discovered that this actually was a bug reported and fixed by PHP . I also discovered, that there is a workaround. If you use the
DateTime::getTimestamp() call immediately after invoking
DateTime::setTimezone(), the internal unixtimestamp will be reset to it’s correct value. So the workaround I’ve added for the next version of EE core to be released is to simply implement that wherever timezone is changed.
A Summary of the Bug
- Affects PHP versions 5.5.10-5.6.x, 7.0.0-7.0.16, 7.1.0-7.1.2 – PHP 7.2 branch is not affected and earlier versions are not affected because earlier versions of PHP don’t have the ability to setTimeZone with a UTC offset.
- Only happens when calling
setTimezone()with a UTC offset.
- Only demonstrates itself when immediately after changing the timezone using
format('U')to get the unixtimestamp. In my testing if you used
getTimestamp()to get the unixtimestamp you’d get the expected value.