google/gson · error · IndexOutOfBoundsException

Mismatching time zone indicator: {} given, resolves to {}

Error message

Mismatching time zone indicator: {} given, resolves to {}

What it means

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.

Source

Thrown at gson/src/main/java/com/google/gson/internal/bind/util/ISO8601Utils.java:270

          // 18-Jun-2015, tatu: Looks like offsets only work from GMT, not UTC...
          //    not sure why, but that's the way it looks. Further, Javadocs for
          //    `java.util.TimeZone` specifically instruct use of GMT as base for
          //    custom timezones... odd.
          String timezoneId = "GMT" + timezoneOffset;
          // String timezoneId = "UTC" + timezoneOffset;

          timezone = TimeZone.getTimeZone(timezoneId);

          String act = timezone.getID();
          if (!act.equals(timezoneId)) {
            /* 22-Jan-2015, tatu: Looks like canonical version has colons, but we may be given
             *    one without. If so, don't sweat.
             *   Yes, very inefficient. Hopefully not hit often.
             *   If it becomes a perf problem, add 'loose' comparison instead.
             */
            String cleaned = act.replace(":", "");
            if (!cleaned.equals(timezoneId)) {
              throw new IndexOutOfBoundsException(
                  "Mismatching time zone indicator: "
                      + timezoneId
                      + " given, resolves to "
                      + timezone.getID());
            }
          }
        }
      } else {
        throw new IndexOutOfBoundsException(
            "Invalid time zone indicator '" + timezoneIndicator + "'");
      }

      Calendar calendar = new GregorianCalendar(timezone);
      calendar.setLenient(false);
      calendar.set(Calendar.YEAR, year);
      calendar.set(Calendar.MONTH, month - 1);
      calendar.set(Calendar.DAY_OF_MONTH, day);
      calendar.set(Calendar.HOUR_OF_DAY, hour);

View on GitHub (pinned to 310ac341f2)

Solutions

  1. Fix the producer to emit valid ISO8601 offsets (-12:00..+14:00, minutes 00/30/45 typical).
  2. Sanitize offsets at ingestion: parse with a regex and normalize before Gson.
  3. Register a custom date TypeAdapter that uses java.time.OffsetDateTime / ZoneOffset which validates strictly and never silently substitutes.
  4. Reject the record and log it for upstream correction rather than accepting a substituted zone.

Example fix

// before
// JSON: {"at":"2020-01-01T12:00:00+99:99"} -> mismatch

// after: use java.time with strict parsing
Gson g = new GsonBuilder()
  .registerTypeAdapter(Date.class, new TypeAdapter<Date>() {
    private final java.time.format.DateTimeFormatter f =
      java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
    public Date read(JsonReader in) throws IOException {
      // throws DateTimeParseException on bad offset instead of substituting
      return Date.from(java.time.OffsetDateTime.parse(in.nextString(), f).toInstant());
    }
    public void write(JsonWriter out, Date v) throws IOException {
      out.value(f.format(v.toInstant().atOffset(java.time.ZoneOffset.UTC)));
    }
  }).create();
Defensive patterns

Strategy: validation

Validate before calling

private static final Pattern OFFSET =
  Pattern.compile("([+-])(\\d{2}):(\\d{2})$");
java.util.regex.Matcher m = OFFSET.matcher(raw);
if (m.find()) {
  int hh = Integer.parseInt(m.group(2)), mm = Integer.parseInt(m.group(3));
  if (hh > 14 || mm > 59) throw new IllegalArgumentException("Invalid offset: " + raw);
} else {
  throw new IllegalArgumentException("Missing/invalid offset: " + raw);
}

Try / catch

try {
  return gson.fromJson(json, Event.class);
} catch (JsonSyntaxException e) {
  if (e.getCause() instanceof ParseException
      && e.getCause().getMessage().contains("Mismatching time zone indicator")) {
    // reject or normalize offset, then retry with a corrected string
    throw new IllegalArgumentException("Malformed timezone offset in payload", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of google/gson@310ac341f2 (2026-08-10). Data as JSON: /api/errors/21db5382c95a0294. Report an issue: GitHub.