93

I am using the Retrofit library for my REST calls. Most of what I have done has been smooth as butter but for some reason I am having issues converting JSON timestamp strings into java.util.Date objects. The JSON that is coming in looks like this.

{
    "date": "2013-07-16",
    "created_at": "2013-07-16T22:52:36Z",
} 

How can I tell Retrofit or Gson to convert these strings into java.util.Date objects?

giampaolo
  • 6,906
  • 5
  • 45
  • 73
jpotts18
  • 4,951
  • 5
  • 31
  • 31
  • Looks like a duplicate of [this question](http://stackoverflow.com/questions/6696082/set-date-format-in-gson-in-jsp). – Davmrtl Aug 27 '13 at 23:12
  • @Davmrtl: it's not the same since there are two different date formats here. So it requires a different approach. – giampaolo Sep 02 '13 at 23:32
  • Convert `created_at` and all your dates on the server as `date.getTime()` (EPOCH) to get the milliseconds, then on android you just need to call `Date(milliseconds)` – Iglesias Leonardo Jan 30 '23 at 22:24

7 Answers7

191
Gson gson = new GsonBuilder()
    .setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
    .create();

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint(API_BASE_URL)
    .setConverter(new GsonConverter.create(gson))
    .build();

Or the Kotlin equivalent:

val gson = GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()
RestAdapter restAdapter = Retrofit.Builder()
    .baseUrl(API_BASE_URL)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()
    .create(T::class.java)

You can set your customized Gson parser to retrofit. More here: Retrofit Website

Look at Ondreju's response to see how to implement this in retrofit 2.

jobbert
  • 3,297
  • 27
  • 43
gderaco
  • 2,322
  • 1
  • 16
  • 18
  • 8
    Please explain your code in brief so that it is more useful for OP and other readers. – Mohit Jain May 24 '14 at 10:47
  • How can I do this for all of my requests? So Add it to my `ServiceGenerator`? – Zapnologica Nov 19 '15 at 05:34
  • 4
    This actually doesn't apply locale for me. `2016-02-11T13:42:14.401Z` gets deserialized to a date object which is for `13:42:14` in my locale. – Alireza Mirian Feb 11 '16 at 16:02
  • I have tried this, Downloaded items seems to be working now. But when I post a Json Object to the server. Will it then remove my time zone? – Zapnologica Jan 14 '17 at 07:29
  • I spent so much time for detecting problem with crashing my app with no reason after implementing this solution. When I copied and paste dete format something changed a quotation mark from 'T' to **’T’** - there is a different - watch carefully! – LukaszTaraszka Apr 21 '17 at 13:01
  • Find this after hours of searching and worked like a charm. Thanks – maddy23285 Dec 20 '19 at 10:03
  • 2
    Why have you not included the 'Z' parameter for timezone in the end of the string that goes on 'setDateFormat'? I wasted 2 hours here to discover that i should use '"yyyy-MM-dd'T'HH:mm:ss'Z'" instead. – Bruno Dec 10 '20 at 23:52
95

@gderaco's answer updated to retrofit 2.0:

Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create();

Retrofit retrofitAdapter = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
blizzard
  • 5,275
  • 2
  • 34
  • 48
Andrzej Purtak
  • 991
  • 7
  • 9
15

Here is how I did it:

Create DateTime class extending Date and then write a custom deserializer:

public class DateTime extends java.util.Date {

    public DateTime(long readLong) {
        super(readLong);
    }

    public DateTime(Date date) {
        super(date.getTime());
    }       
}

Now for the deserializer part where we register both Date and DateTime converters:

public static Gson gsonWithDate(){
    final GsonBuilder builder = new GsonBuilder();

    builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {  

        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");  
        @Override  
        public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {  
            try {  
                return df.parse(json.getAsString());  
            } catch (final java.text.ParseException e) {  
                e.printStackTrace();  
                return null;  
            }  
        }
    });

    builder.registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {  

        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        @Override  
        public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {  
            try {  
                return new DateTime(df.parse(json.getAsString()));  
            } catch (final java.text.ParseException e) {
                e.printStackTrace();  
                return null;  
            }  
        }
    });

    return builder.create();
}

And when you create your RestAdapter, do the following:

