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
- 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.
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
- 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.
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
- Invalid time zone indicator '{}'
- No time zone indicator
- Invalid number: {}
- Failed parsing '{}' as SQL Date; at path {}
- Failed parsing '{}' as SQL Time; at path {}
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/21db5382c95a0294.
Report an issue: GitHub.