{"record":{"id":"259e4b186f2efdf8","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":"gson/src/main/java/com/google/gson/internal/bind/util/ISO8601Utils.java","lineNumber":231,"sourceCode":"              switch (parseEndOffset - offset) { // number of digits parsed\n                case 2:\n                  milliseconds = fraction * 10;\n                  break;\n                case 1:\n                  milliseconds = fraction * 100;\n                  break;\n                default:\n                  milliseconds = fraction;\n              }\n              offset = endOffset;\n            }\n          }\n        }\n      }\n\n      // extract timezone\n      if (date.length() <= offset) {\n        throw new IllegalArgumentException(\"No time zone indicator\");\n      }\n\n      TimeZone timezone = null;\n      char timezoneIndicator = date.charAt(offset);\n\n      if (timezoneIndicator == 'Z') {\n        timezone = TIMEZONE_UTC;\n        offset += 1;\n      } else if (timezoneIndicator == '+' || timezoneIndicator == '-') {\n        String timezoneOffset = date.substring(offset);\n\n        // When timezone has no minutes, we should append it, valid timezones are, for example:\n        // +00:00, +0000 and +00\n        timezoneOffset = timezoneOffset.length() >= 5 ? timezoneOffset : timezoneOffset + \"00\";\n\n        offset += timezoneOffset.length();\n        // 18-Jun-2015, tatu: Minor simplification, skip offset of \"+0000\"/\"+00:00\"\n        if (timezoneOffset.equals(\"+0000\") || timezoneOffset.equals(\"+00:00\")) {","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/google/gson/blob/310ac341f2f92a454b229bf21f70d2d18b2b6db7/gson/src/main/java/com/google/gson/internal/bind/util/ISO8601Utils.java#L213-L249","documentation":"Thrown by Gson's ISO8601Utils.parse when a date string that contains a time component ('T') has no trailing timezone indicator. After parsing year/month/day/[hour/min/sec], the parser checks position; if the string ends without a 'Z' or +/- offset, the date is considered incomplete and IllegalArgumentException(\"No time zone indicator\") is thrown, which ISO8601Utils rewraps into a ParseException.","triggerScenarios":"Parsing a date-time string in ISO8601 form missing the timezone, e.g. \"2020-01-01T12:00:00\" (no trailing Z or offset), or \"2020-01-01T12:00\". This occurs when Gson deserializes into java.util.Date / java.util.Calendar using the default ISO format and the input omits the zone.","commonSituations":"Backends emitting 'local' datetimes without zone; databases returning TIMESTAMP without timezone; legacy formats like \"yyyy-MM-dd'T'HH:mm:ss\" produced by older Jackson/Joda configs; mismatches between producer (no zone) and Gson consumer (expects zone).","solutions":["Ensure the producer appends 'Z' (for UTC) or an explicit offset like '+00:00' to every date-time string.","Register a custom TypeAdapter<Date> that uses SimpleDateFormat with a lenient pattern matching zoneless strings and a default TimeZone.","If you cannot change the producer, pre-process the JSON to append a default zone before parsing.","Switch the target field to java.time.LocalDateTime (with a custom adapter) if the value is genuinely zoneless."],"exampleFix":"// before\npublic class Event { public Date at; }\n// JSON: {\"at\":\"2020-01-01T12:00:00\"} -> 'No time zone indicator'\n\n// after: producer emits UTC zone indicator\n// {\"at\":\"2020-01-01T12:00:00Z\"}\n\n// or custom adapter accepting zoneless as UTC:\nGson g = new GsonBuilder()\n  .registerTypeAdapter(Date.class, new TypeAdapter<Date>() {\n    private final DateFormat f = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss\");\n    { f.setTimeZone(TimeZone.getTimeZone(\"UTC\")); }\n    public Date read(JsonReader in) throws IOException {\n      try { return f.parse(in.nextString()); }\n      catch (ParseException e) { throw new JsonSyntaxException(e); }\n    }\n    public void write(JsonWriter out, Date v) throws IOException { out.value(f.format(v)); }\n  }).create();","handlingStrategy":"validation","validationCode":"private static final Pattern ISO_Z =\n  Pattern.compile(\"^\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}(\\\\.\\\\d+)?(Z|[+-]\\\\d{2}:\\\\d{2})$\");\nString raw = jsonNode.get(\"at\").getAsString();\nif (raw != null && !ISO_Z.matcher(raw).matches()) {\n  // append 'Z' if the producer omits zone, or reject\n  raw = raw + \"Z\";\n}","typeGuard":null,"tryCatchPattern":"try {\n  return gson.fromJson(json, Event.class);\n} catch (JsonSyntaxException e) {\n  if (e.getCause() instanceof ParseException && e.getCause().getMessage().contains(\"No time zone indicator\")) {\n    // append default zone and retry once\n    JsonObject patched = jsonNode.deepCopy();\n    patched.addProperty(\"at\", patched.get(\"at\").getAsString() + \"Z\");\n    return gson.fromJson(patched, Event.class);\n  }\n  throw e;\n}","preventionTips":["Mandate ISO8601 with explicit zone ('Z' or offset) in producer contracts.","Pin producer timestamp formatting through a single utility (e.g. Instant.toString).","Contract-test that all date fields include a zone indicator.","Consider java.time.Instant/OffsetDateTime typed fields with custom adapters for strict semantics."],"tags":["gson","date-parsing","iso8601","timezone","parse-exception"],"backgroundTag":null,"analyzedSha":"310ac341f2f92a454b229bf21f70d2d18b2b6db7","analyzedAt":"2026-08-10T02:58:47.455Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}