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.Format;
021import java.util.Calendar;
022import java.util.Date;
023import java.util.Locale;
024import java.util.TimeZone;
025
026/**
027 * <p><strong>Date Validation</strong> and Conversion routines ({@code java.util.Date}).</p>
028 *
029 * <p>This validator provides a number of methods for validating/converting
030 *    a {@link String} date value to a {@code java.util.Date} using
031 *    {@link DateFormat} to parse either:</p>
032 *    <ul>
033 *       <li>using the default format for the default {@link Locale}</li>
034 *       <li>using a specified pattern with the default {@link Locale}</li>
035 *       <li>using the default format for a specified {@link Locale}</li>
036 *       <li>using a specified pattern with a specified {@link Locale}</li>
037 *    </ul>
038 *
039 * <p>For each of the above mechanisms, conversion method (that is, the
040 *    {@code validate} methods) implementations are provided which
041 *    either use the default {@code TimeZone} or allow the
042 *    {@code TimeZone} to be specified.</p>
043 *
044 * <p>Use one of the {@code isValid()} methods to just validate or
045 *    one of the {@code validate()} methods to validate and receive a
046 *    <em>converted</em> {@code Date} value.</p>
047 *
048 * <p>Implementations of the {@code validate()} method are provided
049 *    to create {@code Date} objects for different <em>time zones</em>
050 *    if the system default is not appropriate.</p>
051 *
052 * <p>Once a value has been successfully converted the following
053 *    methods can be used to perform various date comparison checks:</p>
054 *    <ul>
055 *       <li>{@code compareDates()} compares the day, month and
056 *           year of two dates, returning 0, -1 or +1 indicating
057 *           whether the first date is equal, before or after the second.</li>
058 *       <li>{@code compareWeeks()} compares the week and
059 *           year of two dates, returning 0, -1 or +1 indicating
060 *           whether the first week is equal, before or after the second.</li>
061 *       <li>{@code compareMonths()} compares the month and
062 *           year of two dates, returning 0, -1 or +1 indicating
063 *           whether the first month is equal, before or after the second.</li>
064 *       <li>{@code compareQuarters()} compares the quarter and
065 *           year of two dates, returning 0, -1 or +1 indicating
066 *           whether the first quarter is equal, before or after the second.</li>
067 *       <li>{@code compareYears()} compares the
068 *           year of two dates, returning 0, -1 or +1 indicating
069 *           whether the first year is equal, before or after the second.</li>
070 *    </ul>
071 *
072 * <p>So that the same mechanism used for parsing an <em>input</em> value
073 *    for validation can be used to format <em>output</em>, corresponding
074 *    {@code format()} methods are also provided. That is you can
075 *    format either:</p>
076 *    <ul>
077 *       <li>using a specified pattern</li>
078 *       <li>using the format for a specified {@link Locale}</li>
079 *       <li>using the format for the <em>default</em> {@link Locale}</li>
080 *    </ul>
081 *
082 * @since 1.3.0
083 */
084public class DateValidator extends AbstractCalendarValidator {
085
086    private static final long serialVersionUID = -3966328400469953190L;
087
088    private static final DateValidator VALIDATOR = new DateValidator();
089
090    /**
091     * Gets the singleton instance of this validator.
092     *
093     * @return A singleton instance of the DateValidator.
094     */
095    public static DateValidator getInstance() {
096        return VALIDATOR;
097    }
098
099    /**
100     * Constructs a <em>strict</em> instance with <em>short</em>
101     * date style.
102     */
103    public DateValidator() {
104        this(true, DateFormat.SHORT);
105    }
106
107    /**
108     * Constructs an instance with the specified <em>strict</em>
109     * and <em>date style</em> parameters.
110     *
111     * @param strict {@code true} if strict
112     *        {@code Format} parsing should be used.
113     * @param dateStyle the date style to use for Locale validation.
114     */
115    public DateValidator(final boolean strict, final int dateStyle) {
116        super(strict, dateStyle, -1);
117    }
118
119    /**
120     * <p>Compare Dates (day, month and year - not time).</p>
121     *
122     * @param value The {@link Calendar} value to check.
123     * @param compare The {@link Calendar} to compare the value to.
124     * @param timeZone The Time Zone used to compare the dates, system default if null.
125     * @return Zero if the dates are equal, -1 if first
126     * date is less than the seconds and +1 if the first
127     * date is greater than.
128     */
129    public int compareDates(final Date value, final Date compare, final TimeZone timeZone) {
130        final Calendar calendarValue = getCalendar(value, timeZone);
131        final Calendar calendarCompare = getCalendar(compare, timeZone);
132        return compare(calendarValue, calendarCompare, Calendar.DATE);
133    }
134
135    /**
136     * <p>Compare Months (month and year).</p>
137     *
138     * @param value The {@code Date} value to check.
139     * @param compare The {@code Date} to compare the value to.
140     * @param timeZone The Time Zone used to compare the dates, system default if null.
141     * @return Zero if the months are equal, -1 if first
142     * parameter's month is less than the seconds and +1 if the first
143     * parameter's month is greater than.
144     */
145    public int compareMonths(final Date value, final Date compare, final TimeZone timeZone) {
146        final Calendar calendarValue = getCalendar(value, timeZone);
147        final Calendar calendarCompare = getCalendar(compare, timeZone);
148        return compare(calendarValue, calendarCompare, Calendar.MONTH);
149    }
150
151    /**
152     * <p>Compare Quarters (quarter and year).</p>
153     *
154     * @param value The {@code Date} value to check.
155     * @param compare The {@code Date} to compare the value to.
156     * @param timeZone The Time Zone used to compare the dates, system default if null.
157     * @return Zero if the months are equal, -1 if first
158     * parameter's quarter is less than the seconds and +1 if the first
159     * parameter's quarter is greater than.
160     */
161    public int compareQuarters(final Date value, final Date compare, final TimeZone timeZone) {
162        return compareQuarters(value, compare, timeZone, 1);
163    }
164
165    /**
166     * <p>Compare Quarters (quarter and year).</p>
167     *
168     * @param value The {@code Date} value to check.
169     * @param compare The {@code Date} to compare the value to.
170     * @param timeZone The Time Zone used to compare the dates, system default if null.
171     * @param monthOfFirstQuarter The  month that the first quarter starts.
172     * @return Zero if the quarters are equal, -1 if first
173     * parameter's quarter is less than the seconds and +1 if the first
174     * parameter's quarter is greater than.
175     */
176    public int compareQuarters(final Date value, final Date compare, final TimeZone timeZone, final int monthOfFirstQuarter) {
177        final Calendar calendarValue = getCalendar(value, timeZone);
178        final Calendar calendarCompare = getCalendar(compare, timeZone);
179        return super.compareQuarters(calendarValue, calendarCompare, monthOfFirstQuarter);
180    }
181
182    /**
183     * <p>Compare Weeks (week and year).</p>
184     *
185     * @param value The {@code Date} value to check.
186     * @param compare The {@code Date} to compare the value to.
187     * @param timeZone The Time Zone used to compare the dates, system default if null.
188     * @return Zero if the weeks are equal, -1 if first
189     * parameter's week is less than the seconds and +1 if the first
190     * parameter's week is greater than.
191     */
192    public int compareWeeks(final Date value, final Date compare, final TimeZone timeZone) {
193        final Calendar calendarValue = getCalendar(value, timeZone);
194        final Calendar calendarCompare = getCalendar(compare, timeZone);
195        return compare(calendarValue, calendarCompare, Calendar.WEEK_OF_YEAR);
196    }
197
198    /**
199     * <p>Compare Years.</p>
200     *
201     * @param value The {@code Date} value to check.
202     * @param compare The {@code Date} to compare the value to.
203     * @param timeZone The Time Zone used to compare the dates, system default if null.
204     * @return Zero if the years are equal, -1 if first
205     * parameter's year is less than the seconds and +1 if the first
206     * parameter's year is greater than.
207     */
208    public int compareYears(final Date value, final Date compare, final TimeZone timeZone) {
209        final Calendar calendarValue = getCalendar(value, timeZone);
210        final Calendar calendarCompare = getCalendar(compare, timeZone);
211        return compare(calendarValue, calendarCompare, Calendar.YEAR);
212    }
213
214    /**
215     * <p>Convert a {@code Date} to a {@link Calendar}.</p>
216     *
217     * @param value The date value to be converted.
218     * @return The converted {@link Calendar}.
219     */
220    private Calendar getCalendar(final Date value, final TimeZone timeZone) {
221        final Calendar calendar;
222        if (timeZone != null) {
223            calendar = Calendar.getInstance(timeZone);
224        } else {
225            calendar = Calendar.getInstance();
226        }
227        calendar.setTime(value);
228        return calendar;
229
230    }
231
232    /**
233     * <p>Returns the parsed {@code Date} unchanged.</p>
234     *
235     * @param value The parsed {@code Date} object created.
236     * @param formatter The Format used to parse the value with.
237     * @return The parsed value converted to a {@link Calendar}.
238     */
239    @Override
240    protected Object processParsedValue(final Object value, final Format formatter) {
241        return value;
242    }
243
244    /**
245     * <p>Validate/convert a {@code Date} using the default
246     *    {@link Locale} and {@code TimeZone}.
247     *
248     * @param value The value validation is being performed on.
249     * @return The parsed {@code Date} if valid or {@code null}
250     *  if invalid.
251     */
252    public Date validate(final String value) {
253        return (Date) parse(value, (String) null, (Locale) null, (TimeZone) null);
254    }
255
256    /**
257     * <p>Validate/convert a {@code Date} using the specified
258     *    {@link Locale} and default {@code TimeZone}.
259     *
260     * @param value The value validation is being performed on.
261     * @param locale The locale to use for the date format, system default if null.
262     * @return The parsed {@code Date} if valid or {@code null} if invalid.
263     */
264    public Date validate(final String value, final Locale locale) {
265        return (Date) parse(value, (String) null, locale, (TimeZone) null);
266    }
267
268    /**
269     * <p>Validate/convert a {@code Date} using the specified
270     *    {@link Locale} and {@code TimeZone}.
271     *
272     * @param value The value validation is being performed on.
273     * @param locale The locale to use for the date format, system default if null.
274     * @param timeZone The Time Zone used to parse the date, system default if null.
275     * @return The parsed {@code Date} if valid or {@code null} if invalid.
276     */
277    public Date validate(final String value, final Locale locale, final TimeZone timeZone) {
278        return (Date) parse(value, (String) null, locale, timeZone);
279    }
280
281    /**
282     * <p>Validate/convert a {@code Date} using the specified
283     *    <em>pattern</em> and default {@code TimeZone}.
284     *
285     * @param value The value validation is being performed on.
286     * @param pattern The pattern used to validate the value against, or the
287     *        default for the {@link Locale} if {@code null}.
288     * @return The parsed {@code Date} if valid or {@code null} if invalid.
289     */
290    public Date validate(final String value, final String pattern) {
291        return (Date) parse(value, pattern, (Locale) null, (TimeZone) null);
292    }
293
294    /**
295     * <p>Validate/convert a {@code Date} using the specified pattern
296     *    and {@link Locale} and the default {@code TimeZone}.
297     *
298     * @param value The value validation is being performed on.
299     * @param pattern The pattern used to validate the value against, or the
300     *        default for the {@link Locale} if {@code null}.
301     * @param locale The locale to use for the date format, system default if null.
302     * @return The parsed {@code Date} if valid or {@code null} if invalid.
303     */
304    public Date validate(final String value, final String pattern, final Locale locale) {
305        return (Date) parse(value, pattern, locale, (TimeZone) null);
306    }
307
308    /**
309     * <p>Validate/convert a {@code Date} using the specified
310     *    pattern, and {@link Locale} and {@code TimeZone}.
311     *
312     * @param value The value validation is being performed on.
313     * @param pattern The pattern used to validate the value against, or the
314     *        default for the {@link Locale} if {@code null}.
315     * @param locale The locale to use for the date format, system default if null.
316     * @param timeZone The Time Zone used to parse the date, system default if null.
317     * @return The parsed {@code Date} if valid or {@code null} if invalid.
318     */
319    public Date validate(final String value, final String pattern, final Locale locale, final TimeZone timeZone) {
320        return (Date) parse(value, pattern, locale, timeZone);
321    }
322
323    /**
324     * <p>Validate/convert a {@code Date} using the specified
325     *    <em>pattern</em> and {@code TimeZone}.
326     *
327     * @param value The value validation is being performed on.
328     * @param pattern The pattern used to validate the value against, or the
329     *        default for the {@link Locale} if {@code null}.
330     * @param timeZone The Time Zone used to parse the date, system default if null.
331     * @return The parsed {@code Date} if valid or {@code null} if invalid.
332     */
333    public Date validate(final String value, final String pattern, final TimeZone timeZone) {
334        return (Date) parse(value, pattern, (Locale) null, timeZone);
335    }
336
337    /**
338     * <p>Validate/convert a {@code Date} using the specified
339     *    {@code TimeZone} and default {@link Locale}.
340     *
341     * @param value The value validation is being performed on.
342     * @param timeZone The Time Zone used to parse the date, system default if null.
343     * @return The parsed {@code Date} if valid or {@code null} if invalid.
344     */
345    public Date validate(final String value, final TimeZone timeZone) {
346        return (Date) parse(value, (String) null, (Locale) null, timeZone);
347    }
348
349}