Aritmética con DateTime

Los ejemplos siguientes muestran algunos problemas de la aritmética de DateTime en lo que respecta a las transiciones DST y los meses con diferente número de días.

Ejemplo #1 DateTimeImmutable::add/sub añadir un intervalo de tiempo transcurrido

Añadir PT24H más allá de una transición DST parecerá añadir 23/25 horas (para la mayoría de los husos horarios).

<?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->add(new DateInterval("PT3H"));
echo
"End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
?>

El resultado del ejemplo sería:

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

Ejemplo #2 DateTimeImmutable::modify y strtotime incrementar o decrementar valores individuales

Añadir +24 horas más allá de una transición DST puede añadir exactamente 24 horas como se ve con la cadena fecha/hora (excepto si la hora de inicio o fin está en un punto de transición).

<?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;
?>

El resultado del ejemplo sería:

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

Ejemplo #3 La adición o sustracción de fechas/horas puede exceder (en más o en menos) fechas

Como para 31 de Enero + 1 mes dará como resultado 2 de Marzo (año bisiesto) o 3 de Marzo (año normal).

<?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;
?>

El resultado del ejemplo sería:

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

Para obtener el último día del mes próximo (es decir, para prever el excedente), el formato last day of está disponible.

<?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;
?>

El resultado del ejemplo sería:

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

add a note

User Contributed Notes 1 note

up
0
laurent dot payetFORBOTS at FORBOTSsupervan dot fr
3 months 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-30T01:59:00+01:00 < correct
// 2025-03-30T03:00:00+02:00 < correct
// 2025-03-30T03: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-30T01:58:00+01:00 < correct
// 2025-03-30T03: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-30T03:02:00+02:00 < correct
// 2025-03-30T03: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-30T03:02:00+02:00 < correct
// 2025-03-30T03:58:00+02:00 < incorrect !!!
To Top