1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.onehippo.forge.utilities.commons.jcrmockup;
18
19 import java.text.DecimalFormat;
20 import java.text.ParseException;
21 import java.util.Calendar;
22 import java.util.GregorianCalendar;
23 import java.util.TimeZone;
24
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public final class ISO8601 {
56
57
58
59 private static final DecimalFormat XX_FORMAT = new DecimalFormat("00");
60 private static final DecimalFormat XXX_FORMAT = new DecimalFormat("000");
61 private static final DecimalFormat XXXX_FORMAT = new DecimalFormat("0000");
62
63 private static final int YEAR_LENGTH = 4;
64 private static final int MONTH_LENGTH = 2;
65 private static final int DAY_LENGTH = 2;
66 private static final int HOUR_LENGTH = 2;
67 private static final int MINUTE_LENGTH = 2;
68 private static final int SECOND_LENGTH = 2;
69 private static final int MILLISECOND_LENGTH = 3;
70 private static final int MILLISECONDS_PER_SECOND = 1000;
71 private static final int SECONDS_PER_MINUTE = 60;
72 private static final int MINUTES_PER_HOUR = 60;
73 private static final int MAX_FOUR_DIGIT_NUMBER = 9999;
74
75 private static final Logger log = LoggerFactory.getLogger(ISO8601.class);
76
77 private ISO8601() {
78
79 }
80
81
82
83
84
85
86
87
88
89
90
91
92 public static Calendar parse(String text) {
93 if (text == null) {
94 throw new IllegalArgumentException("argument can not be null");
95 }
96
97 DateTimeParser parser = new DateTimeParser(text);
98
99 char sign;
100 int year, month, day, hour, min, sec, ms;
101 TimeZone tz;
102
103 try {
104 sign = parser.parseSign();
105 year = parser.parseInt(YEAR_LENGTH);
106 parser.parseDelimiter('-');
107 month = parser.parseInt(MONTH_LENGTH);
108 parser.parseDelimiter('-');
109 day = parser.parseInt(DAY_LENGTH);
110 parser.parseDelimiter('T');
111 hour = parser.parseInt(HOUR_LENGTH);
112 parser.parseDelimiter(':');
113 min = parser.parseInt(MINUTE_LENGTH);
114 parser.parseDelimiter(':');
115 sec = parser.parseInt(SECOND_LENGTH);
116 parser.parseDelimiter('.');
117 ms = parser.parseInt(MILLISECOND_LENGTH);
118 tz = parser.parseTimeZone();
119 } catch (IndexOutOfBoundsException e) {
120 log.debug("Could not parse'" + text + "'", e);
121 return null;
122 } catch (NumberFormatException e) {
123 log.debug("Could not parse '" + text + "'", e);
124 return null;
125 } catch (ParseException e) {
126 log.debug("Could not parse '" + text + "'", e);
127 return null;
128 }
129
130
131 Calendar cal = Calendar.getInstance(tz);
132 cal.setLenient(false);
133
134 if (sign == '-' || year == 0) {
135
136 cal.set(Calendar.YEAR, year + 1);
137 cal.set(Calendar.ERA, GregorianCalendar.BC);
138 } else {
139 cal.set(Calendar.YEAR, year);
140 cal.set(Calendar.ERA, GregorianCalendar.AD);
141 }
142
143 cal.set(Calendar.MONTH, month - 1);
144
145 cal.set(Calendar.DAY_OF_MONTH, day);
146
147 cal.set(Calendar.HOUR_OF_DAY, hour);
148
149 cal.set(Calendar.MINUTE, min);
150
151 cal.set(Calendar.SECOND, sec);
152
153 cal.set(Calendar.MILLISECOND, ms);
154
155 try {
156
157
158
159
160 cal.getTime();
161 } catch (IllegalArgumentException e) {
162 return null;
163 }
164
165 return cal;
166 }
167
168
169
170
171
172
173
174
175
176
177
178 public static String format(Calendar cal) {
179 if (cal == null) {
180 throw new IllegalArgumentException("argument can not be null");
181 }
182
183
184
185
186
187
188
189
190 StringBuffer buf = new StringBuffer();
191
192 buf.append(XXXX_FORMAT.format(getYear(cal)));
193 buf.append('-');
194
195 buf.append(XX_FORMAT.format(cal.get(Calendar.MONTH) + 1));
196 buf.append('-');
197
198 buf.append(XX_FORMAT.format(cal.get(Calendar.DAY_OF_MONTH)));
199 buf.append('T');
200
201 buf.append(XX_FORMAT.format(cal.get(Calendar.HOUR_OF_DAY)));
202 buf.append(':');
203
204 buf.append(XX_FORMAT.format(cal.get(Calendar.MINUTE)));
205 buf.append(':');
206
207 buf.append(XX_FORMAT.format(cal.get(Calendar.SECOND)));
208 buf.append('.');
209
210 buf.append(XXX_FORMAT.format(cal.get(Calendar.MILLISECOND)));
211
212 TimeZone tz = cal.getTimeZone();
213
214 int offset = tz.getOffset(cal.getTimeInMillis());
215 if (offset != 0) {
216 int hours = Math.abs((offset / (SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND)) / MINUTES_PER_HOUR);
217 int minutes = Math.abs((offset / (SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND)) % MINUTES_PER_HOUR);
218 buf.append(offset < 0 ? '-' : '+');
219 buf.append(XX_FORMAT.format(hours));
220 buf.append(':');
221 buf.append(XX_FORMAT.format(minutes));
222 } else {
223 buf.append('Z');
224 }
225 return buf.toString();
226 }
227
228
229
230
231
232
233
234
235
236
237 public static int getYear(Calendar cal) {
238
239 int year = cal.get(Calendar.YEAR);
240 if (cal.isSet(Calendar.ERA)
241 && cal.get(Calendar.ERA) == GregorianCalendar.BC) {
242
243
244
245
246 year = 0 - year + 1;
247 }
248
249 if (year > MAX_FOUR_DIGIT_NUMBER || year < -MAX_FOUR_DIGIT_NUMBER) {
250 throw new IllegalArgumentException("Calendar has more than four " +
251 "year digits, cannot be formatted as ISO8601: " + year);
252 }
253 return year;
254 }
255
256 private static class DateTimeParser {
257
258 private final String text;
259 private int offset;
260
261 DateTimeParser(String text) {
262 this.text = text;
263 offset = 0;
264 }
265
266 char parseSign() {
267 char sign = text.charAt(offset);
268
269 if (sign == '-' || sign == '+') {
270 offset++;
271 return sign;
272 } else {
273
274 return '+';
275 }
276 }
277
278 int parseInt(int length) {
279 int result = Integer.parseInt(text.substring(offset, offset + length));
280 offset += length;
281 return result;
282 }
283
284 void parseDelimiter(char expected) throws ParseException {
285 if (text.charAt(offset) != expected) {
286 throw new ParseException("Expected delimiter '" + expected + "'", offset);
287 }
288 offset++;
289 }
290
291 TimeZone parseTimeZone() throws ParseException {
292
293 final char sign = text.charAt(offset);
294 String tzId;
295
296 if (sign == '+' || sign == '-') {
297
298 tzId = "GMT" + text.substring(offset);
299 } else if (sign == 'Z') {
300 tzId = "GMT";
301 } else {
302 throw new ParseException("Invalid time zone, cannot start with '" + sign + "'", offset);
303 }
304
305 TimeZone tz = TimeZone.getTimeZone(tzId);
306
307
308 if (!tz.getID().equals(tzId)) {
309
310 throw new ParseException("Invalid time zone: '" + tzId + "'", offset);
311 }
312
313 return tz;
314 }
315
316 }
317
318 }