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.
<?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";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

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')).