Date and Time serialization between PHP and Javascript
TLDR: When trying to create dates acceptable by Javascript in PHP, use the intl library and format dates with the following pattern: "y-MM-dd’T’HH:mm:ssZ"this will grant your application compatibility with older versions of ICU library while still being able to understand dates with timezone on JavaScript. For a long version and description of great learning experience this was for me, read the full article ??
This week while building an ReactJS application I found an interesting behavior of Date and Time serialization in CakePHP. CakePHP in its latest version is using Intl extension to handle Date and Time parsing and serialization among other things.
By using Intl they take away the need to have libraries built-in in the framework to do that kind of work. There are some nice articles that stress the complexity of handling Date serialization that is locale aware, here is one that was particularly helpful to me on this journey I am about to describe: A long journey to formatting a date internationally in PHP.
So there I was taking care of the API for an React app when my colleague called me so we could define an standard date format between ReactJS and CakePHP, then I thought, lucky me CakePHP offers me a great API of Time and Date so I can only call Time::setJsonEncodeFormat and I will be done.
The problem is that I chose to deploy our API using ElasticBeans Talk and the Linux running on the PHP platform is an Amazon Linux based of a RHEL distro. This being said, the packages on the machine were crazy behind in terms of version. For example, the ICU Data library, which is the backend used by Intl PHP extension, was in version 51 when the newest version is 57 (that is 6 major versions behind ??).
The final result of it was that my first pattern chosen for the dates were resulting in wrong formats, even though it was working on my local machine??. The pattern was: "y-MM-dd’T’H:mm:ssX", I made this pattern by looking on ICU documentation which is linked on the setJsonEncodeFormat() docs, but it was not giving the same results as my local. What was the problem? Had to investigate.
So I started by investigating the code where the serialization was happening and found this on DateFormatTrait::_formatObject():
<?php
if (!isset(static::$_formatters[$key])) {
if ($timezone === '+00:00' || $timezone === 'Z') {
$timezone = 'UTC';
} elseif ($timezone[0] === '+' || $timezone[0] === '-') {
$timezone = 'GMT' . $timezone;
}
static::$_formatters[$key] = datefmt_create(
$locale,
$dateFormat,
$timeFormat,
$timezone,
$calendar,
$pattern
);
}
return static::$_formatters[$key]->format($date->format('U'));
Here CakePHP is instantiating a new IntlDateFormatter, putting this instance in a in-memory cache and finally formatting the date object according with the configuration passed on the Time::setJsonEncodingFormat() method.
I resolved to isolate that behavior and tested a code that did not depend in CakePHP API whatsoever, I ended up with the following script:
<?php
$fmt = datefmt_create(
'en_US',
IntlDateFormatter::FULL,
IntlDateFormatter::FULL,
'UTC',
IntlDateFormatter::GREGORIAN,
"y-MM-dd'T'H:mm:ssX"
);
$date = new DateTime();
echo $fmt->format($date->getTimestamp());
In my local machine it was giving me time strings like: "2018–03–26T15:17:22Z" which is totally acceptable by Date constructor on JavaScript. I was happy, but software development is a box full of surprises. When I decided to push the code the resulting date on the server was: “2018–03–26T15:17:22”, the difference is minimal, but we miss the Z there to say we are using UTC (or Zulu Timezone).
After more investigation and comparing phpinfo() outputs I found out that the only thing different between the server and my local machine was the ICU library versions, mine was 55.1 and the server was 51.2. On the article I cited in the beginning of this article I discovered that different ICU library versions might give different outputs for formatting, that hit me like a ton of bricks (but also was my aha-moment).
From there I finally knew my problem, I just had to solve it, and I really like to do this. Since I really like infrastructure I first thought of developing a custom environment for ElasticBeans Talk with ubuntu (since it has ICU lib in version 55.1). But, after seeing the amount of work it would take me and seeing that I would totally in charge of the security and configuration of the machine I preferred to take a second look at the code. This was the moment I had my second aha-moment.
I found in ICU page a section about Timezones and the possibility to use the Z as suffix for expressing the timezone, when I tested the new pattern ("y-MM-dd’T’H:mm:ssZ") in both environments with both ICU versions it finally gave me the same result, the string was now: “2018–03–26T15:17:22+0000” which is totally acceptable with JS. I had found my solution without having to do a huge infrastructure change.
The last detail I discovered later that week was that my pattern was initially wrong concerning the hours, since JS needs a zero-padded hour I needed to add an additional H, this left with my final version of my pattern to serialize dates: “y-MM-dd’T’HH:mm:ssZ”.
This is all folks, this was a very interesting experience for me, I had to browse a lot of documentation for both CakePHP and AWS ElasticBeans and also had to surf a lot the web trying to find what the heck could be generating all those errors. Hope this article will be useful to someone with similar problems.