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