Fechas de un acta en la API: por qué siempre con zona horaria

Una hora sin desplazamiento no identifica un instante: cada sistema la completa con su propia suposición y el día termina corriéndose en filtros, listados y exportaciones.

Foto de Marco FerreiroMarco FerreiroAutor en multas.ar
4 min de lectura
Reloj de sol de piedra gastada en una plaza arbolada, con la sombra del gnomon marcada por la luz lateral de la tarde.
Reloj de sol de piedra gastada en una plaza arbolada, con la sombra del gnomon marcada por la luz lateral de la tarde.

Un campo con el valor 2026-09-01T00:20:00 parece completo y no lo es. Esa cadena no identifica un instante: identifica una hora de reloj que cada lector completa con la suposición de su propio entorno. El servidor la escribió pensando en hora argentina, el motor la guardó sin conversión, un cliente la interpretó como UTC y el listado de infracciones terminó mostrando las 21:20 del 31 de agosto.

En una integración de infracciones ese corrimiento no es cosmético. Cambia el día que se le atribuye a un acta, mueve los bordes de un filtro por mes, altera el orden de un listado y desplaza el límite que el usuario está mirando.

La regla operativa es corta: todo instante viaja con su desplazamiento respecto de UTC. La excepción importa: no todos los campos temporales de una API son instantes.

Un instante y una fecha de calendario son tipos distintos

RFC 3339, el perfil de ISO 8601 recomendado para los protocolos de Internet, define dos formas que conviene no mezclar: date-time, que exige un desplazamiento explícito —Z o ±hh:mm—, y full-date, una fecha sin hora ni zona.

Un instante es un punto en una línea de tiempo global: cuándo se observó una infracción, cuándo se procesó, cuándo respondió la consulta. Ese campo se serializa siempre con desplazamiento.

Una fecha de calendario es una unidad administrativa: el día que declara un acta, un vencimiento, una audiencia. No tiene hora. Si se le agrega una, típicamente 00:00 con la zona del servidor, deja de ser un día y pasa a ser un instante convertible: ahí nace el corrimiento de un día que aparece en pantallas y exportaciones.

La diferencia se declara en el contrato, no en un comentario al costado: OpenAPI ya separa format: date para el día y format: date-time para el instante, ambos apoyados en RFC 3339.

El desplazamiento no es la zona horaria

-03:00 dice cuánto se apartaba el reloj local de UTC en ese momento. No dice en qué zona ocurrió el hecho ni qué regla va a regir dentro de un año. Para algo ya ocurrido alcanza: el instante quedó fijado y el desplazamiento lo ancla.

Para un evento futuro no alcanza. Si una fecha límite se guarda como instante con el desplazamiento calculado hoy y la regla local cambia, el valor almacenado deja de coincidir con la hora de reloj prevista. La base de zonas horarias de IANA registra la historia de la hora local y se actualiza cuando un cuerpo político cambia límites, desplazamientos o reglas de horario de verano: el valor vigente hoy no es una constante del país.

De ahí una separación útil: los hechos pasados —cuándo se labró el acta—, como instante con desplazamiento y la cadena original de la fuente; los eventos futuros, como hora local más identificador de zona, resolviendo el instante recién al usarlo.

Z, -00:00 y las fuentes que no informan zona

Z indica que UTC es la referencia preferida. RFC 3339 reserva -00:00 para otro caso: el instante se conoce en UTC pero se ignora qué desplazamiento local le correspondía —un valor ya normalizado que perdió su zona de origen—. No sirve para marcar una hora de reloj sin zona, porque ahí el instante todavía no está determinado. Varias bibliotecas, además, lo normalizan como +00:00 y borran la distinción; si esa incertidumbre importa, conviene exponerla en un campo propio.

Cuando la jurisdicción nunca informa zona, la salida no debería inventarla: conservamos el valor tal como llegó y documentamos la regla de conversión, en la línea de los tres relojes de una integración.

Los encabezados HTTP son otro sistema

