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 */ 017package org.apache.commons.validator.routines; 018 019import java.text.DateFormat; 020import java.text.DateFormatSymbols; 021import java.text.Format; 022import java.text.SimpleDateFormat; 023import java.util.Calendar; 024import java.util.Locale; 025import java.util.TimeZone; 026 027import org.apache.commons.validator.GenericValidator; 028 029/** 030 * <p>Abstract class for Date/Time/Calendar validation.</p> 031 * 032 * <p>This is a <i>base</i> class for building Date / Time 033 * Validators using format parsing.</p> 034 * 035 * @since 1.3.0 036 */ 037public abstract class AbstractCalendarValidator extends AbstractFormatValidator { 038 039 private static final long serialVersionUID = -1410008585975827379L; 040 041 /** 042 * The date style to use for Locale validation. 043 */ 044 private final int dateStyle; 045 046 /** 047 * The time style to use for Locale validation. 048 */ 049 private final int timeStyle; 050 051 /** 052 * Constructs an instance with the specified <i>strict</i>, 053 * <i>time</i> and <i>date</i> style parameters. 054 * 055 * @param strict {@code true} if strict 056 * <code>Format</code> parsing should be used. 057 * @param dateStyle the date style to use for Locale validation. 058 * @param timeStyle the time style to use for Locale validation. 059 */ 060 public AbstractCalendarValidator(final boolean strict, final int dateStyle, final int timeStyle) { 061 super(strict); 062 this.dateStyle = dateStyle; 063 this.timeStyle = timeStyle; 064 } 065 066 /** 067 * <p>Compares the field from two calendars indicating whether the field for the 068 * first calendar is equal to, less than or greater than the field from the 069 * second calendar. 070 * 071 * @param value The Calendar value. 072 * @param compare The <code>Calendar</code> to check the value against. 073 * @param field The field to compare for the calendars. 074 * @return Zero if the first calendar's field is equal to the seconds, -1 075 * if it is less than the seconds or +1 if it is greater than the seconds. 076 */ 077 private int calculateCompareResult(final Calendar value, final Calendar compare, final int field) { 078 final int difference = value.get(field) - compare.get(field); 079 if (difference < 0) { 080 return -1; 081 } 082 if (difference > 0) { 083 return 1; 084 } 085 return 0; 086 } 087 088 /** 089 * <p>Calculate the quarter for the specified Calendar.</p> 090 * 091 * @param calendar The Calendar value. 092 * @param monthOfFirstQuarter The month that the first quarter starts. 093 * @return The calculated quarter. 094 */ 095 private int calculateQuarter(final Calendar calendar, final int monthOfFirstQuarter) { 096 // Add Year 097 int year = calendar.get(Calendar.YEAR); 098 099 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}