1 /*
   2  * Copyright (c) 2012, 2013, 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      */
 162     Parsed() {
 163     }
 164 
 165     /**
 166      * Creates a copy.
 167      */
 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
 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
 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
 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      */
 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     //-----------------------------------------------------------------------
 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 
 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     //-----------------------------------------------------------------------
 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 
 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     //-----------------------------------------------------------------------
 359     private void resolveDateFields() {
 360         updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle));
 361     }
 362 
 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     //-----------------------------------------------------------------------
 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 
 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 
 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 
 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 
 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 
 595     private void resolveInstant() {
 596         // add instant seconds if we have date, time and zone
 597         if (date != null && time != null) {
 598             if (zone != null) {
 599                 long instant = date.atTime(time).atZone(zone).getLong(ChronoField.INSTANT_SECONDS);
 600                 fieldValues.put(INSTANT_SECONDS, instant);
 601             } else {
 602                 Long offsetSecs = fieldValues.get(OFFSET_SECONDS);
 603                 if (offsetSecs != null) {
 604                     ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue());
 605                     long instant = date.atTime(time).atZone(offset).getLong(ChronoField.INSTANT_SECONDS);
 606                     fieldValues.put(INSTANT_SECONDS, instant);
 607                 }
 608             }
 609         }
 610     }
 611 
 612     private void updateCheckConflict(LocalTime timeToSet, Period periodToSet) {
 613         if (time != null) {
 614             if (time.equals(timeToSet) == false) {
 615                 throw new DateTimeException("Conflict found: Fields resolved to different times: " + time + " " + timeToSet);
 616             }
 617             if (excessDays.isZero() == false && periodToSet.isZero() == false && excessDays.equals(periodToSet) == false) {
 618                 throw new DateTimeException("Conflict found: Fields resolved to different excess periods: " + excessDays + " " + periodToSet);
 619             } else {
 620                 excessDays = periodToSet;
 621             }
 622         } else {
 623             time = timeToSet;
 624             excessDays = periodToSet;
 625         }
 626     }
 627 
 628     //-----------------------------------------------------------------------
 629     private void crossCheck() {
 630         // only cross-check date, time and date-time
 631         // avoid object creation if possible
 632         if (date != null) {
 633             crossCheck(date);
 634         }
 635         if (time != null) {
 636             crossCheck(time);
 637             if (date != null && fieldValues.size() > 0) {
 638                 crossCheck(date.atTime(time));
 639             }
 640         }
 641     }
 642 
 643     private void crossCheck(TemporalAccessor target) {
 644         for (Iterator<Entry<TemporalField, Long>> it = fieldValues.entrySet().iterator(); it.hasNext(); ) {
 645             Entry<TemporalField, Long> entry = it.next();
 646             TemporalField field = entry.getKey();
 647             if (target.isSupported(field)) {
 648                 long val1;
 649                 try {
 650                     val1 = target.getLong(field);
 651                 } catch (RuntimeException ex) {
 652                     continue;
 653                 }
 654                 long val2 = entry.getValue();
 655                 if (val1 != val2) {
 656                     throw new DateTimeException("Conflict found: Field " + field + " " + val1 +
 657                             " differs from " + field + " " + val2 + " derived from " + target);
 658                 }
 659                 it.remove();
 660             }
 661         }
 662     }
 663 
 664     //-----------------------------------------------------------------------
 665     @Override
 666     public String toString() {
 667         StringBuilder buf = new StringBuilder(64);
 668         buf.append(fieldValues).append(',').append(chrono);
 669         if (zone != null) {
 670             buf.append(',').append(zone);
 671         }
 672         if (date != null || time != null) {
 673             buf.append(" resolved to ");
 674             if (date != null) {
 675                 buf.append(date);
 676                 if (time != null) {
 677                     buf.append('T').append(time);
 678                 }
 679             } else {
 680                 buf.append(time);
 681             }
 682         }
 683         return buf.toString();
 684     }
 685 
 686 }