{"record":{"id":"21db5382c95a0294","repo":"google/gson","slug":"mismatching-time-zone-indicator-given-resolve","errorCode":null,"errorMessage":"Mismatching time zone indicator: {} given, resolves to {}","messagePattern":"Mismatching time zone indicator: (.+?) given, resolves to (.+?)","errorType":"exception","errorClass":"IndexOutOfBoundsException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/internal/bind/util/ISO8601Utils.java","lineNumber":270,"sourceCode":"          // 18-Jun-2015, tatu: Looks like offsets only work from GMT, not UTC...\n          //    not sure why, but that's the way it looks. Further, Javadocs for\n          //    `java.util.TimeZone` specifically instruct use of GMT as base for\n          //    custom timezones... odd.\n          String timezoneId = \"GMT\" + timezoneOffset;\n          // String timezoneId = \"UTC\" + timezoneOffset;\n\n          timezone = TimeZone.getTimeZone(timezoneId);\n\n          String act = timezone.getID();\n          if (!act.equals(timezoneId)) {\n            /* 22-Jan-2015, tatu: Looks like canonical version has colons, but we may be given\n             *    one without. If so, don't sweat.\n             *   Yes, very inefficient. Hopefully not hit often.\n             *   If it becomes a perf problem, add 'loose' comparison instead.\n             */\n            String cleaned = act.replace(\":\", \"\");\n            if (!cleaned.equals(timezoneId)) {\n              throw new IndexOutOfBoundsException(\n                  \"Mismatching time zone indicator: \"\n                      + timezoneId\n                      + \" given, resolves to \"\n                      + timezone.getID());\n            }\n          }\n        }\n      } else {\n        throw new IndexOutOfBoundsException(\n            \"Invalid time zone indicator '\" + timezoneIndicator + \"'\");\n      }\n\n      Calendar calendar = new GregorianCalendar(timezone);\n      calendar.setLenient(false);\n      calendar.set(Calendar.YEAR, year);\n      calendar.set(Calendar.MONTH, month - 1);\n      calendar.set(Calendar.DAY_OF_MONTH, day);\n      calendar.set(Calendar.HOUR_OF_DAY, hour);","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/google/gson/blob/310ac341f2f92a454b229bf21f70d2d18b2b6db7/gson/src/main/java/com/google/gson/internal/bind/util/ISO8601Utils.java#L252-L288","documentation":"Thrown by ISO8601Utils when a numeric timezone offset like +05:30 is given but java.util.TimeZone.getTimeZone resolves it to a different zone (its canonical GMT ID differs from the constructed one, even after stripping colons). This indicates an offset that TimeZone cannot represent exactly, e.g. an out-of-range offset or a malformed offset that TimeZone silently substitutes with GMT.","triggerScenarios":"Date string has an offset whose value is invalid (e.g. \"+99:99\") or whose canonical form does not round-trip through TimeZone.getTimeZone; the parser builds \"GMT+HH:mm\" and TimeZone returns a zone whose getID() differs (commonly falling back to \"GMT\"), so the equality check fails.","commonSituations":"Malformed offsets from buggy producers (\"+5:30\", \"+0530extra\"); offsets with minutes > 59 or hours > 23; corrupted timestamps in logs; producers that construct offsets by string concatenation without validation.","solutions":["Fix the producer to emit valid ISO8601 offsets (-12:00..+14:00, minutes 00/30/45 typical).","Sanitize offsets at ingestion: parse with a regex and normalize before Gson.","Register a custom date TypeAdapter that uses java.time.OffsetDateTime / ZoneOffset which validates strictly and never silently substitutes.","Reject the record and log it for upstream correction rather than accepting a substituted zone."],"exampleFix":"// before\n// JSON: {\"at\":\"2020-01-01T12:00:00+99:99\"} -> mismatch\n\n// after: use java.time with strict parsing\nGson g = new GsonBuilder()\n  .registerTypeAdapter(Date.class, new TypeAdapter<Date>() {\n    private final java.time.format.DateTimeFormatter f =\n      java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;\n    public Date read(JsonReader in) throws IOException {\n      // throws DateTimeParseException on bad offset instead of substituting\n      return Date.from(java.time.OffsetDateTime.parse(in.nextString(), f).toInstant());\n    }\n    public void write(JsonWriter out, Date v) throws IOException {\n      out.value(f.format(v.toInstant().atOffset(java.time.ZoneOffset.UTC)));\n    }\n  }).create();","handlingStrategy":"validation","validationCode":"private static final Pattern OFFSET =\n  Pattern.compile(\"([+-])(\\\\d{2}):(\\\\d{2})$\");\njava.util.regex.Matcher m = OFFSET.matcher(raw);\nif (m.find()) {\n  int hh = Integer.parseInt(m.group(2)), mm = Integer.parseInt(m.group(3));\n  if (hh > 14 || mm > 59) throw new IllegalArgumentException(\"Invalid offset: \" + raw);\n} else {\n  throw new IllegalArgumentException(\"Missing/invalid offset: \" + raw);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return gson.fromJson(json, Event.class);\n} catch (JsonSyntaxException e) {\n  if (e.getCause() instanceof ParseException\n      && e.getCause().getMessage().contains(\"Mismatching time zone indicator\")) {\n    // reject or normalize offset, then retry with a corrected string\n    throw new IllegalArgumentException(\"Malformed timezone offset in payload\", e);\n  }\n  throw e;\n}","preventionTips":["Validate numeric offsets are within [-12:00..+14:00] and minutes in {00,30,45} as applicable.","Prefer java.time.ZoneOffset / OffsetDateTime parsing which fails fast on invalid offsets.","Contract-test edge-case offsets (UTC, +05:30, -08:00) to ensure round-trip.","Reject substituted-GMT behavior by validating offset round-trip through TimeZone."],"tags":["gson","date-parsing","iso8601","timezone","offset","index-out-of-bounds"],"backgroundTag":null,"analyzedSha":"310ac341f2f92a454b229bf21f70d2d18b2b6db7","analyzedAt":"2026-08-10T02:58:47.455Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}