001package ca.uhn.fhir.model.primitive; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2017 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import static org.apache.commons.lang3.StringUtils.isBlank; 024 025import java.util.Calendar; 026import java.util.Date; 027import java.util.GregorianCalendar; 028import java.util.TimeZone; 029 030import org.apache.commons.lang3.StringUtils; 031import org.apache.commons.lang3.Validate; 032import org.apache.commons.lang3.time.DateUtils; 033import org.apache.commons.lang3.time.FastDateFormat; 034 035import ca.uhn.fhir.model.api.BasePrimitive; 036import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 037import ca.uhn.fhir.parser.DataFormatException; 038 039public abstract class BaseDateTimeDt extends BasePrimitive<Date> { 040 static final long NANOS_PER_MILLIS = 1000000L; 041 static final long NANOS_PER_SECOND = 1000000000L; 042 043 private static final FastDateFormat ourHumanDateFormat = FastDateFormat.getDateInstance(FastDateFormat.MEDIUM); 044 private static final FastDateFormat ourHumanDateTimeFormat = FastDateFormat.getDateTimeInstance(FastDateFormat.MEDIUM, FastDateFormat.MEDIUM); 045 046 private String myFractionalSeconds; 047 private TemporalPrecisionEnum myPrecision = null; 048 private TimeZone myTimeZone; 049 private boolean myTimeZoneZulu = false; 050 051 /** 052 * Constructor 053 */ 054 public BaseDateTimeDt() { 055 // nothing 056 } 057 058 /** 059 * Constructor 060 * 061 * @throws DataFormatException 062 * If the specified precision is not allowed for this type 063 */ 064 public BaseDateTimeDt(Date theDate, TemporalPrecisionEnum thePrecision) { 065 setValue(theDate, thePrecision); 066 if (isPrecisionAllowed(thePrecision) == false) { 067 throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theDate); 068 } 069 } 070 071 /** 072 * Constructor 073 */ 074 public BaseDateTimeDt(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimeZone) { 075 this(theDate, thePrecision); 076 setTimeZone(theTimeZone); 077 } 078 079 /** 080 * Constructor 081 * 082 * @throws DataFormatException 083 * If the specified precision is not allowed for this type 084 */ 085 public BaseDateTimeDt(String theString) { 086 setValueAsString(theString); 087 if (isPrecisionAllowed(getPrecision()) == false) { 088 throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + getPrecision() + " precision): " + theString); 089 } 090 } 091 092 private void clearTimeZone() { 093 myTimeZone = null; 094 myTimeZoneZulu = false; 095 } 096 097 @Override 098 protected String encode(Date theValue) { 099 if (theValue == null) { 100 return null; 101 } 102 GregorianCalendar cal; 103 if (myTimeZoneZulu) { 104 cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 105 } else if (myTimeZone != null) { 106 cal = new GregorianCalendar(myTimeZone); 107 } else { 108 cal = new GregorianCalendar(); 109 } 110 cal.setTime(theValue); 111 112 StringBuilder b = new StringBuilder(); 113 leftPadWithZeros(cal.get(Calendar.YEAR), 4, b); 114 if (myPrecision.ordinal() > TemporalPrecisionEnum.YEAR.ordinal()) { 115 b.append('-'); 116 leftPadWithZeros(cal.get(Calendar.MONTH) + 1, 2, b); 117 if (myPrecision.ordinal() > TemporalPrecisionEnum.MONTH.ordinal()) { 118 b.append('-'); 119 leftPadWithZeros(cal.get(Calendar.DATE), 2, b); 120 if (myPrecision.ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 121 b.append('T'); 122 leftPadWithZeros(cal.get(Calendar.HOUR_OF_DAY), 2, b); 123 b.append(':'); 124 leftPadWithZeros(cal.get(Calendar.MINUTE), 2, b); 125 if (myPrecision.ordinal() > TemporalPrecisionEnum.MINUTE.ordinal()) { 126 b.append(':'); 127 leftPadWithZeros(cal.get(Calendar.SECOND), 2, b); 128 if (myPrecision.ordinal() > TemporalPrecisionEnum.SECOND.ordinal()) { 129 b.append('.'); 130 b.append(myFractionalSeconds); 131 for (int i = myFractionalSeconds.length(); i < 3; i++) { 132 b.append('0'); 133 } 134 } 135 } 136 137 if (myTimeZoneZulu) { 138 b.append('Z'); 139 } else if (myTimeZone != null) { 140 int offset = myTimeZone.getOffset(theValue.getTime()); 141 if (offset >= 0) { 142 b.append('+'); 143 } else { 144 b.append('-'); 145 offset = Math.abs(offset); 146 } 147 148 int hoursOffset = (int) (offset / DateUtils.MILLIS_PER_HOUR); 149 leftPadWithZeros(hoursOffset, 2, b); 150 b.append(':'); 151 int minutesOffset = (int) (offset % DateUtils.MILLIS_PER_HOUR); 152 minutesOffset = (int) (minutesOffset / DateUtils.MILLIS_PER_MINUTE); 153 leftPadWithZeros(minutesOffset, 2, b); 154 } 155 } 156 } 157 } 158 return b.toString(); 159 } 160 161 /** 162 * Returns the default precision for the given datatype 163 */ 164 protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype(); 165 166 private int getOffsetIndex(String theValueString) { 167 int plusIndex = theValueString.indexOf('+', 16); 168 int minusIndex = theValueString.indexOf('-', 16); 169 int zIndex = theValueString.indexOf('Z', 16); 170 int retVal = Math.max(Math.max(plusIndex, minusIndex), zIndex); 171 if (retVal == -1) { 172 return -1; 173 } 174 if ((retVal - 2) != (plusIndex + minusIndex + zIndex)) { 175 throwBadDateFormat(theValueString); 176 } 177 return retVal; 178 } 179 180 /** 181 * Gets the precision for this datatype (using the default for the given type if not set) 182 * 183 * @see #setPrecision(TemporalPrecisionEnum) 184 */ 185 public TemporalPrecisionEnum getPrecision() { 186 if (myPrecision == null) { 187 return getDefaultPrecisionForDatatype(); 188 } 189 return myPrecision; 190 } 191 192 /** 193 * Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was 194 * supplied. 195 */ 196 public TimeZone getTimeZone() { 197 if (myTimeZoneZulu) { 198 return TimeZone.getTimeZone("GMT"); 199 } 200 return myTimeZone; 201 } 202 203 /** 204 * Returns the value of this object as a {@link GregorianCalendar} 205 */ 206 public GregorianCalendar getValueAsCalendar() { 207 if (getValue() == null) { 208 return null; 209 } 210 GregorianCalendar cal; 211 if (getTimeZone() != null) { 212 cal = new GregorianCalendar(getTimeZone()); 213 } else { 214 cal = new GregorianCalendar(); 215 } 216 cal.setTime(getValue()); 217 return cal; 218 } 219 220 /** 221 * To be implemented by subclasses to indicate whether the given precision is allowed by this type 222 */ 223 abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision); 224 225 /** 226 * Returns true if the timezone is set to GMT-0:00 (Z) 227 */ 228 public boolean isTimeZoneZulu() { 229 return myTimeZoneZulu; 230 } 231 232 /** 233 * Returns <code>true</code> if this object represents a date that is today's date 234 * 235 * @throws NullPointerException 236 * if {@link #getValue()} returns <code>null</code> 237 */ 238 public boolean isToday() { 239 Validate.notNull(getValue(), getClass().getSimpleName() + " contains null value"); 240 return DateUtils.isSameDay(new Date(), getValue()); 241 } 242 243 private void leftPadWithZeros(int theInteger, int theLength, StringBuilder theTarget) { 244 String string = Integer.toString(theInteger); 245 for (int i = string.length(); i < theLength; i++) { 246 theTarget.append('0'); 247 } 248 theTarget.append(string); 249 } 250 251 @Override 252 protected Date parse(String theValue) throws DataFormatException { 253 Calendar cal = new GregorianCalendar(0, 0, 0); 254 cal.setTimeZone(TimeZone.getDefault()); 255 String value = theValue; 256 boolean fractionalSecondsSet = false; 257 258 if (value.length() > 0 && (value.charAt(0) == ' ' || value.charAt(value.length() - 1) == ' ')) { 259 value = value.trim(); 260 } 261 262 int length = value.length(); 263 if (length == 0) { 264 return null; 265 } 266 267 if (length < 4) { 268 throwBadDateFormat(value); 269 } 270 271 TemporalPrecisionEnum precision = null; 272 cal.set(Calendar.YEAR, parseInt(value, value.substring(0, 4), 0, 9999)); 273 precision = TemporalPrecisionEnum.YEAR; 274 if (length > 4) { 275 validateCharAtIndexIs(value, 4, '-'); 276 validateLengthIsAtLeast(value, 7); 277 int monthVal = parseInt(value, value.substring(5, 7), 1, 12) - 1; 278 cal.set(Calendar.MONTH, monthVal); 279 precision = TemporalPrecisionEnum.MONTH; 280 if (length > 7) { 281 validateCharAtIndexIs(value, 7, '-'); 282 validateLengthIsAtLeast(value, 10); 283 cal.set(Calendar.DATE, 1); // for some reason getActualMaximum works incorrectly if date isn't set 284 int actualMaximum = cal.getActualMaximum(Calendar.DAY_OF_MONTH); 285 cal.set(Calendar.DAY_OF_MONTH, parseInt(value, value.substring(8, 10), 1, actualMaximum)); 286 precision = TemporalPrecisionEnum.DAY; 287 if (length > 10) { 288 validateLengthIsAtLeast(value, 17); 289 validateCharAtIndexIs(value, 10, 'T'); // yyyy-mm-ddThh:mm:ss 290 int offsetIdx = getOffsetIndex(value); 291 String time; 292 if (offsetIdx == -1) { 293 //throwBadDateFormat(theValue); 294 // No offset - should this be an error? 295 time = value.substring(11); 296 } else { 297 time = value.substring(11, offsetIdx); 298 String offsetString = value.substring(offsetIdx); 299 setTimeZone(value, offsetString); 300 cal.setTimeZone(getTimeZone()); 301 } 302 int timeLength = time.length(); 303 304 validateCharAtIndexIs(value, 13, ':'); 305 cal.set(Calendar.HOUR_OF_DAY, parseInt(value, value.substring(11, 13), 0, 23)); 306 cal.set(Calendar.MINUTE, parseInt(value, value.substring(14, 16), 0, 59)); 307 precision = TemporalPrecisionEnum.MINUTE; 308 if (timeLength > 5) { 309 validateLengthIsAtLeast(value, 19); 310 validateCharAtIndexIs(value, 16, ':'); // yyyy-mm-ddThh:mm:ss 311 cal.set(Calendar.SECOND, parseInt(value, value.substring(17, 19), 0, 59)); 312 precision = TemporalPrecisionEnum.SECOND; 313 if (timeLength > 8) { 314 validateCharAtIndexIs(value, 19, '.'); // yyyy-mm-ddThh:mm:ss.SSSS 315 validateLengthIsAtLeast(value, 20); 316 int endIndex = getOffsetIndex(value); 317 if (endIndex == -1) { 318 endIndex = value.length(); 319 } 320 int millis; 321 String millisString; 322 if (endIndex > 23) { 323 myFractionalSeconds = value.substring(20, endIndex); 324 fractionalSecondsSet = true; 325 endIndex = 23; 326 millisString = value.substring(20, endIndex); 327 millis = parseInt(value, millisString, 0, 999); 328 } else { 329 millisString = value.substring(20, endIndex); 330 millis = parseInt(value, millisString, 0, 999); 331 myFractionalSeconds = millisString; 332 fractionalSecondsSet = true; 333 } 334 if (millisString.length() == 1) { 335 millis = millis * 100; 336 } else if (millisString.length() == 2) { 337 millis = millis * 10; 338 } 339 cal.set(Calendar.MILLISECOND, millis); 340 precision = TemporalPrecisionEnum.MILLI; 341 } 342 } 343 } 344 } else { 345 cal.set(Calendar.DATE, 1); 346 } 347 } else { 348 cal.set(Calendar.DATE, 1); 349 } 350 351 if (fractionalSecondsSet == false) { 352 myFractionalSeconds = ""; 353 } 354 355 setPrecision(precision); 356 return cal.getTime(); 357 358 } 359 360 private int parseInt(String theValue, String theSubstring, int theLowerBound, int theUpperBound) { 361 int retVal = 0; 362 try { 363 retVal = Integer.parseInt(theSubstring); 364 } catch (NumberFormatException e) { 365 throwBadDateFormat(theValue); 366 } 367 368 if (retVal < theLowerBound || retVal > theUpperBound) { 369 throwBadDateFormat(theValue); 370 } 371 372 return retVal; 373 } 374 375 /** 376 * Sets the precision for this datatype 377 * 378 * @throws DataFormatException 379 */ 380 public BaseDateTimeDt setPrecision(TemporalPrecisionEnum thePrecision) throws DataFormatException { 381 if (thePrecision == null) { 382 throw new NullPointerException("Precision may not be null"); 383 } 384 myPrecision = thePrecision; 385 updateStringValue(); 386 return this; 387 } 388 389 private BaseDateTimeDt setTimeZone(String theWholeValue, String theValue) { 390 391 if (isBlank(theValue)) { 392 throwBadDateFormat(theWholeValue); 393 } else if (theValue.charAt(0) == 'Z') { 394 clearTimeZone(); 395 setTimeZoneZulu(true); 396 } else if (theValue.length() != 6) { 397 throwBadDateFormat(theWholeValue, "Timezone offset must be in the form \"Z\", \"-HH:mm\", or \"+HH:mm\""); 398 } else if (theValue.charAt(3) != ':' || !(theValue.charAt(0) == '+' || theValue.charAt(0) == '-')) { 399 throwBadDateFormat(theWholeValue, "Timezone offset must be in the form \"Z\", \"-HH:mm\", or \"+HH:mm\""); 400 } else { 401 parseInt(theWholeValue, theValue.substring(1, 3), 0, 23); 402 parseInt(theWholeValue, theValue.substring(4, 6), 0, 59); 403 clearTimeZone(); 404 setTimeZone(TimeZone.getTimeZone("GMT" + theValue)); 405 } 406 407 return this; 408 } 409 410 public BaseDateTimeDt setTimeZone(TimeZone theTimeZone) { 411 myTimeZone = theTimeZone; 412 updateStringValue(); 413 return this; 414 } 415 416 public BaseDateTimeDt setTimeZoneZulu(boolean theTimeZoneZulu) { 417 myTimeZoneZulu = theTimeZoneZulu; 418 updateStringValue(); 419 return this; 420 } 421 422 /** 423 * Sets the value for this type using the given Java Date object as the time, and using the default precision for 424 * this datatype (unless the precision is already set), as well as the local timezone as determined by the local operating 425 * system. Both of these properties may be modified in subsequent calls if neccesary. 426 */ 427 @Override 428 public BaseDateTimeDt setValue(Date theValue) { 429 setValue(theValue, getPrecision()); 430 return this; 431 } 432 433 /** 434 * Sets the value for this type using the given Java Date object as the time, and using the specified precision, as 435 * well as the local timezone as determined by the local operating system. Both of 436 * these properties may be modified in subsequent calls if neccesary. 437 * 438 * @param theValue 439 * The date value 440 * @param thePrecision 441 * The precision 442 * @throws DataFormatException 443 */ 444 public void setValue(Date theValue, TemporalPrecisionEnum thePrecision) throws DataFormatException { 445 if (getTimeZone() == null) { 446 setTimeZone(TimeZone.getDefault()); 447 } 448 myPrecision = thePrecision; 449 myFractionalSeconds = ""; 450 if (theValue != null) { 451 long millis = theValue.getTime() % 1000; 452 if (millis < 0) { 453 // This is for times before 1970 (see bug #444) 454 millis = 1000 + millis; 455 } 456 String fractionalSeconds = Integer.toString((int) millis); 457 myFractionalSeconds = StringUtils.leftPad(fractionalSeconds, 3, '0'); 458 } 459 super.setValue(theValue); 460 } 461 462 @Override 463 public void setValueAsString(String theValue) throws DataFormatException { 464 clearTimeZone(); 465 super.setValueAsString(theValue); 466 } 467 468 private void throwBadDateFormat(String theValue) { 469 throw new DataFormatException("Invalid date/time format: \"" + theValue + "\""); 470 } 471 472 private void throwBadDateFormat(String theValue, String theMesssage) { 473 throw new DataFormatException("Invalid date/time format: \"" + theValue + "\": " + theMesssage); 474 } 475 476 /** 477 * Returns a human readable version of this date/time using the system local format. 478 * <p> 479 * <b>Note on time zones:</b> This method renders the value using the time zone that is contained within the value. 480 * For example, if this date object contains the value "2012-01-05T12:00:00-08:00", 481 * the human display will be rendered as "12:00:00" even if the application is being executed on a system in a 482 * different time zone. If this behaviour is not what you want, use 483 * {@link #toHumanDisplayLocalTimezone()} instead. 484 * </p> 485 */ 486 public String toHumanDisplay() { 487 TimeZone tz = getTimeZone(); 488 Calendar value = tz != null ? Calendar.getInstance(tz) : Calendar.getInstance(); 489 value.setTime(getValue()); 490 491 switch (getPrecision()) { 492 case YEAR: 493 case MONTH: 494 case DAY: 495 return ourHumanDateFormat.format(value); 496 case MILLI: 497 case SECOND: 498 default: 499 return ourHumanDateTimeFormat.format(value); 500 } 501 } 502 503 /** 504 * Returns a human readable version of this date/time using the system local format, converted to the local timezone 505 * if neccesary. 506 * 507 * @see #toHumanDisplay() for a method which does not convert the time to the local timezone before rendering it. 508 */ 509 public String toHumanDisplayLocalTimezone() { 510 switch (getPrecision()) { 511 case YEAR: 512 case MONTH: 513 case DAY: 514 return ourHumanDateFormat.format(getValue()); 515 case MILLI: 516 case SECOND: 517 default: 518 return ourHumanDateTimeFormat.format(getValue()); 519 } 520 } 521 522 private void validateCharAtIndexIs(String theValue, int theIndex, char theChar) { 523 if (theValue.charAt(theIndex) != theChar) { 524 throwBadDateFormat(theValue, "Expected character '" + theChar + "' at index " + theIndex + " but found " + theValue.charAt(theIndex)); 525 } 526 } 527 528 private void validateLengthIsAtLeast(String theValue, int theLength) { 529 if (theValue.length() < theLength) { 530 throwBadDateFormat(theValue); 531 } 532 } 533 534 /** 535 * Returns the year, e.g. 2015 536 */ 537 public Integer getYear() { 538 return getFieldValue(Calendar.YEAR); 539 } 540 541 /** 542 * Returns the month with 0-index, e.g. 0=January 543 */ 544 public Integer getMonth() { 545 return getFieldValue(Calendar.MONTH); 546 } 547 548 /** 549 * Returns the month with 1-index, e.g. 1=the first day of the month 550 */ 551 public Integer getDay() { 552 return getFieldValue(Calendar.DAY_OF_MONTH); 553 } 554 555 /** 556 * Returns the hour of the day in a 24h clock, e.g. 13=1pm 557 */ 558 public Integer getHour() { 559 return getFieldValue(Calendar.HOUR_OF_DAY); 560 } 561 562 /** 563 * Returns the minute of the hour in the range 0-59 564 */ 565 public Integer getMinute() { 566 return getFieldValue(Calendar.MINUTE); 567 } 568 569 /** 570 * Returns the second of the minute in the range 0-59 571 */ 572 public Integer getSecond() { 573 return getFieldValue(Calendar.SECOND); 574 } 575 576 /** 577 * Returns the milliseconds within the current second. 578 * <p> 579 * Note that this method returns the 580 * same value as {@link #getNanos()} but with less precision. 581 * </p> 582 */ 583 public Integer getMillis() { 584 return getFieldValue(Calendar.MILLISECOND); 585 } 586 587 /** 588 * Returns the nanoseconds within the current second 589 * <p> 590 * Note that this method returns the 591 * same value as {@link #getMillis()} but with more precision. 592 * </p> 593 */ 594 public Long getNanos() { 595 if (isBlank(myFractionalSeconds)) { 596 return null; 597 } 598 String retVal = StringUtils.rightPad(myFractionalSeconds, 9, '0'); 599 retVal = retVal.substring(0, 9); 600 return Long.parseLong(retVal); 601 } 602 603 /** 604 * Sets the year, e.g. 2015 605 */ 606 public BaseDateTimeDt setYear(int theYear) { 607 setFieldValue(Calendar.YEAR, theYear, null, 0, 9999); 608 return this; 609 } 610 611 /** 612 * Sets the month with 0-index, e.g. 0=January 613 */ 614 public BaseDateTimeDt setMonth(int theMonth) { 615 setFieldValue(Calendar.MONTH, theMonth, null, 0, 11); 616 return this; 617 } 618 619 /** 620 * Sets the month with 1-index, e.g. 1=the first day of the month 621 */ 622 public BaseDateTimeDt setDay(int theDay) { 623 setFieldValue(Calendar.DAY_OF_MONTH, theDay, null, 0, 31); 624 return this; 625 } 626 627 /** 628 * Sets the hour of the day in a 24h clock, e.g. 13=1pm 629 */ 630 public BaseDateTimeDt setHour(int theHour) { 631 setFieldValue(Calendar.HOUR_OF_DAY, theHour, null, 0, 23); 632 return this; 633 } 634 635 /** 636 * Sets the minute of the hour in the range 0-59 637 */ 638 public BaseDateTimeDt setMinute(int theMinute) { 639 setFieldValue(Calendar.MINUTE, theMinute, null, 0, 59); 640 return this; 641 } 642 643 /** 644 * Sets the second of the minute in the range 0-59 645 */ 646 public BaseDateTimeDt setSecond(int theSecond) { 647 setFieldValue(Calendar.SECOND, theSecond, null, 0, 59); 648 return this; 649 } 650 651 /** 652 * Sets the milliseconds within the current second. 653 * <p> 654 * Note that this method sets the 655 * same value as {@link #setNanos(long)} but with less precision. 656 * </p> 657 */ 658 public BaseDateTimeDt setMillis(int theMillis) { 659 setFieldValue(Calendar.MILLISECOND, theMillis, null, 0, 999); 660 return this; 661 } 662 663 /** 664 * Sets the nanoseconds within the current second 665 * <p> 666 * Note that this method sets the 667 * same value as {@link #setMillis(int)} but with more precision. 668 * </p> 669 */ 670 public BaseDateTimeDt setNanos(long theNanos) { 671 validateValueInRange(theNanos, 0, NANOS_PER_SECOND-1); 672 String fractionalSeconds = StringUtils.leftPad(Long.toString(theNanos), 9, '0'); 673 674 // Strip trailing 0s 675 for (int i = fractionalSeconds.length(); i > 0; i--) { 676 if (fractionalSeconds.charAt(i-1) != '0') { 677 fractionalSeconds = fractionalSeconds.substring(0, i); 678 break; 679 } 680 } 681 int millis = (int)(theNanos / NANOS_PER_MILLIS); 682 setFieldValue(Calendar.MILLISECOND, millis, fractionalSeconds, 0, 999); 683 return this; 684 } 685 686 private void setFieldValue(int theField, int theValue, String theFractionalSeconds, int theMinimum, int theMaximum) { 687 validateValueInRange(theValue, theMinimum, theMaximum); 688 Calendar cal; 689 if (getValue() == null) { 690 cal = new GregorianCalendar(0, 0, 0); 691 } else { 692 cal = getValueAsCalendar(); 693 } 694 if (theField != -1) { 695 cal.set(theField, theValue); 696 } 697 if (theFractionalSeconds != null) { 698 myFractionalSeconds = theFractionalSeconds; 699 } else if (theField == Calendar.MILLISECOND) { 700 myFractionalSeconds = StringUtils.leftPad(Integer.toString(theValue), 3, '0'); 701 } 702 super.setValue(cal.getTime()); 703 } 704 705 private void validateValueInRange(long theValue, long theMinimum, long theMaximum) { 706 if (theValue < theMinimum || theValue > theMaximum) { 707 throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum); 708 } 709 } 710 711 private Integer getFieldValue(int theField) { 712 if (getValue() == null) { 713 return null; 714 } 715 Calendar cal = getValueAsCalendar(); 716 return cal.get(theField); 717 } 718 719}