Parsing de RFC 3339 / ISO-8601 em Go: por que até desenvolvedores experientes tropeçam
\n\nVocê já escreveu algo como time.Parse("2006-01-02T15:04:05Z", input) em Go, recebeu um erro e se sentiu como se a linguagem estivesse debochando de você? Familiar. O parsing de datas e horas é um daqueles tópicos onde, aparentemente, tudo é simples, mas na prática existem muitas armadilhas.
No Stack Overflow, a pergunta "Parsing RFC-3339 / ISO-8601 date-time string in Go" acumulou milhares de visualizações. As pessoas se confundem com os formatos, esquecem os nanossegundos, sofrem com fusos horários e se irritam com o número "mágico" 2006-01-02T15:04:05Z. Neste artigo, vamos analisar 5 erros mais comuns e mostrar como evitá-los de uma vez por todas. Vamos lá!
Um pouco de teoria: o que são RFC 3339 e ISO 8601?
\n\nAntes de mergulhar no código, vamos entender rapidamente os termos. ISO 8601 é o padrão internacional para representação de data e hora. RFC 3339 é uma versão "reduzida" dele, popular na web (por exemplo, em JSON, APIs, logs).
\n\nStrings típicas do RFC 3339 são assim:
\n\n2025-01-15T10:30:00Z\n2025-01-15T10:30:00+03:00\n2025-01-15T10:30:00.123456789Z\n\nEm Go, a função usada para parsing é time.Parse(layout, value). E é aqui que a coisa fica interessante.
Erro nº 1: Layout (template) incorreto — a principal causa de dor de cabeça
\n\nGo usa uma abordagem única: o formato não é definido por códigos como YYYY-MM-DD, mas por uma data específica: Mon Jan 2 15:04:05 MST 2006. É fácil de memorizar: 01/02 03:04:05PM '06 -0700 (mês, dia, hora, minuto, segundo, ano, fuso horário).
Iniciantes frequentemente tentam escrever algo como:
\n\n// ❌ Erro!\nt, err := time.Parse("YYYY-MM-DDTHH:MM:SSZ", "2025-01-15T10:30:00Z")\n\nGo simplesmente não entenderá esse formato. O layout correto para RFC 3339 é time.RFC3339:
// ✅ Correto\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\nA constante time.RFC3339 é igual à string "2006-01-02T15:04:05Z07:00". Observe o Z07:00 — é a notação do fuso horário.
Erro nº 2: Ignorar nanossegundos (time.RFC3339 vs time.RFC3339Nano)
\n\nO time.RFC3339 padrão não suporta nanossegundos. Se sua string contiver uma parte fracionária de segundo:
2025-01-15T10:30:00.123456789Z\n\n…o parsing com time.RFC3339 retornará um erro:
// ❌ Erro: 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\nA solução é usar time.RFC3339Nano:
// ✅ Correto\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\nDica: Se você não tiver certeza se haverá nanossegundos, use time.RFC3339Nano — ele é compatível com versões anteriores e funcionará para ambos os casos.
Erro nº 3: Fuso horário — Z ou +00:00 não são a mesma coisa?
\n\nAs strings 2025-01-15T10:30:00Z e 2025-01-15T10:30:00+00:00 significam a mesma coisa (UTC), mas o Go pode processá-las de forma diferente se o layout estiver especificado incorretamente.
// ✅ Ambas as variantes funcionam com 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\nMas se você escrever seu próprio layout, tenha cuidado:
\n\n- \