Categories
learning

PHP Dates & Timezones

From a conversation in a PR on May 4:


As soon as a DateTime object is instantiated, it has the time set to the microsecond. If the arguments provided when creating the DateTime are not sufficiently specific, PHP will fall back to defaults to establish the exact time. Even if all you specify is a day, PHP will still create the DateTime with microsecond accuracy, using 0s as defaults.

Source

In the same way PHP uses 0s as defaults to fill in the units (seconds, milliseconds, etc) of time, it uses a default timezone to align that time to UTC if insufficient data was provided. The default timezone is whatever the server’s PHP config has the timezone set to. So running (new DateTime('2024-12-06 03:02:01.123456'))->getTimestamp() on two different PHP servers may return different results. 😞

PHP parses the first string argument to come up with a datetime, and if necessary, uses the second DateTimeZone arg to determine offset from UTC for that datetime. It only uses the second arg when it deems the first arg was a relative date.

The difference between relative and absolute dates is subtle, but can be seen below:

Relative dates:

  • 2024-12-06
  • 2024-12-06 03:02:01
  • 2024-12-06 03:02:01.123456

Absolute dates:

  • 2024-12-06T03:02:01Z
  • 2024-12-06T03:02:01+05:00
  • 2024-12-06T03:02:01-07:00
  • 2024-12-06T03:02:01.123456Z

The absolute dates are formatted according to the ISO-8601 specification (which you can render in PHP using the DateTime::ATOM constant, but not the DateTime::ISO8601 constant for silly reasons). They have a T separating the Date portion of the string from the Time portion of the string, and they always indicate the offset from UTC after the time (e.g. +05:00, -07:00, or simply Z which is equivalent to +00:00).

Source

So does that mean we are required to use relative dates in order to render dates in a certain timezone? No, but we have to understand how PHP uses the timezone arguments.

As a second argument in the construtor (as demonstrated above), it is:

  • IGNORED entirely when the date-string passed is an absolute date
  • APPLIED when the date-string passed is a relative date as both:
    1. Determining offset from UTC to set the point-in-time of the relative date
    2. Set timezone decoration on the object.

As the argument in the ->setTimezone() method, it:

  • ALWAYS sets the timezone decoration of the object
  • NEVER affects the point-in-time the object is set to

Source

Returning back up to our earlier example of the same command on two PHP servers producing different results – that was a problem because we were using relative dates. If we use absolute dates, we will always get consistent timestamps, regardless of server configuration.

$relative = (new DateTime('2024-12-06 03:02:01'))->getTimestamp();
// $relative will be 1733468521 on a server where PHP is set to the America/New_York timezone
// $relative will be 1733446921 on a server where PHP is set to the Europe/Paris timezone

$absolute = (new DateTime('2024-12-06T03:02:01Z'))->getTimestamp();
// $absolute will be 1733454121 on a server where PHP is set to the America/New_York timezone
// $absolute will be 1733454121 on a server where PHP is set to the Europe/Paris timezone

By bo.

I'm @boyEatsSteak in a lot of places. I have been wanting 🍕 for weeks. I like gadgets and technology and keeping notes and tips for my future self since all I can remember is that I forget things.