{"record":{"id":"d7517f6ba9d1909d","repo":"kataras/iris","slug":"iso8601-missing-timezone-offset","errorCode":null,"errorMessage":"ISO8601: missing timezone offset","messagePattern":"ISO8601: missing timezone offset","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"x/jsonx/iso8601.go","lineNumber":173,"sourceCode":"\t\t\t\ttt, err = time.Parse(ISO8601ZUTCOffsetLayoutWithoutMicroseconds, s)\n\t\t\t}\n\t\t}\n\t} else if s[len(s)-1] == 'Z' {\n\t\ttt, err = time.Parse(ISO8601LayoutWithTimezone, s)\n\t} else {\n\t\ttt, err = time.Parse(ISO8601Layout, s)\n\t}\n\n\tif err != nil {\n\t\treturn ISO8601{}, fmt.Errorf(\"ISO8601: %w\", err)\n\t}\n\treturn ISO8601(tt), nil\n}\n\nfunc parseWithOffset(s string) (time.Time, error) {\n\tidx := strings.LastIndexFunc(s, startUTCOffsetIndexFunc)\n\tif idx == -1 {\n\t\treturn time.Time{}, fmt.Errorf(\"ISO8601: missing timezone offset\")\n\t}\n\n\toffsetText := s[idx:]\n\tsecondsEastUTC, err := parseOffsetToSeconds(offsetText)\n\tif err != nil {\n\t\treturn time.Time{}, err\n\t}\n\n\tloc, ok := fixedEastUTCLocations[secondsEastUTC]\n\tif !ok {\n\t\tloc = time.FixedZone(\"\", secondsEastUTC)\n\t}\n\n\treturn time.ParseInLocation(ISO8601ZUTCOffsetLayoutWithoutMicroseconds, s, loc)\n}\n\nfunc parseOffsetToSeconds(offsetText string) (int, error) {\n\tif len(offsetText) < 6 {","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/kataras/iris/blob/7bedaf55a0b64bbb2248a5845a2c60d81a30996a/x/jsonx/iso8601.go#L155-L191","documentation":"This error means the ISO8601 timestamp string handed to the parser has no timezone offset at all (no +HH:MM / -HH:MM suffix). The library's parseWithOffset path is only used when the string is expected to carry an offset, so finding none is treated as a hard parse failure rather than falling back to a zone-less time. It indicates the datetime part parsed OK positionally but the required offset signature ('+' or '-') is absent.","triggerScenarios":"Calling jsonx.ParseISO8601 (or ISO8601.UnmarshalJSON / ISO8601.Scan) with a string that reaches parseWithOffset yet contains no '+' or '-' rune: e.g. '2024-01-02T15:04:05' with a code path that assumes an offset, or a truncated string like '2024-01-02T15:04:05' where the offset was stripped by upstream normalization.","commonSituations":"Frontends or third-party APIs sending naive local datetimes (no zone) into structs with jsonx.ISO8601 fields; database drivers returning 'timestamp without time zone' strings scanned into ISO8601; config files or CSV imports with datetime strings lacking the offset; truncation of the tail of a timestamp during logging/serialization.","solutions":["Append a valid UTC offset to the input string, e.g. '2024-01-02T15:04:05' -> '2024-01-02T15:04:05+00:00' (or use the 'Z' suffix form '2024-01-02T15:04:05Z' so ParseISO8601 takes the Z-layout branch).","If zone-less input is expected/allowed, do not route it through ParseISO8601 expecting an offset; pre-normalize the string (regex check for [+-]HH:MM) before calling, or parse with time.Parse(ISO8601Layout, s) directly.","Fix upstream producers (API clients, DB column type 'timestamptz', serialization layer) so timestamps always include the offset.","If the string is truncated, verify no middleware/logging pipeline is cutting the tail (min length for offset form is 25 chars: '2006-01-02T15:04:05+07:00')."],"exampleFix":"// before\nvar t jsonx.ISO8601\nerr := t.UnmarshalJSON([]byte(\"2024-01-02T15:04:05\")) // ISO8601: missing timezone offset\n// after\nerr := t.UnmarshalJSON([]byte(\"2024-01-02T15:04:05+00:00\")) // ok\n// or use the Z form:\nerr = t.UnmarshalJSON([]byte(\"2024-01-02T15:04:05Z\"))","handlingStrategy":"validation","validationCode":"var isoOffsetRe = regexp.MustCompile(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([+-]\\d{2}:\\d{2}|Z)$`)\nfunc hasTimezone(s string) bool { return isoOffsetRe.MatchString(s) }\n// call jsonx.ParseISO8601 only if hasTimezone(s); otherwise normalize by appending \"+00:00\"","typeGuard":"func isOffsetForm(s string) bool {\n\ti := strings.LastIndexAny(s, \"+-\")\n\treturn i > 18 && len(s)-i == 6\n}","tryCatchPattern":"tt, err := jsonx.ParseISO8601(s)\nif err != nil {\n\tif strings.Contains(err.Error(), \"missing timezone offset\") {\n\t\t// fall back to naive layout\n\t\tt0, perr := time.Parse(jsonx.ISO8601Layout, s)\n\t\tif perr == nil {\n\t\t\ttt = jsonx.ISO8601(t0)\n\t\t\terr = nil\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"parse timestamp %q: %w\", s, err)\n\t}\n}","preventionTips":["Always emit timestamps with an explicit offset (+00:00 or Z) from producers","Prefer Postgres 'timestamptz' over 'timestamp' for DB columns scanned into ISO8601","Validate timestamp strings at API boundaries with a regex before unmarshaling","Check string length >= 20 and presence of '+'/'-' after position 18 before assuming offset form"],"tags":["go","jsonx","iso8601","timezone","date-parsing"],"backgroundTag":"missing-timezone-offset","analyzedSha":"7bedaf55a0b64bbb2248a5845a2c60d81a30996a","analyzedAt":"2026-08-30T20:38:16.250Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}