Dates7 min readExamples run on PHP 8.5.10

timezone_transitions_get()

Learn how timezone_transitions_get() lists offset and daylight-saving changes for a DateTimeZone, with fixed UTC examples, edge cases and common mistakes.

Signature

timezone_transitions_get(
    DateTimeZone $object,
    int $timestampBegin = PHP_INT_MIN,
    int $timestampEnd = PHP_INT_MAX
): array|false
Available since PHP 5.2.07.48.08.18.28.38.48.5All versions →

Purpose

timezone_transitions_get() is the procedural alias of DateTimeZone::getTransitions(). It reports when a time zone changed its UTC offset, for example when daylight saving time (DST) began or ended. You can use it to explain why a local day is 23 or 25 hours long, to find the next clock change, or to check that two zones behave the same over a period.

Both spellings do the same thing. Use the function form in procedural code and the method form in object-oriented code.

Signature

Parameters and defaults

  • $object is the DateTimeZone to inspect. It is required. Pass an object, not a string such as "Europe/Paris".
  • $timestampBegin is the Unix timestamp where the range starts. The default is PHP_INT_MIN, which means "from the earliest data available".
  • $timestampEnd is the Unix timestamp where the range ends. The default is PHP_INT_MAX.

The defaults can produce a long list, because it covers the whole history stored in the timezone database. In most real code you should pass both bounds.

Return value and failure

On success the function returns a list of arrays. Each entry has these keys:

  • ts: the Unix timestamp of the transition.
  • time: the same moment as an ISO 8601 string in UTC, such as 2024-03-10T07:00:00+00:00.
  • offset: the offset from UTC in seconds, for example -18000 for UTC-5.
  • isdst: a boolean that is true while daylight saving time is in effect.
  • abbr: the abbreviation, such as EST or EDT.

The first element describes the zone state at the start of the range you asked for. Later elements are the actual changes inside the range. The signature allows false on failure, so production code that cannot tolerate a missing result should check for it. Passing something that is not a DateTimeZone is a type error, not a false return.

Example 1: transitions in one year

New York changed its offset twice in 2024. The first entry below is the starting state at the beginning of the range, and the next two are the real changes.

Example
<?php
date_default_timezone_set('UTC');

$zone = new DateTimeZone('America/New_York');
$start = 1704067200; // 2024-01-01T00:00:00Z
$end = 1735689599;   // 2024-12-31T23:59:59Z

$transitions = timezone_transitions_get($zone, $start, $end);

foreach ($transitions as $transition) {
    echo json_encode($transition, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
}
Output
{"ts":1704067200,"time":"2024-01-01T00:00:00+00:00","offset":-18000,"isdst":false,"abbr":"EST"}
{"ts":1710054000,"time":"2024-03-10T07:00:00+00:00","offset":-14400,"isdst":true,"abbr":"EDT"}
{"ts":1730613600,"time":"2024-11-03T06:00:00+00:00","offset":-18000,"isdst":false,"abbr":"EST"}

Example 2: alias equality and a DST summary

The function and the method return identical data. This example also turns the result into a short list of the dates when the clocks changed, skipping the first entry because it is only the start of the range.

Example
<?php
date_default_timezone_set('UTC');

$zone = new DateTimeZone('America/New_York');
$start = 1704067200;
$end = 1735689599;

$fromFunction = timezone_transitions_get($zone, $start, $end);
$fromMethod = $zone->getTransitions($start, $end);

$changes = [];
foreach (array_slice($fromFunction, 1) as $transition) {
    $changes[] = substr($transition['time'], 0, 10) . ' ' . $transition['abbr'];
}

echo json_encode(['same' => $fromFunction === $fromMethod, 'changes' => $changes], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"same":true,"changes":["2024-03-10 EDT","2024-11-03 EST"]}

Example 3: a zone without changes

UTC never changes its offset. A bounded query therefore returns only the starting entry.

Example
<?php
date_default_timezone_set('UTC');

$zone = new DateTimeZone('UTC');
$transitions = timezone_transitions_get($zone, 0, 100000);

echo json_encode(['count' => count($transitions), 'offset' => $transitions[0]['offset']], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"count":1,"offset":0}

Edge cases

  • The first element is not a real change. It is a snapshot of the zone at $timestampBegin. Count it separately from actual transitions.
  • Boundaries matter. A change that happens exactly at your bounds may be included or treated as the starting state, so test boundary timestamps in your own code.
  • Fixed-offset zones have no real changes, so you typically get a single entry.
  • Offsets are seconds. Divide by 3600 for hours, and remember that some zones use half-hour or 45-minute offsets.
  • Data comes from the timezone database bundled with PHP, so results for future dates depend on the rules known to that version and can change after an update.

Common mistakes

  1. Calling it without bounds and then looping over a very large history when only one year was needed.
  2. Passing a timezone name string instead of a DateTimeZone object.
  3. Treating the first entry as a DST change and reporting a clock change that did not happen.
  4. Assuming isdst means the offset is positive. It only says whether the zone is on its daylight rule.
  5. Parsing time as a local time. It is expressed in UTC.
  6. Hard-coding transition dates instead of reading them from the zone data.
  • DateTimeZone::getTransitions(), the method this function aliases.
  • timezone_offset_get() and DateTimeZone::getOffset() for the offset at a single moment.
  • timezone_open() for creating a DateTimeZone procedurally.
  • timezone_name_get() for reading the zone name.
  • DateTimeImmutable::setTimezone() for converting a moment to another zone.

Recap

timezone_transitions_get() returns the offset history of a DateTimeZone between two timestamps. Always bound the range, remember that the first entry is the starting state, read offsets as seconds, and treat time as UTC.

Tested examples

Transitions for New York in 2024
<?php
date_default_timezone_set('UTC');

$zone = new DateTimeZone('America/New_York');
$start = 1704067200; // 2024-01-01T00:00:00Z
$end = 1735689599;   // 2024-12-31T23:59:59Z

$transitions = timezone_transitions_get($zone, $start, $end);

foreach ($transitions as $transition) {
    echo json_encode($transition, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
}
Output
{"ts":1704067200,"time":"2024-01-01T00:00:00+00:00","offset":-18000,"isdst":false,"abbr":"EST"}
{"ts":1710054000,"time":"2024-03-10T07:00:00+00:00","offset":-14400,"isdst":true,"abbr":"EDT"}
{"ts":1730613600,"time":"2024-11-03T06:00:00+00:00","offset":-18000,"isdst":false,"abbr":"EST"}
Alias equality and DST change summary
<?php
date_default_timezone_set('UTC');

$zone = new DateTimeZone('America/New_York');
$start = 1704067200;
$end = 1735689599;

$fromFunction = timezone_transitions_get($zone, $start, $end);
$fromMethod = $zone->getTransitions($start, $end);

$changes = [];
foreach (array_slice($fromFunction, 1) as $transition) {
    $changes[] = substr($transition['time'], 0, 10) . ' ' . $transition['abbr'];
}

echo json_encode(['same' => $fromFunction === $fromMethod, 'changes' => $changes], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"same":true,"changes":["2024-03-10 EDT","2024-11-03 EST"]}
A zone without offset changes
<?php
date_default_timezone_set('UTC');

$zone = new DateTimeZone('UTC');
$transitions = timezone_transitions_get($zone, 0, 100000);

echo json_encode(['count' => count($transitions), 'offset' => $transitions[0]['offset']], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"count":1,"offset":0}