Skip to content

Commit

Permalink
fix Java9 DateFormat changes (#1211)
Browse files Browse the repository at this point in the history
* fix Java9 DateFormat changes

* fix Codacy warnings
  • Loading branch information
amogilev authored and inder123 committed Dec 29, 2017
1 parent c744ccd commit 0aaf5ff
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 57 deletions.
88 changes: 57 additions & 31 deletions gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import com.google.gson.internal.PreJava9DateFormatProvider;
import com.google.gson.internal.bind.util.ISO8601Utils;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.gson.util.VersionUtils;

/**
* This type adapter supports three subclasses of date: Date, Timestamp, and
Expand All @@ -42,42 +46,63 @@ final class DefaultDateTypeAdapter extends TypeAdapter<Date> {
private static final String SIMPLE_NAME = "DefaultDateTypeAdapter";

private final Class<? extends Date> dateType;
private final DateFormat enUsFormat;
private final DateFormat localFormat;


/**
* List of 1 or more different date formats used for de-serialization attempts.
* The first of them is used for serialization as well.
*/
private final List<DateFormat> dateFormats = new ArrayList<DateFormat>();

DefaultDateTypeAdapter(Class<? extends Date> dateType) {
this(dateType,
DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US),
DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
this.dateType = verifyDateType(dateType);
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 (VersionUtils.isJava9OrLater()) {
dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT));
}
}

DefaultDateTypeAdapter(Class<? extends Date> dateType, String datePattern) {
this(dateType, new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern));
this.dateType = verifyDateType(dateType);
dateFormats.add(new SimpleDateFormat(datePattern, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(new SimpleDateFormat(datePattern));
}
}

DefaultDateTypeAdapter(Class<? extends Date> dateType, int style) {
this(dateType, DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style));
this.dateType = verifyDateType(dateType);
dateFormats.add(DateFormat.getDateInstance(style, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(DateFormat.getDateInstance(style));
}
if (VersionUtils.isJava9OrLater()) {
dateFormats.add(PreJava9DateFormatProvider.getUSDateFormat(style));
}
}

public DefaultDateTypeAdapter(int dateStyle, int timeStyle) {
this(Date.class,
DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US),
DateFormat.getDateTimeInstance(dateStyle, timeStyle));
this(Date.class, dateStyle, timeStyle);
}

public DefaultDateTypeAdapter(Class<? extends Date> dateType, int dateStyle, int timeStyle) {
this(dateType,
DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US),
DateFormat.getDateTimeInstance(dateStyle, timeStyle));
this.dateType = verifyDateType(dateType);
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle));
}
if (VersionUtils.isJava9OrLater()) {
dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(dateStyle, timeStyle));
}
}

