001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.validator.routines;
018
019import java.text.DateFormatSymbols;
020import java.text.Format;
021import java.text.DateFormat;
022import java.text.SimpleDateFormat;
023import java.util.Calendar;
024import java.util.Locale;
025import java.util.TimeZone;
026
027/**
028 * <p>Abstract class for Date/Time/Calendar validation.</p>
029 *
030 * <p>This is a <i>base</i> class for building Date / Time
031 *    Validators using format parsing.</p>
032 *
033 * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Do, 05 Jan 2012) $
034 * @since Validator 1.3.0
035 */
036public abstract class AbstractCalendarValidator extends AbstractFormatValidator {
037
038    private static final long serialVersionUID = -1410008585975827379L;
039
040    private final int dateStyle;
041
042    private final int timeStyle;
043
044    /**
045     * Construct an instance with the specified <i>strict</i>,
046     * <i>time</i> and <i>date</i> style parameters.
047     *
048     * @param strict <code>true</code> if strict
049     *        <code>Format</code> parsing should be used.
050     * @param dateStyle the date style to use for Locale validation.
051     * @param timeStyle the time style to use for Locale validation.
052     */
053    public AbstractCalendarValidator(boolean strict, int dateStyle, int timeStyle) {
054        super(strict);
055        this.dateStyle = dateStyle;
056        this.timeStyle = timeStyle;
057    }
058
059    /**
060     * <p>Validate using the specified <code>Locale</code>.
061     *
062     * @param value The value validation is being performed on.
063     * @param pattern The pattern used to format the value.
064     * @param locale The locale to use for the Format, defaults to the default
065     * @return <code>true</code> if the value is valid.
066     */
067    public boolean isValid(String value, String pattern, Locale locale) {
068        Object parsedValue = parse(value, pattern, locale, (TimeZone)null);
069        return (parsedValue == null ? false : true);
070    }
071
072    /**
073     * <p>Format an object into a <code>String</code> using
074     * the default Locale.</p>
075     *
076     * @param value The value validation is being performed on.
077     * @param timeZone The Time Zone used to format the date,
078     *  system default if null (unless value is a <code>Calendar</code>.
079     * @return The value formatted as a <code>String</code>.
080     */
081    public String format(Object value, TimeZone timeZone) {
082        return format(value, (String)null, (Locale)null, timeZone);
083    }
084
085    /**
086     * <p>Format an object into a <code>String</code> using
087     * the specified pattern.</p>
088     *
089     * @param value The value validation is being performed on.
090     * @param pattern The pattern used to format the value.
091     * @param timeZone The Time Zone used to format the date,
092     *  system default if null (unless value is a <code>Calendar</code>.
093     * @return The value formatted as a <code>String</code>.
094     */
095    public String format(Object value, String pattern, TimeZone timeZone) {
096        return format(value, pattern, (Locale)null, timeZone);
097    }
098
099    /**
100     * <p>Format an object into a <code>String</code> using
101     * the specified Locale.</p>
102     *
103     * @param value The value validation is being performed on.
104     * @param locale The locale to use for the Format.
105     * @param timeZone The Time Zone used to format the date,
106     *  system default if null (unless value is a <code>Calendar</code>.
107     * @return The value formatted as a <code>String</code>.
108     */
109    public String format(Object value, Locale locale, TimeZone timeZone) {
110        return format(value, (String)null, locale, timeZone);
111    }
112
113    /**
114     * <p>Format an object using the specified pattern and/or
115     *    <code>Locale</code>.
116     *
117     * @param value The value validation is being performed on.
118     * @param pattern The pattern used to format the value.
119     * @param locale The locale to use for the Format.
120     * @return The value formatted as a <code>String</code>.
121     */
122    public String format(Object value, String pattern, Locale locale) {
123        return format(value, pattern, locale, (TimeZone)null);
124    }
125
126    /**
127     * <p>Format an object using the specified pattern and/or
128     *    <code>Locale</code>.
129     *
130     * @param value The value validation is being performed on.
131     * @param pattern The pattern used to format the value.
132     * @param locale The locale to use for the Format.
133     * @param timeZone The Time Zone used to format the date,
134     *  system default if null (unless value is a <code>Calendar</code>.
135     * @return The value formatted as a <code>String</code>.
136     */
137    public String format(Object value, String pattern, Locale locale, TimeZone timeZone) {
138        DateFormat formatter = (DateFormat)getFormat(pattern, locale);
139        if (timeZone != null) {
140            formatter.setTimeZone(timeZone);
141        } else if (value instanceof Calendar) {
142            formatter.setTimeZone(((Calendar)value).getTimeZone());
143        }
144        return format(value, formatter);
145    }
146
147    /**
148     * <p>Format a value with the specified <code>DateFormat</code>.</p>
149     *
150     * @param value The value to be formatted.
151     * @param formatter The Format to use.
152     * @return The formatted value.
153     */
154    protected String format(Object value, Format formatter) {
155        if (value == null) {
156            return null;
157        } else if (value instanceof Calendar) {
158            value = ((Calendar)value).getTime();
159        }
160        return formatter.format(value);
161    }
162
163    /**
164     * <p>Checks if the value is valid against a specified pattern.</p>
165     *
166     * @param value The value validation is being performed on.
167     * @param pattern The pattern used to validate the value against, or the
168     *        default for the <code>Locale</code> if <code>null</code>.
169     * @param locale The locale to use for the date format, system default if null.
170     * @param timeZone The Time Zone used to parse the date, system default if null.
171     * @return The parsed value if valid or <code>null</code> if invalid.
172     */
173    protected Object parse(String value, String pattern, Locale locale, TimeZone timeZone) {
174
175        value = (value == null ? null : value.trim());
176        if (value == null || value.length() == 0) {
177            return null;
178        }
179        DateFormat formatter = (DateFormat)getFormat(pattern, locale);
180        if (timeZone != null) {
181            formatter.setTimeZone(timeZone);
182        }
183        return parse(value, formatter);
184
185    }
186
187    /**
188     * <p>Process the parsed value, performing any further validation
189     *    and type conversion required.</p>
190     *
191     * @param value The parsed object created.
192     * @param formatter The Format used to parse the value with.
193     * @return The parsed value converted to the appropriate type
194     *         if valid or <code>null</code> if invalid.
195     */
196    protected abstract Object processParsedValue(Object value, Format formatter);
197
198    /**
199     * <p>Returns a <code>DateFormat</code> for the specified <i>pattern</i>
200     *    and/or <code>Locale</code>.</p>
201     *
202     * @param pattern The pattern used to validate the value against or
203     *        <code>null</code> to use the default for the <code>Locale</code>.
204     * @param locale The locale to use for the currency format, system default if null.
205     * @return The <code>DateFormat</code> to created.
206     */
207    protected Format getFormat(String pattern, Locale locale) {
208        DateFormat formatter = null;
209        boolean usePattern = (pattern != null && pattern.length() > 0);
210        if (!usePattern) {
211            formatter = (DateFormat)getFormat(locale);
212        } else if (locale == null) {
213            formatter = new SimpleDateFormat(pattern);
214        } else {
215            DateFormatSymbols symbols = new DateFormatSymbols(locale);
216            formatter = new SimpleDateFormat(pattern, symbols);
217        }
218        formatter.setLenient(false);
219        return formatter;
220    }
221
222    /**
223     * <p>Returns a <code>DateFormat</code> for the specified Locale.</p>
224     *
225     * @param locale The locale a <code>DateFormat</code> is required for,
226     *        system default if null.
227     * @return The <code>DateFormat</code> to created.
228     */
229    protected Format getFormat(Locale locale) {
230
231        DateFormat formatter = null;
232        if (dateStyle >= 0 && timeStyle >= 0) {
233            if (locale == null) {
234                formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
235            } else {
236                formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
237            }
238        } else if (timeStyle >= 0) {
239            if (locale == null) {
240                formatter = DateFormat.getTimeInstance(timeStyle);
241            } else {
242                formatter = DateFormat.getTimeInstance(timeStyle, locale);
243            }
244        } else {
245            int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT;
246            if (locale == null) {
247                formatter = DateFormat.getDateInstance(useDateStyle);
248            } else {
249                formatter = DateFormat.getDateInstance(useDateStyle, locale);
250            }
251        }
252        formatter.setLenient(false);
253        return formatter;
254
255    }
256
257    /**
258     * <p>Compares a calendar value to another, indicating whether it is
259     *    equal, less then or more than at a specified level.</p>
260     *
261     * @param value The Calendar value.
262     * @param compare The <code>Calendar</code> to check the value against.
263     * @param field The field <i>level</i> to compare to - e.g. specifying
264     *        <code>Calendar.MONTH</code> will compare the year and month
265     *        portions of the calendar.
266     * @return Zero if the first value is equal to the second, -1
267     *         if it is less than the second or +1 if it is greater than the second.
268     */
269    protected int compare(Calendar value, Calendar compare, int field) {
270
271        int result = 0;
272
273        // Compare Year
274        result = calculateCompareResult(value, compare, Calendar.YEAR);
275        if (result != 0 || field == Calendar.YEAR) {
276            return result;
277        }
278
279        // Compare Week of Year
280        if (field == Calendar.WEEK_OF_YEAR) {
281            return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR);
282        }
283
284        // Compare Day of the Year
285        if (field == Calendar.DAY_OF_YEAR) {
286            return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR);
287        }
288
289        // Compare Month
290        result = calculateCompareResult(value, compare, Calendar.MONTH);
291        if (result != 0 || field == Calendar.MONTH) {
292            return result;
293        }
294
295        // Compare Week of Month
296        if (field == Calendar.WEEK_OF_MONTH) {
297            return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH);
298        }
299
300        // Compare Date
301        result = calculateCompareResult(value, compare, Calendar.DATE);
302        if (result != 0 || (field == Calendar.DATE ||
303                          field == Calendar.DAY_OF_WEEK ||
304                          field == Calendar.DAY_OF_WEEK_IN_MONTH)) {
305            return result;
306        }
307
308        // Compare Time fields
309        return compareTime(value, compare, field);
310
311    }
312
313    /**
314     * <p>Compares a calendar time value to another, indicating whether it is
315     *    equal, less then or more than at a specified level.</p>
316     *
317     * @param value The Calendar value.
318     * @param compare The <code>Calendar</code> to check the value against.
319     * @param field The field <i>level</i> to compare to - e.g. specifying
320     *        <code>Calendar.MINUTE</code> will compare the hours and minutes
321     *        portions of the calendar.
322     * @return Zero if the first value is equal to the second, -1
323     *         if it is less than the second or +1 if it is greater than the second.
324     */
325    protected int compareTime(Calendar value, Calendar compare, int field) {
326
327        int result = 0;
328
329        // Compare Hour
330        result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY);
331        if (result != 0 || (field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY)) {
332            return result;
333        }
334
335        // Compare Minute
336        result = calculateCompareResult(value, compare, Calendar.MINUTE);
337        if (result != 0 || field == Calendar.MINUTE) {
338            return result;
339        }
340
341        // Compare Second
342        result = calculateCompareResult(value, compare, Calendar.SECOND);
343        if (result != 0 || field == Calendar.SECOND) {
344            return result;
345        }
346
347        // Compare Milliseconds
348        if (field == Calendar.MILLISECOND) {
349            return calculateCompareResult(value, compare, Calendar.MILLISECOND);
350        }
351
352        throw new IllegalArgumentException("Invalid field: " + field);
353
354    }
355
356    /**
357     * <p>Compares a calendar's quarter value to another, indicating whether it is
358     *    equal, less then or more than the specified quarter.</p>
359     *
360     * @param value The Calendar value.
361     * @param compare The <code>Calendar</code> to check the value against.
362     * @param monthOfFirstQuarter The  month that the first quarter starts.
363     * @return Zero if the first quarter is equal to the second, -1
364     *         if it is less than the second or +1 if it is greater than the second.
365     */
366    protected int compareQuarters(Calendar value, Calendar compare, int monthOfFirstQuarter) {
367        int valueQuarter   = calculateQuarter(value, monthOfFirstQuarter);
368        int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter);
369        if (valueQuarter < compareQuarter) {
370            return -1;
371        } else if (valueQuarter > compareQuarter) {
372            return 1;
373        } else {
374            return 0;
375        }
376    }
377
378    /**
379     * <p>Calculate the quarter for the specified Calendar.</p>
380     *
381     * @param calendar The Calendar value.
382     * @param monthOfFirstQuarter The  month that the first quarter starts.
383     * @return The calculated quarter.
384     */
385    private int calculateQuarter(Calendar calendar, int monthOfFirstQuarter) {
386        // Add Year
387        int year = calendar.get(Calendar.YEAR);
388
389        int month = (calendar.get(Calendar.MONTH) + 1);
390        int relativeMonth = (month >= monthOfFirstQuarter)
391                          ? (month - monthOfFirstQuarter)
392                          : (month + (12 - monthOfFirstQuarter));
393        int quarter = ((relativeMonth / 3) + 1);
394        // adjust the year if the quarter doesn't start in January
395        if (month < monthOfFirstQuarter) {
396            --year;
397        }
398        return (year * 10) + quarter;
399    }
400
401    /**
402     * <p>Compares the field from two calendars indicating whether the field for the
403     *    first calendar is equal to, less than or greater than the field from the
404     *    second calendar.
405     *
406     * @param value The Calendar value.
407     * @param compare The <code>Calendar</code> to check the value against.
408     * @param field The field to compare for the calendars.
409     * @return Zero if the first calendar's field is equal to the seconds, -1
410     *         if it is less than the seconds or +1 if it is greater than the seconds.
411     */
412    private int calculateCompareResult(Calendar value, Calendar compare, int field) {
413        int difference = value.get(field) - compare.get(field);
414        if (difference < 0) {
415            return -1;
416        } else if (difference > 0) {
417            return 1;
418        } else {
419            return 0;
420        }
421    }
422}