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