View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.validator.routines;
    18
    19  import java.text.DateFormatSymbols;
    20  import java.text.Format;
    21  import java.text.DateFormat;
    22  import java.text.SimpleDateFormat;
    23  import java.util.Calendar;
    24  import java.util.Locale;
    25  import java.util.TimeZone;
    26
    27  /***
    28   * <p>Abstract class for Date/Time/Calendar validation.</p>
    29   *
    30   * <p>This is a <i>base</i> class for building Date / Time
    31   *    Validators using format parsing.</p>
    32   *
    33   * @version $Revision: 478334 $ $Date: 2006-11-22 21:31:54 +0000 (Wed, 22 Nov 2006) $
    34   * @since Validator 1.3.0
    35   */
    36  public abstract class AbstractCalendarValidator extends AbstractFormatValidator {
    37
    38      private int dateStyle = -1;
    39
    40      private int timeStyle = -1;
    41
    42      /***
    43       * Construct an instance with the specified <i>strict</i>,
    44       * <i>time</i> and <i>date</i> style parameters.
    45       *
    46       * @param strict <code>true</code> if strict
    47       *        <code>Format</code> parsing should be used.
    48       * @param dateStyle the date style to use for Locale validation.
    49       * @param timeStyle the time style to use for Locale validation.
    50       */
    51      public AbstractCalendarValidator(boolean strict, int dateStyle, int timeStyle) {
    52          super(strict);
    53          this.dateStyle = dateStyle;
    54          this.timeStyle = timeStyle;
    55      }
    56
    57      /***
    58       * <p>Validate using the specified <code>Locale</code>.
    59       *
    60       * @param value The value validation is being performed on.
    61       * @param pattern The pattern used to format the value.
    62       * @param locale The locale to use for the Format, defaults to the default
    63       * @return <code>true</code> if the value is valid.
    64       */
    65      public boolean isValid(String value, String pattern, Locale locale) {
    66          Object parsedValue = parse(value, pattern, locale, (TimeZone)null);
    67          return (parsedValue == null ? false : true);
    68      }
    69
    70      /***
    71       * <p>Format an object into a <code>String</code> using
    72       * the default Locale.</p>
    73       *
    74       * @param value The value validation is being performed on.
    75       * @param timeZone The Time Zone used to format the date,
    76       *  system default if null (unless value is a <code>Calendar</code>.
    77       * @return The value formatted as a <code>String</code>.
    78       */
    79      public String format(Object value, TimeZone timeZone) {
    80          return format(value, (String)null, (Locale)null, timeZone);
    81      }
    82
    83      /***
    84       * <p>Format an object into a <code>String</code> using
    85       * the specified pattern.</p>
    86       *
    87       * @param value The value validation is being performed on.
    88       * @param pattern The pattern used to format the value.
    89       * @param timeZone The Time Zone used to format the date,
    90       *  system default if null (unless value is a <code>Calendar</code>.
    91       * @return The value formatted as a <code>String</code>.
    92       */
    93      public String format(Object value, String pattern, TimeZone timeZone) {
    94          return format(value, pattern, (Locale)null, timeZone);
    95      }
    96
    97      /***
    98       * <p>Format an object into a <code>String</code> using
    99       * the specified Locale.</p>
    100      *
    101      * @param value The value validation is being performed on.
    102      * @param locale The locale to use for the Format.
    103      * @param timeZone The Time Zone used to format the date,
    104      *  system default if null (unless value is a <code>Calendar</code>.
    105      * @return The value formatted as a <code>String</code>.
    106      */
    107     public String format(Object value, Locale locale, TimeZone timeZone) {
    108         return format(value, (String)null, locale, timeZone);
    109     }
    110
    111     /***
    112      * <p>Format an object using the specified pattern and/or
    113      *    <code>Locale</code>.
    114      *
    115      * @param value The value validation is being performed on.
    116      * @param pattern The pattern used to format the value.
    117      * @param locale The locale to use for the Format.
    118      * @return The value formatted as a <code>String</code>.
    119      */
    120     public String format(Object value, String pattern, Locale locale) {
    121         return format(value, pattern, locale, (TimeZone)null);
    122     }
    123
    124     /***
    125      * <p>Format an object using the specified pattern and/or
    126      *    <code>Locale</code>.
    127      *
    128      * @param value The value validation is being performed on.
    129      * @param pattern The pattern used to format the value.
    130      * @param locale The locale to use for the Format.
    131      * @param timeZone The Time Zone used to format the date,
    132      *  system default if null (unless value is a <code>Calendar</code>.
    133      * @return The value formatted as a <code>String</code>.
    134      */
    135     public String format(Object value, String pattern, Locale locale, TimeZone timeZone) {
    136         DateFormat formatter = (DateFormat)getFormat(pattern, locale);
    137         if (timeZone != null) {
    138             formatter.setTimeZone(timeZone);
    139         } else if (value instanceof Calendar) {
    140             formatter.setTimeZone(((Calendar)value).getTimeZone());
    141         }
    142         return format(value, formatter);
    143     }
    144
    145     /***
    146      * <p>Format a value with the specified <code>DateFormat</code>.</p>
    147      *
    148      * @param value The value to be formatted.
    149      * @param formatter The Format to use.
    150      * @return The formatted value.
    151      */
    152     protected String format(Object value, Format formatter) {
    153         if (value == null) {
    154             return null;
    155         } else if (value instanceof Calendar) {
    156             value = ((Calendar)value).getTime();
    157         }
    158         return formatter.format(value);
    159     }
    160
    161     /***
    162      * <p>Checks if the value is valid against a specified pattern.</p>
    163      *
    164      * @param value The value validation is being performed on.
    165      * @param pattern The pattern used to validate the value against, or the
    166      *        default for the <code>Locale</code> if <code>null</code>.
    167      * @param locale The locale to use for the date format, system default if null.
    168      * @param timeZone The Time Zone used to parse the date, system default if null.
    169      * @return The parsed value if valid or <code>null</code> if invalid.
    170      */
    171     protected Object parse(String value, String pattern, Locale locale, TimeZone timeZone) {
    172
    173         value = (value == null ? null : value.trim());
    174         if (value == null || value.length() == 0) {
    175             return null;
    176         }
    177         DateFormat formatter = (DateFormat)getFormat(pattern, locale);
    178         if (timeZone != null) {
    179             formatter.setTimeZone(timeZone);
    180         }
    181         return parse(value, formatter);
    182
    183     }
    184
    185     /***
    186      * <p>Process the parsed value, performing any further validation
    187      *    and type conversion required.</p>
    188      *
    189      * @param value The parsed object created.
    190      * @param formatter The Format used to parse the value with.
    191      * @return The parsed value converted to the appropriate type
    192      *         if valid or <code>null</code> if invalid.
    193      */
    194     protected abstract Object processParsedValue(Object value, Format formatter);
    195
    196     /***
    197      * <p>Returns a <code>DateFormat</code> for the specified <i>pattern</i>
    198      *    and/or <code>Locale</code>.</p>
    199      *
    200      * @param pattern The pattern used to validate the value against or
    201      *        <code>null</code> to use the default for the <code>Locale</code>.
    202      * @param locale The locale to use for the currency format, system default if null.
    203      * @return The <code>DateFormat</code> to created.
    204      */
    205     protected Format getFormat(String pattern, Locale locale) {
    206         DateFormat formatter = null;
    207         boolean usePattern = (pattern != null && pattern.length() > 0);
    208         if (!usePattern) {
    209             formatter = (DateFormat)getFormat(locale);
    210         } else if (locale == null) {
    211             formatter = new SimpleDateFormat(pattern);
    212         } else {
    213             DateFormatSymbols symbols = new DateFormatSymbols(locale);
    214             formatter = new SimpleDateFormat(pattern, symbols);
    215         }
    216         formatter.setLenient(false);
    217         return formatter;
    218     }
    219
    220     /***
    221      * <p>Returns a <code>DateFormat</code> for the specified Locale.</p>
    222      *
    223      * @param locale The locale a <code>DateFormat</code> is required for,
    224      *        system default if null.
    225      * @return The <code>DateFormat</code> to created.
    226      */
    227     protected Format getFormat(Locale locale) {
    228
    229         DateFormat formatter = null;
    230         if (dateStyle >= 0 && timeStyle >= 0) {
    231             if (locale == null) {
    232                 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
    233             } else {
    234                 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
    235             }
    236         } else if (timeStyle >= 0) {
    237             if (locale == null) {
    238                 formatter = DateFormat.getTimeInstance(timeStyle);
    239             } else {
    240                 formatter = DateFormat.getTimeInstance(timeStyle, locale);
    241             }
    242         } else {
    243             int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT;
    244             if (locale == null) {
    245                 formatter = DateFormat.getDateInstance(useDateStyle);
    246             } else {
    247                 formatter = DateFormat.getDateInstance(useDateStyle, locale);
    248             }
    249         }
    250         formatter.setLenient(false);
    251         return formatter;
    252
    253     }
    254
    255     /***
    256      * <p>Compares a calendar value to another, indicating whether it is
    257      *    equal, less then or more than at a specified level.</p>
    258      *
    259      * @param value The Calendar value.
    260      * @param compare The <code>Calendar</code> to check the value against.
    261      * @param field The field <i>level</i> to compare to - e.g. specifying
    262      *        <code>Calendar.MONTH</code> will compare the year and month
    263      *        portions of the calendar.
    264      * @return Zero if the first value is equal to the second, -1
    265      *         if it is less than the second or +1 if it is greater than the second.
    266      */
    267     protected int compare(Calendar value, Calendar compare, int field) {
    268
    269         int result = 0;
    270
    271         // Compare Year
    272         result = calculateCompareResult(value, compare, Calendar.YEAR);
    273         if (result != 0 || field == Calendar.YEAR) {
    274             return result;
    275         }
    276
    277         // Compare Week of Year
    278         if (field == Calendar.WEEK_OF_YEAR) {
    279             return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR);
    280         }
    281
    282         // Compare Day of the Year
    283         if (field == Calendar.DAY_OF_YEAR) {
    284             return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR);
    285         }
    286
    287         // Compare Month
    288         result = calculateCompareResult(value, compare, Calendar.MONTH);
    289         if (result != 0 || field == Calendar.MONTH) {
    290             return result;
    291         }
    292
    293         // Compare Week of Month
    294         if (field == Calendar.WEEK_OF_MONTH) {
    295             return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH);
    296         }
    297
    298         // Compare Date
    299         result = calculateCompareResult(value, compare, Calendar.DATE);
    300         if (result != 0 || (field == Calendar.DATE ||
    301                           field == Calendar.DAY_OF_MONTH ||
    302                           field == Calendar.DAY_OF_WEEK ||
    303                           field == Calendar.DAY_OF_WEEK_IN_MONTH)) {
    304             return result;
    305         }
    306
    307         // Compare Time fields
    308         return compareTime(value, compare, field);
    309
    310     }
    311
    312     /***
    313      * <p>Compares a calendar time value to another, indicating whether it is
    314      *    equal, less then or more than at a specified level.</p>
    315      *
    316      * @param value The Calendar value.
    317      * @param compare The <code>Calendar</code> to check the value against.
    318      * @param field The field <i>level</i> to compare to - e.g. specifying
    319      *        <code>Calendar.MINUTE</code> will compare the hours and minutes
    320      *        portions of the calendar.
    321      * @return Zero if the first value is equal to the second, -1
    322      *         if it is less than the second or +1 if it is greater than the second.
    323      */
    324     protected int compareTime(Calendar value, Calendar compare, int field) {
    325
    326         int result = 0;
    327
    328         // Compare Hour
    329         result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY);
    330         if (result != 0 || (field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY)) {
    331             return result;
    332         }
    333
    334         // Compare Minute
    335         result = calculateCompareResult(value, compare, Calendar.MINUTE);
    336         if (result != 0 || field == Calendar.MINUTE) {
    337             return result;
    338         }
    339
    340         // Compare Second
    341         result = calculateCompareResult(value, compare, Calendar.SECOND);
    342         if (result != 0 || field == Calendar.SECOND) {
    343             return result;
    344         }
    345
    346         // Compare Milliseconds
    347         if (field == Calendar.MILLISECOND) {
    348             return calculateCompareResult(value, compare, Calendar.MILLISECOND);
    349         }
    350
    351         throw new IllegalArgumentException("Invalid field: " + field);
    352
    353     }
    354
    355     /***
    356      * <p>Compares a calendar's quarter value to another, indicating whether it is
    357      *    equal, less then or more than the specified quarter.</p>
    358      *
    359      * @param value The Calendar value.
    360      * @param compare The <code>Calendar</code> to check the value against.
    361      * @param monthOfFirstQuarter The  month that the first quarter starts.
    362      * @return Zero if the first quarter is equal to the second, -1
    363      *         if it is less than the second or +1 if it is greater than the second.
    364      */
    365     protected int compareQuarters(Calendar value, Calendar compare, int monthOfFirstQuarter) {
    366         int valueQuarter   = calculateQuarter(value, monthOfFirstQuarter);
    367         int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter);
    368         if (valueQuarter < compareQuarter) {
    369             return -1;
    370         } else if (valueQuarter > compareQuarter) {
    371             return 1;
    372         } else {
    373             return 0;
    374         }
    375     }
    376
    377     /***
    378      * <p>Calculate the quarter for the specified Calendar.</p>
    379      *
    380      * @param calendar The Calendar value.
    381      * @param monthOfFirstQuarter The  month that the first quarter starts.
    382      * @return The calculated quarter.
    383      */
    384     private int calculateQuarter(Calendar calendar, int monthOfFirstQuarter) {
    385         // Add Year
    386         int year = calendar.get(Calendar.YEAR);
    387
    388         int month = (calendar.get(Calendar.MONTH) + 1);
    389         int relativeMonth = (month >= monthOfFirstQuarter)
    390                           ? (month - monthOfFirstQuarter)
    391                           : (month + (12 - monthOfFirstQuarter));
    392         int quarter = ((relativeMonth / 3) + 1);
    393         // adjust the year if the quarter doesn't start in January
    394         if (month < monthOfFirstQuarter) {
    395             --year;
    396         }
    397         return (year * 10) + quarter;
    398     }
    399
    400     /***
    401      * <p>Compares the field from two calendars indicating whether the field for the
    402      *    first calendar is equal to, less than or greater than the field from the
    403      *    second calendar.
    404      *
    405      * @param value The Calendar value.
    406      * @param compare The <code>Calendar</code> to check the value against.
    407      * @param field The field to compare for the calendars.
    408      * @return Zero if the first calendar's field is equal to the seconds, -1
    409      *         if it is less than the seconds or +1 if it is greater than the seconds.
    410      */
    411     private int calculateCompareResult(Calendar value, Calendar compare, int field) {
    412         int difference = value.get(field) - compare.get(field);
    413         if (difference < 0) {
    414             return -1;
    415         } else if (difference > 0) {
    416             return 1;
    417         } else {
    418             return 0;
    419         }
    420     }
    421 }