Parseo de RFC 3339 / ISO-8601 en Go: por qué incluso desarrolladores experimentados tropiezan
\n\n¿Alguna vez has escrito en Go algo como time.Parse("2006-01-02T15:04:05Z", input), recibiste un error y te sentiste como si el lenguaje se estuviera burlando de ti a propósito? Te suena. El parseo de fechas y horas es uno de esos temas donde, aparentemente todo es simple, pero en la práctica acechan muchas trampas.
En Stack Overflow, la pregunta "Parsing RFC-3339 / ISO-8601 date-time string in Go" ha acumulado miles de visitas. La gente se confunde con los formatos, olvida los nanosegundos, sufre con las zonas horarias y se enfada con el número «mágico» 2006-01-02T15:04:05Z. En este artículo analizaremos los 5 errores más comunes y mostraremos cómo evitarlos de una vez por todas. ¡Allá vamos!
Un poco de teoría: ¿qué son RFC 3339 e ISO 8601?
\n\nAntes de sumergirnos en el código, aclaremos rápidamente los términos. ISO 8601 es el estándar internacional para la representación de fecha y hora. RFC 3339 es una versión «recortada» del mismo, popular en la web (por ejemplo, en JSON, API, logs).
\n\nLas cadenas típicas de RFC 3339 se ven así:
\n\n2025-01-15T10:30:00Z\n2025-01-15T10:30:00+03:00\n2025-01-15T10:30:00.123456789Z\n\nEn Go, para el parseo se usa la función time.Parse(layout, value). Y aquí es donde empieza lo interesante.
Error n.º 1: Layout (plantilla) incorrecto — la principal causa de dolor de cabeza
\n\nGo utiliza un enfoque único: el formato no se define con códigos como YYYY-MM-DD, sino con una fecha concreta: Mon Jan 2 15:04:05 MST 2006. Es fácil de recordar: 01/02 03:04:05PM '06 -0700 (mes, día, hora, minuto, segundo, año, zona horaria).
Los principiantes a menudo intentan escribir algo como:
\n\n// ❌ ¡Error!\nt, err := time.Parse("YYYY-MM-DDTHH:MM:SSZ", "2025-01-15T10:30:00Z")\n\nGo simplemente no entenderá ese formato. El layout correcto para RFC 3339 es time.RFC3339:
// ✅ Correcto\nt, err := time.Parse(time.RFC3339, "2025-01-15T10:30:00Z")\nif err != nil {\n log.Fatal(err)\n}\nfmt.Println(t) // 2025-01-15 10:30:00 +0000 UTC\n\nLa constante time.RFC3339 es igual a la cadena "2006-01-02T15:04:05Z07:00". Observa Z07:00 — es la notación de la zona horaria.
Error n.º 2: Ignorar los nanosegundos (time.RFC3339 vs time.RFC3339Nano)
\n\nEl time.RFC3339 estándar no admite nanosegundos. Si tu cadena contiene una parte fraccionaria de segundo:
2025-01-15T10:30:00.123456789Z\n\n…entonces el parseo con time.RFC3339 devolverá un error:
// ❌ Error: parsing time "2025-01-15T10:30:00.123456789Z" as "2006-01-02T15:04:05Z07:00": cannot parse ".123456789Z" as "Z07:00"\nt, err := time.Parse(time.RFC3339, "2025-01-15T10:30:00.123456789Z")\n\nLa solución es usar time.RFC3339Nano:
// ✅ Correcto\nt, err := time.Parse(time.RFC3339Nano, "2025-01-15T10:30:00.123456789Z")\nif err != nil {\n log.Fatal(err)\n}\nfmt.Println(t) // 2025-01-15 10:30:00.123456789 +0000 UTC\n\nConsejo: Si no estás seguro de si habrá nanosegundos, usa time.RFC3339Nano — es compatible hacia atrás y funcionará para ambos casos.
Error n.º 3: Zona horaria — ¿Z o +00:00 no son lo mismo?
\n\nLas cadenas 2025-01-15T10:30:00Z y 2025-01-15T10:30:00+00:00 significan lo mismo (UTC), pero Go puede procesarlas de manera diferente si el layout se especifica incorrectamente.
// ✅ Ambas variantes funcionan con time.RFC3339\nt1, _ := time.Parse(time.RFC3339, "2025-01-15T10:30:00Z")\nt2, _ := time.Parse(time.RFC3339, "2025-01-15T10:30:00+00:00")\nfmt.Println(t1.Equal(t2)) // true\n\nPero si escribes tu propio layout, ten cuidado:
\n\n- \