Time Zones and DateTimeZone

A DateTimeZone names an IANA region such as Asia/Kuala_Lumpur, which knows every offset and daylight saving rule it has had. Avoid abbreviations: CST is US Central, China and Cuba Standard Time, and PHP picks one. Ubuntu 225 's PHP reads the system tzdata (version 0.system), updated by apt upgrade.

The daylight saving traps in New York (zones.php)PHP
<?php
$ny = new DateTimeZone('America/New_York');   // 2026-03-08 has no 02:30; 2026-11-01 has two
$night = new DateTimeImmutable('2026-11-01 00:30', $ny);
$eve = new DateTimeImmutable('2026-03-07 12:00', $ny);
foreach (['02:30 (gap)' => new DateTimeImmutable('2026-03-08 02:30', $ny),
          '01:30 (twice)' => new DateTimeImmutable('2026-11-01 01:30', $ny),
          '01:30 EST' => new DateTimeImmutable('2026-11-01 01:30 EST'),
          '00:30 + PT2H' => $night->add(new DateInterval('PT2H')),
          "00:30 '+2 hours'" => $night->modify('+2 hours'),
          'noon + P1D' => $eve->add(new DateInterval('P1D')),
          'noon + PT24H' => $eve->add(new DateInterval('PT24H'))] as $label => $x) {
    printf("%-17s %s\n", $label, $x->format('M j H:i T'));
}
echo timezone_version_get(), ' ', timezone_name_from_abbr('CST'), "\n";
Output
02:30 (gap)       Mar 8 03:30 EDT
01:30 (twice)     Nov 1 01:30 EDT
01:30 EST         Nov 1 01:30 EST
00:30 + PT2H      Nov 1 01:30 EST
00:30 '+2 hours'  Nov 1 02:30 EST
noon + P1D        Mar 8 12:00 EDT
noon + PT24H      Mar 8 13:00 EDT
0.system America/Chicago
The New York clock on the two 2026 transition nights
The New York clock on the two 2026 transition nights

add(PT2H) adds two elapsed hours, but modify('+2 hours') moved the wall clock and landed three real hours later. P1D keeps the clock time across a 23-hour day and PT24H does not. Store each user's zone name, since offsets change with the season.

Storing times in MySQL 524 . Write UTC text, ->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s'), and convert to the user's zone after reading. DATETIME stores the text unchanged; TIMESTAMP converts from the session time_zone to UTC and back (the same row reads 8 hours later in a +08:00 session) and ends in 2038 (Date and Time Types). Run SET time_zone = '+00:00' on every connection, and read with new DateTimeImmutable($row['at_dt'], new DateTimeZone('UTC')).