Convert String date to Object yields Invalid time zone indicator '0'

Yonatan Nir picture Yonatan Nir · Jan 31, 2017 · Viewed 11.5k times · Source

I got an Android app which receives Json responses from a web service. One of the responses is a json string with a date inside. I get the date in the form of a number like "1476399300000". When I try to create an object with it using GSON I get this error:

Failed to parse date ["1476399300000']: Invalid time zone indicator '0' (at offset 0)

Both sides are working with java.util.Date

How can I fix this issue?

Answer

Lyubomyr Shaydariv picture Lyubomyr Shaydariv · Jan 31, 2017

The value 1476399300000 looks like ms from the Unix epoch beginning. Just add a type adapter to your Gson:

final class UnixEpochDateTypeAdapter
        extends TypeAdapter<Date> {

    private static final TypeAdapter<Date> unixEpochDateTypeAdapter = new UnixEpochDateTypeAdapter();

    private UnixEpochDateTypeAdapter() {
    }

    static TypeAdapter<Date> getUnixEpochDateTypeAdapter() {
        return unixEpochDateTypeAdapter;
    }

    @Override
    public Date read(final JsonReader in)
            throws IOException {
        // this is where the conversion is performed
        return new Date(in.nextLong());
    }

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter out, final Date value)
            throws IOException {
        // write back if necessary or throw UnsupportedOperationException
        out.value(value.getTime());
    }

}

Configure your Gson instance:

final Gson gson = new GsonBuilder()
        .registerTypeAdapter(Date.class, getUnixEpochDateTypeAdapter())
        .create();

Gson instances are thread-safe as well as UnixEpochDateTypeAdapter is, and can exist as one instance globally. Example:

final class Mapping {   
    final Date date = null; 
}
final String json = "{\"date\":1476399300000}";
final Mapping mapping = gson.fromJson(json, Mapping.class);
System.out.println(mapping.date);
System.out.println(gson.toJson(mapping));

would give the following output:

Fri Oct 14 01:55:00 EEST 2016
{"date":1476399300000}

Note that the type adapter is configured to override the default Gson date type adapter. So you might need to use a more complicated analysis to detect whether is just ms of the Unix epoch. Also note, that you could use JsonDeserializer, but the latter works in JSON-tree manner whilst type adapters work in the streaming way that's somewhat more efficient probably not accumulating intermediate results.

Edit:

Also, it may look confusing, but Gson can make value conversions for primitives. Despite your payload has a string value, JsonReader.nextLong() can read a string primitive as a long value. So the UnixEpochDateTypeAdapter.write should be out.value(String.valueOf(value.getTime())); in order not to modify JSON literals.

Edit

There's also a shorter solution (working with JSON in-memory trees rather than data streaming) which is simply:

final Gson builder = new GsonBuilder()
    .registerTypeAdapter(Date.class, new JsonDeserializer<Date>() { 
       public Date deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
          return new Date(jsonElement.getAsJsonPrimitive().getAsLong()); 
       } 
    })
    .create();