    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$
    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}