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