1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springmodules.validation.util.date;
18
19 import java.text.DateFormat;
20 import java.text.ParseException;
21 import java.text.SimpleDateFormat;
22 import java.util.*;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25
26 import org.apache.commons.collections.Predicate;
27
28 /**
29 * <p>DefaultDateParser parses many date formats to a string.
30 * <p/>
31 * <p>The supported date formats are:
32 * <p/>
33 * <ul>
34 * <li>yyyy-MM-dd (^\\d{4}\\-\\d{2}\\-\\d{2}$)
35 * <li>yyyyMMdd (^\\d{8}$)
36 * <li>yyyy-MM-dd HH:mm:ss (^\\d{4}\\-\\d{2}\\-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}$)
37 * <li>yyyyMMdd HHmmss (^\\d{8}\\s+\\d{6}$)
38 * <li>yyyyMMdd HH:mm:ss (^\\d{8}\\s+\\d{2}:\\d{2}:\\d{2}$)
39 * <li>yyyy-MM-dd HHmmss (^\\d{4}\\-\\d{2}\\-\\d{2}\\s+\\d{6}$)
40 * <li>T (^T$)
41 * </ul>
42 * <p/>
43 * <p>Date formats can be added using DefaultDateParser#register(String, String).
44 * <p/>
45 * <p>These modifiers are supported:
46 * <p/>
47 * <ul>
48 * <li>T+?S (add ? milliseconds to T, T can be any valid date format with modifiers)
49 * <li>T-?S (subtract ? milliseconds from T, idem)
50 * <li>T+?s (add ? seconds to T, idem)
51 * <li>T-?s (subtract ? seconds from T, idem)
52 * <li>T+?m (add ? minutes to T, idem)
53 * <li>T-?m (subtract ? minutes from T, idem)
54 * <li>T+?H (add ? hours to T, idem)
55 * <li>T-?H (subtract ? hours from T, idem)
56 * <li>T+?d (add ? hours to T, idem)
57 * <li>T-?d (subtract ? hours from T, idem)
58 * <li>T+?w (add ? weeks to T, idem)
59 * <li>T-?w (subtract ? weeks from T, idem)
60 * <li>T+?M (add ? months to T, idem)
61 * <li>T-?M (subtract ? months from T, idem)
62 * <li>T+?y (add ? years to T, idem)
63 * <li>T-?y (subtract ? years from T, idem)
64 * <li>T<s (shift T to start of current second, idem)
65 * <li>T>s (shift T to start of next second, idem)
66 * <li>T<m (shift T to start of current minute, idem)
67 * <li>T>m (shift T to start of next minute, idem)
68 * <li>T<H (shift T to start of current hour, idem)
69 * <li>T>H (shift T to start of next hour, idem)
70 * <li>T<d (shift T to start of current day, idem)
71 * <li>T>d (shift T to start of next day, idem)
72 * <li>T<w (shift T to start of current week, idem)
73 * <li>T>w (shift T to start of next week, idem)
74 * <li>T<y (shift T to start of current year, idem)
75 * <li>T>y (shift T to start of next year, idem)
76 * </ul>
77 * <p/>
78 * <p>Modifiers can be added using DefaultDateParser#register(String, DateModifier).
79 * <p/>
80 * <p>Modifiers can be combined and are parsed from left to right, for example:
81 * <p/>
82 * <p><code>2005-04-09 23:30:00>M+10d+8H</code> results in <code>2005-05-11 08:00:00</code>.
83 *
84 * @author Steven Devijver
85 * @since 25-04-2005
86 */
87 public class DefaultDateParser implements DateParser {
88
89 private static DefaultDateParser instance = new DefaultDateParser();
90
91 private Map registrations = new HashMap();
92
93 public static DefaultDateParser getInstance() {
94 return instance;
95 }
96
97 public DefaultDateParser() {
98 super();
99 register("^\\d{8}$", "yyyyMMdd");
100 register("^\\d{4}\\-\\d{2}\\-\\d{2}$", "yyyy-MM-dd");
101 register("^\\d{4}\\-\\d{2}\\-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}$", "yyyy-MM-dd HH:mm:ss");
102 register("^\\d{8}\\s+\\d{6}$", "yyyyMMdd HHmmss");
103 register("^\\d{8}\\s+\\d{2}:\\d{2}:\\d{2}$", "yyyyMMdd HH:mm:ss");
104 register("^\\d{4}\\-\\d{2}\\-\\d{2}\\s+\\d{6}$", "yyyy-MM-dd HHmmss");
105
106 register("^T$", new DateModifier() {
107 public void modify(Calendar calendar, String value) {
108 }
109 });
110
111 register("^T\\+(\\d+)S$", new DateModifier() {
112 public void modify(Calendar calendar, String value) {
113 calendar.add(Calendar.MILLISECOND, Integer.parseInt(value));
114 }
115 });
116
117 register("^T\\-(\\d+)S$", new DateModifier() {
118 public void modify(Calendar calendar, String value) {
119 calendar.add(Calendar.MILLISECOND, Integer.parseInt(value) * -1);
120 }
121 });
122
123 register("^T>s$", new DateModifier() {
124 public void modify(Calendar calendar, String value) {
125 calendar.add(Calendar.SECOND, 1);
126 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) + 1 * -1);
127 }
128 });
129
130 register("^T<s$", new DateModifier() {
131 public void modify(Calendar calendar, String value) {
132 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
133 }
134 });
135
136 register("^T\\+(\\d+)s$", new DateModifier() {
137 public void modify(Calendar calendar, String value) {
138 calendar.add(Calendar.SECOND, Integer.parseInt(value));
139 }
140 });
141
142 register("^T\\-(\\d+)s$", new DateModifier() {
143 public void modify(Calendar calendar, String value) {
144 calendar.add(Calendar.SECOND, Integer.parseInt(value) * -1);
145 }
146 });
147
148 register("^T>m$", new DateModifier() {
149 public void modify(Calendar calendar, String value) {
150 calendar.add(Calendar.MINUTE, 1);
151 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
152 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) + 1 * -1);
153 }
154 });
155
156 register("^T<m$", new DateModifier() {
157 public void modify(Calendar calendar, String value) {
158 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
159 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
160 }
161 });
162
163 register("^T\\+(\\d+)m$", new DateModifier() {
164 public void modify(Calendar calendar, String value) {
165 calendar.add(Calendar.MINUTE, Integer.parseInt(value));
166 }
167 });
168
169 register("^T\\-(\\d+)m$", new DateModifier() {
170 public void modify(Calendar calendar, String value) {
171 calendar.add(Calendar.MINUTE, Integer.parseInt(value) * -1);
172 }
173 });
174
175 register("^T>H$", new DateModifier() {
176 public void modify(Calendar calendar, String value) {
177 calendar.add(Calendar.HOUR_OF_DAY, 1);
178 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
179 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
180 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) + 1 * -1);
181 }
182 });
183
184 register("^T<H$", new DateModifier() {
185 public void modify(Calendar calendar, String value) {
186 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
187 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
188 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
189 }
190 });
191
192 register("^T\\+(\\d+)H$", new DateModifier() {
193 public void modify(Calendar calendar, String value) {
194 calendar.add(Calendar.HOUR_OF_DAY, Integer.parseInt(value));
195 }
196 });
197
198 register("^T\\-(\\d+)H$", new DateModifier() {
199 public void modify(Calendar calendar, String value) {
200 calendar.add(Calendar.HOUR_OF_DAY, Integer.parseInt(value) * -1);
201 }
202 });
203
204 register("^T>d$", new DateModifier() {
205 public void modify(Calendar calendar, String value) {
206 calendar.add(Calendar.DAY_OF_YEAR, 1);
207 calendar.add(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) * -1);
208 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
209 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
210 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) + 1 * -1);
211 }
212 });
213
214 register("^T<d$", new DateModifier() {
215 public void modify(Calendar calendar, String value) {
216 calendar.add(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) * -1);
217 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
218 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
219 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
220 }
221 });
222
223 register("^T\\+(\\d+)d$", new DateModifier() {
224 public void modify(Calendar calendar, String value) {
225 calendar.add(Calendar.DAY_OF_YEAR, Integer.parseInt(value));
226 }
227 });
228
229 register("^T\\-(\\d+)d$", new DateModifier() {
230 public void modify(Calendar calendar, String value) {
231 calendar.add(Calendar.DAY_OF_YEAR, Integer.parseInt(value) * -1);
232 }
233 });
234
235 register("^T>w$", new DateModifier() {
236 public void modify(Calendar calendar, String value) {
237 int thisWeek = calendar.get(Calendar.WEEK_OF_YEAR);
238 calendar.add(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) * -1);
239 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
240 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
241 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
242
243 while (thisWeek == calendar.get(Calendar.WEEK_OF_YEAR)) {
244 calendar.add(Calendar.DAY_OF_YEAR, 1);
245 }
246
247 calendar.add(Calendar.MILLISECOND, -1);
248 }
249 });
250
251 register("^T<w$", new DateModifier() {
252 public void modify(Calendar calendar, String value) {
253 int thisWeek = calendar.get(Calendar.WEEK_OF_YEAR);
254 calendar.add(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) * -1);
255 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
256 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
257 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
258
259 while (thisWeek == calendar.get(Calendar.WEEK_OF_YEAR)) {
260 calendar.add(Calendar.DAY_OF_YEAR, -1);
261 }
262 calendar.add(Calendar.DAY_OF_YEAR, 1);
263 }
264 });
265
266 register("^T\\+(\\d+)w$", new DateModifier() {
267 public void modify(Calendar calendar, String value) {
268 calendar.add(Calendar.WEEK_OF_YEAR, Integer.parseInt(value));
269 }
270 });
271
272 register("^T\\-(\\d+)w$", new DateModifier() {
273 public void modify(Calendar calendar, String value) {
274 calendar.add(Calendar.WEEK_OF_YEAR, Integer.parseInt(value) * -1);
275 }
276 });
277
278 register("^T>M$", new DateModifier() {
279 public void modify(Calendar calendar, String value) {
280 int thisMonth = calendar.get(Calendar.MONTH);
281 calendar.add(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) * -1);
282 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
283 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
284 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
285
286 while (thisMonth == calendar.get(Calendar.MONTH)) {
287 calendar.add(Calendar.DAY_OF_YEAR, 1);
288 }
289
290 calendar.add(Calendar.MILLISECOND, -1);
291 }
292 });
293
294 register("^T<M$", new DateModifier() {
295 public void modify(Calendar calendar, String value) {
296 int thisMonth = calendar.get(Calendar.MONTH);
297 calendar.add(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) * -1);
298 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
299 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
300 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
301
302 while (thisMonth == calendar.get(Calendar.MONTH)) {
303 calendar.add(Calendar.DAY_OF_YEAR, -1);
304 }
305 calendar.add(Calendar.DAY_OF_YEAR, 1);
306 }
307 });
308
309 register("^T\\+(\\d+)M$", new DateModifier() {
310 public void modify(Calendar calendar, String value) {
311 calendar.add(Calendar.MONTH, Integer.parseInt(value));
312 }
313 });
314
315 register("^T\\-(\\d+)M$", new DateModifier() {
316 public void modify(Calendar calendar, String value) {
317 calendar.add(Calendar.MONTH, Integer.parseInt(value) * -1);
318 }
319 });
320
321 register("^T>y$", new DateModifier() {
322 public void modify(Calendar calendar, String value) {
323 int thisYear = calendar.get(Calendar.YEAR);
324 calendar.add(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) * -1);
325 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
326 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
327 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
328
329 while (thisYear == calendar.get(Calendar.YEAR)) {
330 calendar.add(Calendar.DAY_OF_YEAR, 1);
331 }
332
333 calendar.add(Calendar.MILLISECOND, -1);
334 }
335 });
336
337 register("^T<y$", new DateModifier() {
338 public void modify(Calendar calendar, String value) {
339 int thisYear = calendar.get(Calendar.YEAR);
340 calendar.add(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) * -1);
341 calendar.add(Calendar.MINUTE, calendar.get(Calendar.MINUTE) * -1);
342 calendar.add(Calendar.SECOND, calendar.get(Calendar.SECOND) * -1);
343 calendar.add(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND) * -1);
344
345 while (thisYear == calendar.get(Calendar.YEAR)) {
346 calendar.add(Calendar.DAY_OF_YEAR, -1);
347 }
348 calendar.add(Calendar.DAY_OF_YEAR, 1);
349 }
350 });
351
352 register("^T\\+(\\d+)y$", new DateModifier() {
353 public void modify(Calendar calendar, String value) {
354 calendar.add(Calendar.YEAR, Integer.parseInt(value));
355 }
356 });
357
358 register("^T\\-(\\d+)y$", new DateModifier() {
359 public void modify(Calendar calendar, String value) {
360 calendar.add(Calendar.YEAR, Integer.parseInt(value) * -1);
361 }
362 });
363 }
364
365 public Date parse(String str) throws DateParseException {
366 Date t = null;
367 String tmpStr = null;
368 int parsedSoFar = 0;
369 boolean firstPass = true;
370
371 if (str == null || str.length() == 0) {
372 throw new IllegalArgumentException("Date string should not be null or blank!");
373 }
374
375 tmpStr = str;
376
377 while (tmpStr.length() > 0) {
378 Date tmpT = null;
379 tmpT = simpleParse(tmpStr, t);
380 if (tmpT != null) {
381 t = tmpT;
382 if (firstPass) {
383 parsedSoFar = tmpStr.length();
384 } else {
385 parsedSoFar += tmpStr.length() - 1;
386 }
387 tmpStr = "T" + str.substring(parsedSoFar);
388 firstPass = false;
389 } else {
390 tmpStr = tmpStr.substring(0, tmpStr.length() - 1);
391 }
392 if (tmpStr.equals("T")) {
393 if (parsedSoFar == str.length()) {
394 break;
395 } else {
396 throw new DateParseException("Could not parse date string [" + str + "]!");
397 }
398 }
399 }
400
401 if (t == null) {
402 throw new DateParseException("Could not parse date string [" + str + "]!");
403 } else {
404 return t;
405 }
406 }
407
408 private Date simpleParse(String str, Date t) throws DateParseException {
409 for (Iterator iter = this.registrations.keySet().iterator(); iter.hasNext();) {
410 RegexpPredicate predicate = (RegexpPredicate) iter.next();
411 if (predicate.evaluate(str)) {
412 Object dateParser = this.registrations.get(predicate);
413 if (dateParser instanceof DateParser) {
414 return ((DateParser) dateParser).parse(str);
415 } else if (dateParser instanceof DateModifier) {
416 Calendar calendar = new GregorianCalendar();
417 calendar.setFirstDayOfWeek(Calendar.MONDAY);
418 if (t == null) {
419 calendar.setTime(new Date());
420 } else {
421 calendar.setTime(t);
422 }
423 ((DateModifier) dateParser).modify(calendar, predicate.getGroup1(str));
424 return calendar.getTime();
425 }
426 }
427 }
428
429 return null;
430 }
431
432 /**
433 * <p>Register a date format for a given regular expression.
434 *
435 * @param regexp the regular expression
436 * @param format the date format
437 */
438 public void register(String regexp, String format) {
439 this.registrations.put(new RegexpPredicate(regexp), new BasicDateParser(format));
440 }
441
442 /**
443 * <p>Register your own date parser for a given regular expression.
444 *
445 * @param regexp the regular expression
446 * @param dateParser the date parser
447 */
448 public void register(String regexp, DateModifier dateParser) {
449 this.registrations.put(new RegexpPredicate(regexp), dateParser);
450 }
451
452 public interface DateModifier {
453
454 public void modify(Calendar calendar, String value);
455 }
456
457 private class RegexpPredicate implements Predicate {
458
459 private Pattern pattern = null;
460
461 public RegexpPredicate(String regexp) {
462 super();
463 if (regexp == null || regexp.length() == 0) {
464 throw new IllegalArgumentException("Regular expression parameter should not be null or blank!");
465 }
466 this.pattern = Pattern.compile(regexp);
467 }
468
469 public boolean evaluate(Object o) {
470 return this.pattern.matcher((String) o).matches();
471 }
472
473 public String getGroup1(Object o) {
474 Matcher matcher = this.pattern.matcher((String) o);
475 if (matcher.matches() && matcher.groupCount() > 0) {
476 return matcher.group(1);
477 } else {
478 return null;
479 }
480 }
481 }
482
483 private class BasicDateParser implements DateParser {
484
485 private DateFormat dateFormat = null;
486
487 public BasicDateParser(String format) {
488 super();
489 if (format == null || format.length() == 0) {
490 throw new IllegalArgumentException("Format parameter should not be null or blank!");
491 }
492 this.dateFormat = new SimpleDateFormat(format);
493 }
494
495 public Date parse(String s) throws DateParseException {
496 try {
497 return this.dateFormat.parse(s);
498 } catch (ParseException e) {
499 return null;
500 }
501 }
502 }
503 }