View Javadoc

1   /*
2    * Copyright 2004-2009 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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&lt;s (shift T to start of current second, idem)
65   * <li>T&gt;s (shift T to start of next second, idem)
66   * <li>T&lt;m (shift T to start of current minute, idem)
67   * <li>T&gt;m (shift T to start of next minute, idem)
68   * <li>T&lt;H (shift T to start of current hour, idem)
69   * <li>T&gt;H (shift T to start of next hour, idem)
70   * <li>T&lt;d (shift T to start of current day, idem)
71   * <li>T&gt;d (shift T to start of next day, idem)
72   * <li>T&lt;w (shift T to start of current week, idem)
73   * <li>T&gt;w (shift T to start of next week, idem)
74   * <li>T&lt;y (shift T to start of current year, idem)
75   * <li>T&gt;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&gt;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 }