{"record":{"id":"22bb0361b9f94435","repo":"kataras/iris","slug":"iso8601-invalid-timezone-offset-sign-c","errorCode":null,"errorMessage":"ISO8601: invalid timezone offset sign: %c","messagePattern":"ISO8601: invalid timezone offset sign: %c","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"x/jsonx/iso8601.go","lineNumber":197,"sourceCode":"\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 {\n\t\treturn 0, fmt.Errorf(\"ISO8601: invalid timezone offset length: %s\", offsetText)\n\t}\n\n\tsign := offsetText[0]\n\tif sign != '-' && sign != '+' {\n\t\treturn 0, fmt.Errorf(\"ISO8601: invalid timezone offset sign: %c\", sign)\n\t}\n\n\thours, err := strconv.Atoi(offsetText[1:3])\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"ISO8601: %w\", err)\n\t}\n\n\tminutes, err := strconv.Atoi(offsetText[4:6])\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"ISO8601: %w\", err)\n\t}\n\n\tsecondsEastUTC := (hours*60 + minutes) * 60\n\tif sign == '-' {\n\t\tsecondsEastUTC = -secondsEastUTC\n\t}\n\n\treturn secondsEastUTC, nil","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/kataras/iris/blob/7bedaf55a0b64bbb2248a5845a2c60d81a30996a/x/jsonx/iso8601.go#L179-L215","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","solutions":["Ensure the offset starts with an explicit sign: '+03:00' or '-05:00', not '03:00'.","For UTC use 'Z' so the parser takes the ISO8601LayoutWithTimezone branch instead of the offset path.","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.","Trace where the string was built; a replace/trim step is likely mangling the sign character."],"exampleFix":"// before\ntt, err := jsonx.ParseISO8601(\"2024-01-02T15:04:05 03:00\") // invalid timezone offset sign: ' '\n// after\ntt, err := jsonx.ParseISO8601(\"2024-01-02T15:04:05+03:00\") // ok","handlingStrategy":"validation","validationCode":"var signedOffsetRe = regexp.MustCompile(`^[+-]\\d{2}:\\d{2}$`)\nfunc offsetIsSigned(s string) bool {\n\ti := strings.LastIndexAny(s, \"+-\")\n\treturn i != -1 && signedOffsetRe.MatchString(s[i:])\n}","typeGuard":"func tailStartsWithSign(s string) bool {\n\ti := strings.LastIndexAny(s, \"+-\")\n\treturn i != -1 && i == len(s)-6\n}","tryCatchPattern":"tt, err := jsonx.ParseISO8601(s)\nif err != nil {\n\tif strings.Contains(err.Error(), \"invalid timezone offset sign\") {\n\t\treturn fmt.Errorf(\"timestamp %q: offset must start with '+' or '-' (or use 'Z' for UTC)\", s)\n\t}\n\treturn err\n}","preventionTips":["Use 'Z' for UTC instead of an offset without a sign","Reject locale-formatted datetimes at input boundaries; accept only ISO8601","Avoid manual string surgery on timestamps that can drop the sign character","Validate the tail matches ^[+-]\\d{2}:\\d{2}$ before calling the parser"],"tags":["go","jsonx","iso8601","timezone-offset","malformed-input"],"backgroundTag":"invalid-iso8601-offset","analyzedSha":"7bedaf55a0b64bbb2248a5845a2c60d81a30996a","analyzedAt":"2026-08-30T20:38:16.250Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}