Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DateTypeAdapter deserialization fails based on user's 24 hour preference #935

Open
anthonycr opened this issue Sep 27, 2016 · 16 comments
Open

Comments

@anthonycr
Copy link

anthonycr commented Sep 27, 2016

I've encountered a bug affecting (de)serialization of dates on Android devices using gson 2.7.

Description/Stacktrace

When trying to deserialize this date (Sep 26, 2016 18:13:24), after switching my device time preferences from 24 hour to 12 hour, using DateTypeAdapter.deserializeToDate, the following exception was thrown:

java.lang.NumberFormatException: Invalid number: Sep 
    at com.google.gson.internal.bind.util.ISO8601Utils.parseInt(ISO8601Utils.java:311)
    at com.google.gson.internal.bind.util.ISO8601Utils.parse(ISO8601Utils.java:129)

Steps to reproduce

Reproduced on a Nexus 5X running Android 7.0. I have also seen this crash reported from varying devices and Android versions.

  1. Create a java.util.Date object at the following time: Sep 26, 2016 18:13:24 (any date should work, but I will use this date for the example).
  2. Serialize the date object using DateTypeAdapter.write, observe that the output is either the string above or Sep 26, 2016 6:13:24 PM depending on your device's 24 hour time settings.
  3. Go to device date & time settings.
  4. Switch to using 24-hour format (or vice versa)
  5. Try to deserialize the date string using DateTypeAdapter.deserializeToDate and observe the exception above being thrown.

If you are using the 24-hour format

  • DateTypeAdapter.deserializeToDate -> Sep 26, 2016 18:13:24 -> works as expected
  • DateTypeAdapter.deserializeToDate -> Sep 26, 2016 6:13:24 PM -> throws an exception

If you are using the 12-hour format

  • DateTypeAdapter.deserializeToDate -> Sep 26, 2016 18:13:24 -> throws an exception
  • DateTypeAdapter.deserializeToDate -> Sep 26, 2016 6:13:24 PM -> works as expected

That the ISO8601Utils cannot deserialize this string does not seem to be the issue. The problem seems to be that the DateFormat object enUsFormat is used by default to serialize the Date object. Then when it deserializes it, it tries to format it using this same DateFormat, if that fails it's falling back to another DateFormat and finally the ISO8601Utils, which also will fail because it's not meant to handle this format. When the 24-hour time preference is switched, the DateFormat object no longer can correctly format the serialized string, and therefore the adapter falls back to ISO8601Utils which throws an error. DefaultDateTypeAdapter also appears to have the same problem as DateTypeAdapter as it is contains the same (de)serialization logic for java.util.Date objects.

A workaround I am using is to create a TypeAdapter to handle dates and to use ISO8601Utils directly rather than default to using DateFormat so that the serialized string will be agnostic of the user's time preferences.

@anthonycr anthonycr changed the title DateTypeAdapter deserialization fails depending on user's 24 hour preference DateTypeAdapter deserialization fails based on user's 24 hour preference Sep 27, 2016
@mittt
Copy link

mittt commented Sep 29, 2016

I was about to file the same issue. In versions below 2.5 the exception is this one:
Caused by: com.google.gson.JsonSyntaxException: Sep 19, 2016 19:51:18

You get it this way:

  1. Set your device to 24h time
  2. Serialize a Date object and store it somewhere
  3. Set your device to 12h time
  4. Deserialize the object
  5. You get com.google.gson.JsonSyntaxException: Sep 19, 2016 19:51:18

The problem is that in Android
enUsFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US);
gives different result, depending on the time setting.

@inder123
Copy link
Collaborator

@ssawchenko
Copy link

ssawchenko commented Jan 12, 2017

+1'ing this post as I spent the morning tracking down an exception being thrown due to this as well.

As per the above suggestion, I took UtcDateTypeAdapter.java and then added in the case to handle and parse out MMM dd, yyyy h:mm:ss a and MMM dd, yyyy hh:mm:ss to help bridge old versions of my app to use the new adapter.

@royerdavid
Copy link

iander123: This adapter gives the same error

@wman1980
Copy link

wman1980 commented May 24, 2017

I am facing the same issue with DateTypeAdapter throwing a JsonSyntaxException.

I have a Java Date Object like this "Sat May 13 00:00:00 GMT+02:00 2017" and save this via Gson via new GsonBuilder().create().toJson(date) with the following json result "May 13, 2017 00:00:00".

