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