Aritmética de DateTime

Los siguientes ejemplos muestran algunos inconvenientes de la artimética de DateTime con respeco a las transiciones de DST y a los meses que tengan diferente número de días.

Ejemplo #1 DateTime::add/sub, añadir intervalos que cubren el tiempo transcurrido

Al añadir PT24H a una transición de DST parecerá que se añaden 23/25 horas (para la mayoría de las zonas horarias).

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

El resultado del ejemplo sería:

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

Ejemplo #2 DateTime::modify y strtotime, aumentar o disminuir valores de componentes individuales

Al añadir +24 hours a una transición de DST se añadirán exactamente 24 horas como se ve en la cadena de fecha/hora (a menos que el inicio o el final esté en un punto de transición).

<?php
$dt 
= new DateTime("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->modify("+24 hours");
echo 
"Fin:    "$dt->format("Y-m-d H:i:s P"), PHP_EOL;
?>

El resultado del ejemplo sería:

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

Ejemplo #3 Añadir o restar horas puede sobrepasar o subpasar fechas

January 31st + 1 month resultará en March 2nd (año bisiesto) o 3rd (año normal).

<?php
echo "Año normal:\n"// February has 28 days
$dt = new DateTime("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->modify("+1 month");
echo 
"Fin:    "$dt->format("Y-m-d H:i:s P"), PHP_EOL;

echo 
"Año bisiesto:\n"// February has 29 days
$dt = new DateTime("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->modify("+1 month");
echo 
"Fin:    "$dt->format("Y-m-d H:i:s P"), PHP_EOL;
?>

El resultado del ejemplo sería:

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 siguiente mes (esto es, para prevenir el desbordamiento), a partir de PHP 5.3.0 está disponible el formato last day of.

<?php
echo "Año normal:\n"// February has 28 days
$dt = new DateTime("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->modify("last day of next month");
echo 
"Fin:    "$dt->format("Y-m-d H:i:s P"), PHP_EOL;

echo 
"Año bisiesto:\n"// February has 29 days
$dt = new DateTime("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->modify("last day of next month");
echo 
"Fin:    "$dt->format("Y-m-d H:i:s P"), PHP_EOL;
?>

El resultado del ejemplo sería:

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