Q:

Gson date deserialization failing

I'm pulling my hair since several hours on this one.
I am using Gson 2.2.4 in an android project (api ver 21 (android 5.0)).
I am using app writen in nodejs/typescript as backend, this backend serializes json data for the android client. 
In android client I am doing this: 
Gson gs = new GsonBuilder()
        .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
        .create();
Type listType = new TypeToken<ArrayList<Recording>>(){}.getType();
final ArrayList<Recording> schedule = gs.fromJson(args[0].toString(), listType);

Serialized classes:
Recording:
package net.misove.mypvrcommon.entities;

/**
 * Created by mhainc on 2/10/2016.
 */
public class Recording {
    public Recording(EPGItem epgItem, String channelName) {
        this.epgItem = epgItem;
        this.channelName = channelName;
    }

    public EPGItem getEpgItem() {
        return epgItem;
    }

    private EPGItem epgItem;

    public String getChannelName() {
        return channelName;
    }

    public void setChannelName(String channelName) {
        this.channelName = channelName;
    }

    private String channelName;
}

EPGItem: 
package net.misove.mypvrcommon.entities;

import net.misove.mypvrcommon.util.Helpers;

import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;

import java.sql.Time;
import java.util.Calendar;
import java.util.Date;

/**
 * Created by mhainc on 2/9/2016.
 */
@Root(name="programme",strict = false)
public class EPGItem {
    @Element(name="id", required = false)
    private Integer id;
    @Element(name="name", required = false)
    private String name;
    @Element(name="premiere", required = false)
    private Integer premiere;
    @Element(name="widescreen", required = false)
    private Integer widescreen;
    @Element(name="stereo", required = false)
    private Integer stereo;
    @Element(name="blackWhite", required = false)
    private Integer blackWhite;
    @Element(name="subtitles", required = false)
    private Integer subtitles;
    @Element(name="hd", required = false)
    private Integer hd;
    @Element(name="shortDescription", required = false)
    private String shortDescription;
    @Element(name="longDescription", required = false)
    private String longDescription;
    @Element(name="series", required = false)
    private Integer series;
    @Element(name="seriesId", required = false)
    private Integer seriesId;
    @Element(name="origin", required = false)
    private String origin;
    //actors
    //directors
    @Element(name="duration", required = false)
    private Integer duration;
    @Element(name="date", required = false)
    private Date date;
    @Element(name="dateTimeInSec", required = false)
    private int dateTimeInSec;
    @Element(name="startDate", required = false)
    private Date startDate;
    @Element(name="startDateTimeInSec", required = false)
    private int startDateTimeInSec;
    @Element(name="startTime", required = false)
    private Time startTime;
    @Element(name="endDate", required = false)
    private Date endDate;
    @Element(name="endDateTimeInSec", required = false)
    private int endDateTimeInSec;
    @Element(name="endTime", required = false)
    private Time endTime;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getPremiere() {
        return premiere;
    }

    public void setPremiere(Integer premiere) {
        this.premiere = premiere;
    }

    public Integer getWidescreen() {
        return widescreen;
    }

    public void setWidescreen(Integer widescreen) {
        this.widescreen = widescreen;
    }

    public Integer getStereo() {
        return stereo;
    }

    public void setStereo(Integer stereo) {
        this.stereo = stereo;
    }

    public Integer getBlackWhite() {
        return blackWhite;
    }

    public void setBlackWhite(Integer blackWhite) {
        this.blackWhite = blackWhite;
    }

    public Integer getSubtitles() {
        return subtitles;
    }

    public void setSubtitles(Integer subtitles) {
        this.subtitles = subtitles;
    }

    public Integer getHd() {
        return hd;
    }

    public void setHd(Integer hd) {
        this.hd = hd;
    }

    public String getShortDescription() {
        return shortDescription;
    }

    public void setShortDescription(String shortDescription) {
        this.shortDescription = shortDescription;
    }

    public String getLongDescription() {
        return longDescription;
    }

    public void setLongDescription(String longDescription) {
        this.longDescription = longDescription;
    }

    public Integer getSeries() {
        return series;
    }

    public void setSeries(Integer series) {
        this.series = series;
    }

    public Integer getSeriesId() {
        return seriesId;
    }

    public void setSeriesId(Integer seriesId) {
        this.seriesId = seriesId;
    }

    public String getOrigin() {
        return origin;
    }

