View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.validator.routines;
18  
19  import java.text.DateFormat;
20  import java.text.DateFormatSymbols;
21  import java.text.Format;
22  import java.text.SimpleDateFormat;
23  import java.util.Calendar;
24  import java.util.Locale;
25  import java.util.TimeZone;
26  
27  /**
28   * <p>Abstract class for Date/Time/Calendar validation.</p>
29   *
30   * <p>This is a <i>base</i> class for building Date / Time
31   *    Validators using format parsing.</p>
32   *
33   * @since 1.3.0
34   */
35  public abstract class AbstractCalendarValidator extends AbstractFormatValidator {
36  
37      private static final long serialVersionUID = -1410008585975827379L;
38  
39      /**
40       * The date style to use for Locale validation.
41       */
42      private final int dateStyle;
43  
44      /**
45       * The time style to use for Locale validation.
46       */
47      private final int timeStyle;
48  
49      /**
50       * Constructs an instance with the specified <i>strict</i>,
51       * <i>time</i> and <i>date</i> style parameters.
52       *
53       * @param strict {@code true} if strict
54       *        <code>Format</code> parsing should be used.
55       * @param dateStyle the date style to use for Locale validation.
56       * @param timeStyle the time style to use for Locale validation.
57       */
58      public AbstractCalendarValidator(final boolean strict, final int dateStyle, final int timeStyle) {
59          super(strict);
60          this.dateStyle = dateStyle;
61          this.timeStyle = timeStyle;
62      }
63  
64      /**
65       * <p>Compares the field from two calendars indicating whether the field for the
66       *    first calendar is equal to, less than or greater than the field from the
67       *    second calendar.
68       *
69       * @param value The Calendar value.
70       * @param compare The <code>Calendar</code> to check the value against.
71       * @param field The field to compare for the calendars.
72       * @return Zero if the first calendar's field is equal to the seconds, -1
73       *         if it is less than the seconds or +1 if it is greater than the seconds.
74       */
75      private int calculateCompareResult(final Calendar value, final Calendar compare, final int field) {
76          final int difference = value.get(field) - compare.get(field);
77          if (difference < 0) {
78              return -1;
79          }
80          if (difference > 0) {
81              return 1;
82          }
83          return 0;
84      }
85  
86      /**
87       * <p>Calculate the quarter for the specified Calendar.</p>
88       *
89       * @param calendar The Calendar value.
90       * @param monthOfFirstQuarter The  month that the first quarter starts.
91       * @return The calculated quarter.
92       */
93      private int calculateQuarter(final Calendar calendar, final int monthOfFirstQuarter) {
94          // Add Year
95          int year = calendar.get(Calendar.YEAR);
96  
97          final int month = calendar.get(Calendar.MONTH) + 1;
98          final int relativeMonth = month >= monthOfFirstQuarter
99                            ? month - monthOfFirstQuarter
100                           : month + 12 - monthOfFirstQuarter; // CHECKSTYLE IGNORE MagicNumber
101         final int quarter = relativeMonth / 3 + 1; // CHECKSTYLE IGNORE MagicNumber
102         // adjust the year if the quarter doesn't start in January
103         if (month < monthOfFirstQuarter) {
104             --year;
105         }
106         return year * 10 + quarter; // CHECKSTYLE IGNORE MagicNumber
107     }
108 
109     /**
110      * <p>Compares a calendar value to another, indicating whether it is
111      *    equal, less then or more than at a specified level.</p>
112      *
113      * @param value The Calendar value.
114      * @param compare The <code>Calendar</code> to check the value against.
115      * @param field The field <i>level</i> to compare to - e.g. specifying
116      *        <code>Calendar.MONTH</code> will compare the year and month
117      *        portions of the calendar.
118      * @return Zero if the first value is equal to the second, -1
119      *         if it is less than the second or +1 if it is greater than the second.
120      */
121     protected int compare(final Calendar value, final Calendar compare, final int field) {
122 
123         int result;
124 
125         // Compare Year
126         result = calculateCompareResult(value, compare, Calendar.YEAR);
127         if (result != 0 || field == Calendar.YEAR) {
128             return result;
129         }
130 
131         // Compare Week of Year
132         if (field == Calendar.WEEK_OF_YEAR) {
133             return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR);
134         }
135 
136         // Compare Day of the Year
137         if (field == Calendar.DAY_OF_YEAR) {
138             return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR);
139         }
140 
141         // Compare Month
142         result = calculateCompareResult(value, compare, Calendar.MONTH);
143         if (result != 0 || field == Calendar.MONTH) {
144             return result;
145         }
146 
147         // Compare Week of Month
148         if (field == Calendar.WEEK_OF_MONTH) {
149             return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH);
150         }
151 
152         // Compare Date
153         result = calculateCompareResult(value, compare, Calendar.DATE);
154         if (result != 0 || field == Calendar.DATE ||
155                           field == Calendar.DAY_OF_WEEK ||
156                           field == Calendar.DAY_OF_WEEK_IN_MONTH) {
157             return result;
158         }
159 
160         // Compare Time fields
161         return compareTime(value, compare, field);
162 
163     }
164 
165     /**
166      * <p>Compares a calendar's quarter value to another, indicating whether it is
167      *    equal, less then or more than the specified quarter.</p>
168      *
169      * @param value The Calendar value.
170      * @param compare The <code>Calendar</code> to check the value against.
171      * @param monthOfFirstQuarter The  month that the first quarter starts.
172      * @return Zero if the first quarter is equal to the second, -1
173      *         if it is less than the second or +1 if it is greater than the second.
174      */
175     protected int compareQuarters(final Calendar value, final Calendar compare, final int monthOfFirstQuarter) {
176         final int valueQuarter = calculateQuarter(value, monthOfFirstQuarter);
177         final int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter);
178         if (valueQuarter < compareQuarter) {
179             return -1;
180         }
181         if (valueQuarter > compareQuarter) {
182             return 1;
183         }
184         return 0;
185     }
186 
187     /**
188      * <p>Compares a calendar time value to another, indicating whether it is
189      *    equal, less then or more than at a specified level.</p>
190      *
191      * @param value The Calendar value.
192      * @param compare The <code>Calendar</code> to check the value against.
193      * @param field The field <i>level</i> to compare to - e.g. specifying
194      *        <code>Calendar.MINUTE</code> will compare the hours and minutes
195      *        portions of the calendar.
196      * @return Zero if the first value is equal to the second, -1
197      *         if it is less than the second or +1 if it is greater than the second.
198      */
199     protected int compareTime(final Calendar value, final Calendar compare, final int field) {
200 
201         int result;
202 
203         // Compare Hour
204         result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY);
205         if (result != 0 || field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY) {
206             return result;
207         }
208 
209         // Compare Minute
210         result = calculateCompareResult(value, compare, Calendar.MINUTE);
211         if (result != 0 || field == Calendar.MINUTE) {
212             return result;
213         }
214 
215         // Compare Second
216         result = calculateCompareResult(value, compare, Calendar.SECOND);
217         if (result != 0 || field == Calendar.SECOND) {
218             return result;
219         }
220 
221         // Compare Milliseconds
222         if (field == Calendar.MILLISECOND) {
223             return calculateCompareResult(value, compare, Calendar.MILLISECOND);
224         }
225 
226         throw new IllegalArgumentException("Invalid field: " + field);
227 
228     }
229 
230     /**
231      * <p>Format a value with the specified <code>DateFormat</code>.</p>
232      *
233      * @param value The value to be formatted.
234      * @param formatter The Format to use.
235      * @return The formatted value.
236      */
237     @Override
238     protected String format(Object value, final Format formatter) {
239         if (value == null) {
240             return null;
241         }
242         if (value instanceof Calendar) {
243             value = ((Calendar)value).getTime();
244         }
245         return formatter.format(value);
246     }
247 
248     /**
249      * <p>Format an object into a <code>String</code> using
250      * the specified Locale.</p>
251      *
252      * @param value The value validation is being performed on.
253      * @param locale The locale to use for the Format.
254      * @param timeZone The Time Zone used to format the date,
255      *  system default if null (unless value is a <code>Calendar</code>.
256      * @return The value formatted as a <code>String</code>.
257      */
258     public String format(final Object value, final Locale locale, final TimeZone timeZone) {
259         return format(value, (String)null, locale, timeZone);
260     }
261 
262     /**
263      * <p>Format an object using the specified pattern and/or
264      *    <code>Locale</code>.
265      *
266      * @param value The value validation is being performed on.
267      * @param pattern The pattern used to format the value.
268      * @param locale The locale to use for the Format.
269      * @return The value formatted as a <code>String</code>.
270      */
271     @Override
272     public String format(final Object value, final String pattern, final Locale locale) {
273         return format(value, pattern, locale, (TimeZone)null);
274     }
275 
276     /**
277      * <p>Format an object using the specified pattern and/or
278      *    <code>Locale</code>.
279      *
280      * @param value The value validation is being performed on.
281      * @param pattern The pattern used to format the value.
282      * @param locale The locale to use for the Format.
283      * @param timeZone The Time Zone used to format the date,
284      *  system default if null (unless value is a <code>Calendar</code>.
285      * @return The value formatted as a <code>String</code>.
286      */
287     public String format(final Object value, final String pattern, final Locale locale, final TimeZone timeZone) {
288         final DateFormat formatter = (DateFormat)getFormat(pattern, locale);
289         if (timeZone != null) {
290             formatter.setTimeZone(timeZone);
291         } else if (value instanceof Calendar) {
292             formatter.setTimeZone(((Calendar)value).getTimeZone());
293         }
294         return format(value, formatter);
295     }
296 
297     /**
298      * <p>Format an object into a <code>String</code> using
299      * the specified pattern.</p>
300      *
301      * @param value The value validation is being performed on.
302      * @param pattern The pattern used to format the value.
303      * @param timeZone The Time Zone used to format the date,
304      *  system default if null (unless value is a <code>Calendar</code>.
305      * @return The value formatted as a <code>String</code>.
306      */
307     public String format(final Object value, final String pattern, final TimeZone timeZone) {
308         return format(value, pattern, (Locale)null, timeZone);
309     }
310 
311     /**
312      * <p>Format an object into a <code>String</code> using
313      * the default Locale.</p>
314      *
315      * @param value The value validation is being performed on.
316      * @param timeZone The Time Zone used to format the date,
317      *  system default if null (unless value is a <code>Calendar</code>.
318      * @return The value formatted as a <code>String</code>.
319      */
320     public String format(final Object value, final TimeZone timeZone) {
321         return format(value, (String)null, (Locale)null, timeZone);
322     }
323 
324     /**
325      * <p>Returns a <code>DateFormat</code> for the specified Locale.</p>
326      *
327      * @param locale The locale a <code>DateFormat</code> is required for,
328      *        system default if null.
329      * @return The <code>DateFormat</code> to created.
330      */
331     protected Format getFormat(final Locale locale) {
332 
333         DateFormat formatter;
334         if (dateStyle >= 0 && timeStyle >= 0) {
335             if (locale == null) {
336                 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
337             } else {
338                 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
339             }
340         } else if (timeStyle >= 0) {
341             if (locale == null) {
342                 formatter = DateFormat.getTimeInstance(timeStyle);
343             } else {
344                 formatter = DateFormat.getTimeInstance(timeStyle, locale);
345             }
346         } else {
347             final int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT;
348             if (locale == null) {
349                 formatter = DateFormat.getDateInstance(useDateStyle);
350             } else {
351                 formatter = DateFormat.getDateInstance(useDateStyle, locale);
352             }
353         }
354         formatter.setLenient(false);
355         return formatter;
356 
357     }
358 
359     /**
360      * <p>Returns a <code>DateFormat</code> for the specified <i>pattern</i>
361      *    and/or <code>Locale</code>.</p>
362      *
363      * @param pattern The pattern used to validate the value against or
364      *        <code>null</code> to use the default for the <code>Locale</code>.
365      * @param locale The locale to use for the currency format, system default if null.
366      * @return The <code>DateFormat</code> to created.
367      */
368     @Override
369     protected Format getFormat(final String pattern, final Locale locale) {
370         DateFormat formatter;
371         final boolean usePattern = pattern != null && !pattern.isEmpty();
372         if (!usePattern) {
373             formatter = (DateFormat)getFormat(locale);
374         } else if (locale == null) {
375             formatter = new SimpleDateFormat(pattern);
376         } else {
377             final DateFormatSymbols symbols = new DateFormatSymbols(locale);
378             formatter = new SimpleDateFormat(pattern, symbols);
379         }
380         formatter.setLenient(false);
381         return formatter;
382     }
383 
384     /**
385      * <p>Validate using the specified <code>Locale</code>.
386      *
387      * @param value The value validation is being performed on.
388      * @param pattern The pattern used to format the value.
389      * @param locale The locale to use for the Format, defaults to the default
390      * @return {@code true} if the value is valid.
391      */
392     @Override
393     public boolean isValid(final String value, final String pattern, final Locale locale) {
394         final Object parsedValue = parse(value, pattern, locale, (TimeZone)null);
395         return parsedValue == null ? false : true;
396     }
397 
398     /**
399      * <p>Checks if the value is valid against a specified pattern.</p>
400      *
401      * @param value The value validation is being performed on.
402      * @param pattern The pattern used to validate the value against, or the
403      *        default for the <code>Locale</code> if <code>null</code>.
404      * @param locale The locale to use for the date format, system default if null.
405      * @param timeZone The Time Zone used to parse the date, system default if null.
406      * @return The parsed value if valid or <code>null</code> if invalid.
407      */
408     protected Object parse(String value, final String pattern, final Locale locale, final TimeZone timeZone) {
409 
410         value = value == null ? null : value.trim();
411         if (value == null || value.isEmpty()) {
412             return null;
413         }
414         final DateFormat formatter = (DateFormat)getFormat(pattern, locale);
415         if (timeZone != null) {
416             formatter.setTimeZone(timeZone);
417         }
418         return parse(value, formatter);
419 
420     }
421 
422     /**
423      * <p>Process the parsed value, performing any further validation
424      *    and type conversion required.</p>
425      *
426      * @param value The parsed object created.
427      * @param formatter The Format used to parse the value with.
428      * @return The parsed value converted to the appropriate type
429      *         if valid or <code>null</code> if invalid.
430      */
431     @Override
432     protected abstract Object processParsedValue(Object value, Format formatter);
433 }