If I want to retrieve my Date object from json I get the JsonSyntaxException: com.google.gson.JsonSyntaxException: May 9, 2017 00:00:00

Any solutions to for this?

@ssawchenko
Copy link

@wman1980 I modified this adapter here - see lines 241+. The file itself won't run as-is because I pulled out some project specific logging includes, but you should be able to re-use the chunk of code I added.

@wman1980
Copy link

wman1980 commented May 4, 2018

@ssawchenko that works. Thanks.

@kishorereddy11
Copy link

@ssawchenko may i know where can i put that adapter in my application and how to specify our date object in my class . Please reply ASAP. Thanks in advance.

@Airsaid
Copy link

Airsaid commented Jan 29, 2019

This problem can be solved by using a Custom TypeAdapter. Example:

public class DateTypeAdapter extends TypeAdapter<Date> {
    @Override
    public void write(JsonWriter out, Date value) throws IOException {
        out.beginObject();
        if (null == value) {
            out.nullValue();
        } else {
            out.name("date").value(value.getTime());
        }
        out.endObject();
    }

    @Override
    public Date read(JsonReader in) throws IOException {
        in.beginObject();
        Date date = null;
        while (in.hasNext()) {
            switch (in.nextName()) {
                case "date":
                    date = new Date(in.nextLong());
                    break;
            }
        }
        in.endObject();
        return date;
    }
}
new GsonBuilder()
                .registerTypeAdapter(Date.class, new DateTypeAdapter())
                .setPrettyPrinting()
                .create();

@AAverin
Copy link

AAverin commented Feb 26, 2019

Is there any official solution to this problem by GSON library?
Are we expected to provide a custom Date parsing implementation?

@JakeWharton
Copy link
Contributor

Yes. Avoid the built-in support for Dates.

@wman1980
Copy link

wman1980 commented Mar 20, 2019

I had to use my own deserializer, because the UtcDateTypeAdapter added timezone differences to the date, e.g. +1 hour etc.

So I am using now my own:

public class DateDeserializer implements JsonDeserializer<Date> {

    @Override
    public Date deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
        String date = element.getAsString();

        // Try 12 hour
        try {
            SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy h:mm:ss a", Locale.ENGLISH);
            Date result = formatter.parse(date);
            return result;
        } catch (ParseException e) {
            e.printStackTrace();
        }

        // Try 24 hour
        try {
            SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy hh:mm:ss");
            Date result = formatter.parse(date);
            return result;
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return null;
    }
}

Maybe it helps.

@bercik
Copy link

bercik commented May 8, 2021

2021 and there is still no fix, this is ridiculous...

@bercik
Copy link

bercik commented May 8, 2021

@wman1980 I've tried your solution and it works. I've created GsonProvider and use it instead of doing new Gson() in the code:

public class GsonProvider {

    public static Gson provide() {
        return new GsonBuilder()
                .registerTypeAdapter(Date.class, new DateDeserializer())
                .create();
    }
}

Usage:
Gson gson = GsonProvider.provide();

I had to use my own deserializer, because the UtcDateTypeAdapter added timezone differences to the date, e.g. +1 hour etc.

So I am using now my own:

public class DateDeserializer implements JsonDeserializer<Date> {

    @Override
    public Date deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
        String date = element.getAsString();

        // Try 12 hour
        try {
            SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy h:mm:ss a", Locale.ENGLISH);
            Date result = formatter.parse(date);
            return result;
        } catch (ParseException e) {
            e.printStackTrace();
        }

        // Try 24 hour
        try {
            SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy hh:mm:ss");
            Date result = formatter.parse(date);
            return result;
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return null;
    }
}

Maybe it helps.

@bercik
Copy link

bercik commented May 26, 2021

One more thing, I've needed to add Locale.US inside try 24 hour format, like this:

        // Try 24 hour
        try {
            SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy hh:mm:ss", Locale.US);
            Date result = formatter.parse(date);
            return result;
        } catch (ParseException e) {
            e.printStackTrace();
        }

Otherwise it seems that for some users it still won't work (I guess depending on their locale)

@danielwilson1702
Copy link

The proposed DateDeserializer here doesn't deserialize 24hr dates correctly - if I serialize the time with a device in 24 hour mode of May 6, 2022 12:13:36, when I deserialize it I get Fri May 06 00:13:36 GMT+01:00 2022, so 12 midday is translated to midnight.

The reason appears to be that "MMM dd, yyyy hh:mm:ss" should be "MMM dd, yyyy HH:mm:ss", where capitals represent 24 hour time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests