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