Un fallo en febrero del año 0: cronología de una rareza en PHP y timelib

Fuentes: A glitch in February of the year 0

El equipo del proyecto 28times detectó, mientras añadía compatibilidad con marcas de tiempo muy anteriores a la era común, que ciertos instantes no se procesaban correctamente. El caso se reproduce de forma fiable con la marca 0000-02-03 04:00 Europe/Oslo, y afecta a todas las zonas horarias, aunque únicamente dentro de febrero del año 0 y los últimos días de enero, un periodo muy poco habitual en series temporales reales pero que el equipo decidió cubrir de forma estricta.

El año 0 presenta dos particularidades: no existe en el calendario juliano tradicional, donde los historiadores hablarían de 1 a. C., y, en el calendario gregoriano proléptico con numeración astronómica, es un año bisiesto secular. Los años divisibles entre cien no lo son, salvo cuando también lo son entre cuatrocientos, y el año 0 cumple esa regla. La sospecha inicial apuntaba a ese detalle, pero otros bisiestos seculares, como 2000, no mostraban el problema.

La raíz del fallo resultó no estar en el código de 28times, sino en PHP. El proyecto utiliza DateTimeImmutable y calcula internamente una marca Unix. De las tres formas de convertir esa marca en un objeto DateTimeImmutable, la tercera variante —empleada por el equipo— devuelve un resultado desplazado un día en febrero del año 0 en todas las versiones recientes de PHP. La solución inmediata fue sustituir ese patrón por uno de los otros dos.

Además, se abrió una solicitud de cambios en timelib, la biblioteca de fecha y hora que PHP emplea internamente. El origen está en que timelib dispone de dos implementaciones para convertir marcas Unix a fechas gregorianas prolépticas, y una de ellas aplica una comprobación de rango con una fecha de referencia situada en enero del año 0, antes del bisiesto secular, lo que descuadra en un día todos los resultados previos a ese día. La corrección busca unificar los llamantes en el algoritmo correcto.