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