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: 1739356 $
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    @Override
068    public boolean isValid(String value, String pattern, Locale locale) {
069        Object parsedValue = parse(value, pattern, locale, (TimeZone)null);
070        return (parsedValue == null ? false : true);
071    }
072
073    /**
074     * <p>Format an object into a <code>String</code> using
075     * the default Locale.</p>
076     *
077     * @param value The value validation is being performed on.
078     * @param timeZone The Time Zone used to format the date,
079     *  system default if null (unless value is a <code>Calendar</code>.
080     * @return The value formatted as a <code>String</code>.
081     */
082    public String format(Object value, TimeZone timeZone) {
083        return format(value, (String)null, (Locale)null, timeZone);
084    }
085
086    /**
087     * <p>Format an object into a <code>String</code> using
088     * the specified pattern.</p>
089     *
090     * @param value The value validation is being performed on.
091     * @param pattern The pattern used to format the value.
092     * @param timeZone The Time Zone used to format the date,
093     *  system default if null (unless value is a <code>Calendar</code>.
094     * @return The value formatted as a <code>String</code>.
095     */
096    public String format(Object value, String pattern, TimeZone timeZone) {
097        return format(value, pattern, (Locale)null, timeZone);
098    }
099
100    /**
101     * <p>Format an object into a <code>String</code> using
102     * the specified Locale.</p>
103     *
104     * @param value The value validation is being performed on.
105     * @param locale The locale to use for the Format.
106     * @param timeZone The Time Zone used to format the date,
107     *  system default if null (unless value is a <code>Calendar</code>.
108     * @return The value formatted as a <code>String</code>.
109     */
110    public String format(Object value, Locale locale, TimeZone timeZone) {
111        return format(value, (String)null, locale, timeZone);
112    }
113
114    /**
115     * <p>Format an object using the specified pattern and/or
116     *    <code>Locale</code>.
117     *
118     * @param value The value validation is being performed on.
119     * @param pattern The pattern used to format the value.
120     * @param locale The locale to use for the Format.
121     * @return The value formatted as a <code>String</code>.
122     */
123    @Override
124    public String format(Object value, String pattern, Locale locale) {
125        return format(value, pattern, locale, (TimeZone)null);
126    }
127
128    /**
129     * <p>Format an object using the specified pattern and/or
130     *    <code>Locale</code>.
131     *
132     * @param value The value validation is being performed on.
133     * @param pattern The pattern used to format the value.
134     * @param locale The locale to use for the Format.
135     * @param timeZone The Time Zone used to format the date,
136     *  system default if null (unless value is a <code>Calendar</code>.
137     * @return The value formatted as a <code>String</code>.
138     */
139    public String format(Object value, String pattern, Locale locale, TimeZone timeZone) {
140        DateFormat formatter = (DateFormat)getFormat(pattern, locale);
141        if (timeZone != null) {
142            formatter.setTimeZone(timeZone);
143        } else if (value instanceof Calendar) {
144            formatter.setTimeZone(((Calendar)value).getTimeZone());
145        }
146        return format(value, formatter);
147    }
148
149    /**
150     * <p>Format a value with the specified <code>DateFormat</code>.</p>
151     *
152     * @param value The value to be formatted.
153     * @param formatter The Format to use.
154     * @return The formatted value.
155     */
156    @Override
157    protected String format(Object value, Format formatter) {
158        if (value == null) {
159            return null;
160        } else if (value instanceof Calendar) {
161            value = ((Calendar)value).getTime();
162        }
163        return formatter.format(value);
164    }
165
166    /**
167     * <p>Checks if the value is valid against a specified pattern.</p>
168     *
169     * @param value The value validation is being performed on.
170     * @param pattern The pattern used to validate the value against, or the
171     *        default for the <code>Locale</code> if <code>null</code>.
172     * @param locale The locale to use for the date format, system default if null.
173     * @param timeZone The Time Zone used to parse the date, system default if null.
174     * @return The parsed value if valid or <code>null</code> if invalid.
175     */
176    protected Object parse(String value, String pattern, Locale locale, TimeZone timeZone) {
177
178        value = (value == null ? null : value.trim());
179        if (value == null || value.length() == 0) {
180            return null;
181        }
182        DateFormat formatter = (DateFormat)getFormat(pattern, locale);
183        if (timeZone != null) {
184            formatter.setTimeZone(timeZone);
185        }
186        return parse(value, formatter);
187
188    }
189
190    /**
191     * <p>Process the parsed value, performing any further validation
192     *    and type conversion required.</p>
193     *
194     * @param value The parsed object created.
195     * @param formatter The Format used to parse the value with.
196     * @return The parsed value converted to the appropriate type
197     *         if valid or <code>null</code> if invalid.
198     */
199    @Override
200    protected abstract Object processParsedValue(Object value, Format formatter);
201
202    /**
203     * <p>Returns a <code>DateFormat</code> for the specified <i>pattern</i>
204     *    and/or <code>Locale</code>.</p>
205     *
206     * @param pattern The pattern used to validate the value against or
207     *        <code>null</code> to use the default for the <code>Locale</code>.
208     * @param locale The locale to use for the currency format, system default if null.
209     * @return The <code>DateFormat</code> to created.
210     */
211    @Override
212    protected Format getFormat(String pattern, Locale locale) {
213        DateFormat formatter = null;
214        boolean usePattern = (pattern != null && pattern.length() > 0);
215        if (!usePattern) {
216            formatter = (DateFormat)getFormat(locale);
217        } else if (locale == null) {
218            formatter = new SimpleDateFormat(pattern);
219        } else {
220            DateFormatSymbols symbols = new DateFormatSymbols(locale);
221            formatter = new SimpleDateFormat(pattern, symbols);
222        }
223        formatter.setLenient(false);
224        return formatter;
225    }
226
227    /**
228     * <p>Returns a <code>DateFormat</code> for the specified Locale.</p>
229     *
230     * @param locale The locale a <code>DateFormat</code> is required for,
231     *        system default if null.
232     * @return The <code>DateFormat</code> to created.
233     */
234    protected Format getFormat(Locale locale) {
235
236        DateFormat formatter = null;
237        if (dateStyle >= 0 && timeStyle >= 0) {
238            if (locale == null) {
239                formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
240            } else {
241                formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
242            }
243        } else if (timeStyle >= 0) {
244            if (locale == null) {
245                formatter = DateFormat.getTimeInstance(timeStyle);
246            } else {
247                formatter = DateFormat.getTimeInstance(timeStyle, locale);
248            }
249        } else {
250            int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT;
251            if (locale == null) {
252                formatter = DateFormat.getDateInstance(useDateStyle);
253            } else {
254                formatter = DateFormat.getDateInstance(useDateStyle, locale);
255            }
256        }
257        formatter.setLenient(false);
258        return formatter;
259
260    }
261
262    /**
263     * <p>Compares a calendar value to another, indicating whether it is
264     *    equal, less then or more than at a specified level.</p>
265     *
266     * @param value The Calendar value.
267     * @param compare The <code>Calendar</code> to check the value against.
268     * @param field The field <i>level</i> to compare to - e.g. specifying
269     *        <code>Calendar.MONTH</code> will compare the year and month
270     *        portions of the calendar.
271     * @return Zero if the first value is equal to the second, -1
272     *         if it is less than the second or +1 if it is greater than the second.
273     */
274    protected int compare(Calendar value, Calendar compare, int field) {
275
276        int result = 0;
277
278        // Compare Year
279        result = calculateCompareResult(value, compare, Calendar.YEAR);
280        if (result != 0 || field == Calendar.YEAR) {
281            return result;
282        }
283
284        // Compare Week of Year
285        if (field == Calendar.WEEK_OF_YEAR) {
286            return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR);
287        }
288
289        // Compare Day of the Year
290        if (field == Calendar.DAY_OF_YEAR) {
291            return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR);
292        }
293
294        // Compare Month
295        result = calculateCompareResult(value, compare, Calendar.MONTH);
296        if (result != 0 || field == Calendar.MONTH) {
297            return result;
298        }
299
300        // Compare Week of Month
301        if (field == Calendar.WEEK_OF_MONTH) {
302            return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH);
303        }
304
305        // Compare Date
306        result = calculateCompareResult(value, compare, Calendar.DATE);
307        if (result != 0 || (field == Calendar.DATE ||
308                          field == Calendar.DAY_OF_WEEK ||
309                          field == Calendar.DAY_OF_WEEK_IN_MONTH)) {
310            return result;
311        }
312
313        // Compare Time fields
314        return compareTime(value, compare, field);
315
316    }
317
318    /**
319     * <p>Compares a calendar time value to another, indicating whether it is
320     *    equal, less then or more than at a specified level.</p>
321     *
322     * @param value The Calendar value.
323     * @param compare The <code>Calendar</code> to check the value against.
324     * @param field The field <i>level</i> to compare to - e.g. specifying
325     *        <code>Calendar.MINUTE</code> will compare the hours and minutes
326     *        portions of the calendar.
327     * @return Zero if the first value is equal to the second, -1
328     *         if it is less than the second or +1 if it is greater than the second.
329     */
330    protected int compareTime(Calendar value, Calendar compare, int field) {
331
332        int result = 0;
333
334        // Compare Hour
335        result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY);
336        if (result != 0 || (field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY)) {
337            return result;
338        }
339
340        // Compare Minute
341        result = calculateCompareResult(value, compare, Calendar.MINUTE);
342        if (result != 0 || field == Calendar.MINUTE) {
343            return result;
344        }
345
346        // Compare Second
347        result = calculateCompareResult(value, compare, Calendar.SECOND);
348        if (result != 0 || field == Calendar.SECOND) {
349            return result;
350        }
351
352        // Compare Milliseconds
353        if (field == Calendar.MILLISECOND) {
354            return calculateCompareResult(value, compare, Calendar.MILLISECOND);
355        }
356
357        throw new IllegalArgumentException("Invalid field: " + field);
358
359    }
360
361    /**
362     * <p>Compares a calendar's quarter value to another, indicating whether it is
363     *    equal, less then or more than the specified quarter.</p>
364     *
365     * @param value The Calendar value.
366     * @param compare The <code>Calendar</code> to check the value against.
367     * @param monthOfFirstQuarter The  month that the first quarter starts.
368     * @return Zero if the first quarter is equal to the second, -1
369     *         if it is less than the second or +1 if it is greater than the second.
370     */
371    protected int compareQuarters(Calendar value, Calendar compare, int monthOfFirstQuarter) {
372        int valueQuarter   = calculateQuarter(value, monthOfFirstQuarter);
373        int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter);
374        if (valueQuarter < compareQuarter) {
375            return -1;
376        } else if (valueQuarter > compareQuarter) {
377            return 1;
378        } else {
379            return 0;
380        }
381    }
382
383    /**
384     * <p>Calculate the quarter for the specified Calendar.</p>
385     *
386     * @param calendar The Calendar value.
387     * @param monthOfFirstQuarter The  month that the first quarter starts.
388     * @return The calculated quarter.
389     */
390    private int calculateQuarter(Calendar calendar, int monthOfFirstQuarter) {
391        // Add Year
392        int year = calendar.get(Calendar.YEAR);
393
394        int month = (calendar.get(Calendar.MONTH) + 1);
395        int relativeMonth = (month >= monthOfFirstQuarter)
396                          ? (month - monthOfFirstQuarter)
397                          : (month + (12 - monthOfFirstQuarter)); // CHECKSTYLE IGNORE MagicNumber
398        int quarter = ((relativeMonth / 3) + 1); // CHECKSTYLE IGNORE MagicNumber
399        // adjust the year if the quarter doesn't start in January
400        if (month < monthOfFirstQuarter) {
401            --year;
402        }
403        return (year * 10) + quarter; // CHECKSTYLE IGNORE MagicNumber
404    }
405
406    /**
407     * <p>Compares the field from two calendars indicating whether the field for the
408     *    first calendar is equal to, less than or greater than the field from the
409     *    second calendar.
410     *
411     * @param value The Calendar value.
412     * @param compare The <code>Calendar</code> to check the value against.
413     * @param field The field to compare for the calendars.
414     * @return Zero if the first calendar's field is equal to the seconds, -1
415     *         if it is less than the seconds or +1 if it is greater than the seconds.
416     */
417    private int calculateCompareResult(Calendar value, Calendar compare, int field) {
418        int difference = value.get(field) - compare.get(field);
419        if (difference < 0) {
420            return -1;
421        } else if (difference > 0) {
422            return 1;
423        } else {
424            return 0;
425        }
426    }
427}