1 /* 2 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * This file is available under and governed by the GNU General Public 28 * License version 2 only, as published by the Free Software Foundation. 29 * However, the following notice accompanied the original version of this 30 * file: 31 * 32 * Copyright (c) 2008-2013, Stephen Colebourne & Michael Nascimento Santos 33 * 34 * All rights reserved. 35 * 36 * Redistribution and use in source and binary forms, with or without 37 * modification, are permitted provided that the following conditions are met: 38 * 39 * * Redistributions of source code must retain the above copyright notice, 40 * this list of conditions and the following disclaimer. 41 * 42 * * Redistributions in binary form must reproduce the above copyright notice, 43 * this list of conditions and the following disclaimer in the documentation 44 * and/or other materials provided with the distribution. 45 * 46 * * Neither the name of JSR-310 nor the names of its contributors 47 * may be used to endorse or promote products derived from this software 48 * without specific prior written permission. 49 * 50 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 51 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 52 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 53 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 54 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 55 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 58 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 59 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 */ 62 package java.time.format; 63 64 import static java.time.temporal.ChronoField.AMPM_OF_DAY; 65 import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; 66 import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; 67 import static java.time.temporal.ChronoField.HOUR_OF_AMPM; 68 import static java.time.temporal.ChronoField.HOUR_OF_DAY; 69 import static java.time.temporal.ChronoField.INSTANT_SECONDS; 70 import static java.time.temporal.ChronoField.MICRO_OF_DAY; 71 import static java.time.temporal.ChronoField.MICRO_OF_SECOND; 72 import static java.time.temporal.ChronoField.MILLI_OF_DAY; 73 import static java.time.temporal.ChronoField.MILLI_OF_SECOND; 74 import static java.time.temporal.ChronoField.MINUTE_OF_DAY; 75 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; 76 import static java.time.temporal.ChronoField.NANO_OF_DAY; 77 import static java.time.temporal.ChronoField.NANO_OF_SECOND; 78 import static java.time.temporal.ChronoField.OFFSET_SECONDS; 79 import static java.time.temporal.ChronoField.SECOND_OF_DAY; 80 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; 81 82 import java.time.DateTimeException; 83 import java.time.Instant; 84 import java.time.LocalDate; 85 import java.time.LocalTime; 86 import java.time.Period; 87 import java.time.ZoneId; 88 import java.time.ZoneOffset; 89 import java.time.chrono.ChronoLocalDate; 90 import java.time.chrono.ChronoLocalDateTime; 91 import java.time.chrono.ChronoZonedDateTime; 92 import java.time.chrono.Chronology; 93 import java.time.temporal.ChronoField; 94 import java.time.temporal.TemporalAccessor; 95 import java.time.temporal.TemporalField; 96 import java.time.temporal.TemporalQueries; 97 import java.time.temporal.TemporalQuery; 98 import java.time.temporal.UnsupportedTemporalTypeException; 99 import java.util.HashMap; 100 import java.util.Iterator; 101 import java.util.Map; 102 import java.util.Map.Entry; 103 import java.util.Objects; 104 import java.util.Set; 105 106 /** 107 * A store of parsed data. 108 * <p> 109 * This class is used during parsing to collect the data. Part of the parsing process 110 * involves handling optional blocks and multiple copies of the data get created to 111 * support the necessary backtracking. 112 * <p> 113 * Once parsing is completed, this class can be used as the resultant {@code TemporalAccessor}. 114 * In most cases, it is only exposed once the fields have been resolved. 115 * 116 * @implSpec 117 * This class is a mutable context intended for use from a single thread. 118 * Usage of the class is thread-safe within standard parsing as a new instance of this class 119 * is automatically created for each parse and parsing is single-threaded 120 * 121 * @since 1.8 122 */ 123 final class Parsed implements TemporalAccessor { 124 // some fields are accessed using package scope from DateTimeParseContext 125 126 /** 127 * The parsed fields. 128 */ 129 final Map<TemporalField, Long> fieldValues = new HashMap<>(); 130 /** 131 * The parsed zone. 132 */ 133 ZoneId zone; 134 /** 135 * The parsed chronology. 136 */ 137 Chronology chrono; 138 /** 139 * Whether a leap-second is parsed. 140 */ 141 boolean leapSecond; 142 /** 143 * The resolver style to use. 144 */ 145 private ResolverStyle resolverStyle; 146 /** 147 * The resolved date. 148 */ 149 private ChronoLocalDate date; 150 /** 151 * The resolved time. 152 */ 153 private LocalTime time; 154 /** 155 * The excess period from time-only parsing. 156 */ 157 Period excessDays = Period.ZERO; 158 159 /** 160 * Creates an instance. 161 */ Parsed()162 Parsed() { 163 } 164 165 /** 166 * Creates a copy. 167 */ copy()168 Parsed copy() { 169 // only copy fields used in parsing stage 170 Parsed cloned = new Parsed(); 171 cloned.fieldValues.putAll(this.fieldValues); 172 cloned.zone = this.zone; 173 cloned.chrono = this.chrono; 174 cloned.leapSecond = this.leapSecond; 175 return cloned; 176 } 177 178 //----------------------------------------------------------------------- 179 @Override isSupported(TemporalField field)180 public boolean isSupported(TemporalField field) { 181 if (fieldValues.containsKey(field) || 182 (date != null && date.isSupported(field)) || 183 (time != null && time.isSupported(field))) { 184 return true; 185 } 186 return field != null && (field instanceof ChronoField == false) && field.isSupportedBy(this); 187 } 188 189 @Override getLong(TemporalField field)190 public long getLong(TemporalField field) { 191 Objects.requireNonNull(field, "field"); 192 Long value = fieldValues.get(field); 193 if (value != null) { 194 return value; 195 } 196 if (date != null && date.isSupported(field)) { 197 return date.getLong(field); 198 } 199 if (time != null && time.isSupported(field)) { 200 return time.getLong(field); 201 } 202 if (field instanceof ChronoField) { 203 throw new UnsupportedTemporalTypeException("Unsupported field: " + field); 204 } 205 return field.getFrom(this); 206 } 207 208 @SuppressWarnings("unchecked") 209 @Override query(TemporalQuery<R> query)210 public <R> R query(TemporalQuery<R> query) { 211 if (query == TemporalQueries.zoneId()) { 212 return (R) zone; 213 } else if (query == TemporalQueries.chronology()) { 214 return (R) chrono; 215 } else if (query == TemporalQueries.localDate()) { 216 return (R) (date != null ? LocalDate.from(date) : null); 217 } else if (query == TemporalQueries.localTime()) { 218 return (R) time; 219 } else if (query == TemporalQueries.offset()) { 220 Long offsetSecs = fieldValues.get(OFFSET_SECONDS); 221 if (offsetSecs != null) { 222 return (R) ZoneOffset.ofTotalSeconds(offsetSecs.intValue()); 223 } 224 if (zone instanceof ZoneOffset) { 225 return (R)zone; 226 } 227 return query.queryFrom(this); 228 } else if (query == TemporalQueries.zone()) { 229 return query.queryFrom(this); 230 } else if (query == TemporalQueries.precision()) { 231 return null; // not a complete date/time 232 } 233 // inline TemporalAccessor.super.query(query) as an optimization 234 // non-JDK classes are not permitted to make this optimization 235 return query.queryFrom(this); 236 } 237 238 //----------------------------------------------------------------------- 239 /** 240 * Resolves the fields in this context. 241 * 242 * @param resolverStyle the resolver style, not null 243 * @param resolverFields the fields to use for resolving, null for all fields 244 * @return this, for method chaining 245 * @throws DateTimeException if resolving one field results in a value for 246 * another field that is in conflict 247 */ resolve(ResolverStyle resolverStyle, Set<TemporalField> resolverFields)248 TemporalAccessor resolve(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) { 249 if (resolverFields != null) { 250 fieldValues.keySet().retainAll(resolverFields); 251 } 252 this.resolverStyle = resolverStyle; 253 resolveFields(); 254 resolveTimeLenient(); 255 crossCheck(); 256 resolvePeriod(); 257 resolveFractional(); 258 resolveInstant(); 259 return this; 260 } 261 262 //----------------------------------------------------------------------- resolveFields()263 private void resolveFields() { 264 // resolve ChronoField 265 resolveInstantFields(); 266 resolveDateFields(); 267 resolveTimeFields(); 268 269 // if any other fields, handle them 270 // any lenient date resolution should return epoch-day 271 if (fieldValues.size() > 0) { 272 int changedCount = 0; 273 outer: 274 while (changedCount < 50) { 275 for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) { 276 TemporalField targetField = entry.getKey(); 277 TemporalAccessor resolvedObject = targetField.resolve(fieldValues, this, resolverStyle); 278 if (resolvedObject != null) { 279 if (resolvedObject instanceof ChronoZonedDateTime) { 280 ChronoZonedDateTime<?> czdt = (ChronoZonedDateTime<?>) resolvedObject; 281 if (zone == null) { 282 zone = czdt.getZone(); 283 } else if (zone.equals(czdt.getZone()) == false) { 284 throw new DateTimeException("ChronoZonedDateTime must use the effective parsed zone: " + zone); 285 } 286 resolvedObject = czdt.toLocalDateTime(); 287 } 288 if (resolvedObject instanceof ChronoLocalDateTime) { 289 ChronoLocalDateTime<?> cldt = (ChronoLocalDateTime<?>) resolvedObject; 290 updateCheckConflict(cldt.toLocalTime(), Period.ZERO); 291 updateCheckConflict(cldt.toLocalDate()); 292 changedCount++; 293 continue outer; // have to restart to avoid concurrent modification 294 } 295 if (resolvedObject instanceof ChronoLocalDate) { 296 updateCheckConflict((ChronoLocalDate) resolvedObject); 297 changedCount++; 298 continue outer; // have to restart to avoid concurrent modification 299 } 300 if (resolvedObject instanceof LocalTime) { 301 updateCheckConflict((LocalTime) resolvedObject, Period.ZERO); 302 changedCount++; 303 continue outer; // have to restart to avoid concurrent modification 304 } 305 throw new DateTimeException("Method resolve() can only return ChronoZonedDateTime, " + 306 "ChronoLocalDateTime, ChronoLocalDate or LocalTime"); 307 } else if (fieldValues.containsKey(targetField) == false) { 308 changedCount++; 309 continue outer; // have to restart to avoid concurrent modification 310 } 311 } 312 break; 313 } 314 if (changedCount == 50) { // catch infinite loops 315 throw new DateTimeException("One of the parsed fields has an incorrectly implemented resolve method"); 316 } 317 // if something changed then have to redo ChronoField resolve 318 if (changedCount > 0) { 319 resolveInstantFields(); 320 resolveDateFields(); 321 resolveTimeFields(); 322 } 323 } 324 } 325 updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue)326 private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) { 327 Long old = fieldValues.put(changeField, changeValue); 328 if (old != null && old.longValue() != changeValue.longValue()) { 329 throw new DateTimeException("Conflict found: " + changeField + " " + old + 330 " differs from " + changeField + " " + changeValue + 331 " while resolving " + targetField); 332 } 333 } 334 335 //----------------------------------------------------------------------- resolveInstantFields()336 private void resolveInstantFields() { 337 // resolve parsed instant seconds to date and time if zone available 338 if (fieldValues.containsKey(INSTANT_SECONDS)) { 339 if (zone != null) { 340 resolveInstantFields0(zone); 341 } else { 342 Long offsetSecs = fieldValues.get(OFFSET_SECONDS); 343 if (offsetSecs != null) { 344 ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue()); 345 resolveInstantFields0(offset); 346 } 347 } 348 } 349 } 350 resolveInstantFields0(ZoneId selectedZone)351 private void resolveInstantFields0(ZoneId selectedZone) { 352 Instant instant = Instant.ofEpochSecond(fieldValues.remove(INSTANT_SECONDS)); 353 ChronoZonedDateTime<?> zdt = chrono.zonedDateTime(instant, selectedZone); 354 updateCheckConflict(zdt.toLocalDate()); 355 updateCheckConflict(INSTANT_SECONDS, SECOND_OF_DAY, (long) zdt.toLocalTime().toSecondOfDay()); 356 } 357 358 //----------------------------------------------------------------------- resolveDateFields()359 private void resolveDateFields() { 360 updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle)); 361 } 362 updateCheckConflict(ChronoLocalDate cld)363 private void updateCheckConflict(ChronoLocalDate cld) { 364 if (date != null) { 365 if (cld != null && date.equals(cld) == false) { 366 throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld); 367 } 368 } else if (cld != null) { 369 if (chrono.equals(cld.getChronology()) == false) { 370 throw new DateTimeException("ChronoLocalDate must use the effective parsed chronology: " + chrono); 371 } 372 date = cld; 373 } 374 } 375 376 //----------------------------------------------------------------------- resolveTimeFields()377 private void resolveTimeFields() { 378 // simplify fields 379 if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) { 380 // lenient allows anything, smart allows 0-24, strict allows 1-24 381 long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY); 382 if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) { 383 CLOCK_HOUR_OF_DAY.checkValidValue(ch); 384 } 385 updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch); 386 } 387 if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) { 388 // lenient allows anything, smart allows 0-12, strict allows 1-12 389 long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM); 390 if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) { 391 CLOCK_HOUR_OF_AMPM.checkValidValue(ch); 392 } 393 updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch); 394 } 395 if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) { 396 long ap = fieldValues.remove(AMPM_OF_DAY); 397 long hap = fieldValues.remove(HOUR_OF_AMPM); 398 if (resolverStyle == ResolverStyle.LENIENT) { 399 updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, Math.addExact(Math.multiplyExact(ap, 12), hap)); 400 } else { // STRICT or SMART 401 AMPM_OF_DAY.checkValidValue(ap); 402 HOUR_OF_AMPM.checkValidValue(ap); 403 updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap); 404 } 405 } 406 if (fieldValues.containsKey(NANO_OF_DAY)) { 407 long nod = fieldValues.remove(NANO_OF_DAY); 408 if (resolverStyle != ResolverStyle.LENIENT) { 409 NANO_OF_DAY.checkValidValue(nod); 410 } 411 updateCheckConflict(NANO_OF_DAY, HOUR_OF_DAY, nod / 3600_000_000_000L); 412 updateCheckConflict(NANO_OF_DAY, MINUTE_OF_HOUR, (nod / 60_000_000_000L) % 60); 413 updateCheckConflict(NANO_OF_DAY, SECOND_OF_MINUTE, (nod / 1_000_000_000L) % 60); 414 updateCheckConflict(NANO_OF_DAY, NANO_OF_SECOND, nod % 1_000_000_000L); 415 } 416 if (fieldValues.containsKey(MICRO_OF_DAY)) { 417 long cod = fieldValues.remove(MICRO_OF_DAY); 418 if (resolverStyle != ResolverStyle.LENIENT) { 419 MICRO_OF_DAY.checkValidValue(cod); 420 } 421 updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1_000_000L); 422 updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1_000_000L); 423 } 424 if (fieldValues.containsKey(MILLI_OF_DAY)) { 425 long lod = fieldValues.remove(MILLI_OF_DAY); 426 if (resolverStyle != ResolverStyle.LENIENT) { 427 MILLI_OF_DAY.checkValidValue(lod); 428 } 429 updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1_000); 430 updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1_000); 431 } 432 if (fieldValues.containsKey(SECOND_OF_DAY)) { 433 long sod = fieldValues.remove(SECOND_OF_DAY); 434 if (resolverStyle != ResolverStyle.LENIENT) { 435 SECOND_OF_DAY.checkValidValue(sod); 436 } 437 updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600); 438 updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60); 439 updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60); 440 } 441 if (fieldValues.containsKey(MINUTE_OF_DAY)) { 442 long mod = fieldValues.remove(MINUTE_OF_DAY); 443 if (resolverStyle != ResolverStyle.LENIENT) { 444 MINUTE_OF_DAY.checkValidValue(mod); 445 } 446 updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60); 447 updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60); 448 } 449 450 // combine partial second fields strictly, leaving lenient expansion to later 451 if (fieldValues.containsKey(NANO_OF_SECOND)) { 452 long nos = fieldValues.get(NANO_OF_SECOND); 453 if (resolverStyle != ResolverStyle.LENIENT) { 454 NANO_OF_SECOND.checkValidValue(nos); 455 } 456 if (fieldValues.containsKey(MICRO_OF_SECOND)) { 457 long cos = fieldValues.remove(MICRO_OF_SECOND); 458 if (resolverStyle != ResolverStyle.LENIENT) { 459 MICRO_OF_SECOND.checkValidValue(cos); 460 } 461 nos = cos * 1000 + (nos % 1000); 462 updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, nos); 463 } 464 if (fieldValues.containsKey(MILLI_OF_SECOND)) { 465 long los = fieldValues.remove(MILLI_OF_SECOND); 466 if (resolverStyle != ResolverStyle.LENIENT) { 467 MILLI_OF_SECOND.checkValidValue(los); 468 } 469 updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, los * 1_000_000L + (nos % 1_000_000L)); 470 } 471 } 472 473 // convert to time if all four fields available (optimization) 474 if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) && 475 fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) { 476 long hod = fieldValues.remove(HOUR_OF_DAY); 477 long moh = fieldValues.remove(MINUTE_OF_HOUR); 478 long som = fieldValues.remove(SECOND_OF_MINUTE); 479 long nos = fieldValues.remove(NANO_OF_SECOND); 480 resolveTime(hod, moh, som, nos); 481 } 482 } 483 resolveTimeLenient()484 private void resolveTimeLenient() { 485 // leniently create a time from incomplete information 486 // done after everything else as it creates information from nothing 487 // which would break updateCheckConflict(field) 488 489 if (time == null) { 490 // NANO_OF_SECOND merged with MILLI/MICRO above 491 if (fieldValues.containsKey(MILLI_OF_SECOND)) { 492 long los = fieldValues.remove(MILLI_OF_SECOND); 493 if (fieldValues.containsKey(MICRO_OF_SECOND)) { 494 // merge milli-of-second and micro-of-second for better error message 495 long cos = los * 1_000 + (fieldValues.get(MICRO_OF_SECOND) % 1_000); 496 updateCheckConflict(MILLI_OF_SECOND, MICRO_OF_SECOND, cos); 497 fieldValues.remove(MICRO_OF_SECOND); 498 fieldValues.put(NANO_OF_SECOND, cos * 1_000L); 499 } else { 500 // convert milli-of-second to nano-of-second 501 fieldValues.put(NANO_OF_SECOND, los * 1_000_000L); 502 } 503 } else if (fieldValues.containsKey(MICRO_OF_SECOND)) { 504 // convert micro-of-second to nano-of-second 505 long cos = fieldValues.remove(MICRO_OF_SECOND); 506 fieldValues.put(NANO_OF_SECOND, cos * 1_000L); 507 } 508 509 // merge hour/minute/second/nano leniently 510 Long hod = fieldValues.get(HOUR_OF_DAY); 511 if (hod != null) { 512 Long moh = fieldValues.get(MINUTE_OF_HOUR); 513 Long som = fieldValues.get(SECOND_OF_MINUTE); 514 Long nos = fieldValues.get(NANO_OF_SECOND); 515 516 // check for invalid combinations that cannot be defaulted 517 if ((moh == null && (som != null || nos != null)) || 518 (moh != null && som == null && nos != null)) { 519 return; 520 } 521 522 // default as necessary and build time 523 long mohVal = (moh != null ? moh : 0); 524 long somVal = (som != null ? som : 0); 525 long nosVal = (nos != null ? nos : 0); 526 resolveTime(hod, mohVal, somVal, nosVal); 527 fieldValues.remove(HOUR_OF_DAY); 528 fieldValues.remove(MINUTE_OF_HOUR); 529 fieldValues.remove(SECOND_OF_MINUTE); 530 fieldValues.remove(NANO_OF_SECOND); 531 } 532 } 533 534 // validate remaining 535 if (resolverStyle != ResolverStyle.LENIENT && fieldValues.size() > 0) { 536 for (Entry<TemporalField, Long> entry : fieldValues.entrySet()) { 537 TemporalField field = entry.getKey(); 538 if (field instanceof ChronoField && field.isTimeBased()) { 539 ((ChronoField) field).checkValidValue(entry.getValue()); 540 } 541 } 542 } 543 } 544 resolveTime(long hod, long moh, long som, long nos)545 private void resolveTime(long hod, long moh, long som, long nos) { 546 if (resolverStyle == ResolverStyle.LENIENT) { 547 long totalNanos = Math.multiplyExact(hod, 3600_000_000_000L); 548 totalNanos = Math.addExact(totalNanos, Math.multiplyExact(moh, 60_000_000_000L)); 549 totalNanos = Math.addExact(totalNanos, Math.multiplyExact(som, 1_000_000_000L)); 550 totalNanos = Math.addExact(totalNanos, nos); 551 int excessDays = (int) Math.floorDiv(totalNanos, 86400_000_000_000L); // safe int cast 552 long nod = Math.floorMod(totalNanos, 86400_000_000_000L); 553 updateCheckConflict(LocalTime.ofNanoOfDay(nod), Period.ofDays(excessDays)); 554 } else { // STRICT or SMART 555 int mohVal = MINUTE_OF_HOUR.checkValidIntValue(moh); 556 int nosVal = NANO_OF_SECOND.checkValidIntValue(nos); 557 // handle 24:00 end of day 558 if (resolverStyle == ResolverStyle.SMART && hod == 24 && mohVal == 0 && som == 0 && nosVal == 0) { 559 updateCheckConflict(LocalTime.MIDNIGHT, Period.ofDays(1)); 560 } else { 561 int hodVal = HOUR_OF_DAY.checkValidIntValue(hod); 562 int somVal = SECOND_OF_MINUTE.checkValidIntValue(som); 563 updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal), Period.ZERO); 564 } 565 } 566 } 567 resolvePeriod()568 private void resolvePeriod() { 569 // add whole days if we have both date and time 570 if (date != null && time != null && excessDays.isZero() == false) { 571 date = date.plus(excessDays); 572 excessDays = Period.ZERO; 573 } 574 } 575 resolveFractional()576 private void resolveFractional() { 577 // ensure fractional seconds available as ChronoField requires 578 // resolveTimeLenient() will have merged MICRO_OF_SECOND/MILLI_OF_SECOND to NANO_OF_SECOND 579 if (time == null && 580 (fieldValues.containsKey(INSTANT_SECONDS) || 581 fieldValues.containsKey(SECOND_OF_DAY) || 582 fieldValues.containsKey(SECOND_OF_MINUTE))) { 583 if (fieldValues.containsKey(NANO_OF_SECOND)) { 584 long nos = fieldValues.get(NANO_OF_SECOND); 585 fieldValues.put(MICRO_OF_SECOND, nos / 1000); 586 fieldValues.put(MILLI_OF_SECOND, nos / 1000000); 587 } else { 588 fieldValues.put(NANO_OF_SECOND, 0L); 589 fieldValues.put(MICRO_OF_SECOND, 0L); 590 fieldValues.put(MILLI_OF_SECOND, 0L); 591 } 592 } 593 } 594 resolveInstant()595 private void resolveInstant() { 596 // add instant seconds if we have date, time and zone 597 // Offset (if present) will be given priority over the zone. 598 if (date != null && time != null) { 599 Long offsetSecs = fieldValues.get(OFFSET_SECONDS); 600 if (offsetSecs != null) { 601 ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue()); 602 long instant = date.atTime(time).atZone(offset).toEpochSecond(); 603 fieldValues.put(INSTANT_SECONDS, instant); 604 } else { 605 if (zone != null) { 606 long instant = date.atTime(time).atZone(zone).toEpochSecond(); 607 fieldValues.put(INSTANT_SECONDS, instant); 608 } 609 } 610 } 611 } 612 updateCheckConflict(LocalTime timeToSet, Period periodToSet)613 private void updateCheckConflict(LocalTime timeToSet, Period periodToSet) { 614 if (time != null) { 615 if (time.equals(timeToSet) == false) { 616 throw new DateTimeException("Conflict found: Fields resolved to different times: " + time + " " + timeToSet); 617 } 618 if (excessDays.isZero() == false && periodToSet.isZero() == false && excessDays.equals(periodToSet) == false) { 619 throw new DateTimeException("Conflict found: Fields resolved to different excess periods: " + excessDays + " " + periodToSet); 620 } else { 621 excessDays = periodToSet; 622 } 623 } else { 624 time = timeToSet; 625 excessDays = periodToSet; 626 } 627 } 628 629 //----------------------------------------------------------------------- crossCheck()630 private void crossCheck() { 631 // only cross-check date, time and date-time 632 // avoid object creation if possible 633 if (date != null) { 634 crossCheck(date); 635 } 636 if (time != null) { 637 crossCheck(time); 638 if (date != null && fieldValues.size() > 0) { 639 crossCheck(date.atTime(time)); 640 } 641 } 642 } 643 crossCheck(TemporalAccessor target)644 private void crossCheck(TemporalAccessor target) { 645 for (Iterator<Entry<TemporalField, Long>> it = fieldValues.entrySet().iterator(); it.hasNext(); ) { 646 Entry<TemporalField, Long> entry = it.next(); 647 TemporalField field = entry.getKey(); 648 if (target.isSupported(field)) { 649 long val1; 650 try { 651 val1 = target.getLong(field); 652 } catch (RuntimeException ex) { 653 continue; 654 } 655 long val2 = entry.getValue(); 656 if (val1 != val2) { 657 throw new DateTimeException("Conflict found: Field " + field + " " + val1 + 658 " differs from " + field + " " + val2 + " derived from " + target); 659 } 660 it.remove(); 661 } 662 } 663 } 664 665 //----------------------------------------------------------------------- 666 @Override toString()667 public String toString() { 668 StringBuilder buf = new StringBuilder(64); 669 buf.append(fieldValues).append(',').append(chrono); 670 if (zone != null) { 671 buf.append(',').append(zone); 672 } 673 if (date != null || time != null) { 674 buf.append(" resolved to "); 675 if (date != null) { 676 buf.append(date); 677 if (time != null) { 678 buf.append('T').append(time); 679 } 680 } else { 681 buf.append(time); 682 } 683 } 684 return buf.toString(); 685 } 686 687 } 688