Parsen von RFC 3339 / ISO-8601 in Go: Warum selbst erfahrene Entwickler stolpern
\n\nHaben Sie jemals in Go so etwas wie time.Parse("2006-01-02T15:04:05Z", input) geschrieben, einen Fehler erhalten und sich gefühlt, als würde die Sprache Sie absichtlich ärgern? Kommt Ihnen bekannt vor. Das Parsen von Datums- und Zeitangaben ist eines dieser Themen, bei denen alles einfach erscheint, aber in der Praxis viele Fallstricke lauern.
Auf Stack Overflow hat die Frage "Parsing RFC-3339 / ISO-8601 date-time string in Go" tausende Aufrufe gesammelt. Menschen verwirren sich mit Formaten, vergessen Nanosekunden, leiden unter Zeitzonen und ärgern sich über die "magische" Zahl 2006-01-02T15:04:05Z. In diesem Artikel analysieren wir die 5 häufigsten Fehler und zeigen, wie man sie ein für alle Mal vermeidet. Los geht's!
Ein wenig Theorie: Was sind RFC 3339 und ISO 8601?
\n\nBevor wir in den Code eintauchen, klären wir kurz die Begriffe. ISO 8601 ist der internationale Standard für die Darstellung von Datum und Uhrzeit. RFC 3339 ist eine "abgespeckte" Version davon, die im Web weit verbreitet ist (z.B. in JSON, APIs, Logs).
\n\nTypische RFC 3339-Strings sehen so aus:
\n\n2025-01-15T10:30:00Z\n2025-01-15T10:30:00+03:00\n2025-01-15T10:30:00.123456789Z\n\nIn Go wird zum Parsen die Funktion time.Parse(layout, value) verwendet. Und hier beginnt das Interessante.
Fehler Nr. 1: Falscher Layout (Vorlage) – die Hauptursache für Kopfschmerzen
\n\nGo verwendet einen einzigartigen Ansatz: Das Format wird nicht durch Codes wie YYYY-MM-DD angegeben, sondern durch ein konkretes Datum: Mon Jan 2 15:04:05 MST 2006. Es ist leicht zu merken: 01/02 03:04:05PM '06 -0700 (Monat, Tag, Stunde, Minute, Sekunde, Jahr, Zeitzone).
Anfänger versuchen oft, so etwas zu schreiben:
\n\n// ❌ Fehler!\nt, err := time.Parse("YYYY-MM-DDTHH:MM:SSZ", "2025-01-15T10:30:00Z")\n\nGo wird ein solches Format einfach nicht verstehen. Das korrekte Layout für RFC 3339 ist time.RFC3339:
// ✅ Richtig\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\nDie Konstante time.RFC3339 entspricht dem String "2006-01-02T15:04:05Z07:00". Beachten Sie das Z07:00 – dies ist die Bezeichnung für die Zeitzone.
Fehler Nr. 2: Ignorieren von Nanosekunden (time.RFC3339 vs time.RFC3339Nano)
\n\nDas Standard-time.RFC3339 unterstützt keine Nanosekunden. Wenn Ihr String einen Bruchteil einer Sekunde enthält:
2025-01-15T10:30:00.123456789Z\n\n…dann wird das Parsen mit time.RFC3339 einen Fehler zurückgeben:
// ❌ Fehler: 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\nDie Lösung ist die Verwendung von time.RFC3339Nano:
// ✅ Richtig\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\nTipp: Wenn Sie sich nicht sicher sind, ob Nanosekunden vorkommen, verwenden Sie time.RFC3339Nano – es ist abwärtskompatibel und funktioniert in beiden Fällen.
Fehler Nr. 3: Zeitzone – Z oder +00:00 – ist das nicht dasselbe?
\n\nDie Strings 2025-01-15T10:30:00Z und 2025-01-15T10:30:00+00:00 bedeuten dasselbe (UTC), aber Go kann sie unterschiedlich verarbeiten, wenn das Layout falsch angegeben ist.
// ✅ Beide Varianten funktionieren mit 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\nAber wenn Sie Ihr eigenes Layout schreiben, seien Sie vorsichtig:
\n\n- \