Conviene no mezclar el cuerpo con el protocolo. Las fechas de encabezado que define RFC 9110, como Date o Last-Modified, usan su propio formato y se expresan en GMT. Sirven para caché, validación y trazas de transporte; no reponen la zona de un dato de negocio ni deberían ordenar registros de infracciones.

Dónde se rompe una integración

Los síntomas suelen aparecer lejos del campo mal serializado:

  • rangos de consulta: “agosto” es una intención de calendario local que hay que traducir a instantes; con la zona equivocada entran o faltan registros en los bordes;
  • deduplicación y cursores: dos representaciones de la misma marca temporal producen claves distintas;
  • exportaciones: una planilla puede reinterpretar la cadena según la configuración regional de quien la abre;
  • auditoría: un historial que compara fechas de sistemas distintos necesita que todas sean instantes comparables.

Qué verificar antes de congelar el contrato

Revisá campo por campo: cada valor temporal declara si es día o instante; ningún instante sale sin desplazamiento; ninguna fecha de calendario recibe hora ni conversión; los eventos futuros llevan identificador de zona; y la cadena original de la fuente se conserva al lado del valor normalizado.

Después probalo donde duele. Un acta a las 23:40 y otra a las 00:20 de hora local, leídas desde un proceso configurado en otra zona, muestran en minutos si el contrato aguanta. Un formato sin ambigüedad no arregla un dato mal informado en el origen, pero evita que la integración le sume un error propio encima.

Metodología

Alcance
Contrato de serialización de campos temporales en una API de infracciones: instantes, fechas de calendario, eventos futuros y encabezados de transporte. No cubre el cómputo de plazos administrativos ni la validación del dato en la fuente.
Unidad de análisis
Cada campo temporal del contrato público y la cadena exacta con la que se serializa en la respuesta.
Cobertura
RFC 3339, la base de zonas horarias de IANA, el formato date-time de OpenAPI 3.0.4 y las fechas de encabezado definidas por HTTP, consultados el 29 de agosto de 2026.

Limitaciones

  • El formato no valida el contenido: una hora bien serializada puede seguir siendo incorrecta en el registro original.
  • Las bibliotecas de cada lenguaje difieren al parsear desplazamientos, segundos fraccionarios y valores límite; el contrato debe probarse con los clientes reales.
  • La nota describe cómo representar fechas, no cómo se computan plazos, que dependen de la normativa de cada jurisdicción.

Fuentes

Preguntas frecuentes

¿Alcanza con guardar todo en UTC?

Para comparar y ordenar instantes, sí. Pero convertir a UTC y descartar el original pierde información: la hora de reloj que vio la persona, el desplazamiento que informó la fuente y la zona de un evento futuro. Conviene normalizar para operar y conservar el valor de origen para auditar.

¿Una fecha de vencimiento también lleva zona horaria?

Como fecha de calendario, no: viaja como día suelto y se interpreta en la zona de la jurisdicción que lo fijó. El problema aparece cuando se la convierte en instante con una hora arbitraria, porque cualquier conversión posterior puede mostrarla un día antes o un día después.

¿`-00:00` y `Z` significan lo mismo?

No según RFC 3339: `Z` indica que UTC es la referencia preferida y `-00:00`, que el instante se conoce en UTC pero se ignora qué desplazamiento local le correspondía. No es una forma de marcar una hora local sin zona, porque en ese caso el instante todavía no está determinado. Muchas bibliotecas normalizan ambos igual, así que si la distinción importa conviene exponerla en un campo aparte.

¿Un validador de fechas debería aceptar el segundo 60?

Sí. RFC 3339 admite el valor `60` al cierre de los meses en que ocurre un segundo intercalar —hasta hoy, junio o diciembre— y aclara que en zonas distintas de `Z` ese punto se corre según el desplazamiento. Los segundos fraccionarios, en cambio, son opcionales. Un validador que sólo tolera `HH:MM:SS` sin decimales y con máximo `59` rechaza cadenas que el formato permite.

Notas relacionadas