001package org.hl7.fhir.dstu2.model; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 034import org.apache.commons.lang3.StringUtils; 035import org.apache.commons.lang3.Validate; 036import org.apache.commons.lang3.time.DateUtils; 037import org.apache.commons.lang3.time.FastDateFormat; 038import org.hl7.fhir.utilities.DateTimeUtil; 039 040import java.text.ParseException; 041import java.util.*; 042import java.util.regex.Pattern; 043 044import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.*; 045 046public abstract class BaseDateTimeType extends PrimitiveType<Date> { 047 048 private static final long serialVersionUID = 1L; 049 050 /* 051 * Add any new formatters to the static block below!! 052 */ 053 private static final List<FastDateFormat> ourFormatters; 054 055 private static final Pattern ourYearDashMonthDashDayPattern = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}"); 056 private static final Pattern ourYearDashMonthPattern = Pattern.compile("[0-9]{4}-[0-9]{2}"); 057 private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy"); 058 private static final FastDateFormat ourYearMonthDayFormat = FastDateFormat.getInstance("yyyy-MM-dd"); 059 private static final FastDateFormat ourYearMonthDayNoDashesFormat = FastDateFormat.getInstance("yyyyMMdd"); 060 private static final Pattern ourYearMonthDayPattern = Pattern.compile("[0-9]{4}[0-9]{2}[0-9]{2}"); 061 private static final FastDateFormat ourYearMonthDayTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss"); 062 private static final FastDateFormat ourYearMonthDayTimeMilliFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS"); 063 private static final FastDateFormat ourYearMonthDayTimeMilliUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("UTC")); 064 private static final FastDateFormat ourYearMonthDayTimeMilliZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ"); 065 private static final FastDateFormat ourYearMonthDayTimeUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC")); 066 private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ"); 067 private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM"); 068 private static final FastDateFormat ourYearMonthNoDashesFormat = FastDateFormat.getInstance("yyyyMM"); 069 private static final Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}"); 070 private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}"); 071 private static final FastDateFormat ourYearMonthDayTimeMinsFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm"); 072 private static final FastDateFormat ourYearMonthDayTimeMinsUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC")); 073 private static final FastDateFormat ourYearMonthDayTimeMinsZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mmZZ"); 074 075 private static final FastDateFormat ourHumanDateTimeFormat = FastDateFormat.getDateTimeInstance(FastDateFormat.MEDIUM, FastDateFormat.MEDIUM); 076 private static final FastDateFormat ourHumanDateFormat = FastDateFormat.getDateInstance(FastDateFormat.MEDIUM); 077 078 static { 079 ArrayList<FastDateFormat> formatters = new ArrayList<FastDateFormat>(); 080 formatters.add(ourYearFormat); 081 formatters.add(ourYearMonthDayFormat); 082 formatters.add(ourYearMonthDayNoDashesFormat); 083 formatters.add(ourYearMonthDayTimeFormat); 084 formatters.add(ourYearMonthDayTimeUTCZFormat); 085 formatters.add(ourYearMonthDayTimeZoneFormat); 086 formatters.add(ourYearMonthDayTimeMilliFormat); 087 formatters.add(ourYearMonthDayTimeMilliUTCZFormat); 088 formatters.add(ourYearMonthDayTimeMilliZoneFormat); 089 formatters.add(ourYearMonthDayTimeMinsFormat); 090 formatters.add(ourYearMonthDayTimeMinsUTCZFormat); 091 formatters.add(ourYearMonthDayTimeMinsZoneFormat); 092 formatters.add(ourYearMonthFormat); 093 formatters.add(ourYearMonthNoDashesFormat); 094 ourFormatters = Collections.unmodifiableList(formatters); 095 } 096 097 private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND; 098 099 private TimeZone myTimeZone; 100 private boolean myTimeZoneZulu = false; 101 102 /** 103 * Constructor 104 */ 105 public BaseDateTimeType() { 106 // nothing 107 } 108 109 /** 110 * Constructor 111 * 112 * @throws IllegalArgumentException 113 * If the specified precision is not allowed for this type 114 */ 115 public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision) { 116 setValue(theDate, thePrecision); 117 if (isPrecisionAllowed(thePrecision) == false) { 118 throw new IllegalArgumentException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theDate); 119 } 120 } 121 122 /** 123 * Constructor 124 * 125 * @throws IllegalArgumentException 126 * If the specified precision is not allowed for this type 127 */ 128 public BaseDateTimeType(String theString) { 129 setValueAsString(theString); 130 if (isPrecisionAllowed(getPrecision()) == false) { 131 throw new IllegalArgumentException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + getPrecision() + " precision): " + theString); 132 } 133 } 134 135 /** 136 * Constructor 137 */ 138 public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimeZone) { 139 this(theDate, thePrecision); 140 setTimeZone(theTimeZone); 141 } 142 143 private void clearTimeZone() { 144 myTimeZone = null; 145 myTimeZoneZulu = false; 146 } 147 148 @Override 149 protected String encode(Date theValue) { 150 if (theValue == null) { 151 return null; 152 } else { 153 switch (myPrecision) { 154 case DAY: 155 return ourYearMonthDayFormat.format(theValue); 156 case MONTH: 157 return ourYearMonthFormat.format(theValue); 158 case YEAR: 159 return ourYearFormat.format(theValue); 160 case MINUTE: 161 if (myTimeZoneZulu) { 162 GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 163 cal.setTime(theValue); 164 return ourYearMonthDayTimeMinsFormat.format(cal) + "Z"; 165 } else if (myTimeZone != null) { 166 GregorianCalendar cal = new GregorianCalendar(myTimeZone); 167 cal.setTime(theValue); 168 return (ourYearMonthDayTimeMinsZoneFormat.format(cal)); 169 } else { 170 return ourYearMonthDayTimeMinsFormat.format(theValue); 171 } 172 case SECOND: 173 if (myTimeZoneZulu) { 174 GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 175 cal.setTime(theValue); 176 return ourYearMonthDayTimeFormat.format(cal) + "Z"; 177 } else if (myTimeZone != null) { 178 GregorianCalendar cal = new GregorianCalendar(myTimeZone); 179 cal.setTime(theValue); 180 return (ourYearMonthDayTimeZoneFormat.format(cal)); 181 } else { 182 return ourYearMonthDayTimeFormat.format(theValue); 183 } 184 case MILLI: 185 if (myTimeZoneZulu) { 186 GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 187 cal.setTime(theValue); 188 return ourYearMonthDayTimeMilliFormat.format(cal) + "Z"; 189 } else if (myTimeZone != null) { 190 GregorianCalendar cal = new GregorianCalendar(myTimeZone); 191 cal.setTime(theValue); 192 return (ourYearMonthDayTimeMilliZoneFormat.format(cal)); 193 } else { 194 return ourYearMonthDayTimeMilliFormat.format(theValue); 195 } 196 } 197 throw new IllegalStateException("Invalid precision (this is a bug, shouldn't happen https://xkcd.com/2200/): " + myPrecision); 198 } 199 } 200 201 /** 202 * Returns the default precision for the given datatype 203 */ 204 protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype(); 205 206 /** 207 * Gets the precision for this datatype (using the default for the given type if not set) 208 * 209 * @see #setPrecision(TemporalPrecisionEnum) 210 */ 211 public TemporalPrecisionEnum getPrecision() { 212 if (myPrecision == null) { 213 return getDefaultPrecisionForDatatype(); 214 } 215 return myPrecision; 216 } 217 218 /** 219 * Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was 220 * supplied. 221 */ 222 public TimeZone getTimeZone() { 223 return myTimeZone; 224 } 225 226 private boolean hasOffset(String theValue) { 227 boolean inTime = false; 228 for (int i = 0; i < theValue.length(); i++) { 229 switch (theValue.charAt(i)) { 230 case 'T': 231 inTime = true; 232 break; 233 case '+': 234 case '-': 235 if (inTime) { 236 return true; 237 } 238 break; 239 } 240 } 241 return false; 242 } 243 244 /** 245 * To be implemented by subclasses to indicate whether the given precision is allowed by this type 246 */ 247 abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision); 248 249 public boolean isTimeZoneZulu() { 250 return myTimeZoneZulu; 251 } 252 253 /** 254 * Returns <code>true</code> if this object represents a date that is today's date 255 * 256 * @throws NullPointerException 257 * if {@link #getValue()} returns <code>null</code> 258 */ 259 public boolean isToday() { 260 Validate.notNull(getValue(), getClass().getSimpleName() + " contains null value"); 261 return DateUtils.isSameDay(new Date(), getValue()); 262 } 263 264 @Override 265 protected Date parse(String theValue) throws IllegalArgumentException { 266 try { 267 if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) { 268 if (!isPrecisionAllowed(YEAR)) { 269 // ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + 270 // " does not support YEAR precision): " + theValue); 271 } 272 setPrecision(YEAR); 273 clearTimeZone(); 274 return ((ourYearFormat).parse(theValue)); 275 } else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) { 276 // Eg. 198401 (allow this just to be lenient) 277 if (!isPrecisionAllowed(MONTH)) { 278 // ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + 279 // " does not support DAY precision): " + theValue); 280 } 281 setPrecision(MONTH); 282 clearTimeZone(); 283 return ((ourYearMonthNoDashesFormat).parse(theValue)); 284 } else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) { 285 // E.g. 1984-01 (this is valid according to the spec) 286 if (!isPrecisionAllowed(MONTH)) { 287 // ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + 288 // " does not support MONTH precision): " + theValue); 289 } 290 setPrecision(MONTH); 291 clearTimeZone(); 292 return ((ourYearMonthFormat).parse(theValue)); 293 } else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) { 294 // Eg. 19840101 (allow this just to be lenient) 295 if (!isPrecisionAllowed(DAY)) { 296 // ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + 297 // " does not support DAY precision): " + theValue); 298 } 299 setPrecision(DAY); 300 clearTimeZone(); 301 return ((ourYearMonthDayNoDashesFormat).parse(theValue)); 302 } else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) { 303 // E.g. 1984-01-01 (this is valid according to the spec) 304 if (!isPrecisionAllowed(DAY)) { 305 // ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + 306 // " does not support DAY precision): " + theValue); 307 } 308 setPrecision(DAY); 309 clearTimeZone(); 310 return ((ourYearMonthDayFormat).parse(theValue)); 311 } else if (theValue.length() >= 16) { // date and time with possible time zone 312 int firstColonIndex = theValue.indexOf(':'); 313 if (firstColonIndex == -1) { 314 throw new IllegalArgumentException("Invalid date/time string: " + theValue); 315 } 316 317 boolean hasSeconds = theValue.length() > firstColonIndex+3 ? theValue.charAt(firstColonIndex+3) == ':' : false; 318 319 int dotIndex = theValue.length() >= 18 ? theValue.indexOf('.', 18): -1; 320 boolean hasMillis = dotIndex > -1; 321 322// if (!hasMillis && !isPrecisionAllowed(SECOND)) { 323 // ourLog.debug("Invalid date/time string (data type does not support SECONDS precision): " + 324 // theValue); 325// } else if (hasMillis && !isPrecisionAllowed(MILLI)) { 326 // ourLog.debug("Invalid date/time string (data type " + getClass().getSimpleName() + 327 // " does not support MILLIS precision):" + theValue); 328// } 329 330 Date retVal; 331 if (hasMillis) { 332 try { 333 if (hasOffset(theValue)) { 334 retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue); 335 } else if (theValue.endsWith("Z")) { 336 retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue); 337 } else { 338 retVal = ourYearMonthDayTimeMilliFormat.parse(theValue); 339 } 340 } catch (ParseException p2) { 341 throw new IllegalArgumentException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); 342 } 343 setTimeZone(theValue, hasMillis); 344 setPrecision(TemporalPrecisionEnum.MILLI); 345 } else if (hasSeconds) { 346 try { 347 if (hasOffset(theValue)) { 348 retVal = ourYearMonthDayTimeZoneFormat.parse(theValue); 349 } else if (theValue.endsWith("Z")) { 350 retVal = ourYearMonthDayTimeUTCZFormat.parse(theValue); 351 } else { 352 retVal = ourYearMonthDayTimeFormat.parse(theValue); 353 } 354 } catch (ParseException p2) { 355 throw new IllegalArgumentException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); 356 } 357 358 setTimeZone(theValue, hasMillis); 359 setPrecision(TemporalPrecisionEnum.SECOND); 360 } else { 361 try { 362 if (hasOffset(theValue)) { 363 retVal = ourYearMonthDayTimeMinsZoneFormat.parse(theValue); 364 } else if (theValue.endsWith("Z")) { 365 retVal = ourYearMonthDayTimeMinsUTCZFormat.parse(theValue); 366 } else { 367 retVal = ourYearMonthDayTimeMinsFormat.parse(theValue); 368 } 369 } catch (ParseException p2) { 370 throw new IllegalArgumentException("Invalid data/time string (" + p2.getMessage() + "): " + theValue, p2); 371 } 372 373 setTimeZone(theValue, hasMillis); 374 setPrecision(TemporalPrecisionEnum.MINUTE); 375 } 376 377 return retVal; 378 } else { 379 throw new IllegalArgumentException("Invalid date/time string (invalid length): " + theValue); 380 } 381 } catch (ParseException e) { 382 throw new IllegalArgumentException("Invalid date string (" + e.getMessage() + "): " + theValue); 383 } 384 } 385 386 /** 387 * Sets the precision for this datatype using field values from {@link Calendar}. Valid values are: 388 * <ul> 389 * <li>{@link Calendar#SECOND} 390 * <li>{@link Calendar#DAY_OF_MONTH} 391 * <li>{@link Calendar#MONTH} 392 * <li>{@link Calendar#YEAR} 393 * </ul> 394 * 395 * @throws IllegalArgumentException 396 */ 397 public void setPrecision(TemporalPrecisionEnum thePrecision) throws IllegalArgumentException { 398 if (thePrecision == null) { 399 throw new NullPointerException("Precision may not be null"); 400 } 401 myPrecision = thePrecision; 402 updateStringValue(); 403 } 404 405 private void setTimeZone(String theValueString, boolean hasMillis) { 406 clearTimeZone(); 407 int timeZoneStart = 19; 408 if (hasMillis) 409 timeZoneStart += 4; 410 if (theValueString.endsWith("Z")) { 411 setTimeZoneZulu(true); 412 } else if (theValueString.indexOf("GMT", timeZoneStart) != -1) { 413 setTimeZone(TimeZone.getTimeZone(theValueString.substring(timeZoneStart))); 414 } else if (theValueString.indexOf('+', timeZoneStart) != -1 || theValueString.indexOf('-', timeZoneStart) != -1) { 415 setTimeZone(TimeZone.getTimeZone("GMT" + theValueString.substring(timeZoneStart))); 416 } 417 } 418 419 public void setTimeZone(TimeZone theTimeZone) { 420 myTimeZone = theTimeZone; 421 updateStringValue(); 422 } 423 424 public void setTimeZoneZulu(boolean theTimeZoneZulu) { 425 myTimeZoneZulu = theTimeZoneZulu; 426 updateStringValue(); 427 } 428 429 /** 430 * Sets the value of this date/time using the default level of precision 431 * for this datatype 432 * using the system local time zone 433 * 434 * @param theValue 435 * The date value 436 */ 437 @Override 438 public BaseDateTimeType setValue(Date theValue) { 439 if (myTimeZoneZulu == false && myTimeZone == null) { 440 myTimeZone = TimeZone.getDefault(); 441 } 442 myPrecision = getDefaultPrecisionForDatatype(); 443 BaseDateTimeType retVal = (BaseDateTimeType) super.setValue(theValue); 444 return retVal; 445 } 446 447 /** 448 * Sets the value of this date/time using the specified level of precision 449 * using the system local time zone 450 * 451 * @param theValue 452 * The date value 453 * @param thePrecision 454 * The precision 455 * @throws IllegalArgumentException 456 */ 457 public void setValue(Date theValue, TemporalPrecisionEnum thePrecision) throws IllegalArgumentException { 458 if (myTimeZoneZulu == false && myTimeZone == null) { 459 myTimeZone = TimeZone.getDefault(); 460 } 461 myPrecision = thePrecision; 462 super.setValue(theValue); 463 } 464 465 @Override 466 public void setValueAsString(String theValue) throws IllegalArgumentException { 467 clearTimeZone(); 468 super.setValueAsString(theValue); 469 } 470 471 /** 472 * For unit tests only 473 */ 474 static List<FastDateFormat> getFormatters() { 475 return ourFormatters; 476 } 477 478 public boolean before(DateTimeType theDateTimeType) { 479 return getValue().before(theDateTimeType.getValue()); 480 } 481 482 public boolean after(DateTimeType theDateTimeType) { 483 return getValue().after(theDateTimeType.getValue()); 484 } 485 486 /** 487 * Returns a human readable version of this date/time using the system local format. 488 * <p> 489 * <b>Note on time zones:</b> This method renders the value using the time zone that is contained within the value. 490 * For example, if this date object contains the value "2012-01-05T12:00:00-08:00", 491 * the human display will be rendered as "12:00:00" even if the application is being executed on a system in a 492 * different time zone. If this behaviour is not what you want, use 493 * {@link #toHumanDisplayLocalTimezone()} instead. 494 * </p> 495 */ 496 public String toHumanDisplay() { 497 return DateTimeUtil.toHumanDisplay(getTimeZone(), getPrecision(), getValue(), getValueAsString()); 498 } 499 500 /** 501 * Returns a human readable version of this date/time using the system local format, converted to the local timezone 502 * if neccesary. 503 * 504 * @see #toHumanDisplay() for a method which does not convert the time to the local timezone before rendering it. 505 */ 506 public String toHumanDisplayLocalTimezone() { 507 return DateTimeUtil.toHumanDisplayLocalTimezone(getPrecision(), getValue(), getValueAsString()); 508 } 509 510 511 /** 512 * Returns a view of this date/time as a Calendar object 513 */ 514 public Calendar toCalendar() { 515 Calendar retVal = Calendar.getInstance(); 516 retVal.setTime(getValue()); 517 retVal.setTimeZone(getTimeZone()); 518 return retVal; 519 } 520 521 /** 522 * Sets the TimeZone offset in minutes relative to GMT 523 */ 524 public void setOffsetMinutes(int theZoneOffsetMinutes) { 525 int offsetAbs = Math.abs(theZoneOffsetMinutes); 526 527 int mins = offsetAbs % 60; 528 int hours = offsetAbs / 60; 529 530 if (theZoneOffsetMinutes < 0) { 531 setTimeZone(TimeZone.getTimeZone("GMT-" + hours + ":" + mins)); 532 } else { 533 setTimeZone(TimeZone.getTimeZone("GMT+" + hours + ":" + mins)); 534 } 535 } 536 537 /** 538 * Returns the time in millis as represented by this Date/Time 539 */ 540 public long getTime() { 541 return getValue().getTime(); 542 } 543 544 /** 545 * Adds the given amount to the field specified by theField 546 * 547 * @param theField 548 * The field, uses constants from {@link Calendar} such as {@link Calendar#YEAR} 549 * @param theValue 550 * The number to add (or subtract for a negative number) 551 */ 552 public void add(int theField, int theValue) { 553 switch (theField) { 554 case Calendar.YEAR: 555 setValue(DateUtils.addYears(getValue(), theValue), getPrecision()); 556 break; 557 case Calendar.MONTH: 558 setValue(DateUtils.addMonths(getValue(), theValue), getPrecision()); 559 break; 560 case Calendar.DATE: 561 setValue(DateUtils.addDays(getValue(), theValue), getPrecision()); 562 break; 563 case Calendar.HOUR: 564 setValue(DateUtils.addHours(getValue(), theValue), getPrecision()); 565 break; 566 case Calendar.MINUTE: 567 setValue(DateUtils.addMinutes(getValue(), theValue), getPrecision()); 568 break; 569 case Calendar.SECOND: 570 setValue(DateUtils.addSeconds(getValue(), theValue), getPrecision()); 571 break; 572 case Calendar.MILLISECOND: 573 setValue(DateUtils.addMilliseconds(getValue(), theValue), getPrecision()); 574 break; 575 default: 576 throw new IllegalArgumentException("Unknown field constant: " + theField); 577 } 578 } 579 580 protected void setValueAsV3String(String theV3String) { 581 if (StringUtils.isBlank(theV3String)) { 582 setValue(null); 583 } else { 584 StringBuilder b = new StringBuilder(); 585 String timeZone = null; 586 for (int i = 0; i < theV3String.length(); i++) { 587 char nextChar = theV3String.charAt(i); 588 if (nextChar == '+' || nextChar == '-' || nextChar == 'Z') { 589 timeZone = (theV3String.substring(i)); 590 break; 591 } 592 593 // assertEquals("2013-02-02T20:13:03-05:00", DateAndTime.parseV3("20130202201303-0500").toString()); 594 if (i == 4 || i == 6) { 595 b.append('-'); 596 } else if (i == 8) { 597 b.append('T'); 598 } else if (i == 10 || i == 12) { 599 b.append(':'); 600 } 601 602 b.append(nextChar); 603 } 604 605 if (b.length() == 16) 606 b.append(":00"); // schema rule, must have seconds 607 if (timeZone != null && b.length() > 10) { 608 if (timeZone.length() ==5) { 609 b.append(timeZone.substring(0, 3)); 610 b.append(':'); 611 b.append(timeZone.substring(3)); 612 }else { 613 b.append(timeZone); 614 } 615 } 616 617 setValueAsString(b.toString()); 618 } 619 } 620 621}