kataras/iris · error

ISO8601: invalid timezone offset sign: %c

Error message

ISO8601: invalid timezone offset sign: %c

What it means

The 6+-character offset tail does not begin with '+' or '-'; the first byte is something else, so it is not a signed UTC offset. parseOffsetToSeconds checks the sign character explicitly before parsing hours/minutes. This usually means the LastIndexFunc match found a '+'/'-' earlier in the string and the actual tail is not an offset, or the string is malformed.

Source

Thrown at x/jsonx/iso8601.go:197

		return time.Time{}, err
	}

	loc, ok := fixedEastUTCLocations[secondsEastUTC]
	if !ok {
		loc = time.FixedZone("", secondsEastUTC)
	}

	return time.ParseInLocation(ISO8601ZUTCOffsetLayoutWithoutMicroseconds, s, loc)
}

func parseOffsetToSeconds(offsetText string) (int, error) {
	if len(offsetText) < 6 {
		return 0, fmt.Errorf("ISO8601: invalid timezone offset length: %s", offsetText)
	}

	sign := offsetText[0]
	if sign != '-' && sign != '+' {
		return 0, fmt.Errorf("ISO8601: invalid timezone offset sign: %c", sign)
	}

	hours, err := strconv.Atoi(offsetText[1:3])
	if err != nil {
		return 0, fmt.Errorf("ISO8601: %w", err)
	}

	minutes, err := strconv.Atoi(offsetText[4:6])
	if err != nil {
		return 0, fmt.Errorf("ISO8601: %w", err)
	}

	secondsEastUTC := (hours*60 + minutes) * 60
	if sign == '-' {
		secondsEastUTC = -secondsEastUTC
	}

	return secondsEastUTC, nil

View on GitHub (pinned to 7bedaf55a0)

Solutions

  1. Ensure the offset starts with an explicit sign: '+03:00' or '-05:00', not '03:00'.
  2. For UTC use 'Z' so the parser takes the ISO8601LayoutWithTimezone branch instead of the offset path.
  3. Validate input with a regex like ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([+-]\d{2}:\d{2}|Z)?$ before parsing and reject bad values with a clear app-level message.
  4. Trace where the string was built; a replace/trim step is likely mangling the sign character.

Example fix

// before
tt, err := jsonx.ParseISO8601("2024-01-02T15:04:05 03:00") // invalid timezone offset sign: ' '
// after
tt, err := jsonx.ParseISO8601("2024-01-02T15:04:05+03:00") // ok
Defensive patterns

Strategy: validation

Validate before calling

var signedOffsetRe = regexp.MustCompile(`^[+-]\d{2}:\d{2}$`)
func offsetIsSigned(s string) bool {
	i := strings.LastIndexAny(s, "+-")
	return i != -1 && signedOffsetRe.MatchString(s[i:])
}

Type guard

func tailStartsWithSign(s string) bool {
	i := strings.LastIndexAny(s, "+-")
	return i != -1 && i == len(s)-6
}

Try / catch

tt, err := jsonx.ParseISO8601(s)
if err != nil {
	if strings.Contains(err.Error(), "invalid timezone offset sign") {
		return fmt.Errorf("timestamp %q: offset must start with '+' or '-' (or use 'Z' for UTC)", s)
	}
	return err
}

Prevention

When it happens

Trigger: parseWithOffset passes a tail like 'T15:04:05' (no sign at position 0), e.g. input '2024-01-02T15:04:05' where LastIndexFunc found the '-' inside the date when offsets are searched incorrectly, or garbage tails like '...+x3:00' where offsetText[0] is not reached — more precisely any offsetText whose byte 0 is neither '+' nor '-': e.g. ' 03:00' or 'Z03:00'.

Common situations: Strings containing a dash in the date combined with odd slicing by upstream code; timestamps where 'Z' was replaced by a space ('2024-01-02T15:04:05 03:00'); locale-formatted datetimes sneaking into an ISO8601 field; corrupted strings after manual string manipulation.

Related errors


AI-assisted analysis of kataras/iris@7bedaf55a0 (2026-08-30). Data as JSON: /api/errors/22bb0361b9f94435. Report an issue: GitHub.