## 日付/時刻 の算術

DST (夏時間) への移行タイミングをまたいで 24時間を足してしまうと、 (ほとんどのタイムゾーンでは) 23/25時間を足しているように見える

<?php
\$dt
= new DateTimeImmutable("2015-11-01 00:00:00", new DateTimeZone("America/New_York"));
echo
"Start: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
echo
"End: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
?>

Start: 2015-11-01 00:00:00 -04:00
End:   2015-11-01 02:00:00 -05:00

DST (夏時間) の移行のタイミングをまたいで24時間を足すと、日付/時刻 文字列に正確に24時間足されることになります (開始時刻と終了時刻が同じ移行ポイントにない場合) point).

<?php
\$dt
= new DateTimeImmutable("2015-11-01 00:00:00", new DateTimeZone("America/New_York"));
echo
"Start: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
\$dt = \$dt->modify("+24 hours");
echo
"End: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
?>

Start: 2015-11-01 00:00:00 -04:00
End:   2015-11-02 00:00:00 -05:00

1月31日 + 1ヶ月 のような計算をすると、(うるう年のときは) 3月2日、 または (通常の年は) 3月3日になります。

<?php
echo "Normal year:\n"; // February has 28 days
\$dt = new DateTimeImmutable("2015-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo
"Start: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
\$dt = \$dt->modify("+1 month");
echo
"End: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;

echo
"Leap year:\n"; // February has 29 days
\$dt = new DateTimeImmutable("2016-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo
"Start: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
\$dt = \$dt->modify("+1 month");
echo
"End: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
?>

Normal year:
Start: 2015-01-31 00:00:00 -05:00
End:   2015-03-03 00:00:00 -05:00
Leap year:
Start: 2016-01-31 00:00:00 -05:00
End:   2016-03-02 00:00:00 -05:00

<?php
echo "Normal year:\n"; // February has 28 days
\$dt = new DateTimeImmutable("2015-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo
"Start: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
\$dt = \$dt->modify("last day of next month");
echo
"End: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;

echo
"Leap year:\n"; // February has 29 days
\$dt = new DateTimeImmutable("2016-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo
"Start: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
\$dt = \$dt->modify("last day of next month");
echo
"End: ", \$dt->format("Y-m-d H:i:s P"), PHP_EOL;
?>

Normal year:
Start: 2015-01-31 00:00:00 -05:00
End:   2015-02-28 00:00:00 -05:00
Leap year:
Start: 2016-01-31 00:00:00 -05:00
End:   2016-02-29 00:00:00 -05:00

### User Contributed Notes 1 note

info at mobger dot de
2 years ago
There is a pitfall with the summertime in the timezone Europe/Berlin.

Beginning Summer-Timer
=======================

The hour between 2020-03-29 02:00:00 and 2020-03-29 03:00:00 is missing in the Timezone 'Europe/Berlin'.

Explanation to the code below
----------------------------------
The DateInterval of 20700 minutes point to 2020-03-29 03:00:00.
The DateInterval of 20701 minutes points to the not existing '2020-03-29 02:59:00', which is resolved to '2020-03-29 03:59:59' by rounding up.
The DateInterval-object of 20761 minutes calculate the existing '2020-03-29 01:59:00'. The hour between 2:00 - 3:00 does not exist in the euopean summertime for the date 2020-03-29. The missing hour will be additionally calculated in the substraction. This explain the result '2020-03-29 00:59:00'. It seems, that the addition of the DateInterval-object does not recalculate the missing hour for some (?) reasons.

<?php

\$test
= [];

\$check = date_create_from_format('Y-m-d H:i:s', '2020-04-12 12:00:00', new DateTimeZone('Europe/Berlin'));
for (
\$i = 0; \$i < 30000; \$i++) {

\$expectEaster = clone \$check;

\$interval = new DateInterval('PT' . \$i . 'M');

\$expectEaster->sub(\$interval);

\$dummy = clone \$expectEaster;

if (!
in_array(\$expectEaster->format('Y-m-d H:i:s'), \$test)) {

\$test[\$i] = \$expectEaster->format('Y-m-d H:i:s');
echo(
\$i . ' near summertime start ' . \$expectEaster->format('Y-m-d H:i:s') . '[' . \$dummy->format('Y-m-d H:i:s') . ']' . "\n");
}
}

//        0 near summertime start 2020-04-12 12:00:00[2020-04-12 12:00:00]
//    20701 near summertime start 2020-04-12 13:00:00[2020-03-29 03:59:00]
//    20761 near summertime start 2020-04-12 11:00:00[2020-03-29 00:59:00]

?>

Ending Summer Timer
====================
The european summertime defines the existence of two hours with the notation 2020-10-25 02:00:00 - 2020-10-25 03:00:00 (a) and 2020-10-25 02:00:00 - 2020-10-25 03:00:00 (b).
It seems, that the dateTime-object handles the additional hour on an simple internal way. It seems, that you can't differ between this two hours (a) and (b) with the DateTime-object in a simple way.

<?php
\$check
= date_create_from_format('Y-m-d H:i:s', '2020-10-25 06:00:00', new DateTimeZone('Europe/Berlin'));
for (
\$i = 0; \$i < 361; \$i = \$i + 60) {

\$expectEaster = clone \$check;

\$interval = new DateInterval('PT' . \$i . 'M');

\$expectEaster->sub(\$interval);

\$dummy = clone \$expectEaster;

\$test[\$i] = \$expectEaster->format('Y-m-d H:i:s');
echo(
\$i . ' near summertime end ' . \$expectEaster->format('Y-m-d H:i:s') .

'[' . \$dummy->format('Y-m-d H:i:s') . ' / ' . \$dummy->getTimestamp() . ']' .

"\n");
}
//      0 near summertime end 2020-10-25 06:00:00[2020-10-25 06:00:00 / 1603602000]
//     60 near summertime end 2020-10-25 06:00:00[2020-10-25 05:00:00 / 1603598400]
//    120 near summertime end 2020-10-25 06:00:00[2020-10-25 04:00:00 / 1603594800]
//    180 near summertime end 2020-10-25 06:00:00[2020-10-25 03:00:00 / 1603591200]
//    240 near summertime end 2020-10-25 06:00:00[2020-10-25 02:00:00 / 1603587600]
//    300 near summertime end 2020-10-25 06:00:00[2020-10-25 02:00:00 / 1603587600]
//    360 near summertime end 2020-10-25 06:00:00[2020-10-25 01:00:00 / 1603580400]    }

?>