    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
    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 * @since 1.3.0
    034 */
    035public abstract class AbstractCalendarValidator extends AbstractFormatValidator {
    036
    037    private static final long serialVersionUID = -1410008585975827379L;
    038
    039    /**
    040     * The date style to use for Locale validation.
    041     */
    042    private final int dateStyle;
    043
    044    /**
    045     * The time style to use for Locale validation.
    046     */
    047    private final int timeStyle;
    048
    049    /**
    050     * Constructs an instance with the specified <i>strict</i>,
    051     * <i>time</i> and <i>date</i> style parameters.
    052     *
    053     * @param strict {@code true} if strict
    054     *        <code>Format</code> parsing should be used.
    055     * @param dateStyle the date style to use for Locale validation.
    056     * @param timeStyle the time style to use for Locale validation.
    057     */
    058    public AbstractCalendarValidator(final boolean strict, final int dateStyle, final int timeStyle) {
    059        super(strict);
    060        this.dateStyle = dateStyle;
    061        this.timeStyle = timeStyle;
    062    }
    063
    064    /**
    065     * <p>Compares the field from two calendars indicating whether the field for the
    066     *    first calendar is equal to, less than or greater than the field from the
    067     *    second calendar.
    068     *
    069     * @param value The Calendar value.
    070     * @param compare The <code>Calendar</code> to check the value against.
    071     * @param field The field to compare for the calendars.
    072     * @return Zero if the first calendar's field is equal to the seconds, -1
    073     *         if it is less than the seconds or +1 if it is greater than the seconds.
    074     */
    075    private int calculateCompareResult(final Calendar value, final Calendar compare, final int field) {
    076        final int difference = value.get(field) - compare.get(field);
    077        if (difference < 0) {
    078            return -1;
    079        }
    080        if (difference > 0) {
    081            return 1;
    082        }
    083        return 0;
    084    }
    085
    086    /**
    087     * <p>Calculate the quarter for the specified Calendar.</p>
    088     *
    089     * @param calendar The Calendar value.
    090     * @param monthOfFirstQuarter The  month that the first quarter starts.
    091     * @return The calculated quarter.
    092     */
    093    private int calculateQuarter(final Calendar calendar, final int monthOfFirstQuarter) {
    094        // Add Year
    095        int year = calendar.get(Calendar.YEAR);
    096
    097        final int month = calendar.get(Calendar.MONTH) + 1;
    098        final int relativeMonth = month >= monthOfFirstQuarter
    099                          ? 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}