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