    public void setOrigin(String origin) {
        this.origin = origin;
    }

    public Integer getDuration() {
        return duration;
    }

    public void setDuration(Integer duration) {
        this.duration = duration;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public int getDateTimeInSec() {
        return dateTimeInSec;
    }

    public void setDateTimeInSec(int dateTimeInSec) {
        this.dateTimeInSec = dateTimeInSec;
    }

    public Date getStartDate() {
        return startDate;
    }

    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }

    public int getStartDateTimeInSec() {
        return startDateTimeInSec;
    }

    public void setStartDateTimeInSec(int startDateTimeInSec) {
        this.startDateTimeInSec = startDateTimeInSec;
    }

    public Time getStartTime() {
        return startTime;
    }

    public void setStartTime(Time startTime) {
        this.startTime = startTime;
    }

    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }

    public int getEndDateTimeInSec() {
        return endDateTimeInSec;
    }

    public void setEndDateTimeInSec(int endDateTimeInSec) {
        this.endDateTimeInSec = endDateTimeInSec;
    }

    public Time getEndTime() {
        return endTime;
    }

    public void setEndTime(Time endTime) {
        this.endTime = endTime;
    }
    //pictures
    //programme-type

    public Date getStartDateTime(Calendar cal)
    {
        cal.setTime(new Date(0));
        cal.add(Calendar.SECOND, getStartDateTimeInSec());
        /*
        Date date = getDate();
        cal.setTime(date);
        cal.add(Calendar.HOUR, startTime.getHours());
        cal.add(Calendar.MINUTE, startTime.getMinutes());
        */
        Date startDate = cal.getTime();
        return startDate;
    }

    public Date getEndDateTime(Calendar cal)
    {
        cal.setTime(new Date(0));
        cal.add(Calendar.SECOND, getEndDateTimeInSec());
        /*
        cal.setTime(date);
        cal.add(Calendar.HOUR, endTime.getHours());
        cal.add(Calendar.MINUTE, endTime.getMinutes());
        */
        Date endDate = cal.getTime();
        return endDate;
    }

    public int getProgress()
    {
        Date nowDt = new Date();
        long now = nowDt.getTime();
        Calendar cal = Helpers.getCalendar();
        long start = getStartDateTime(cal).getTime();
        long end = getEndDateTime(cal).getTime();

        long duration = Math.abs(end - start);
        long played = now - start;

        float onepercent = (float)duration / (float)100;
        float percentplayed =  (float)played / onepercent;
        return Math.round(percentplayed);
    }

}

the variable args[0].toString() JSON content is: 
[{
    "epgItem": {
        "name": "Jak svet stvoril Ameriku",
        "date": "2017-08-31T22:00:00.000Z",
        "startTime": "2017-09-01T19:00:00.000Z",
        "endTime": "2017-09-01T21:00:00.000Z"
    },
    "channelName": "History HD"
}]

