/* * Copyright (C) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.gson.internal.bind; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.JavaVersion; import com.google.gson.internal.PreJava9DateFormatProvider; import com.google.gson.internal.bind.util.ISO8601Utils; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.text.DateFormat; import java.text.ParseException; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; /** * Adapter for Date. Although this class appears stateless, it is not. * DateFormat captures its time zone and locale when it is created, which gives * this class state. DateFormat isn't thread safe either, so this class has * to synchronize its read and write methods. */ public final class DateTypeAdapter extends TypeAdapter { public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @Override public TypeAdapter create(Gson gson, TypeToken typeToken) { return typeToken.getRawType() == Date.class ? (TypeAdapter) new DateTypeAdapter() : null; } }; /** * List of 1 or more different date formats used for de-serialization attempts. * The first of them (default US format) is used for serialization as well. */ private final List dateFormats = new ArrayList<>(); public DateTypeAdapter() { dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US)); if (!Locale.getDefault().equals(Locale.US)) { dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)); } if (JavaVersion.isJava9OrLater()) { dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT)); } } @Override public Date read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } return deserializeToDate(in); } private Date deserializeToDate(JsonReader in) throws IOException { String s = in.nextString(); synchronized (dateFormats) { for (DateFormat dateFormat : dateFormats) { try { return dateFormat.parse(s); } catch (ParseException ignored) {} } } try { return ISO8601Utils.parse(s, new ParsePosition(0)); } catch (ParseException e) { throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e); } } @Override public void write(JsonWriter out, Date value) throws IOException { if (value == null) { out.nullValue(); return; } DateFormat dateFormat = dateFormats.get(0); String dateFormatAsString; synchronized (dateFormats) { dateFormatAsString = dateFormat.format(value); } out.value(dateFormatAsString); } }