rfc:datetime_and_daylight_saving_time

Request for Comments: DateTime and Daylight Saving Time Transitions

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.

Changelog

rfc/datetime_and_daylight_saving_time.txt · Last modified: 2011/11/02 14:02 by danielc