• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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