DefaultDateTypeAdapter(final Class<? extends Date> dateType, DateFormat enUsFormat, DateFormat localFormat) {
private static Class<? extends Date> verifyDateType(Class<? extends Date> dateType) {
if ( dateType != Date.class && dateType != java.sql.Date.class && dateType != Timestamp.class ) {
throw new IllegalArgumentException("Date type must be one of " + Date.class + ", " + Timestamp.class + ", or " + java.sql.Date.class + " but was " + dateType);
}
this.dateType = dateType;
this.enUsFormat = enUsFormat;
this.localFormat = localFormat;
return dateType;
}

// These methods need to be synchronized since JDK DateFormat classes are not thread-safe
Expand All @@ -88,8 +113,8 @@ public void write(JsonWriter out, Date value) throws IOException {
out.nullValue();
return;
}
synchronized (localFormat) {
String dateFormatAsString = enUsFormat.format(value);
synchronized(dateFormats) {
String dateFormatAsString = dateFormats.get(0).format(value);
out.value(dateFormatAsString);
}
}
Expand All @@ -114,13 +139,12 @@ public Date read(JsonReader in) throws IOException {
}

private Date deserializeToDate(String s) {
synchronized (localFormat) {
try {
return localFormat.parse(s);
} catch (ParseException ignored) {}
try {
return enUsFormat.parse(s);
} catch (ParseException ignored) {}
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) {
Expand All @@ -131,9 +155,11 @@ private Date deserializeToDate(String s) {

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(SIMPLE_NAME);
sb.append('(').append(localFormat.getClass().getSimpleName()).append(')');
return sb.toString();
DateFormat defaultFormat = dateFormats.get(0);
if (defaultFormat instanceof SimpleDateFormat) {
return SIMPLE_NAME + '(' + ((SimpleDateFormat) defaultFormat).toPattern() + ')';
} else {
return SIMPLE_NAME + '(' + defaultFormat.getClass().getSimpleName() + ')';
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2017 The Gson authors
*
* 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;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;

/**
* Provides DateFormats for US locale with patterns which were the default ones before Java 9.
*/
public class PreJava9DateFormatProvider {

/**
* Returns the same DateFormat as {@code DateFormat.getDateInstance(style, Locale.US)} in Java 8 or below.
*/
public static DateFormat getUSDateFormat(int style) {
return new SimpleDateFormat(getDateFormatPattern(style), Locale.US);
}

/**
* Returns the same DateFormat as {@code DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US)}
* in Java 8 or below.
*/
public static DateFormat getUSDateTimeFormat(int dateStyle, int timeStyle) {
String pattern = getDatePartOfDateTimePattern(dateStyle) + " " + getTimePartOfDateTimePattern(timeStyle);
return new SimpleDateFormat(pattern, Locale.US);
}

private static String getDateFormatPattern(int style) {
switch (style) {
case DateFormat.SHORT:
return "M/d/yy";
case DateFormat.MEDIUM:
return "MMM d, y";
case DateFormat.LONG:
return "MMMM d, y";
case DateFormat.FULL:
return "EEEE, MMMM d, y";
default:
throw new IllegalArgumentException("Unknown DateFormat style: " + style);
}
}

private static String getDatePartOfDateTimePattern(int dateStyle) {
switch (dateStyle) {
case DateFormat.SHORT:
return "M/d/yy";
case DateFormat.MEDIUM:
return "MMM d, yyyy";
case DateFormat.LONG:
return "MMMM d, yyyy";
case DateFormat.FULL:
return "EEEE, MMMM d, yyyy";
default:
throw new IllegalArgumentException("Unknown DateFormat style: " + dateStyle);
}
}

private static String getTimePartOfDateTimePattern(int timeStyle) {
switch (timeStyle) {
case DateFormat.SHORT:
return "h:mm a";
case DateFormat.MEDIUM:
return "h:mm:ss a";
case DateFormat.FULL:
case DateFormat.LONG:
return "h:mm:ss a z";
default:
throw new IllegalArgumentException("Unknown DateFormat style: " + timeStyle);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,21 @@
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
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 com.google.gson.util.VersionUtils;

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;

/**
Expand All @@ -46,10 +51,21 @@ public final class DateTypeAdapter extends TypeAdapter<Date> {
}
};

private final DateFormat enUsFormat
= DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US);
private final DateFormat localFormat
= DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT);
/**
* 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<DateFormat> dateFormats = new ArrayList<DateFormat>();

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 (VersionUtils.isJava9OrLater()) {
dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT));
}
}

@Override public Date read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
Expand All @@ -60,13 +76,10 @@ public final class DateTypeAdapter extends TypeAdapter<Date> {
}

private synchronized Date deserializeToDate(String json) {
try {
return localFormat.parse(json);
} catch (ParseException ignored) {
}
try {
return enUsFormat.parse(json);
} catch (ParseException ignored) {
for (DateFormat dateFormat : dateFormats) {
try {
return dateFormat.parse(json);
} catch (ParseException ignored) {}
}
try {
return ISO8601Utils.parse(json, new ParsePosition(0));
Expand All @@ -80,7 +93,7 @@ private synchronized Date deserializeToDate(String json) {
out.nullValue();
return;
}
String dateFormatAsString = enUsFormat.format(value);
String dateFormatAsString = dateFormats.get(0).format(value);
out.value(dateFormatAsString);
}

Expand Down
49 changes: 49 additions & 0 deletions gson/src/main/java/com/google/gson/util/VersionUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2017 The Gson authors
*
* 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.util;

/**
* Utility to check the major Java version of the current JVM.
*/
public class VersionUtils {

private static final int majorJavaVersion = determineMajorJavaVersion();

private static int determineMajorJavaVersion() {
String[] parts = System.getProperty("java.version").split("[._]");
int firstVer = Integer.parseInt(parts[0]);
if (firstVer == 1 && parts.length > 1) {
return Integer.parseInt(parts[1]);
} else {
return firstVer;
}
}

/**
* @return the major Java version, i.e. '8' for Java 1.8, '9' for Java 9 etc.
*/
public static int getMajorJavaVersion() {
return majorJavaVersion;
}

/**
* @return {@code true} if the application is running on Java 9 or later; and {@code false} otherwise.
*/
public static boolean isJava9OrLater() {
return majorJavaVersion >= 9;
}
}
Loading

0 comments on commit 0aaf5ff

Please sign in to comment.