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