5
5
import java .text .ParseException ;
6
6
7
7
/**
8
- * Utilities methods for manipulating dates in iso8601 format. This is much much faster and GC friendly than
9
- * using SimpleDateFormat so highly suitable if you (un)serialize lots of date objects.
8
+ * Utilities methods for manipulating dates in iso8601 format. This is much much faster and GC friendly than using SimpleDateFormat so
9
+ * highly suitable if you (un)serialize lots of date objects.
10
+ *
11
+ * Supported parse format: [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]
12
+ *
13
+ * @see http://www.w3.org/TR/NOTE-datetime
10
14
*/
11
15
public class ISO8601Utils {
12
16
@@ -20,26 +24,24 @@ public class ISO8601Utils {
20
24
*/
21
25
private static final TimeZone TIMEZONE_GMT = TimeZone .getTimeZone (GMT_ID );
22
26
23
- /*
24
- /**********************************************************
25
- /* Static factories
26
- /**********************************************************
27
- */
28
-
27
+ /* ********************************************************* */
28
+ /* Static factories */
29
+ /* ********************************************************* */
30
+
29
31
/**
30
32
* Accessor for static GMT timezone instance.
31
33
*/
32
- public static TimeZone timeZoneGMT () { return TIMEZONE_GMT ; }
34
+ public static TimeZone timeZoneGMT () {
35
+ return TIMEZONE_GMT ;
36
+ }
37
+
38
+ /* ********************************************************* */
39
+ /* Formatting */
40
+ /* ********************************************************* */
33
41
34
- /*
35
- /**********************************************************
36
- /* Formatting
37
- /**********************************************************
38
- */
39
-
40
42
/**
41
43
* Format a date into 'yyyy-MM-ddThh:mm:ssZ' (GMT timezone, no milliseconds precision)
42
- *
44
+ *
43
45
* @param date the date to format
44
46
* @return the date formatted as 'yyyy-MM-ddThh:mm:ssZ'
45
47
*/
@@ -49,8 +51,8 @@ public static String format(Date date) {
49
51
50
52
/**
51
53
* Format a date into 'yyyy-MM-ddThh:mm:ss[.sss]Z' (GMT timezone)
52
- *
53
- * @param date the date to format
54
+ *
55
+ * @param date the date to format
54
56
* @param millis true to include millis precision otherwise false
55
57
* @return the date formatted as 'yyyy-MM-ddThh:mm:ss[.sss]Z'
56
58
*/
@@ -60,10 +62,10 @@ public static String format(Date date, boolean millis) {
60
62
61
63
/**
62
64
* Format date into yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
63
- *
64
- * @param date the date to format
65
+ *
66
+ * @param date the date to format
65
67
* @param millis true to include millis precision otherwise false
66
- * @param tz timezone to use for the formatting (GMT will produce 'Z')
68
+ * @param tz timezone to use for the formatting (GMT will produce 'Z')
67
69
* @return the date formatted as yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
68
70
*/
69
71
public static String format (Date date , boolean millis , TimeZone tz ) {
@@ -107,55 +109,73 @@ public static String format(Date date, boolean millis, TimeZone tz) {
107
109
return formatted .toString ();
108
110
}
109
111
110
- /*
111
- /**********************************************************
112
- /* Parsing
113
- /**********************************************************
114
- */
112
+ /* ********************************************************* */
113
+ /* Parsing */
114
+ /* ********************************************************* */
115
115
116
116
/**
117
- * Parse a date from ISO-8601 formatted string. It expects a format yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
118
- *
117
+ * Parse a date from ISO-8601 formatted string. It expects a format
118
+ * [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]
119
+ *
119
120
* @param date ISO string to parse in the appropriate format.
120
121
* @param pos The position to start parsing from, updated to where parsing stopped.
121
122
* @return the parsed date
122
123
* @throws ParseException if the date is not in the appropriate format
123
124
*/
124
- public static Date parse (String date , ParsePosition pos ) throws ParseException
125
- {
125
+ public static Date parse (String date , ParsePosition pos ) throws ParseException {
126
126
Exception fail = null ;
127
127
try {
128
128
int offset = pos .getIndex ();
129
129
130
130
// extract year
131
131
int year = parseInt (date , offset , offset += 4 );
132
- checkOffset (date , offset , '-' );
132
+ if (checkOffset (date , offset , '-' )) {
133
+ offset += 1 ;
134
+ }
133
135
134
136
// extract month
135
- int month = parseInt (date , offset += 1 , offset += 2 );
136
- checkOffset (date , offset , '-' );
137
+ int month = parseInt (date , offset , offset += 2 );
138
+ if (checkOffset (date , offset , '-' )) {
139
+ offset += 1 ;
140
+ }
137
141
138
142
// extract day
139
- int day = parseInt (date , offset += 1 , offset += 2 );
140
- checkOffset (date , offset , 'T' );
141
-
142
- // extract hours, minutes, seconds and milliseconds
143
- int hour = parseInt (date , offset += 1 , offset += 2 );
144
- checkOffset (date , offset , ':' );
143
+ int day = parseInt (date , offset , offset += 2 );
144
+ // default time value
145
+ int hour = 0 ;
146
+ int minutes = 0 ;
147
+ int seconds = 0 ;
148
+ int milliseconds = 0 ; // always use 0 otherwise returned date will include millis of current time
149
+ if (checkOffset (date , offset , 'T' )) {
145
150
146
- int minutes = parseInt (date , offset += 1 , offset += 2 );
147
- checkOffset (date , offset , ':' );
151
+ // extract hours, minutes, seconds and milliseconds
152
+ hour = parseInt (date , offset += 1 , offset += 2 );
153
+ if (checkOffset (date , offset , ':' )) {
154
+ offset += 1 ;
155
+ }
148
156
149
- int seconds = parseInt (date , offset += 1 , offset += 2 );
150
- // milliseconds can be optional in the format
151
- int milliseconds = 0 ; // always use 0 otherwise returned date will include millis of current time
152
- if (date .charAt (offset ) == '.' ) {
153
- checkOffset (date , offset , '.' );
154
- milliseconds = parseInt (date , offset += 1 , offset += 3 );
157
+ minutes = parseInt (date , offset , offset += 2 );
158
+ if (checkOffset (date , offset , ':' )) {
159
+ offset += 1 ;
160
+ }
161
+ // second and milliseconds can be optional
162
+ if (date .length () > offset ) {
163
+ char c = date .charAt (offset );
164
+ if (c != 'Z' && c != '+' && c != '-' ) {
165
+ seconds = parseInt (date , offset , offset += 2 );
166
+ // milliseconds can be optional in the format
167
+ if (checkOffset (date , offset , '.' )) {
168
+ milliseconds = parseInt (date , offset += 1 , offset += 3 );
169
+ }
170
+ }
171
+ }
155
172
}
156
173
157
174
// extract timezone
158
175
String timezoneId ;
176
+ if (date .length () <= offset ) {
177
+ throw new IndexOutOfBoundsException ("No time zone indicator " );
178
+ }
159
179
char timezoneIndicator = date .charAt (offset );
160
180
if (timezoneIndicator == '+' || timezoneIndicator == '-' ) {
161
181
String timezoneOffset = date .substring (offset );
@@ -185,41 +205,37 @@ public static Date parse(String date, ParsePosition pos) throws ParseException
185
205
186
206
pos .setIndex (offset );
187
207
return calendar .getTime ();
188
- //If we get a ParseException it'll already have the right message/offset.
189
- //Other exception types can convert here.
208
+ // If we get a ParseException it'll already have the right message/offset.
209
+ // Other exception types can convert here.
190
210
} catch (IndexOutOfBoundsException e ) {
191
211
fail = e ;
192
212
} catch (NumberFormatException e ) {
193
213
fail = e ;
194
214
} catch (IllegalArgumentException e ) {
195
215
fail = e ;
196
216
}
197
- String input = (date == null ) ? null : ('"' +date +"'" );
198
- throw new ParseException ("Failed to parse date [" +input
199
- +"]: " +fail .getMessage (), pos .getIndex ());
217
+ String input = (date == null ) ? null : ('"' + date + "'" );
218
+ throw new ParseException ("Failed to parse date [" + input + "]: " + fail .getMessage (), pos .getIndex ());
200
219
}
201
220
202
221
/**
203
- * Check if the expected character exist at the given offset of the
204
- *
205
- * @param value the string to check at the specified offset
206
- * @param offset the offset to look for the expected character
222
+ * Check if the expected character exist at the given offset in the value.
223
+ *
224
+ * @param value the string to check at the specified offset
225
+ * @param offset the offset to look for the expected character
207
226
* @param expected the expected character
208
- * @throws IndexOutOfBoundsException if the expected character is not found
227
+ * @return true if the expected character exist at the given offset
209
228
*/
210
- private static void checkOffset (String value , int offset , char expected ) throws ParseException {
211
- char found = value .charAt (offset );
212
- if (found != expected ) {
213
- throw new ParseException ("Expected '" + expected + "' character but found '" + found + "'" , offset );
214
- }
229
+ private static boolean checkOffset (String value , int offset , char expected ) {
230
+ return value .length () > offset ? (value .charAt (offset ) == expected ) : false ;
215
231
}
216
232
217
233
/**
218
234
* Parse an integer located between 2 given offsets in a string
219
- *
220
- * @param value the string to parse
235
+ *
236
+ * @param value the string to parse
221
237
* @param beginIndex the start index for the integer in the string
222
- * @param endIndex the end index for the integer in the string
238
+ * @param endIndex the end index for the integer in the string
223
239
* @return the int
224
240
* @throws NumberFormatException if the value is not a number
225
241
*/
@@ -251,9 +267,9 @@ private static int parseInt(String value, int beginIndex, int endIndex) throws N
251
267
252
268
/**
253
269
* Zero pad a number to a specified length
254
- *
270
+ *
255
271
* @param buffer buffer to use for padding
256
- * @param value the integer value to pad if necessary.
272
+ * @param value the integer value to pad if necessary.
257
273
* @param length the length of the string we should zero pad
258
274
*/
259
275
private static void padInt (StringBuilder buffer , int value , int length ) {
@@ -264,4 +280,3 @@ private static void padInt(StringBuilder buffer, int value, int length) {
264
280
buffer .append (strValue );
265
281
}
266
282
}
267
-
0 commit comments