Java SimpleDateFormat cannot handle valid ISO8601 time zone strings, fixed

This commit is contained in:
Andrew Donald Kennedy 2012-02-07 18:48:01 +00:00
parent 241a33b5c8
commit 3623918de5
2 changed files with 49 additions and 23 deletions

View File

@ -27,14 +27,15 @@ import java.util.regex.Pattern;
*/ */
public class DateUtils { 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) { public static String trimToMillis(String toParse) {
Matcher matcher = MILLIS_PATTERN.matcher(toParse); Matcher matcher = MILLIS_PATTERN.matcher(toParse);
if (matcher.find()) { if (matcher.find()) {
toParse = matcher.group(1) + 'Z'; toParse = matcher.group(1);
} }
return toParse; return toParse;
} }
@ -44,11 +45,25 @@ public class DateUtils {
public static String trimTZ(String toParse) { public static String trimTZ(String toParse) {
Matcher matcher = TZ_PATTERN.matcher(toParse); Matcher matcher = TZ_PATTERN.matcher(toParse);
if (matcher.find()) { 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()) 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; 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 "";
}
}
} }

View File

@ -17,8 +17,7 @@
* under the License. * under the License.
*/ */
package org.jclouds.date.internal; package org.jclouds.date.internal;
import static org.jclouds.date.internal.DateUtils.trimToMillis; import static org.jclouds.date.internal.DateUtils.*;
import static org.jclouds.date.internal.DateUtils.trimTZ;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -42,20 +41,16 @@ public class SimpleDateFormatDateService implements DateService {
* guard against the lack of thread safety. * guard against the lack of thread safety.
*/ */
// @GuardedBy("this") // @GuardedBy("this")
private static final SimpleDateFormat iso8601SecondsSimpleDateFormat = new SimpleDateFormat( private static final SimpleDateFormat iso8601SecondsSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
"yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
// @GuardedBy("this") // @GuardedBy("this")
private static final SimpleDateFormat iso8601SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", private static final SimpleDateFormat iso8601SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
Locale.US);
// @GuardedBy("this") // @GuardedBy("this")
private static final SimpleDateFormat rfc822SimpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", private static final SimpleDateFormat rfc822SimpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
Locale.US);
// @GuardedBy("this") // @GuardedBy("this")
private static final SimpleDateFormat cSimpleDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss '+0000' yyyy", private static final SimpleDateFormat cSimpleDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss '+0000' yyyy", Locale.US);
Locale.US);
static { static {
iso8601SimpleDateFormat.setTimeZone(new SimpleTimeZone(0, "GMT")); iso8601SimpleDateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
@ -64,83 +59,99 @@ public class SimpleDateFormatDateService implements DateService {
cSimpleDateFormat.setTimeZone(new SimpleTimeZone(0, "GMT")); cSimpleDateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
} }
@Override
public final Date fromSeconds(long seconds) { public final Date fromSeconds(long seconds) {
return new Date(seconds * 1000); return new Date(seconds * 1000);
} }
@Override
public final String cDateFormat(Date date) { public final String cDateFormat(Date date) {
synchronized (cSimpleDateFormat) { synchronized (cSimpleDateFormat) {
return cSimpleDateFormat.format(date); return cSimpleDateFormat.format(date);
} }
} }
@Override
public final String cDateFormat() { public final String cDateFormat() {
return cDateFormat(new Date()); return cDateFormat(new Date());
} }
@Override
public final Date cDateParse(String toParse) { public final Date cDateParse(String toParse) {
synchronized (cSimpleDateFormat) { synchronized (cSimpleDateFormat) {
try { try {
return cSimpleDateFormat.parse(toParse); return cSimpleDateFormat.parse(toParse);
} catch (ParseException e) { } catch (ParseException pe) {
throw new RuntimeException(e); throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
} }
} }
} }
@Override
public final String rfc822DateFormat(Date date) { public final String rfc822DateFormat(Date date) {
synchronized (rfc822SimpleDateFormat) { synchronized (rfc822SimpleDateFormat) {
return rfc822SimpleDateFormat.format(date); return rfc822SimpleDateFormat.format(date);
} }
} }
@Override
public final String rfc822DateFormat() { public final String rfc822DateFormat() {
return rfc822DateFormat(new Date()); return rfc822DateFormat(new Date());
} }
@Override
public final Date rfc822DateParse(String toParse) { public final Date rfc822DateParse(String toParse) {
synchronized (rfc822SimpleDateFormat) { synchronized (rfc822SimpleDateFormat) {
try { try {
return rfc822SimpleDateFormat.parse(toParse); return rfc822SimpleDateFormat.parse(toParse);
} catch (ParseException e) { } catch (ParseException pe) {
throw new RuntimeException(e); throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
} }
} }
} }
@Override
public final String iso8601SecondsDateFormat() { public final String iso8601SecondsDateFormat() {
return iso8601SecondsDateFormat(new Date()); return iso8601SecondsDateFormat(new Date());
} }
@Override
public final String iso8601DateFormat(Date date) { public final String iso8601DateFormat(Date date) {
synchronized (iso8601SimpleDateFormat) { synchronized (iso8601SimpleDateFormat) {
return iso8601SimpleDateFormat.format(date); return iso8601SimpleDateFormat.format(date);
} }
} }
@Override
public final String iso8601DateFormat() { public final String iso8601DateFormat() {
return iso8601DateFormat(new Date()); return iso8601DateFormat(new Date());
} }
@Override
public final Date iso8601DateParse(String toParse) { public final Date iso8601DateParse(String toParse) {
String tz = findTZ(toParse);
toParse = trimTZ(toParse); toParse = trimTZ(toParse);
toParse = trimToMillis(toParse); toParse = trimToMillis(toParse);
toParse += tz; // Usable TZ added back
synchronized (iso8601SimpleDateFormat) { synchronized (iso8601SimpleDateFormat) {
try { try {
return iso8601SimpleDateFormat.parse(toParse); return iso8601SimpleDateFormat.parse(toParse);
} catch (ParseException e) { } catch (ParseException pe) {
throw new RuntimeException(e); throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
} }
} }
} }
@Override
public final Date iso8601SecondsDateParse(String toParse) { public final Date iso8601SecondsDateParse(String toParse) {
String tz = findTZ(toParse);
toParse = trimTZ(toParse); toParse = trimTZ(toParse);
toParse += tz; // Usable TZ added back
synchronized (iso8601SecondsSimpleDateFormat) { synchronized (iso8601SecondsSimpleDateFormat) {
try { try {
return iso8601SecondsSimpleDateFormat.parse(toParse); return iso8601SecondsSimpleDateFormat.parse(toParse);
} catch (ParseException e) { } catch (ParseException pe) {
throw new RuntimeException(e); throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
} }
} }
} }