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