From 431cdc9c3776dddfdacab3a99c5e38d727f5c4cb Mon Sep 17 00:00:00 2001 From: xhh Date: Tue, 17 Nov 2015 17:54:13 +0800 Subject: [PATCH] Java okhttp-gson: fix datetime format for Android by detecting the current environment (Android SDK version and Java version) and determine a default datetime format accordingly Closes #1573 --- .../libraries/okhttp-gson/ApiClient.mustache | 87 ++++++++++++++++--- .../java/io/swagger/client/ApiClient.java | 87 ++++++++++++++++--- 2 files changed, 148 insertions(+), 26 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache b/modules/swagger-codegen/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache index f238c37fb47..8ce2407cbe3 100644 --- a/modules/swagger-codegen/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache +++ b/modules/swagger-codegen/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache @@ -64,6 +64,38 @@ import {{invokerPackage}}.auth.ApiKeyAuth; import {{invokerPackage}}.auth.OAuth; public class ApiClient { + public static final double JAVA_VERSION; + public static final boolean IS_ANDROID; + public static final int ANDROID_SDK_VERSION; + + static { + JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version")); + boolean isAndroid; + try { + Class.forName("android.app.Activity"); + isAndroid = true; + } catch (ClassNotFoundException e) { + isAndroid = false; + } + IS_ANDROID = isAndroid; + int sdkVersion = 0; + if (IS_ANDROID) { + try { + sdkVersion = Class.forName("android.os.Build$VERSION").getField("SDK_INT").getInt(null); + } catch (Exception e) { + try { + sdkVersion = Integer.parseInt((String) Class.forName("android.os.Build$VERSION").getField("SDK").get(null)); + } catch (Exception e2) { } + } + } + ANDROID_SDK_VERSION = sdkVersion; + } + + /** + * The datetime format to be used when lenientDatetimeFormat is enabled. + */ + public static final String LENIENT_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + private String basePath = "{{basePath}}"; private boolean lenientOnJson = false; private boolean debugging = false; @@ -100,8 +132,7 @@ public class ApiClient { this.dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // Always use UTC as the default time zone when dealing with date (without time). this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - // Use the system's default time zone when dealing with datetime (mainly formatting). - this.datetimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + initDatetimeFormat(); // Be lenient on datetime formats when parsing datetime from string. // See parseDatetime. @@ -263,25 +294,30 @@ public class ApiClient { if (str == null) return null; + DateFormat format; if (lenientDatetimeFormat) { /* - * When lenientDatetimeFormat is enabled, process the given string - * to support various formats defined by ISO 8601. + * When lenientDatetimeFormat is enabled, normalize the date string + * into LENIENT_DATETIME_FORMAT to support various formats + * defined by ISO 8601. */ // normalize time zone - // trailing "Z": 2015-08-16T08:20:05Z => 2015-08-16T08:20:05+00:00 - str = str.replaceAll("[zZ]\\z", "+00:00"); - // add colon: 2015-08-16T08:20:05+0000 => 2015-08-16T08:20:05+00:00 - str = str.replaceAll("([+-]\\d{2})(\\d{2})\\z", "$1:$2"); - // expand time zone: 2015-08-16T08:20:05+00 => 2015-08-16T08:20:05+00:00 - str = str.replaceAll("([+-]\\d{2})\\z", "$1:00"); + // trailing "Z": 2015-08-16T08:20:05Z => 2015-08-16T08:20:05+0000 + str = str.replaceAll("[zZ]\\z", "+0000"); + // remove colon in time zone: 2015-08-16T08:20:05+00:00 => 2015-08-16T08:20:05+0000 + str = str.replaceAll("([+-]\\d{2}):(\\d{2})\\z", "$1$2"); + // expand time zone: 2015-08-16T08:20:05+00 => 2015-08-16T08:20:05+0000 + str = str.replaceAll("([+-]\\d{2})\\z", "$100"); // add milliseconds when missing - // 2015-08-16T08:20:05+00:00 => 2015-08-16T08:20:05.000+00:00 - str = str.replaceAll("(:\\d{1,2})([+-]\\d{2}:\\d{2})\\z", "$1.000$2"); + // 2015-08-16T08:20:05+0000 => 2015-08-16T08:20:05.000+0000 + str = str.replaceAll("(:\\d{1,2})([+-]\\d{4})\\z", "$1.000$2"); + format = new SimpleDateFormat(LENIENT_DATETIME_FORMAT); + } else { + format = this.datetimeFormat; } try { - return datetimeFormat.parse(str); + return format.parse(str); } catch (ParseException e) { throw new RuntimeException(e); } @@ -916,6 +952,31 @@ public class ApiClient { } } + /** + * Initialize datetime format according to the current environment, e.g. Java 1.7 and Android. + */ + private void initDatetimeFormat() { + String formatWithTimeZone = null; + if (IS_ANDROID) { + if (ANDROID_SDK_VERSION >= 18) { + // The time zone format "ZZZZZ" is available since Android 4.3 (SDK version 18) + formatWithTimeZone = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"; + } + } else if (JAVA_VERSION >= 1.7) { + // The time zone format "XXX" is available since Java 1.7 + formatWithTimeZone = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + } + if (formatWithTimeZone != null) { + this.datetimeFormat = new SimpleDateFormat(formatWithTimeZone); + // NOTE: Use the system's default time zone (mainly for datetime formatting). + } else { + // Use a common format that works across all systems. + this.datetimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + // Always use the UTC time zone as we are using a constant trailing "Z" here. + this.datetimeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + } + /** * Apply SSL related settings to httpClient according to the current values of * verifyingSsl and sslCaCert. diff --git a/samples/client/petstore/java/okhttp-gson/src/main/java/io/swagger/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson/src/main/java/io/swagger/client/ApiClient.java index 51c3d5464f2..6e4e299ad3a 100644 --- a/samples/client/petstore/java/okhttp-gson/src/main/java/io/swagger/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson/src/main/java/io/swagger/client/ApiClient.java @@ -64,6 +64,38 @@ import io.swagger.client.auth.ApiKeyAuth; import io.swagger.client.auth.OAuth; public class ApiClient { + public static final double JAVA_VERSION; + public static final boolean IS_ANDROID; + public static final int ANDROID_SDK_VERSION; + + static { + JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version")); + boolean isAndroid; + try { + Class.forName("android.app.Activity"); + isAndroid = true; + } catch (ClassNotFoundException e) { + isAndroid = false; + } + IS_ANDROID = isAndroid; + int sdkVersion = 0; + if (IS_ANDROID) { + try { + sdkVersion = Class.forName("android.os.Build$VERSION").getField("SDK_INT").getInt(null); + } catch (Exception e) { + try { + sdkVersion = Integer.parseInt((String) Class.forName("android.os.Build$VERSION").getField("SDK").get(null)); + } catch (Exception e2) { } + } + } + ANDROID_SDK_VERSION = sdkVersion; + } + + /** + * The datetime format to be used when lenientDatetimeFormat is enabled. + */ + public static final String LENIENT_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + private String basePath = "http://petstore.swagger.io/v2"; private boolean lenientOnJson = false; private boolean debugging = false; @@ -100,8 +132,7 @@ public class ApiClient { this.dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // Always use UTC as the default time zone when dealing with date (without time). this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - // Use the system's default time zone when dealing with datetime (mainly formatting). - this.datetimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + initDatetimeFormat(); // Be lenient on datetime formats when parsing datetime from string. // See parseDatetime. @@ -262,25 +293,30 @@ public class ApiClient { if (str == null) return null; + DateFormat format; if (lenientDatetimeFormat) { /* - * When lenientDatetimeFormat is enabled, process the given string - * to support various formats defined by ISO 8601. + * When lenientDatetimeFormat is enabled, normalize the date string + * into LENIENT_DATETIME_FORMAT to support various formats + * defined by ISO 8601. */ // normalize time zone - // trailing "Z": 2015-08-16T08:20:05Z => 2015-08-16T08:20:05+00:00 - str = str.replaceAll("[zZ]\\z", "+00:00"); - // add colon: 2015-08-16T08:20:05+0000 => 2015-08-16T08:20:05+00:00 - str = str.replaceAll("([+-]\\d{2})(\\d{2})\\z", "$1:$2"); - // expand time zone: 2015-08-16T08:20:05+00 => 2015-08-16T08:20:05+00:00 - str = str.replaceAll("([+-]\\d{2})\\z", "$1:00"); + // trailing "Z": 2015-08-16T08:20:05Z => 2015-08-16T08:20:05+0000 + str = str.replaceAll("[zZ]\\z", "+0000"); + // remove colon in time zone: 2015-08-16T08:20:05+00:00 => 2015-08-16T08:20:05+0000 + str = str.replaceAll("([+-]\\d{2}):(\\d{2})\\z", "$1$2"); + // expand time zone: 2015-08-16T08:20:05+00 => 2015-08-16T08:20:05+0000 + str = str.replaceAll("([+-]\\d{2})\\z", "$100"); // add milliseconds when missing - // 2015-08-16T08:20:05+00:00 => 2015-08-16T08:20:05.000+00:00 - str = str.replaceAll("(:\\d{1,2})([+-]\\d{2}:\\d{2})\\z", "$1.000$2"); + // 2015-08-16T08:20:05+0000 => 2015-08-16T08:20:05.000+0000 + str = str.replaceAll("(:\\d{1,2})([+-]\\d{4})\\z", "$1.000$2"); + format = new SimpleDateFormat(LENIENT_DATETIME_FORMAT); + } else { + format = this.datetimeFormat; } try { - return datetimeFormat.parse(str); + return format.parse(str); } catch (ParseException e) { throw new RuntimeException(e); } @@ -915,6 +951,31 @@ public class ApiClient { } } + /** + * Initialize datetime format according to the current environment, e.g. Java 1.7 and Android. + */ + private void initDatetimeFormat() { + String formatWithTimeZone = null; + if (IS_ANDROID) { + if (ANDROID_SDK_VERSION >= 18) { + // The time zone format "ZZZZZ" is available since Android 4.3 (SDK version 18) + formatWithTimeZone = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"; + } + } else if (JAVA_VERSION >= 1.7) { + // The time zone format "XXX" is available since Java 1.7 + formatWithTimeZone = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + } + if (formatWithTimeZone != null) { + this.datetimeFormat = new SimpleDateFormat(formatWithTimeZone); + // NOTE: Use the system's default time zone (mainly for datetime formatting). + } else { + // Use a common format that works across all systems. + this.datetimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + // Always use the UTC time zone as we are using a constant trailing "Z" here. + this.datetimeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + } + /** * Apply SSL related settings to httpClient according to the current values of * verifyingSsl and sslCaCert.