2
2
3
3
import java .text .ParseException ;
4
4
import java .text .SimpleDateFormat ;
5
+ import java .time .Instant ;
6
+ import java .time .OffsetDateTime ;
7
+ import java .time .format .DateTimeFormatter ;
8
+ import java .time .format .DateTimeFormatterBuilder ;
9
+ import java .time .temporal .ChronoField ;
5
10
import java .util .Calendar ;
6
11
import java .util .Date ;
7
12
import java .util .Map ;
8
13
import java .util .TimeZone ;
9
14
import java .util .concurrent .ConcurrentHashMap ;
10
15
11
- import javax .xml .bind .DatatypeConverter ;
12
-
13
16
/**
14
17
* This class provides utility methods for parsing and formatting ISO8601 formatted dates.
15
18
*/
16
19
public class ISO8601 {
17
20
18
21
public static final String PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ" ;
22
+ public static final String MSEC_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ;
19
23
public static final String SPACEY_PATTERN = "yyyy-MM-dd HH:mm:ss Z" ;
24
+ public static final String SPACEY_MSEC_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS Z" ;
20
25
public static final String PATTERN_MSEC = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ;
21
26
public static final String OUTPUT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'" ;
22
27
public static final String OUTPUT_MSEC_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" ;
23
28
public static final String UTC_PATTERN = "yyyy-MM-dd HH:mm:ss 'UTC'" ;
24
29
25
- private static final String PATTERN_REGEX = "\\ d\\ d\\ d\\ d-\\ d\\ d-\\ d\\ dT\\ d\\ d:\\ d\\ d:\\ d\\ d[-+]\\ d\\ d\\ d\\ d" ;
26
- private static final String SPACEY_PATTERN_REGEX = "\\ d\\ d\\ d\\ d-\\ d\\ d-\\ d\\ d \\ d\\ d:\\ d\\ d:\\ d\\ d [-+]\\ d\\ d\\ d\\ d" ;
30
+ private static final DateTimeFormatter ODT_WITH_MSEC_PARSER = new DateTimeFormatterBuilder ().appendPattern ("yyyy-MM-dd[['T'][ ]HH:mm:ss.SSS[ ][XXXXX][XXXX]]" ).toFormatter ();
31
+ private static final DateTimeFormatter ODT_PARSER = new DateTimeFormatterBuilder ().appendPattern ("yyyy-MM-dd[['T'][ ]HH:mm:ss[.SSS][ ][X][XXX]]" )
32
+ .parseDefaulting (ChronoField .HOUR_OF_DAY , 0 )
33
+ .parseDefaulting (ChronoField .MINUTE_OF_HOUR , 0 )
34
+ .parseDefaulting (ChronoField .SECOND_OF_MINUTE , 0 )
35
+ .parseDefaulting (ChronoField .MILLI_OF_SECOND , 0 )
36
+ .parseDefaulting (ChronoField .OFFSET_SECONDS , 0 )
37
+ .toFormatter ();
27
38
28
39
// Set up ThreadLocal storage to save a thread local SimpleDateFormat keyed with the format string
29
40
private static final class SafeDateFormatter {
@@ -116,39 +127,43 @@ public static String toString(Date date) {
116
127
}
117
128
118
129
/**
119
- * Parses an ISO8601 formatted string a returns a Date instance.
130
+ * Parses an ISO8601 formatted string a returns an Instant instance.
120
131
*
121
132
* @param dateTimeString the ISO8601 formatted string
122
- * @return a Date instance for the ISO8601 formatted string
133
+ * @return an Instant instance for the ISO8601 formatted string
123
134
* @throws ParseException if the provided string is not in the proper format
124
135
*/
125
- public static Date toDate (String dateTimeString ) throws ParseException {
136
+ public static Instant toInstant (String dateTimeString ) throws ParseException {
126
137
127
138
if (dateTimeString == null ) {
128
139
return (null );
129
140
}
130
141
131
142
dateTimeString = dateTimeString .trim ();
132
- if (dateTimeString .endsWith ("UTC" )) {
133
- return (SafeDateFormatter . getDateFormat ( UTC_PATTERN ) .parse (dateTimeString ));
143
+ if (dateTimeString .endsWith ("Z" ) || dateTimeString . endsWith ( " UTC" )) {
144
+ return (Instant .parse (dateTimeString ));
134
145
} else {
135
- try {
136
- Calendar cal = DatatypeConverter .parseDateTime (dateTimeString );
137
- return (cal .getTime ());
138
- } catch (Exception e ) {
139
- if (dateTimeString .matches (PATTERN_REGEX )) {
140
- // Try using the ISO8601 format
141
- return (SafeDateFormatter .getDateFormat (PATTERN ).parse (dateTimeString ));
142
- } else if (dateTimeString .matches (SPACEY_PATTERN_REGEX )) {
143
- // Try using the invalid ISO8601 format with spaces, GitLab sometimes uses this
144
- return (SafeDateFormatter .getDateFormat (SPACEY_PATTERN ).parse (dateTimeString ));
145
- } else {
146
- throw e ;
147
- }
148
- }
146
+
147
+ OffsetDateTime odt = (dateTimeString .length () > 25 ?
148
+ OffsetDateTime .parse (dateTimeString , ODT_WITH_MSEC_PARSER ) :
149
+ OffsetDateTime .parse (dateTimeString , ODT_PARSER ));
150
+
151
+ return (odt .toInstant ());
149
152
}
150
153
}
151
154
155
+ /**
156
+ * Parses an ISO8601 formatted string a returns a Date instance.
157
+ *
158
+ * @param dateTimeString the ISO8601 formatted string
159
+ * @return a Date instance for the ISO8601 formatted string
160
+ * @throws ParseException if the provided string is not in the proper format
161
+ */
162
+ public static Date toDate (String dateTimeString ) throws ParseException {
163
+ Instant instant = toInstant (dateTimeString );
164
+ return (instant != null ? Date .from (instant ) : null );
165
+ }
166
+
152
167
/**
153
168
* Parses an ISO8601 formatted string a returns a Calendar instance.
154
169
*
@@ -159,6 +174,10 @@ public static Date toDate(String dateTimeString) throws ParseException {
159
174
public static Calendar toCalendar (String dateTimeString ) throws ParseException {
160
175
161
176
Date date = toDate (dateTimeString );
177
+ if (date == null ) {
178
+ return (null );
179
+ }
180
+
162
181
Calendar cal = Calendar .getInstance ();
163
182
cal .setTime (date );
164
183
return (cal );
0 commit comments