From 3623918de520adac0a7f19ebe71dcb46e2096164 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 7 Feb 2012 18:48:01 +0000 Subject: [PATCH] Java SimpleDateFormat cannot handle valid ISO8601 time zone strings, fixed --- .../org/jclouds/date/internal/DateUtils.java | 25 ++++++++-- .../internal/SimpleDateFormatDateService.java | 47 ++++++++++++------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/org/jclouds/date/internal/DateUtils.java b/core/src/main/java/org/jclouds/date/internal/DateUtils.java index 0135d86c5a..02220e6379 100644 --- a/core/src/main/java/org/jclouds/date/internal/DateUtils.java +++ b/core/src/main/java/org/jclouds/date/internal/DateUtils.java @@ -27,14 +27,15 @@ import java.util.regex.Pattern; */ public class DateUtils { - public static final Pattern MILLIS_PATTERN = Pattern.compile("(.*\\.[0-9][0-9][0-9])[0-9]*Z?"); + public static final Pattern MILLIS_PATTERN = Pattern.compile("(.*\\.[0-9][0-9][0-9])[0-9]*"); - public static final Pattern TZ_PATTERN = Pattern.compile("(.*)[+-][0-9][0-9]:?[0-9][0-9]Z?"); + // This regexp will match all TZ forms that are valid is ISO 8601 + public static final Pattern TZ_PATTERN = Pattern.compile("(.*)([+-][0-9][0-9](:?[0-9][0-9])?|Z)"); public static String trimToMillis(String toParse) { Matcher matcher = MILLIS_PATTERN.matcher(toParse); if (matcher.find()) { - toParse = matcher.group(1) + 'Z'; + toParse = matcher.group(1); } return toParse; } @@ -44,11 +45,25 @@ public class DateUtils { public static String trimTZ(String toParse) { Matcher matcher = TZ_PATTERN.matcher(toParse); if (matcher.find()) { - toParse = matcher.group(1) + 'Z'; + toParse = matcher.group(1); } + // TODO explain why this check is here if (toParse.length() == 25 && SECOND_PATTERN.matcher(toParse).matches()) - toParse = toParse.substring(0, toParse.length() - 6) + 'Z'; + toParse = toParse.substring(0, toParse.length() - 6); return toParse; } + public static String findTZ(String toParse) { + Matcher matcher = TZ_PATTERN.matcher(toParse); + if (matcher.find()) { + // Remove ':' from the TZ string, as SimpleDateFormat can't handle it + String tz = matcher.group(2).replace(":", ""); + // Append '00; if we only have a two digit TZ, as SimpleDateFormat + if (tz.length() == 2) tz += "00"; + return tz; + } else { + return ""; + } + } + } \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java b/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java index 1b3ac583ca..69d0f62fc0 100644 --- a/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java +++ b/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java @@ -17,8 +17,7 @@ * under the License. */ package org.jclouds.date.internal; -import static org.jclouds.date.internal.DateUtils.trimToMillis; -import static org.jclouds.date.internal.DateUtils.trimTZ; +import static org.jclouds.date.internal.DateUtils.*; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -42,20 +41,16 @@ public class SimpleDateFormatDateService implements DateService { * guard against the lack of thread safety. */ // @GuardedBy("this") - private static final SimpleDateFormat iso8601SecondsSimpleDateFormat = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + private static final SimpleDateFormat iso8601SecondsSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); // @GuardedBy("this") - private static final SimpleDateFormat iso8601SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", - Locale.US); + private static final SimpleDateFormat iso8601SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); // @GuardedBy("this") - private static final SimpleDateFormat rfc822SimpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", - Locale.US); + private static final SimpleDateFormat rfc822SimpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); // @GuardedBy("this") - private static final SimpleDateFormat cSimpleDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss '+0000' yyyy", - Locale.US); + private static final SimpleDateFormat cSimpleDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss '+0000' yyyy", Locale.US); static { iso8601SimpleDateFormat.setTimeZone(new SimpleTimeZone(0, "GMT")); @@ -64,83 +59,99 @@ public class SimpleDateFormatDateService implements DateService { cSimpleDateFormat.setTimeZone(new SimpleTimeZone(0, "GMT")); } + @Override public final Date fromSeconds(long seconds) { return new Date(seconds * 1000); } + @Override public final String cDateFormat(Date date) { synchronized (cSimpleDateFormat) { return cSimpleDateFormat.format(date); } } + @Override public final String cDateFormat() { return cDateFormat(new Date()); } + @Override public final Date cDateParse(String toParse) { synchronized (cSimpleDateFormat) { try { return cSimpleDateFormat.parse(toParse); - } catch (ParseException e) { - throw new RuntimeException(e); + } catch (ParseException pe) { + throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe); } } } + @Override public final String rfc822DateFormat(Date date) { synchronized (rfc822SimpleDateFormat) { return rfc822SimpleDateFormat.format(date); } } + @Override public final String rfc822DateFormat() { return rfc822DateFormat(new Date()); } + @Override public final Date rfc822DateParse(String toParse) { synchronized (rfc822SimpleDateFormat) { try { return rfc822SimpleDateFormat.parse(toParse); - } catch (ParseException e) { - throw new RuntimeException(e); + } catch (ParseException pe) { + throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe); } } } + @Override public final String iso8601SecondsDateFormat() { return iso8601SecondsDateFormat(new Date()); } + @Override public final String iso8601DateFormat(Date date) { synchronized (iso8601SimpleDateFormat) { return iso8601SimpleDateFormat.format(date); } } + @Override public final String iso8601DateFormat() { return iso8601DateFormat(new Date()); } + @Override public final Date iso8601DateParse(String toParse) { + String tz = findTZ(toParse); toParse = trimTZ(toParse); toParse = trimToMillis(toParse); + toParse += tz; // Usable TZ added back synchronized (iso8601SimpleDateFormat) { try { return iso8601SimpleDateFormat.parse(toParse); - } catch (ParseException e) { - throw new RuntimeException(e); + } catch (ParseException pe) { + throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe); } } } + @Override public final Date iso8601SecondsDateParse(String toParse) { + String tz = findTZ(toParse); toParse = trimTZ(toParse); + toParse += tz; // Usable TZ added back synchronized (iso8601SecondsSimpleDateFormat) { try { return iso8601SecondsSimpleDateFormat.parse(toParse); - } catch (ParseException e) { - throw new RuntimeException(e); + } catch (ParseException pe) { + throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe); } } }