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     */
017    package org.apache.commons.validator.routines;
018    
019    import java.text.DateFormatSymbols;
020    import java.text.Format;
021    import java.text.DateFormat;
022    import java.text.SimpleDateFormat;
023    import java.util.Calendar;
024    import java.util.Locale;
025    import 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: 594917 $ $Date: 2007-11-14 16:36:40 +0100 (Mi, 14. Nov 2007) $
034     * @since Validator 1.3.0
035     */
036    public abstract class AbstractCalendarValidator extends AbstractFormatValidator {
037    
038        private final int dateStyle;
039    
040        private final int timeStyle;
041    
042        /**
043         * Construct an instance with the specified <i>strict</i>, 
044         * <i>time</i> and <i>date</i> style parameters.
045         * 
046         * @param strict <code>true</code> if strict 
047         *        <code>Format</code> parsing should be used.
048         * @param dateStyle the date style to use for Locale validation.
049         * @param timeStyle the time style to use for Locale validation.
050         */
051        public AbstractCalendarValidator(boolean strict, int dateStyle, int timeStyle) {
052            super(strict);
053            this.dateStyle = dateStyle;
054            this.timeStyle = timeStyle;
055        }
056    
057        /**
058         * <p>Validate using the specified <code>Locale</code>. 
059         * 
060         * @param value The value validation is being performed on.
061         * @param pattern The pattern used to format the value.
062         * @param locale The locale to use for the Format, defaults to the default
063         * @return <code>true</code> if the value is valid.
064         */
065        public boolean isValid(String value, String pattern, Locale locale) {
066            Object parsedValue = parse(value, pattern, locale, (TimeZone)null);
067            return (parsedValue == null ? false : true);
068        }
069    
070        /**
071         * <p>Format an object into a <code>String</code> using
072         * the default Locale.</p>
073         *
074         * @param value The value validation is being performed on.
075         * @param timeZone The Time Zone used to format the date,
076         *  system default if null (unless value is a <code>Calendar</code>.
077         * @return The value formatted as a <code>String</code>.
078         */
079        public String format(Object value, TimeZone timeZone) {
080            return format(value, (String)null, (Locale)null, timeZone);
081        }
082    
083        /**
084         * <p>Format an object into a <code>String</code> using
085         * the specified pattern.</p>
086         *
087         * @param value The value validation is being performed on.
088         * @param pattern The pattern used to format the value.
089         * @param timeZone The Time Zone used to format the date,
090         *  system default if null (unless value is a <code>Calendar</code>.
091         * @return The value formatted as a <code>String</code>.
092         */
093        public String format(Object value, String pattern, TimeZone timeZone) {
094            return format(value, pattern, (Locale)null, timeZone);
095        }
096    
097        /**
098         * <p>Format an object into a <code>String</code> using
099         * 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    }