{"id":"749d35c36a0d474b","repo":"google/gson","slug":"no-time-zone-indicator","errorCode":null,"errorMessage":"No time zone indicator","messagePattern":"No time zone indicator","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extras/src/main/java/com/google/gson/typeadapters/UtcDateTypeAdapter.java","lineNumber":193,"sourceCode":"          offset += 1;\n        }\n        // second and milliseconds can be optional\n        if (date.length() > offset) {\n          char c = date.charAt(offset);\n          if (c != 'Z' && c != '+' && c != '-') {\n            seconds = parseInt(date, offset, offset += 2);\n            // milliseconds can be optional in the format\n            if (checkOffset(date, offset, '.')) {\n              milliseconds = parseInt(date, offset += 1, offset += 3);\n            }\n          }\n        }\n      }\n\n      // extract timezone\n      String timezoneId;\n      if (date.length() <= offset) {\n        throw new IllegalArgumentException(\"No time zone indicator\");\n      }\n      char timezoneIndicator = date.charAt(offset);\n      if (timezoneIndicator == '+' || timezoneIndicator == '-') {\n        String timezoneOffset = date.substring(offset);\n        timezoneId = GMT_ID + timezoneOffset;\n        offset += timezoneOffset.length();\n      } else if (timezoneIndicator == 'Z') {\n        timezoneId = GMT_ID;\n        offset += 1;\n      } else {\n        throw new IndexOutOfBoundsException(\"Invalid time zone indicator \" + timezoneIndicator);\n      }\n\n      TimeZone timezone = TimeZone.getTimeZone(timezoneId);\n      if (!timezone.getID().equals(timezoneId)) {\n        throw new IndexOutOfBoundsException();\n      }\n","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/extras/src/main/java/com/google/gson/typeadapters/UtcDateTypeAdapter.java#L175-L211","documentation":"Thrown internally by UtcDateTypeAdapter's ISO-8601 parser when the date string has no characters left at the position where a time-zone indicator ('Z', '+', or '-') is expected. The adapter requires every parsed date to carry an explicit timezone; a bare date like \"2024-01-01T00:00:00\" without a zone is rejected. This IllegalArgumentException is caught and converted into a ParseException, which read() then wraps as a JsonParseException.","triggerScenarios":"Parsing a date string that ends right after the time component with no zone, e.g. \"2024-01-01T12:00:00\"; a date-only string \"20240101\" where the optional time section is absent and nothing follows; truncated input. Surfaced to the Gson caller as JsonParseException during fromJson when the UtcDateTypeAdapter is registered.","commonSituations":"Backend sends ISO-8601 without timezone (assumed local/UTC implicitly); mixing date formats where some payloads omit the zone; data from systems that produce \"local\" timestamps; trimming trailing characters during transport.","solutions":["Ensure the date string includes an explicit timezone: append 'Z' for UTC or '+HH:mm'/'-HH:mm' for an offset (e.g. \"2024-01-01T12:00:00Z\").","Normalize date strings on the producer to always emit a timezone before sending.","If you cannot change the input, register a custom TypeAdapter<Date> that defaults omitted zones to UTC instead of using UtcDateTypeAdapter.","Pre-validate the string with a regex like .*[ZzZ+-]\\d\\d:?\\d\\d$ before parsing."],"exampleFix":"// before\nString json = \"\\\"2024-01-01T12:00:00\\\"\"; // no timezone indicator\nDate d = gson.fromJson(json, Date.class); // throws (via JsonParseException)\n\n// after\nString json = \"\\\"2024-01-01T12:00:00Z\\\"\";\nDate d = gson.fromJson(json, Date.class);","handlingStrategy":"validation","validationCode":"// Validate the date string has a timezone indicator before parsing\nstatic void requireTimezone(String date) {\n  // last meaningful char must be Z, +, or -; or end with offset like +HH:mm\n  if (!date.matches(\".*[Zz]$|.*/[+-]\\\\d{2}:?\\\\d{2}$\")) {\n    throw new IllegalArgumentException(\"Date missing timezone: \" + date);\n  }\n}","typeGuard":"static boolean hasTimezoneIndicator(String date) {\n  if (date == null || date.isEmpty()) return false;\n  char last = date.charAt(date.length() - 1);\n  if (last == 'Z' || last == 'z') return true;\n  // tolerate trailing offset +HH:mm / -HH:mm\n  return date.matches(\".*[+-]\\\\d{2}:?\\\\d{2}$\");\n}","tryCatchPattern":"try {\n  Date d = gson.fromJson(json, Date.class);\n} catch (JsonParseException e) {\n  Throwable c = e.getCause();\n  if (c != null && c.getMessage() != null && c.getMessage().contains(\"No time zone indicator\")) {\n    // retry after appending 'Z' if UTC was intended, or reject\n  } else throw e;\n}","preventionTips":["Always emit explicit timezones (UTC 'Z' or an offset) on the producer side.","Pre-validate incoming date strings with a regex before parsing.","If input legitimately omits the zone, register a custom Date adapter that defaults to UTC.","Prefer java.time Instant/OffsetDateTime in APIs to make the zone requirement explicit."],"tags":["date","iso8601","utc-adapter","parsing","missing-timezone"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}