44import java .text .SimpleDateFormat ;
55import java .util .Calendar ;
66import java .util .Date ;
7+ import java .util .Map ;
78import java .util .TimeZone ;
9+ import java .util .concurrent .ConcurrentHashMap ;
810
911import javax .xml .bind .DatatypeConverter ;
1012
1113/**
1214 * This class provides utility methods for parsing and formatting ISO8601 formatted dates.
1315 */
1416public class ISO8601 {
17+
1518 public static final String PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ" ;
1619 public static final String PATTERN_MSEC = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ;
1720 public static final String OUTPUT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'" ;
1821 public static final String OUTPUT_MSEC_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" ;
1922 public static final String UTC_PATTERN = "yyyy-MM-dd HH:mm:ss 'UTC'" ;
2023
21- private static final SimpleDateFormat iso8601Format ;
22- private static final SimpleDateFormat iso8601MsecFormat ;
23- private static final SimpleDateFormat iso8601OutputFormat ;
24- private static final SimpleDateFormat iso8601OutputMsecFormat ;
25- private static final SimpleDateFormat iso8601UtcFormat ;
26- static {
27- iso8601Format = new SimpleDateFormat (PATTERN );
28- iso8601Format .setLenient (true );
29- iso8601Format .setTimeZone (TimeZone .getTimeZone ("UTC" ));
30- iso8601MsecFormat = new SimpleDateFormat (PATTERN_MSEC );
31- iso8601MsecFormat .setLenient (true );
32- iso8601MsecFormat .setTimeZone (TimeZone .getTimeZone ("UTC" ));
33- iso8601OutputFormat = new SimpleDateFormat (OUTPUT_PATTERN );
34- iso8601OutputFormat .setLenient (true );
35- iso8601OutputFormat .setTimeZone (TimeZone .getTimeZone ("UTC" ));
36- iso8601OutputMsecFormat = new SimpleDateFormat (OUTPUT_MSEC_PATTERN );
37- iso8601OutputMsecFormat .setLenient (true );
38- iso8601OutputMsecFormat .setTimeZone (TimeZone .getTimeZone ("UTC" ));
39- iso8601UtcFormat = new SimpleDateFormat (UTC_PATTERN );
40- iso8601UtcFormat .setLenient (true );
41- iso8601UtcFormat .setTimeZone (TimeZone .getTimeZone ("UTC" ));
24+ // Set up ThreadLocal storage to save a thread local SimpleDateFormat keyed with the format stringf
25+ private static final class SafeDateFormatter {
26+
27+ private static final ThreadLocal <Map <String , SimpleDateFormat >> safeFormats = new ThreadLocal <Map <String , SimpleDateFormat >>() {
28+
29+ @ Override
30+ public Map <String , SimpleDateFormat > initialValue () {
31+ return (new ConcurrentHashMap <>());
32+ }
33+ };
34+
35+ private static SimpleDateFormat getDateFormat (String formatSpec ) {
36+
37+ Map <String , SimpleDateFormat > formatMap = safeFormats .get ();
38+ SimpleDateFormat format = formatMap .get (formatSpec );
39+ if (format == null ) {
40+ format = new SimpleDateFormat (formatSpec );
41+ format .setLenient (true );
42+ format .setTimeZone (TimeZone .getTimeZone ("UTC" ));
43+ formatMap .put (formatSpec , format );
44+ }
45+
46+ return (format );
47+ }
4248 }
4349
4450 /**
@@ -47,7 +53,7 @@ public class ISO8601 {
4753 * @return a ISO8601 formatted string for the current date and time
4854 */
4955 public static String getTimestamp () {
50- return (iso8601Format .format (new Date ()));
56+ return (SafeDateFormatter . getDateFormat ( PATTERN ) .format (new Date ()));
5157 }
5258
5359 /**
@@ -57,7 +63,8 @@ public static String getTimestamp() {
5763 * @return a ISO8601 formatted string for the current date and time
5864 */
5965 public static String getTimestamp (boolean withMsec ) {
60- return (withMsec ? iso8601MsecFormat .format (new Date ()) : iso8601Format .format (new Date ()));
66+ return (withMsec ? SafeDateFormatter .getDateFormat (PATTERN_MSEC ).format (new Date ()) :
67+ SafeDateFormatter .getDateFormat (PATTERN ).format (new Date ()));
6168 }
6269
6370 /**
@@ -89,7 +96,9 @@ public static synchronized String toString(Date date, boolean withMsec) {
8996 }
9097
9198 long time = date .getTime ();
92- return (withMsec && time % 1000 != 0 ? iso8601OutputMsecFormat .format (date ) : iso8601OutputFormat .format (date ));
99+ return (withMsec && time % 1000 != 0 ?
100+ SafeDateFormatter .getDateFormat (OUTPUT_MSEC_PATTERN ).format (date ) :
101+ SafeDateFormatter .getDateFormat (OUTPUT_PATTERN ).format (date ));
93102 }
94103
95104 /**
@@ -117,9 +126,7 @@ public static Date toDate(String dateTimeString) throws ParseException {
117126
118127 dateTimeString = dateTimeString .trim ();
119128 if (dateTimeString .endsWith ("UTC" )) {
120- synchronized (iso8601UtcFormat ) {
121- return (iso8601UtcFormat .parse (dateTimeString ));
122- }
129+ return (SafeDateFormatter .getDateFormat (UTC_PATTERN ).parse (dateTimeString ));
123130 } else {
124131 Calendar cal = DatatypeConverter .parseDateTime (dateTimeString );
125132 return (cal .getTime ());
0 commit comments