Cuidado al usar Zend_Date

En un proyecto, estaba utilizando Zend_Date para manejo de horas en diferentes zonas horarias, cuando lo estaba desarrollando empecé a tener un error porque olvidé que la fecha se actualiza cuando cambias un parámetro y el problema surge cuando cambias el mes o día y se encuentra con una fecha inexistente aunque en el código parezca correcta, por ejemplo, supongamos que estamos en el mes de febrero y queremos crear una fecha de marzo (asignaré la fecha para reproducir el error, aunque el error es cuando se utiliza la fecha actual)

include_once("Zend/Date.php");
$fecha_febrero = mktime(0, 0, 0, 2, 1, 2010);
$fecha = new Zend_Date($fecha_febrero);
$fecha->setDay(31);
$fecha->setMonth(3);
print "Fecha Marzo: ".date("d/m/Y", $fecha->getTimestamp());

Uno esperaría que el resultado fuera: 31/03/2010

Pero en su lugar mostrará 03/03/2010

Esto es porque al hacer $fecha->setDay(31) el sistema intenta crear la fecha: 31/02/2010 que no existe, por lo que “recorre” los días restantes para quedar en 03/03/2010, al asignar el mes, no tiene efecto.

La solución es simplemente hacerlo en orden inverso:

include_once("Zend/Date.php");
$fecha_febrero = mktime(0, 0, 0, 2, 1, 2010);
$fecha = new Zend_Date($fecha_febrero);
$fecha->setMonth(3);
$fecha->setDay(31);
print "Fecha Marzo: ".date("d/m/Y", $fecha->getTimestamp());

Eso parecería la solución ideal (y es la recomendada por la documentación), pero no es totalmente cierta, si por ejemplo estamos en enero 31 del 2010 e intentamos asignar una fecha de febrero, siempre obtendremos una fecha de marzo:

include_once 'Zend/Date.php';
$fecha_enero = mktime(0, 0, 0, 1, 31, 2010);
$fecha = new Zend_Date($fecha_enero);
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Uno esperaría que mostrara la fecha:
10/02/2010

Pero en realidad muestra
10/03/2010

Esto porque cuando asignamos el mes, el sistema intenta crear la fecha 31/03/2010 que una vez más es incorrecta, por lo que crea la fecha 03/03/2010, pero al asignar la fecha, el resultado es un error en el mes.

Aunque esto no es un error, es muy peligroso porque uno puede suponer que la fecha es correcta porque en el código lo parece, la solución sin embargo es trivial, asignar el primer día de un mes que tenga 31 días y asignar los valores en orden inverso (primero año, luego mes y por último día), así el código sin error sería:

include_once 'Zend/Date.php';
$fecha = new Zend_Date(0, 0, 0, 1, 1, date('Y'));
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Resultado (el esperado)
10/02/2010

Esto funciona en la mayoría de los casos; aunque si van a manejar diferentes zonas horarias mediante setTimezone() el código anterior tendrá el mismo error en los casos que la zona horaria sea menor a la zona horaria del servidor, por lo que hay que hacer algo más elaborado:

include_once 'Zend/Date.php';
$fecha = new Zend_Date();
//eliminar "errores"
$fecha->setTimezone('mi_codigo_zona_horaria');
$fecha->setDay(1);
$fecha->setMonth(1);

//nuestro código
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Lo peligroso del comportamiento de Zend_Date es que funcionará con errores durante algunos días/meses dependiendo de la manera en como se programe y es un poco difícil de diagnosticar por su misma naturaleza, con esto se elimina cualquier tipo de errores.

Temas similares

  • http://zydrick.com/articles/2010/04/trabajarcon_fechas_en_php/ Trabajar con fechas eficientemente en PHP – ZydRick

    [...] eso hace tiempo liberé unas funciones para hacer estas cosas… Que visto lo visto con Zend_Date quizás le venga bien a más de [...]