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