I am receiving this exception when the deserializer hits any of the date fields: 
com.google.gson.JsonSyntaxException: java.text.ParseException: Unparseable date: "2017-09-01T19:00:00.000Z" (at offset 4)
at com.google.gson.internal.bind.TimeTypeAdapter.read(TimeTypeAdapter.java:59)
at com.google.gson.internal.bind.TimeTypeAdapter.read(TimeTypeAdapter.java:40)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:81)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:60)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at net.misove.mypvr.ScheduleViewActivity$2.call(ScheduleViewActivity.java:61)
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117)
at com.github.nkzawa.socketio.client.Socket.onevent(Socket.java:308)
at com.github.nkzawa.socketio.client.Socket.onpacket(Socket.java:272)
at com.github.nkzawa.socketio.client.Socket.access$100(Socket.java:18)
at com.github.nkzawa.socketio.client.Socket$2$2.call(Socket.java:99)
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117)
at com.github.nkzawa.socketio.client.Manager.ondecoded(Manager.java:347)
at com.github.nkzawa.socketio.client.Manager.access$1300(Manager.java:19)
at com.github.nkzawa.socketio.client.Manager$3.call(Manager.java:321)
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117)
at com.github.nkzawa.socketio.parser.Parser$Decoder.add(Parser.java:156)
at com.github.nkzawa.socketio.client.Manager.ondata(Manager.java:339)
at com.github.nkzawa.socketio.client.Manager.access$1100(Manager.java:19)
at com.github.nkzawa.socketio.client.Manager$2.call(Manager.java:312)
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117)
at com.github.nkzawa.engineio.client.Socket.onPacket(Socket.java:482)
at com.github.nkzawa.engineio.client.Socket.access$800(Socket.java:29)
at com.github.nkzawa.engineio.client.Socket$5.call(Socket.java:285)
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117)
at com.github.nkzawa.engineio.client.Transport.onPacket(Transport.java:121)
at com.github.nkzawa.engineio.client.Transport.onData(Transport.java:113)
at com.github.nkzawa.engineio.client.transports.WebSocket.access$100(WebSocket.java:19)
at com.github.nkzawa.engineio.client.transports.WebSocket$1$3.run(WebSocket.java:74)
at com.github.nkzawa.thread.EventThread$2.run(EventThread.java:75)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.text.ParseException: Unparseable date: "2017-09-01T19:00:00.000Z" (at offset 4)
at java.text.DateFormat.parse(DateFormat.java:579)
at com.google.gson.internal.bind.TimeTypeAdapter.read(TimeTypeAdapter.java:56)
at com.google.gson.internal.bind.TimeTypeAdapter.read(TimeTypeAdapter.java:40) 
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93) 
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172) 
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93) 
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172) 
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40) 
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:81) 
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:60) 
at com.google.gson.Gson.fromJson(Gson.java:803) 
at com.google.gson.Gson.fromJson(Gson.java:768) 
at com.google.gson.Gson.fromJson(Gson.java:717) 
at net.misove.mypvr.ScheduleViewActivity$2.call(ScheduleViewActivity.java:61) 
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117) 
at com.github.nkzawa.socketio.client.Socket.onevent(Socket.java:308) 
at com.github.nkzawa.socketio.client.Socket.onpacket(Socket.java:272) 
at com.github.nkzawa.socketio.client.Socket.access$100(Socket.java:18) 
at com.github.nkzawa.socketio.client.Socket$2$2.call(Socket.java:99) 
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117) 
at com.github.nkzawa.socketio.client.Manager.ondecoded(Manager.java:347) 
at com.github.nkzawa.socketio.client.Manager.access$1300(Manager.java:19) 
at com.github.nkzawa.socketio.client.Manager$3.call(Manager.java:321) 
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117) 
at com.github.nkzawa.socketio.parser.Parser$Decoder.add(Parser.java:156) 
at com.github.nkzawa.socketio.client.Manager.ondata(Manager.java:339) 
at com.github.nkzawa.socketio.client.Manager.access$1100(Manager.java:19) 
at com.github.nkzawa.socketio.client.Manager$2.call(Manager.java:312) 
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117) 
at com.github.nkzawa.engineio.client.Socket.onPacket(Socket.java:482) 
at com.github.nkzawa.engineio.client.Socket.access$800(Socket.java:29) 
at com.github.nkzawa.engineio.client.Socket$5.call(Socket.java:285) 
at com.github.nkzawa.emitter.Emitter.emit(Emitter.java:117) 
at com.github.nkzawa.engineio.client.Transport.onPacket(Transport.java:121) 
at com.github.nkzawa.engineio.client.Transport.onData(Transport.java:113) 
at com.github.nkzawa.engineio.client.transports.WebSocket.access$100(WebSocket.java:19) 
at com.github.nkzawa.engineio.client.transports.WebSocket$1$3.run(WebSocket.java:74) 
at com.github.nkzawa.thread.EventThread$2.run(EventThread.java:75) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
at java.lang.Thread.run(Thread.java:818) 

I'm especially wondering what is the "(at offset 4)" in the error message... offset 4 in the date format is "-" and so it is in the date values in JSON... I'm bit confused, and would appreciate any insights on this. 
One thing I'd like to try is to write own type deserialization adapter for dates and plug it into gson, but that seemed too much for a simple datetime deserialization. 
UPDATE: 
The approach with the own deserialization adapter for dates proposed by "S A" in his answer did not work either. Throws the same exception.

A:

I think that I ran into an issue like this some time ago. Try building your gson instance like this:
new GsonBuilder()
 .registerTypeAdapter(Date.class, new ISO8601DateAdapter())
 .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
 .create();

Here you have the classes:
ISO8601DateAdapter.java
ISO8601DateParser.java
ISO8601TimeAdapter.java
Let me know if this does the trick or not.

