Request for Comments: DateTime and Daylight Saving Time Transitions
- Version: 1.0
- Date: 2011-10-18
- Author: Daniel Convissor danielc@php.net with feedback from Derick Rethans derick@php.net
- Status: Accepted (voting results)
- First Published at: https://wiki.php.net/rfc/datetime_and_daylight_saving_time
Introduction
PHP's DateTime class has unexpected outcomes when dealing with the transitions between Daylight Saving Time and Standard Time.
Properly defining, documenting and unit testing DateTime's behaviors is important for PHP's future. This document seeks agreement on what the expected behaviors should be.
Getting these issues straightened out before 5.4 goes into Release Candidate status seems wise.
Zone Types
PHP uses three different time zone data structures internally.
Zone Type 3 objects contain the transition data required for proper calculations around the Daylight/Standard time changes.
Zone Type 1 and 2 objects just know their offset from UTC, so addition and subtraction on them ignore crossing over Daylight/Standard thresholds.
This RFC focuses on the changes needed to make Zone Type 3 work in a comprehensive manner. Zone Type 1 and 2 behaviors are covered to clarify their expected behavior.
Examples
Examples of how to create DateTime objects containing each zone type:
Zone Type 1: Offset
$d = new DateTime('2011-09-13 14:15:16 -0400');
Zone Type 2: Abbreviation
$d = new DateTime('2011-09-13 14:15:16 EDT');
Zone Type 3: ID
$d = new DateTime('2011-09-13 14:15:16'); // or $d = new DateTime('2011-09-13 14:15:16', new DateTimeZone('America/New_York'));
Time Zone for Sake of Discussion
The America/New_York
time zone will be used for demonstration purposes.
The Standard/Daylight transition in this zone happens at
2:00 am. The forward transition (in spring) jumps to 3:00 am, skipping
over the hour of 2 am. The backward transition (in fall) jumps back to
1:00 am, repeating the hour of 1 am.
Other time zone changes have different lengths of time and take effect at different days/times.
Construction During Transitions
Forward Transitions
Times between 2:00:00 am
and 2:59:59 am
do not exist during the
Forward Transitions transition. Attempts to create such times should be
rounded forward, like PHP currently does when trying to create February 29th
on a non-leap year.
date_default_timezone_set('America/New_York'); $date = new DateTime('2010-03-14 02:30:00'); echo $date->format('Y-m-d H:i:s T') . "\n"; // Expected: 2010-03-14 03:30:00 EDT // Actual: 2010-03-14 02:30:00 EDT
Backward Transitions
PHP needs a means to create objects representing times during the hour
repeated on the Daylight/Standard transition day
(between 1:00:00 am
and 1:59:59 am
Standard Time).
This RFC proposes adjusting the time string parser to handle strings containing
DST
and ST
. When DST
or ST
are not present, PHP will use
the current behavior, which uses the time zone offset in place at the
specified time and uses DST offset during the repeated hour during backward
transitions.
Force Daylight Saving Time:
date_default_timezone_set('America/New_York'); $date = new DateTime('2010-11-07 01:30:00 DST'); echo $date->format('Y-m-d H:i:s T') . "\n"; // Expected: 2010-11-07 01:30:00 EDT
Force Standard Time:
date_default_timezone_set('America/New_York'); $date = new DateTime('2010-11-07 01:30:00 ST'); echo $date->format('Y-m-d H:i:s T') . "\n"; // Expected: 2010-11-07 01:30:00 EST
Default to Daylight Time during repeated hour, PHP's current behavior:
date_default_timezone_set('America/New_York'); $date = new DateTime('2010-11-07 01:30:00'); echo $date->format('Y-m-d H:i:s T') . "\n"; // Expected & Actual: 2010-11-07 01:30:00 EDT
The ST
or DST
modifiers can only be used when specifying times
during the backward transition period. Using the modifiers at other times
will throw an exception in object-oriented style code while procedural
style code will return false without triggering errors.
Math
The date format used in this section is for display purposes only, does not correspond to a PHP format, nor is intended to be parsed by PHP.
Reminder: the time zone in the examples is America/New_York
.
So “(ST)” represents Eastern Standard Time/EST/-0500 and “(DT)”
represents Eastern Daylight Time/EDT/-0400.
The behaviors indicated here are covered by unit tests in ext/date/tests
:
- rfc-datetime_and_daylight_saving_time-type1.phpt
- rfc-datetime_and_daylight_saving_time-type2.phpt
- rfc-datetime_and_daylight_saving_time-type3.phpt
Each output line in the test files is prefixed with an identifier
that corresponds to the phpt
columns in the tables below.
Actual results for Zone Types 1 and 2 are shown as N/A
for the moment.
All Zone Type 1 tests pass. Zone Type 2 results are tainted by
Bug 55253.
Forward Transitions
Zone Type 3
diff()
End | Start | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-03-14T03:00:00 (DT) | 2010-03-14T01:59:59 (ST) | PT0H0M1S | fd1 | PT1H0M1S |
2010-03-14T04:30:00 (DT) | 2010-03-13T04:30:00 (ST) | P1DT0H | fd2 | |
2010-03-14T03:30:00 (DT) | 2010-03-13T04:30:00 (ST) | P0DT22H | fd3 | P0DT23H |
2010-03-14T01:30:00 (ST) | 2010-03-13T04:30:00 (ST) | P0DT21H | fd4 | |
2010-03-14T01:30:00 (ST) | 2010-03-13T01:30:00 (ST) | P1DT0H | fd5 | |
2010-03-14T03:30:00 (DT) | 2010-03-13T03:30:00 (ST) | P1DT0H | fd6 | |
2010-03-14T03:30:00 (DT) | 2010-03-13T02:30:00 (ST) | P1DT1H | fd7 |
add()
Start | Interval | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-03-14T01:59:59 (ST) | PT1S | 2010-03-14T03:00:00 (DT) | fa1 | |
2010-03-13T04:30:00 (ST) | P1D | 2010-03-14T04:30:00 (DT) | fa2 | |
2010-03-13T04:30:00 (ST) | PT22H | 2010-03-14T03:30:00 (DT) | fa3 | |
2010-03-13T04:30:00 (ST) | PT21H | 2010-03-14T01:30:00 (ST) | fa4 | |
2010-03-13T01:30:00 (ST) | P1D | 2010-03-14T01:30:00 (ST) | fa5 | |
2010-03-13T02:30:00 (ST) | P1D | 2010-03-14T02:30:00 (DT) | fa6 |
sub()
End | Interval | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-03-14T03:00:00 (DT) | PT1S | 2010-03-14T01:59:59 (ST) | fs1 | 2010-03-14T03:59:59 (DT) |
2010-03-14T04:30:00 (DT) | P1D | 2010-03-13T04:30:00 (ST) | fs2 | |
2010-03-14T03:30:00 (DT) | PT22H | 2010-03-13T04:30:00 (ST) | fs3 | 2010-03-13T05:30:00 (ST) |
2010-03-14T01:30:00 (ST) | PT21H | 2010-03-13T04:30:00 (ST) | fs4 | |
2010-03-14T01:30:00 (ST) | P1D | 2010-03-13T01:30:00 (ST) | fs5 | |
2010-03-15T03:30:00 (DT) | P1D | 2010-03-14T03:30:00 (DT) | fs6 | |
2010-03-15T02:30:00 (DT) | P1D | 2010-03-14T03:30:00 (DT) | fs7 |
Zone Types 1 & 2
diff()
End | Start | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-03-14T03:00:00 (DT) | 2010-03-14T01:59:59 (ST) | PT0H0M1S | fd1 | |
2010-03-14T04:30:00 (DT) | 2010-03-13T04:30:00 (ST) | P0DT23H | fd2 | |
2010-03-14T03:30:00 (DT) | 2010-03-13T04:30:00 (ST) | P0DT22H | fd3 | |
2010-03-14T01:30:00 (ST) | 2010-03-13T04:30:00 (ST) | P0DT21H | fd4 | |
2010-03-14T01:30:00 (ST) | 2010-03-13T01:30:00 (ST) | P1DT0H | fd5 | |
2010-03-14T03:30:00 (DT) | 2010-03-13T03:30:00 (ST) | P0DT23H | fd6 | |
2010-03-14T03:30:00 (DT) | 2010-03-13T02:30:00 (ST) | P1DT0H | fd7 |
add()
Start | Interval | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-03-14T01:59:59 (ST) | PT1S | 2010-03-14T02:00:00 (ST) | fa1 | N/A |
2010-03-13T04:30:00 (ST) | P1D | 2010-03-14T04:30:00 (ST) | fa2 | N/A |
2010-03-13T04:30:00 (ST) | PT22H | 2010-03-14T02:30:00 (ST) | fa3 | N/A |
2010-03-13T04:30:00 (ST) | PT21H | 2010-03-14T01:30:00 (ST) | fa4 | N/A |
2010-03-13T01:30:00 (ST) | P1D | 2010-03-14T01:30:00 (ST) | fa5 | N/A |
2010-03-13T02:30:00 (ST) | P1D | 2010-03-14T02:30:00 (ST) | fa6 | N/A |
sub()
End | Interval | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-03-14T03:00:00 (DT) | PT1S | 2010-03-14T02:59:59 (DT) | fs1 | N/A |
2010-03-14T04:30:00 (DT) | P1D | 2010-03-13T04:30:00 (DT) | fs2 | N/A |
2010-03-14T03:30:00 (DT) | PT22H | 2010-03-13T05:30:00 (DT) | fs3 | N/A |
2010-03-14T01:30:00 (ST) | PT21H | 2010-03-13T04:30:00 (ST) | fs4 | N/A |
2010-03-14T01:30:00 (ST) | P1D | 2010-03-13T01:30:00 (ST) | fs5 | N/A |
2010-03-15T03:30:00 (DT) | P1D | 2010-03-14T03:30:00 (DT) | fs6 | N/A |
2010-03-15T02:30:00 (DT) | P1D | 2010-03-14T02:30:00 (DT) | fs7 | N/A |
Backward Transitions
Zone Type 3
diff()
End | Start | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-11-07T01:00:00 (ST) | 2010-11-07T01:59:59 (DT) | PT0H0M1S | bd1 | PT0H59M59S |
2010-11-07T04:30:00 (ST) | 2010-11-06T04:30:00 (DT) | P1DT0H | bd2 | |
2010-11-07T03:30:00 (ST) | 2010-11-06T04:30:00 (DT) | P0DT24H | bd3 | P0DT23H |
2010-11-07T02:30:00 (ST) | 2010-11-06T04:30:00 (DT) | P0DT23H | bd4 | P0DT22H |
2010-11-07T01:30:00 (ST) | 2010-11-06T04:30:00 (DT) | P0DT22H | bd5 | P0DT21H |
2010-11-07T01:30:00 (DT) | 2010-11-06T04:30:00 (DT) | P0DT21H | bd6 | |
2010-11-07T01:30:00 (DT) | 2010-11-06T01:30:00 (DT) | P1DT0H | bd7 | |
2010-11-07T01:30:00 (ST) | 2010-11-06T01:30:00 (DT) | P1DT1H | bd8 | P1DT1H |
add()
Start | Interval | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-11-07T01:59:59 (DT) | PT1S | 2010-11-07T01:00:00 (ST) | ba1 | 2010-11-07T01:00:00 (ST) |
2010-11-06T04:30:00 (DT) | P1D | 2010-11-07T04:30:00 (ST) | ba2 | |
2010-11-06T04:30:00 (DT) | PT24H | 2010-11-07T03:30:00 (ST) | ba3 | 2010-11-07T04:30:00 (ST) |
2010-11-06T04:30:00 (DT) | PT23H | 2010-11-07T02:30:00 (ST) | ba4 | 2010-11-07T03:30:00 (ST) |
2010-11-06T04:30:00 (DT) | PT22H | 2010-11-07T01:30:00 (ST) | ba5 | 2010-11-07T02:30:00 (ST) |
2010-11-06T04:30:00 (DT) | PT21H | 2010-11-07T01:30:00 (DT) | ba6 | |
2010-11-06T01:30:00 (DT) | P1D | 2010-11-07T01:30:00 (DT) | ba7 | |
2010-11-06T01:30:00 (DT) | P1DT1H | 2010-11-07T01:30:00 (ST) | ba8 | 2010-11-07T02:30:00 (ST) |
2010-11-06T04:30:00 (DT) | PT25H | 2010-11-07T04:30:00 (ST) | ba9 | 2010-11-07T05:30:00 (ST) |
2010-11-06T03:30:00 (DT) | P1D | 2010-11-07T03:30:00 (ST) | ba10 | |
2010-11-06T02:30:00 (DT) | P1D | 2010-11-07T02:30:00 (ST) | ba11 |
sub()
End | Interval | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-11-07T01:00:00 (ST) | PT1S | 2010-11-07T01:59:59 (DT) | bs1 | 2010-11-07T00:59:59 (DT) |
2010-11-07T04:30:00 (ST) | P1D | 2010-11-06T04:30:00 (DT) | bs2 | |
2010-11-07T03:30:00 (ST) | PT24H | 2010-11-06T04:30:00 (DT) | bs3 | 2010-11-06T03:30:00 (DT) |
2010-11-07T02:30:00 (ST) | PT23H | 2010-11-06T04:30:00 (DT) | bs4 | 2010-11-06T03:30:00 (DT) |
2010-11-07T01:30:00 (ST) | PT22H | 2010-11-06T04:30:00 (DT) | bs5 | 2010-11-06T03:30:00 (DT) |
2010-11-07T01:30:00 (DT) | PT21H | 2010-11-06T04:30:00 (DT) | bs6 | |
2010-11-07T01:30:00 (DT) | P1D | 2010-11-06T01:30:00 (DT) | bs7 | |
2010-11-07T01:30:00 (ST) | P1DT1H | 2010-11-06T00:30:00 (DT) | bs8 | |
2010-11-07T03:30:00 (ST) | P1D | 2010-11-06T03:30:00 (DT) | bs9 | |
2010-11-07T02:30:00 (ST) | P1D | 2010-11-06T02:30:00 (DT) | bs10 |
Zone Types 1 & 2
diff()
End | Start | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-11-07T01:00:00 (ST) | 2010-11-07T01:59:59 (DT) | PT0H0M1S | bd1 | |
2010-11-07T04:30:00 (ST) | 2010-11-06T04:30:00 (DT) | P1DT1H | bd2 | |
2010-11-07T03:30:00 (ST) | 2010-11-06T04:30:00 (DT) | P1DT0H | bd3 | |
2010-11-07T02:30:00 (ST) | 2010-11-06T04:30:00 (DT) | P0DT23H | bd4 | |
2010-11-07T01:30:00 (ST) | 2010-11-06T04:30:00 (DT) | P0DT22H | bd5 | |
2010-11-07T01:30:00 (DT) | 2010-11-06T04:30:00 (DT) | P0DT21H | bd6 | |
2010-11-07T01:30:00 (DT) | 2010-11-06T01:30:00 (DT) | P1DT0H | bd7 | |
2010-11-07T01:30:00 (ST) | 2010-11-06T01:30:00 (DT) | P1DT1H | bd8 |
add()
Start | Interval | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-11-07T01:59:59 (DT) | PT1S | 2010-11-07T02:00:00 (DT) | ba1 | N/A |
2010-11-06T04:30:00 (DT) | P1D | 2010-11-07T04:30:00 (DT) | ba2 | N/A |
2010-11-06T04:30:00 (DT) | PT24H | 2010-11-07T04:30:00 (DT) | ba3 | N/A |
2010-11-06T04:30:00 (DT) | PT23H | 2010-11-07T03:30:00 (DT) | ba4 | N/A |
2010-11-06T04:30:00 (DT) | PT22H | 2010-11-07T02:30:00 (DT) | ba5 | N/A |
2010-11-06T04:30:00 (DT) | PT21H | 2010-11-07T01:30:00 (DT) | ba6 | N/A |
2010-11-06T01:30:00 (DT) | P1D | 2010-11-07T01:30:00 (DT) | ba7 | N/A |
2010-11-06T01:30:00 (DT) | P1DT1H | 2010-11-07T02:30:00 (DT) | ba8 | N/A |
2010-11-06T04:30:00 (DT) | PT25H | 2010-11-07T05:30:00 (DT) | ba9 | N/A |
2010-11-06T03:30:00 (DT) | P1D | 2010-11-07T03:30:00 (DT) | ba10 | N/A |
2010-11-06T02:30:00 (DT) | P1D | 2010-11-07T02:30:00 (DT) | ba11 | N/A |
sub()
End | Interval | Expected | phpt | Actual (if wrong) |
---|---|---|---|---|
2010-11-07T01:00:00 (ST) | PT1S | 2010-11-07T00:59:59 (ST) | bs1 | N/A |
2010-11-07T04:30:00 (ST) | P1D | 2010-11-06T04:30:00 (ST) | bs2 | N/A |
2010-11-07T03:30:00 (ST) | PT24H | 2010-11-06T03:30:00 (ST) | bs3 | N/A |
2010-11-07T02:30:00 (ST) | PT23H | 2010-11-06T03:30:00 (ST) | bs4 | N/A |
2010-11-07T01:30:00 (ST) | PT22H | 2010-11-06T03:30:00 (ST) | bs5 | N/A |
2010-11-07T01:30:00 (DT) | PT21H | 2010-11-06T04:30:00 (DT) | bs6 | N/A |
2010-11-07T01:30:00 (DT) | P1D | 2010-11-06T01:30:00 (DT) | bs7 | N/A |
2010-11-07T01:30:00 (ST) | P1DT1H | 2010-11-06T00:30:00 (ST) | bs8 | N/A |
2010-11-07T03:30:00 (ST) | P1D | 2010-11-06T03:30:00 (ST) | bs9 | N/A |
2010-11-07T02:30:00 (ST) | P1D | 2010-11-06T02:30:00 (ST) | bs10 | N/A |
Other Issues?
If you know of other problems, please bring them to the attention of the authors.