Parsing RFC 3339 / ISO-8601 en Go : pourquoi même les développeurs expérimentés trébuchent
\n\nAvez-vous déjà écrit en Go quelque chose comme time.Parse("2006-01-02T15:04:05Z", input), obtenu une erreur, et eu l'impression que le langage se moquait de vous ? Familier. Le parsing des dates et heures est l'un de ces sujets où tout semble simple, mais où de nombreux pièges vous attendent en pratique.
Sur Stack Overflow, la question "Parsing RFC-3339 / ISO-8601 date-time string in Go" a rassemblé des milliers de vues. Les gens se perdent dans les formats, oublient les nanosecondes, souffrent des fuseaux horaires et s'énervent contre le nombre « magique » 2006-01-02T15:04:05Z. Dans cet article, nous allons analyser 5 erreurs les plus fréquentes et montrer comment les éviter une fois pour toutes. C'est parti !
Un peu de théorie : qu'est-ce que RFC 3339 et ISO 8601 ?
\n\nAvant de plonger dans le code, clarifions rapidement les termes. ISO 8601 est une norme internationale pour la représentation de la date et de l'heure. RFC 3339 en est une version « allégée », populaire sur le web (par exemple, dans JSON, les API, les logs).
\n\nLes chaînes RFC 3339 typiques ressemblent à ceci :
\n\n2025-01-15T10:30:00Z\n2025-01-15T10:30:00+03:00\n2025-01-15T10:30:00.123456789Z\n\nEn Go, la fonction utilisée pour le parsing est time.Parse(layout, value). Et c'est là que les choses deviennent intéressantes.
Erreur n°1 : Mauvais layout (modèle) — la principale cause de maux de tête
\n\nGo utilise une approche unique : le format n'est pas défini par des codes comme YYYY-MM-DD, mais par une date spécifique : Mon Jan 2 15:04:05 MST 2006. Il est facile à retenir : 01/02 03:04:05PM '06 -0700 (mois, jour, heure, minute, seconde, année, fuseau horaire).
Les débutants essaient souvent d'écrire quelque chose comme :
\n\n// ❌ Erreur !\nt, err := time.Parse("YYYY-MM-DDTHH:MM:SSZ", "2025-01-15T10:30:00Z")\n\nGo ne comprendra tout simplement pas ce format. Le layout correct pour RFC 3339 est time.RFC3339 :
// ✅ Correct\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 est égale à la chaîne "2006-01-02T15:04:05Z07:00". Notez le Z07:00 — c'est la désignation du fuseau horaire.
Erreur n°2 : Ignorer les nanosecondes (time.RFC3339 vs time.RFC3339Nano)
\n\nLe time.RFC3339 standard ne prend pas en charge les nanosecondes. Si votre chaîne contient une partie fractionnaire de seconde :
2025-01-15T10:30:00.123456789Z\n\n…alors le parsing avec time.RFC3339 renverra une erreur :
// ❌ Erreur : 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 solution est d'utiliser time.RFC3339Nano :
// ✅ Correct\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\nConseil : Si vous n'êtes pas sûr de la présence de nanosecondes, utilisez time.RFC3339Nano — il est rétrocompatible et fonctionnera dans les deux cas.
Erreur n°3 : Fuseau horaire — Z ou +00:00 — est-ce la même chose ?
\n\nLes chaînes 2025-01-15T10:30:00Z et 2025-01-15T10:30:00+00:00 signifient la même chose (UTC), mais Go peut les traiter différemment si le layout est mal spécifié.
// ✅ Les deux variantes fonctionnent avec 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\nMais si vous écrivez votre propre layout, soyez attentif :
\n\n- \