PHP 8.6.0 Beta 1 is available for testing

Арифметика даты/времени

В следующих примерах показываются некоторые подводные камни вычислений даты/времени, относительно переходов на летнее и зимнее время (DST), и месяцев, имеющих разное количество дней.

Пример #1 DateTimeImmutable::add/sub добавляет интервалы, охватывающие прошедшее время

Добавление PT24H через переход DST приведёт к добавлению 23/25 часов (для большинства часовых поясов).

<?php
$dt = new DateTimeImmutable("2015-11-01 00:00:00", new DateTimeZone("America/New_York"));
echo "Начало: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->add(new DateInterval("PT3H"));
echo "Конец: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

Результат выполнения приведённого примера:

Начало: 2015年11月01日 00:00:00 -04:00
Конец: 2015年11月01日 02:00:00 -05:00

Пример #2 DateTimeImmutable::modify и strtotime увеличит или уменьшит значения индивидуальных компонентов

Добавление +24 часов через переход DST добавит точно 24 часов (вместо учёта перехода на зимнее или летнее время).

<?php
$dt = new DateTimeImmutable("2015-11-01 00:00:00", new DateTimeZone("America/New_York"));
echo "Начало: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+24 hours");
echo "Конец: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

Результат выполнения приведённого примера:

Начало: 2015年11月01日 00:00:00 -04:00
Конец: 2015年11月02日 00:00:00 -05:00

Пример #3 Добавление или вычитание времени может уменьшить или увеличить дату

Например, 31 января + 1 месяц вернёт 2 марта (високосный год) или 3 марта (обычный год).

<?php
echo "Обычный год:\n"; // В феврале 28 дней
$dt = new DateTimeImmutable("2015-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Начало: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+1 month");
echo "Конец: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
echo "Високосный год:\n"; // В феврале 29 дней
$dt = new DateTimeImmutable("2016-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Начало: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+1 month");
echo "Конец: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

Результат выполнения приведённого примера:

Обычный год:
Начало: 2015年01月31日 00:00:00 -05:00
Конец: 2015年03月03日 00:00:00 -05:00
Високосный год:
Начало: 2016年01月31日 00:00:00 -05:00
Конец: 2016年03月02日 00:00:00 -05:00

Для получения последнего дня следующего месяца (то есть чтобы предотвратить переполнение) существует директива last day of.

<?php
echo "Обычный год:\n"; // Февраль содержит 28 дней
$dt = new DateTimeImmutable("2015-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Начало: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("last day of next month");
echo "Конец: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
echo "Високосный год:\n"; // Февраль содержит 29 дней
$dt = new DateTimeImmutable("2016-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Начало: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("last day of next month");
echo "Конец: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

Результат выполнения приведённого примера:

Обычный год:
Начало: 2015年01月31日 00:00:00 -05:00
Конец: 2015年02月28日 00:00:00 -05:00
Високосный год:
Начало: 2016年01月31日 00:00:00 -05:00
Конец: 2016年02月29日 00:00:00 -05:00

Нашли ошибку?

ИнструкцияИсправлениеСообщение об ошибке
+Добавить

Примечания пользователей 1 note

up
0
laurent dot payetFORBOTS at FORBOTSsupervan dot fr
1 year ago
Be careful when subtracting a duration crossing a DST change, it can output a resulting date AFTER your base date.
// tested in 8.3.18, 7.4.33
// DST timezone shift is applied properly when setting time explicitely
echo (new DateTime('2025-03-30'))->setTimeZone(new DateTimeZone('Europe/Paris'))->setTime(1,59,0)->format('c').PHP_EOL;
echo (new DateTime('2025-03-30'))->setTimeZone(new DateTimeZone('Europe/Paris'))->setTime(2,0,0)->format('c').PHP_EOL;
echo (new DateTime('2025-03-30'))->setTimeZone(new DateTimeZone('Europe/Paris'))->setTime(2,1,0)->format('c').PHP_EOL;
// 2025年03月30日T01:59:00+01:00 < correct
// 2025年03月30日T03:00:00+02:00 < correct
// 2025年03月30日T03:01:00+02:00 < correct
echo PHP_EOL;
// DST timezone shift is applied properly when addind a duration
$startDateTime = (new DateTime('2025-03-30'))->setTimeZone(new DateTimeZone('Europe/Paris'))->setTime(1,58,0);
echo $startDateTime->format('c').PHP_EOL;
$endDatetime = clone($startDateTime)->add(DateInterval::createFromDateString('4 minutes'));
echo $endDatetime->format('c').PHP_EOL;
// 2025年03月30日T01:58:00+01:00 < correct
// 2025年03月30日T03:02:00+02:00 < correct
echo PHP_EOL;
// DST timezone shift is applied improperly when subtracting a duration
$startDateTime = (new DateTime('2025-03-30'))->setTimeZone(new DateTimeZone('Europe/Paris'))->setTime(3,2,0);
echo $startDateTime->format('c').PHP_EOL;
$endDatetime = clone($startDateTime)->sub(DateInterval::createFromDateString('4 minutes'));
echo $endDatetime->format('c').PHP_EOL;
// 2025年03月30日T03:02:00+02:00 < correct
// 2025年03月30日T03:58:00+02:00 < incorrect !!!
echo PHP_EOL;
// DST timezone shift is still applied improperly when adding a negative duration
$startDateTime = (new DateTime('2025-03-30'))->setTimeZone(new DateTimeZone('Europe/Paris'))->setTime(3,2,0);
echo $startDateTime->format('c').PHP_EOL;
$endDatetime = clone($startDateTime)->add(DateInterval::createFromDateString('-4 minutes'));
echo $endDatetime->format('c').PHP_EOL;
// 2025年03月30日T03:02:00+02:00 < correct
// 2025年03月30日T03:58:00+02:00 < incorrect !!!
+Добавить

AltStyle によって変換されたページ (->オリジナル) /