new RestAdapter.Builder().setConverter(gsonWithDate());

Your Foo should look like this:

class Foo {
    Date date;
    DateTime created_at;
}
8

Gson can handle only one datetime format (those specified in builder) plus the iso8601 if parsing with custom format is not possible. So, a solution could be to write your custom deserializer. To solve your problem I defined:

package stackoverflow.questions.q18473011;

import java.util.Date;

public class Foo {

    Date date;
    Date created_at;

    public Foo(Date date, Date created_at){
       this.date = date;
       this.created_at = created_at;
    }

    @Override
    public String toString() {
       return "Foo [date=" + date + ", created_at=" + created_at + "]";
    }

}

with this deserializer:

package stackoverflow.questions.q18473011;

import java.lang.reflect.Type;
import java.text.*;
import java.util.Date;

import com.google.gson.*;

public class FooDeserializer implements JsonDeserializer<Foo> {

     public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        String a = json.getAsJsonObject().get("date").getAsString();
        String b = json.getAsJsonObject().get("created_at").getAsString();

        SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdfDateWithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

        Date date, created;
        try {
           date = sdfDate.parse(a);
           created = sdfDateWithTime.parse(b);
        } catch (ParseException e) {
           throw new RuntimeException(e);
        }

        return new Foo(date, created);
    }

}

Final step is to create a Gson instance with right adapter:

package stackoverflow.questions.q18473011;

import com.google.gson.*;

public class Question {

    /**
     * @param args
     */
    public static void main(String[] args) {
      String s = "{ \"date\": \"2013-07-16\",    \"created_at\": \"2013-07-16T22:52:36Z\"}";


      GsonBuilder builder = new GsonBuilder();
      builder.registerTypeAdapter(Foo.class, new FooDeserializer());

      Gson gson = builder.create();
      Foo myObject = gson.fromJson(s, Foo.class);

      System.out.println("Result: "+myObject);
    }

}

My result:

Result: Foo [date=Tue Jul 16 00:00:00 CEST 2013, created_at=Tue Jul 16 22:52:36 CEST 2013]
giampaolo
  • 6,906
  • 5
  • 45
  • 73
0

Quite literally if you already have an Date object with the name "created_at" in the class you are creating then it is this easy:

Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").create();
YourObject parsedObject1 = gson.fromJson(JsonStringYouGotSomehow, YourObject.class);

And you're done. no complicated overriding needed.

cpoole
  • 346
  • 3
  • 5
  • You should have tried it with Retrofit first before "And you're done. no complicated overriding needed." – Farid Jul 29 '18 at 22:27
0

You can define two new classes like this:

import java.util.Date;

public class MyDate extends Date {
}

and

import java.util.Date;

public class CreatedAtDate extends Date {
}

Your POJO will be like this:

import MyDate;
import CreatedAtDate;

public class Foo {
    MyDate date;
    CreatedAtDate created_at;
}

Finally set your custom deserializer:

public class MyDateDeserializer implements JsonDeserializer<Date> {

    public static final SimpleDateFormat sServerDateDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    @Override
    public MyDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
         if (json != null) {
            final String jsonString = json.getAsString();
            try {
                return (MyDate) sServerDateDateFormat.parse(jsonString);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

and

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyDate.class, new MyDateDeserializer());
shiami
  • 7,174
  • 16
  • 53
  • 68
0

This doesn't answer directly the question asked, but is in my opinion the "state of the art" if the coder has the full freedom of choice on how to solve the problem.

First of all, it's not best solution to use java.util.Date. Reason is that those classes had no ideal behaviour in some corner cases so where superseeded by the Java Instant class etc. check the answer of Basil Bourque in this S.O. question: Creating Date objects in Kotlin for API level less than or equal to 16

So I used the Instant class of ThreeTenABP, and using Kotlin, on Android:

val gson = GsonBuilder().registerTypeAdapter(Instant::class.java,
    JsonDeserializer<Instant> { json: JsonElement, _: Type?, _: JsonDeserializationContext? ->
        ZonedDateTime.parse(
            json.asJsonPrimitive.asString
        ).toInstant()
    }
).create()

val retrofit = Retrofit.Builder()
    .baseUrl(baseUrl)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()
AndrewBloom
  • 2,171
  • 20
  • 30