kataras/iris · error
invalid offset hours: %d: %s
Error message
invalid offset hours: %d: %s
What it means
After parsing the hours of an unconventional '+HH:MM:SS' offset, adjustForUnconventionalOffset rejects hour values greater than 24, since a UTC offset hour component can never exceed 24. The parsed hours and the full offset string are included in the message.
Source
Thrown at x/jsonx/iso8601.go:241
func adjustForUnconventionalOffset(t time.Time, offset string) (time.Time, error) {
sign := 1
if offset[0] == '-' {
sign = -1
}
offset = offset[1:]
offsetParts := strings.Split(offset, ":")
if len(offsetParts) != 3 {
return time.Time{}, fmt.Errorf("invalid offset format: %s", offset)
}
hours, err := strconv.Atoi(offsetParts[0])
if err != nil {
return time.Time{}, fmt.Errorf("error parsing offset hours: %s: %w", offset, err)
}
if hours > 24 {
return time.Time{}, fmt.Errorf("invalid offset hours: %d: %s", hours, offset)
}
minutes, err := strconv.Atoi(offsetParts[1])
if err != nil {
return time.Time{}, fmt.Errorf("error parsing offset minutes: %s: %w", offset, err)
}
if minutes > 60 {
return time.Time{}, fmt.Errorf("invalid offset minutes: %d: %s", minutes, offset)
}
seconds, err := strconv.Atoi(offsetParts[2])
if err != nil {
return time.Time{}, fmt.Errorf("error parsing offset seconds: %s: %w", offset, err)
}
if seconds > 60 {
return time.Time{}, fmt.Errorf("invalid offset seconds: %d: %s", seconds, offset)
}View on GitHub (pinned to 7bedaf55a0)
Solutions
- Correct the offset so its hour component is within 0–24 (e.g. '+03:00:00').
- Verify the producer is not swapping fields (e.g. putting a year or day value into the offset hours slot).
- Validate offsets at ingestion with a regex like ^[+-](0?[0-9]|1[0-9]|2[0-4]):[0-5]?[0-9]:[0-5]?[0-9]$ before parsing.
Example fix
// before
ParseISO8601("2024-01-02T15:04:05+30:00:00") // invalid offset hours: 30
// after
ParseISO8601("2024-01-02T15:04:05+03:00:00") Defensive patterns
Strategy: validation
Validate before calling
func offsetHoursInRange(ts string) bool {
i := strings.LastIndexAny(ts, "+-")
if i < 0 { return false }
parts := strings.Split(ts[i+1:], ":")
if len(parts) != 3 { return false }
h, err := strconv.Atoi(parts[0])
return err == nil && h >= 0 && h <= 24
} Try / catch
tt, err := jsonx.ParseISO8601(s)
if err != nil {
if strings.Contains(err.Error(), "invalid offset hours") {
return fmt.Errorf("timestamp %q has out-of-range UTC offset", s)
}
return err
} Prevention
- Validate offsets are within ±24:00:00 before parsing.
- Check serializers for field-order bugs when composing offsets.
- Prefer standard offsets like Z or +03:00 that skip the unconventional path.
When it happens
Trigger: ParseISO8601 on a timestamp with an offset like '+30:00:00' or '+99:00:00' — digits parse fine but exceed the 24-hour bound.
Common situations: Malformed timestamps produced by buggy serializers (e.g. concatenating wrong fields); hand-built offset strings; tests with deliberately bogus offsets.
Related errors
- invalid offset minutes: %d: %s
- invalid offset seconds: %d: %s
- ISO8601: invalid timezone format: %s
- invalid offset format: %s
- error parsing offset hours: %s: %w
AI-assisted analysis of kataras/iris@7bedaf55a0 (2026-08-30).
Data as JSON: /api/errors/24f50d5cab1753ca.
Report an issue: GitHub.