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.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 <em>base</em> 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 <em>strict</em>, 053 * <em>time</em> and <em>date</em> style parameters. 054 * 055 * @param strict {@code true} if strict 056 * {@code Format} 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 {@link Calendar} 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 return Integer.compare(value.get(field), compare.get(field)); 079 } 080 081 /** 082 * <p>Calculate the quarter for the specified Calendar.</p> 083 * 084 * @param calendar The Calendar value. 085 * @param monthOfFirstQuarter The month that the first quarter starts. 086 * @return The calculated quarter. 087 */ 088 private int calculateQuarter(final Calendar calendar, final int monthOfFirstQuarter) { 089 // Add Year 090 int year = calendar.get(Calendar.YEAR); 091 092 final int month = calendar.get(Calendar.MONTH) + 1; 093 final int relativeMonth = month >= monthOfFirstQuarter 094 ? month - monthOfFirstQuarter 095 : month + 12 - monthOfFirstQuarter; // CHECKSTYLE IGNORE MagicNumber 096 final int quarter = relativeMonth / 3 + 1; // CHECKSTYLE IGNORE MagicNumber 097 // adjust the year if the quarter doesn't start in January 098 if (month < monthOfFirstQuarter) { 099 --year; 100 } 101 return year * 10 + quarter; // CHECKSTYLE IGNORE MagicNumber 102 } 103 104 /** 105 * <p>Compares a calendar value to another, indicating whether it is 106 * equal, less than or more than at a specified level.</p> 107 * 108 * @param value The Calendar value. 109 * @param compare The {@link Calendar} to check the value against. 110 * @param field The field <em>level</em> to compare to - for example, specifying 111 * {@code Calendar.MONTH} will compare the year and month 112 * portions of the calendar. 113 * @return Zero if the first value is equal to the second, -1 114 * if it is less than the second or +1 if it is greater than the second. 115 */ 116 protected int compare(final Calendar value, final Calendar compare, final int field) { 117 118 int result; 119 120 // Compare Year 121 result = calculateCompareResult(value, compare, Calendar.YEAR); 122 if (result != 0 || field == Calendar.YEAR) { 123 return result; 124 } 125 126 // Compare Week of Year 127 if (field == Calendar.WEEK_OF_YEAR) { 128 return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR); 129 } 130 131 // Compare Day of the Year 132 if (field == Calendar.DAY_OF_YEAR) { 133 return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR); 134 } 135 136 // Compare Month 137 result = calculateCompareResult(value, compare, Calendar.MONTH); 138 if (result != 0 || field == Calendar.MONTH) { 139 return result; 140 } 141 142 // Compare Week of Month 143 if (field == Calendar.WEEK_OF_MONTH) { 144 return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH); 145 } 146 147 // Compare Date 148 result = calculateCompareResult(value, compare, Calendar.DATE); 149 if (result != 0 || field == Calendar.DATE || 150 field == Calendar.DAY_OF_WEEK || 151 field == Calendar.DAY_OF_WEEK_IN_MONTH) { 152 return result; 153 } 154 155 // Compare Time fields 156 return compareTime(value, compare, field); 157 158 } 159 160 /** 161 * <p>Compares a calendar's quarter value to another, indicating whether it is 162 * equal, less than or more than the specified quarter.</p> 163 * 164 * @param value The Calendar value. 165 * @param compare The {@link Calendar} to check the value against. 166 * @param monthOfFirstQuarter The month that the first quarter starts. 167 * @return Zero if the first quarter is equal to the second, -1 168 * if it is less than the second or +1 if it is greater than the second. 169 */ 170 protected int compareQuarters(final Calendar value, final Calendar compare, final int monthOfFirstQuarter) { 171 final int valueQuarter = calculateQuarter(value, monthOfFirstQuarter); 172 final int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter); 173 return Integer.compare(valueQuarter, compareQuarter); 174 } 175 176 /** 177 * <p>Compares a calendar time value to another, indicating whether it is 178 * equal, less than or more than at a specified level.</p> 179 * 180 * @param value The Calendar value. 181 * @param compare The {@link Calendar} to check the value against. 182 * @param field The field <em>level</em> to compare to - for example, specifying 183 * {@code Calendar.MINUTE} will compare the hours and minutes 184 * portions of the calendar. 185 * @return Zero if the first value is equal to the second, -1 186 * if it is less than the second or +1 if it is greater than the second. 187 */ 188 protected int compareTime(final Calendar value, final Calendar compare, final int field) { 189 190 int result; 191 192 // Compare Hour 193 result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY); 194 if (result != 0 || field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY) { 195 return result; 196 } 197 198 // Compare Minute 199 result = calculateCompareResult(value, compare, Calendar.MINUTE); 200 if (result != 0 || field == Calendar.MINUTE) { 201 return result; 202 } 203 204 // Compare Second 205 result = calculateCompareResult(value, compare, Calendar.SECOND); 206 if (result != 0 || field == Calendar.SECOND) { 207 return result; 208 } 209 210 // Compare Milliseconds 211 if (field == Calendar.MILLISECOND) { 212 return calculateCompareResult(value, compare, Calendar.MILLISECOND); 213 } 214 215 throw new IllegalArgumentException("Invalid field: " + field); 216 217 } 218 219 /** 220 * <p>Format a value with the specified {@code DateFormat}.</p> 221 * 222 * @param value The value to be formatted. 223 * @param formatter The Format to use. 224 * @return The formatted value. 225 */ 226 @Override 227 protected String format(Object value, final Format formatter) { 228 if (value == null) { 229 return null; 230 } 231 if (value instanceof Calendar) { 232 value = ((Calendar) value).getTime(); 233 } 234 return formatter.format(value); 235 } 236 237 /** 238 * <p>Format an object into a {@link String} using 239 * the specified Locale.</p> 240 * 241 * @param value The value validation is being performed on. 242 * @param locale The locale to use for the Format. 243 * @param timeZone The Time Zone used to format the date, 244 * system default if null unless value is a {@link Calendar}. 245 * @return The value formatted as a {@link String}. 246 */ 247 public String format(final Object value, final Locale locale, final TimeZone timeZone) { 248 return format(value, (String) null, locale, timeZone); 249 } 250 251 /** 252 * <p>Format an object using the specified pattern and/or 253 * {@link Locale}. 254 * 255 * @param value The value validation is being performed on. 256 * @param pattern The pattern used to format the value. 257 * @param locale The locale to use for the Format. 258 * @return The value formatted as a {@link String}. 259 */ 260 @Override 261 public String format(final Object value, final String pattern, final Locale locale) { 262 return format(value, pattern, locale, (TimeZone) null); 263 } 264 265 /** 266 * <p>Format an object using the specified pattern and/or 267 * {@link Locale}. 268 * 269 * @param value The value validation is being performed on. 270 * @param pattern The pattern used to format the value. 271 * @param locale The locale to use for the Format. 272 * @param timeZone The Time Zone used to format the date, 273 * system default if null unless value is a {@link Calendar}. 274 * @return The value formatted as a {@link String}. 275 */ 276 public String format(final Object value, final String pattern, final Locale locale, final TimeZone timeZone) { 277 final DateFormat formatter = (DateFormat) getFormat(pattern, locale); 278 if (timeZone != null) { 279 formatter.setTimeZone(timeZone); 280 } else if (value instanceof Calendar) { 281 formatter.setTimeZone(((Calendar) value).getTimeZone()); 282 } 283 return format(value, formatter); 284 } 285 286 /** 287 * <p>Format an object into a {@link String} using 288 * the specified pattern.</p> 289 * 290 * @param value The value validation is being performed on. 291 * @param pattern The pattern used to format the value. 292 * @param timeZone The Time Zone used to format the date, 293 * system default if null unless value is a {@link Calendar}. 294 * @return The value formatted as a {@link String}. 295 */ 296 public String format(final Object value, final String pattern, final TimeZone timeZone) { 297 return format(value, pattern, (Locale) null, timeZone); 298 } 299 300 /** 301 * <p>Format an object into a {@link String} using 302 * the default Locale.</p> 303 * 304 * @param value The value validation is being performed on. 305 * @param timeZone The Time Zone used to format the date, 306 * system default if null unless value is a {@link Calendar}. 307 * @return The value formatted as a {@link String}. 308 */ 309 public String format(final Object value, final TimeZone timeZone) { 310 return format(value, (String) null, (Locale) null, timeZone); 311 } 312 313 /** 314 * <p>Returns a {@code DateFormat} for the specified Locale.</p> 315 * 316 * @param locale The locale a {@code DateFormat} is required for, 317 * system default if null. 318 * @return The {@code DateFormat} to created. 319 */ 320 protected Format getFormat(final Locale locale) { 321 final DateFormat formatter; 322 if (dateStyle >= 0 && timeStyle >= 0) { 323 if (locale == null) { 324 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle); 325 } else { 326 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale); 327 } 328 } else if (timeStyle >= 0) { 329 if (locale == null) { 330 formatter = DateFormat.getTimeInstance(timeStyle); 331 } else { 332 formatter = DateFormat.getTimeInstance(timeStyle, locale); 333 } 334 } else { 335 final int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT; 336 if (locale == null) { 337 formatter = DateFormat.getDateInstance(useDateStyle); 338 } else { 339 formatter = DateFormat.getDateInstance(useDateStyle, locale); 340 } 341 } 342 formatter.setLenient(false); 343 return formatter; 344 } 345 346 /** 347 * <p>Returns a {@code DateFormat} for the specified <em>pattern</em> 348 * and/or {@link Locale}.</p> 349 * 350 * @param pattern The pattern used to validate the value against or 351 * {@code null} to use the default for the {@link Locale}. 352 * @param locale The locale to use for the currency format, system default if null. 353 * @return The {@code DateFormat} to created. 354 */ 355 @Override 356 protected Format getFormat(final String pattern, final Locale locale) { 357 final DateFormat formatter; 358 final boolean usePattern = !GenericValidator.isBlankOrNull(pattern); 359 if (!usePattern) { 360 formatter = (DateFormat) getFormat(locale); 361 } else if (locale == null) { 362 formatter = new SimpleDateFormat(pattern); 363 } else { 364 final DateFormatSymbols symbols = new DateFormatSymbols(locale); 365 formatter = new SimpleDateFormat(pattern, symbols); 366 } 367 formatter.setLenient(false); 368 return formatter; 369 } 370 371 /** 372 * <p>Validate using the specified {@link Locale}. 373 * 374 * @param value The value validation is being performed on. 375 * @param pattern The pattern used to format the value. 376 * @param locale The locale to use for the Format, defaults to the default 377 * @return {@code true} if the value is valid. 378 */ 379 @Override 380 public boolean isValid(final String value, final String pattern, final Locale locale) { 381 return parse(value, pattern, locale, (TimeZone) null) != null; 382 } 383 384 /** 385 * <p>Checks if the value is valid against a specified pattern.</p> 386 * 387 * @param value The value validation is being performed on. 388 * @param pattern The pattern used to validate the value against, or the 389 * default for the {@link Locale} if {@code null}. 390 * @param locale The locale to use for the date format, system default if null. 391 * @param timeZone The Time Zone used to parse the date, system default if null. 392 * @return The parsed value if valid or {@code null} if invalid. 393 */ 394 protected Object parse(String value, final String pattern, final Locale locale, final TimeZone timeZone) { 395 value = value == null ? null : value.trim(); 396 final String value1 = value; 397 if (GenericValidator.isBlankOrNull(value1)) { 398 return null; 399 } 400 final DateFormat formatter = (DateFormat) getFormat(pattern, locale); 401 if (timeZone != null) { 402 formatter.setTimeZone(timeZone); 403 } 404 return parse(value, formatter); 405 406 } 407 408 /** 409 * <p>Process the parsed value, performing any further validation 410 * and type conversion required.</p> 411 * 412 * @param value The parsed object created. 413 * @param formatter The Format used to parse the value with. 414 * @return The parsed value converted to the appropriate type 415 * if valid or {@code null} if invalid. 416 */ 417 @Override 418 protected abstract Object processParsedValue(Object value, Format formatter); 419}