PHP 8.6.0 Beta 3 is available for testing

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

El ejemplo anterior mostrará:

Inicio: 2015-11-01 00:00:00 -04:00
Fin:    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 "Inicio: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+24 hours");
echo "Fin:    ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

El ejemplo anterior mostrará:

Inicio: 2015-11-01 00:00:00 -04:00
Fin:    2015-11-02 00:00:00 -05:00

Ejemplo #3 DateInterval::createFromDateString crea intervalos que incrementan o decrementan los valores de cada componente

DateInterval::createFromDateString() utiliza el analizador de fecha/hora, y la parte horaria del intervalo resultante incrementa o decrementa los valores de cada componente. Para un intervalo creado con DateInterval::__construct() la parte horaria representa en cambio tiempo transcurrido, de modo que ambos no son intercambiables cuando una transición de zona horaria cae dentro de esa parte horaria. La parte de fecha se aplica de la misma forma en ambos casos, por lo que P1D y 1 day dan siempre el mismo resultado.

<?php
$dt = new DateTimeImmutable("2015-11-01 00:00:00", new DateTimeZone("America/New_York"));
echo "Inicio:   ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
echo "PT24H:    ", $dt->add(new DateInterval("PT24H"))->format("Y-m-d H:i:s P"), PHP_EOL;
echo "24 hours: ", $dt->add(DateInterval::createFromDateString("24 hours"))->format("Y-m-d H:i:s P"), PHP_EOL;

El ejemplo anterior mostrará:

Inicio:   2015-11-01 00:00:00 -04:00
PT24H:    2015-11-01 23:00:00 -05:00
24 hours: 2015-11-02 00:00:00 -05:00

Ejemplo #4 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 "Año normal:\n"; // febrero tiene 28 días
$dt = new DateTimeImmutable("2015-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Inicio: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+1 month");
echo "Fin:    ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

echo "Año bisiesto:\n"; // febrero tiene 29 días
$dt = new DateTimeImmutable("2016-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Inicio: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+1 month");
echo "Fin:    ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

El ejemplo anterior mostrará:

Año normal:
Inicio: 2015-01-31 00:00:00 -05:00
Fin:    2015-03-03 00:00:00 -05:00
Año bisiesto:
Inicio: 2016-01-31 00:00:00 -05:00
Fin:    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 "Año normal:\n"; // febrero tiene 28 días
$dt = new DateTimeImmutable("2015-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Inicio: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("last day of next month");
echo "Fin:    ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

echo "Año bisiesto:\n"; // febrero tiene 29 días
$dt = new DateTimeImmutable("2016-01-31 00:00:00", new DateTimeZone("America/New_York"));
echo "Inicio: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("last day of next month");
echo "Fin:    ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;

El ejemplo anterior mostrará:

Año normal:
Inicio: 2015-01-31 00:00:00 -05:00
Fin:    2015-02-28 00:00:00 -05:00
Año bisiesto:
Inicio: 2016-01-31 00:00:00 -05:00
Fin:    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
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-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