View Javadoc
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.MICRO_OF_DAY;
70  import static java.time.temporal.ChronoField.MICRO_OF_SECOND;
71  import static java.time.temporal.ChronoField.MILLI_OF_DAY;
72  import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
73  import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
74  import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
75  import static java.time.temporal.ChronoField.NANO_OF_DAY;
76  import static java.time.temporal.ChronoField.NANO_OF_SECOND;
77  import static java.time.temporal.ChronoField.SECOND_OF_DAY;
78  import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
79  
80  import java.time.DateTimeException;
81  import java.time.LocalDate;
82  import java.time.LocalTime;
83  import java.time.Period;
84  import java.time.ZoneId;
85  import java.time.chrono.ChronoLocalDate;
86  import java.time.chrono.ChronoLocalDateTime;
87  import java.time.chrono.ChronoZonedDateTime;
88  import java.time.chrono.Chronology;
89  import java.time.temporal.ChronoField;
90  import java.time.temporal.TemporalAccessor;
91  import java.time.temporal.TemporalField;
92  import java.time.temporal.TemporalQueries;
93  import java.time.temporal.TemporalQuery;
94  import java.time.temporal.UnsupportedTemporalTypeException;
95  import java.util.HashMap;
96  import java.util.Iterator;
97  import java.util.Map;
98  import java.util.Map.Entry;
99  import java.util.Objects;
100 import java.util.Set;
101 
102 /**
103  * A store of parsed data.
104  * <p>
105  * This class is used during parsing to collect the data. Part of the parsing process
106  * involves handling optional blocks and multiple copies of the data get created to
107  * support the necessary backtracking.
108  * <p>
109  * Once parsing is completed, this class can be used as the resultant {@code TemporalAccessor}.
110  * In most cases, it is only exposed once the fields have been resolved.
111  *
112  * @implSpec
113  * This class is a mutable context intended for use from a single thread.
114  * Usage of the class is thread-safe within standard parsing as a new instance of this class
115  * is automatically created for each parse and parsing is single-threaded
116  *
117  * @since 1.8
118  */
119 final class Parsed implements TemporalAccessor {
120     // some fields are accessed using package scope from DateTimeParseContext
121 
122     /**
123      * The parsed fields.
124      */
125     final Map<TemporalField, Long> fieldValues = new HashMap<>();
126     /**
127      * The parsed zone.
128      */
129     ZoneId zone;
130     /**
131      * The parsed chronology.
132      */
133     Chronology chrono;
134     /**
135      * Whether a leap-second is parsed.
136      */
137     boolean leapSecond;
138     /**
139      * The effective chronology.
140      */
141     Chronology effectiveChrono;
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.zone() || query == TemporalQueries.offset()) {
220             return query.queryFrom(this);
221         } else if (query == TemporalQueries.precision()) {
222             return null;  // not a complete date/time
223         }
224         // inline TemporalAccessor.super.query(query) as an optimization
225         // non-JDK classes are not permitted to make this optimization
226         return query.queryFrom(this);
227     }
228 
229     //-----------------------------------------------------------------------
230     /**
231      * Resolves the fields in this context.
232      *
233      * @param resolverStyle  the resolver style, not null
234      * @param resolverFields  the fields to use for resolving, null for all fields
235      * @return this, for method chaining
236      * @throws DateTimeException if resolving one field results in a value for
237      *  another field that is in conflict
238      */
239     TemporalAccessor resolve(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) {
240         if (resolverFields != null) {
241             fieldValues.keySet().retainAll(resolverFields);
242         }
243         this.resolverStyle = resolverStyle;
244         chrono = effectiveChrono;
245         resolveFields();
246         resolveTimeLenient();
247         crossCheck();
248         resolvePeriod();
249         return this;
250     }
251 
252     //-----------------------------------------------------------------------
253     private void resolveFields() {
254         // resolve ChronoField
255         resolveDateFields();
256         resolveTimeFields();
257 
258         // if any other fields, handle them
259         // any lenient date resolution should return epoch-day
260         if (fieldValues.size() > 0) {
261             int changedCount = 0;
262             outer:
263             while (changedCount < 50) {
264                 for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
265                     TemporalField targetField = entry.getKey();
266                     TemporalAccessor resolvedObject = targetField.resolve(fieldValues, this, resolverStyle);
267                     if (resolvedObject != null) {
268                         if (resolvedObject instanceof ChronoZonedDateTime) {
269                             ChronoZonedDateTime<?> czdt = (ChronoZonedDateTime) resolvedObject;
270                             if (zone.equals(czdt.getZone()) == false) {
271                                 throw new DateTimeException("ChronoZonedDateTime must use the effective parsed zone: " + zone);
272                             }
273                             resolvedObject = czdt.toLocalDateTime();
274                         }
275                         if (resolvedObject instanceof ChronoLocalDateTime) {
276                             ChronoLocalDateTime<?> cldt = (ChronoLocalDateTime) resolvedObject;
277                             updateCheckConflict(cldt.toLocalTime(), Period.ZERO);
278                             updateCheckConflict(cldt.toLocalDate());
279                             changedCount++;
280                             continue outer;  // have to restart to avoid concurrent modification
281                         }
282                         if (resolvedObject instanceof ChronoLocalDate) {
283                             updateCheckConflict((ChronoLocalDate) resolvedObject);
284                             changedCount++;
285                             continue outer;  // have to restart to avoid concurrent modification
286                         }
287                         if (resolvedObject instanceof LocalTime) {
288                             updateCheckConflict((LocalTime) resolvedObject, Period.ZERO);
289                             changedCount++;
290                             continue outer;  // have to restart to avoid concurrent modification
291                         }
292                         throw new DateTimeException("Method resolveFields() can only return ChronoZonedDateTime," +
293                                 "ChronoLocalDateTime, ChronoLocalDate or LocalTime");
294                     } else if (fieldValues.containsKey(targetField) == false) {
295                         changedCount++;
296                         continue outer;  // have to restart to avoid concurrent modification
297                     }
298                 }
299                 break;
300             }
301             if (changedCount == 50) {  // catch infinite loops
302                 throw new DateTimeException("One of the parsed fields has an incorrectly implemented resolve method");
303             }
304             // if something changed then have to redo ChronoField resolve
305             if (changedCount > 0) {
306                 resolveDateFields();
307                 resolveTimeFields();
308             }
309         }
310     }
311 
312     private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) {
313         Long old = fieldValues.put(changeField, changeValue);
314         if (old != null && old.longValue() != changeValue.longValue()) {
315             throw new DateTimeException("Conflict found: " + changeField + " " + old +
316                     " differs from " + changeField + " " + changeValue +
317                     " while resolving  " + targetField);
318         }
319     }
320 
321     //-----------------------------------------------------------------------
322     private void resolveDateFields() {
323         updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle));
324     }
325 
326     private void updateCheckConflict(ChronoLocalDate cld) {
327         if (date != null) {
328             if (cld != null && date.equals(cld) == false) {
329                 throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld);
330             }
331         } else if (cld != null) {
332             if (chrono.equals(cld.getChronology()) == false) {
333                 throw new DateTimeException("ChronoLocalDate must use the effective parsed chronology: " + chrono);
334             }
335             date = cld;
336         }
337     }
338 
339     //-----------------------------------------------------------------------
340     private void resolveTimeFields() {
341         // simplify fields
342         if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) {
343             // lenient allows anything, smart allows 0-24, strict allows 1-24
344             long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY);
345             if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) {
346                 CLOCK_HOUR_OF_DAY.checkValidValue(ch);
347             }
348             updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch);
349         }
350         if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) {
351             // lenient allows anything, smart allows 0-12, strict allows 1-12
352             long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM);
353             if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) {
354                 CLOCK_HOUR_OF_AMPM.checkValidValue(ch);
355             }
356             updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch);
357         }
358         if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) {
359             long ap = fieldValues.remove(AMPM_OF_DAY);
360             long hap = fieldValues.remove(HOUR_OF_AMPM);
361             if (resolverStyle == ResolverStyle.LENIENT) {
362                 updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, Math.addExact(Math.multiplyExact(ap, 12), hap));
363             } else {  // STRICT or SMART
364                 AMPM_OF_DAY.checkValidValue(ap);
365                 HOUR_OF_AMPM.checkValidValue(ap);
366                 updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap);
367             }
368         }
369         if (fieldValues.containsKey(NANO_OF_DAY)) {
370             long nod = fieldValues.remove(NANO_OF_DAY);
371             if (resolverStyle != ResolverStyle.LENIENT) {
372                 NANO_OF_DAY.checkValidValue(nod);
373             }
374             updateCheckConflict(NANO_OF_DAY, HOUR_OF_DAY, nod / 3600_000_000_000L);
375             updateCheckConflict(NANO_OF_DAY, MINUTE_OF_HOUR, (nod / 60_000_000_000L) % 60);
376             updateCheckConflict(NANO_OF_DAY, SECOND_OF_MINUTE, (nod / 1_000_000_000L) % 60);
377             updateCheckConflict(NANO_OF_DAY, NANO_OF_SECOND, nod % 1_000_000_000L);
378         }
379         if (fieldValues.containsKey(MICRO_OF_DAY)) {
380             long cod = fieldValues.remove(MICRO_OF_DAY);
381             if (resolverStyle != ResolverStyle.LENIENT) {
382                 MICRO_OF_DAY.checkValidValue(cod);
383             }
384             updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1_000_000L);
385             updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1_000_000L);
386         }
387         if (fieldValues.containsKey(MILLI_OF_DAY)) {
388             long lod = fieldValues.remove(MILLI_OF_DAY);
389             if (resolverStyle != ResolverStyle.LENIENT) {
390                 MILLI_OF_DAY.checkValidValue(lod);
391             }
392             updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1_000);
393             updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1_000);
394         }
395         if (fieldValues.containsKey(SECOND_OF_DAY)) {
396             long sod = fieldValues.remove(SECOND_OF_DAY);
397             if (resolverStyle != ResolverStyle.LENIENT) {
398                 SECOND_OF_DAY.checkValidValue(sod);
399             }
400             updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600);
401             updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60);
402             updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60);
403         }
404         if (fieldValues.containsKey(MINUTE_OF_DAY)) {
405             long mod = fieldValues.remove(MINUTE_OF_DAY);
406             if (resolverStyle != ResolverStyle.LENIENT) {
407                 MINUTE_OF_DAY.checkValidValue(mod);
408             }
409             updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60);
410             updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60);
411         }
412 
413         // combine partial second fields strictly, leaving lenient expansion to later
414         if (fieldValues.containsKey(NANO_OF_SECOND)) {
415             long nos = fieldValues.get(NANO_OF_SECOND);
416             if (resolverStyle != ResolverStyle.LENIENT) {
417                 NANO_OF_SECOND.checkValidValue(nos);
418             }
419             if (fieldValues.containsKey(MICRO_OF_SECOND)) {
420                 long cos = fieldValues.remove(MICRO_OF_SECOND);
421                 if (resolverStyle != ResolverStyle.LENIENT) {
422                     MICRO_OF_SECOND.checkValidValue(cos);
423                 }
424                 nos = cos * 1000 + (nos % 1000);
425                 updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, nos);
426             }
427             if (fieldValues.containsKey(MILLI_OF_SECOND)) {
428                 long los = fieldValues.remove(MILLI_OF_SECOND);
429                 if (resolverStyle != ResolverStyle.LENIENT) {
430                     MILLI_OF_SECOND.checkValidValue(los);
431                 }
432                 updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, los * 1_000_000L + (nos % 1_000_000L));
433             }
434         }
435 
436         // convert to time if all four fields available (optimization)
437         if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) &&
438                 fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) {
439             long hod = fieldValues.remove(HOUR_OF_DAY);
440             long moh = fieldValues.remove(MINUTE_OF_HOUR);
441             long som = fieldValues.remove(SECOND_OF_MINUTE);
442             long nos = fieldValues.remove(NANO_OF_SECOND);
443             resolveTime(hod, moh, som, nos);
444         }
445     }
446 
447     private void resolveTimeLenient() {
448         // leniently create a time from incomplete information
449         // done after everything else as it creates information from nothing
450         // which would break updateCheckConflict(field)
451 
452         if (time == null) {
453             // NANO_OF_SECOND merged with MILLI/MICRO above
454             if (fieldValues.containsKey(MILLI_OF_SECOND)) {
455                 long los = fieldValues.remove(MILLI_OF_SECOND);
456                 if (fieldValues.containsKey(MICRO_OF_SECOND)) {
457                     // merge milli-of-second and micro-of-second for better error message
458                     long cos = los * 1_000 + (fieldValues.get(MICRO_OF_SECOND) % 1_000);
459                     updateCheckConflict(MILLI_OF_SECOND, MICRO_OF_SECOND, cos);
460                     fieldValues.remove(MICRO_OF_SECOND);
461                     fieldValues.put(NANO_OF_SECOND, cos * 1_000L);
462                 } else {
463                     // convert milli-of-second to nano-of-second
464                     fieldValues.put(NANO_OF_SECOND, los * 1_000_000L);
465                 }
466             } else if (fieldValues.containsKey(MICRO_OF_SECOND)) {
467                 // convert micro-of-second to nano-of-second
468                 long cos = fieldValues.remove(MICRO_OF_SECOND);
469                 fieldValues.put(NANO_OF_SECOND, cos * 1_000L);
470             }
471 
472             // merge hour/minute/second/nano leniently
473             Long hod = fieldValues.get(HOUR_OF_DAY);
474             if (hod != null) {
475                 Long moh = fieldValues.get(MINUTE_OF_HOUR);
476                 Long som = fieldValues.get(SECOND_OF_MINUTE);
477                 Long nos = fieldValues.get(NANO_OF_SECOND);
478 
479                 // check for invalid combinations that cannot be defaulted
480                 if ((moh == null && (som != null || nos != null)) ||
481                         (moh != null && som == null && nos != null)) {
482                     return;
483                 }
484 
485                 // default as necessary and build time
486                 long mohVal = (moh != null ? moh : 0);
487                 long somVal = (som != null ? som : 0);
488                 long nosVal = (nos != null ? nos : 0);
489                 resolveTime(hod, mohVal, somVal, nosVal);
490                 fieldValues.remove(HOUR_OF_DAY);
491                 fieldValues.remove(MINUTE_OF_HOUR);
492                 fieldValues.remove(SECOND_OF_MINUTE);
493                 fieldValues.remove(NANO_OF_SECOND);
494             }
495         }
496 
497         // validate remaining
498         if (resolverStyle != ResolverStyle.LENIENT && fieldValues.size() > 0) {
499             for (Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
500                 TemporalField field = entry.getKey();
501                 if (field instanceof ChronoField && field.isTimeBased()) {
502                     ((ChronoField) field).checkValidValue(entry.getValue());
503                 }
504             }
505         }
506     }
507 
508     private void resolveTime(long hod, long moh, long som, long nos) {
509         if (resolverStyle == ResolverStyle.LENIENT) {
510             long totalNanos = Math.multiplyExact(hod, 3600_000_000_000L);
511             totalNanos = Math.addExact(totalNanos, Math.multiplyExact(moh, 60_000_000_000L));
512             totalNanos = Math.addExact(totalNanos, Math.multiplyExact(som, 1_000_000_000L));
513             totalNanos = Math.addExact(totalNanos, nos);
514             int excessDays = (int) Math.floorDiv(totalNanos, 86400_000_000_000L);  // safe int cast
515             long nod = Math.floorMod(totalNanos, 86400_000_000_000L);
516             updateCheckConflict(LocalTime.ofNanoOfDay(nod), Period.ofDays(excessDays));
517         } else {  // STRICT or SMART
518             int mohVal = MINUTE_OF_HOUR.checkValidIntValue(moh);
519             int nosVal = NANO_OF_SECOND.checkValidIntValue(nos);
520             // handle 24:00 end of day
521             if (resolverStyle == ResolverStyle.SMART && hod == 24 && mohVal == 0 && som == 0 && nosVal == 0) {
522                 updateCheckConflict(LocalTime.MIDNIGHT, Period.ofDays(1));
523             } else {
524                 int hodVal = HOUR_OF_DAY.checkValidIntValue(hod);
525                 int somVal = SECOND_OF_MINUTE.checkValidIntValue(som);
526                 updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal), Period.ZERO);
527             }
528         }
529     }
530 
531     private void resolvePeriod() {
532         // add whole days if we have both date and time
533         if (date != null && time != null && excessDays.isZero() == false) {
534             date = date.plus(excessDays);
535             excessDays = Period.ZERO;
536         }
537     }
538 
539     private void updateCheckConflict(LocalTime timeToSet, Period periodToSet) {
540         if (time != null) {
541             if (time.equals(timeToSet) == false) {
542                 throw new DateTimeException("Conflict found: Fields resolved to different times: " + time + " " + timeToSet);
543             }
544             if (excessDays.isZero() == false && periodToSet.isZero() == false && excessDays.equals(periodToSet) == false) {
545                 throw new DateTimeException("Conflict found: Fields resolved to different excess periods: " + excessDays + " " + periodToSet);
546             } else {
547                 excessDays = periodToSet;
548             }
549         } else {
550             time = timeToSet;
551             excessDays = periodToSet;
552         }
553     }
554 
555     //-----------------------------------------------------------------------
556     private void crossCheck() {
557         // only cross-check date, time and date-time
558         // avoid object creation if possible
559         if (date != null) {
560             crossCheck(date);
561         }
562         if (time != null) {
563             crossCheck(time);
564             if (date != null && fieldValues.size() > 0) {
565                 crossCheck(date.atTime(time));
566             }
567         }
568     }
569 
570     private void crossCheck(TemporalAccessor target) {
571         for (Iterator<Entry<TemporalField, Long>> it = fieldValues.entrySet().iterator(); it.hasNext(); ) {
572             Entry<TemporalField, Long> entry = it.next();
573             TemporalField field = entry.getKey();
574             long val1;
575             try {
576                 val1 = target.getLong(field);
577             } catch (RuntimeException ex) {
578                 continue;
579             }
580             long val2 = entry.getValue();
581             if (val1 != val2) {
582                 throw new DateTimeException("Conflict found: Field " + field + " " + val1 +
583                         " differs from " + field + " " + val2 + " derived from " + target);
584             }
585             it.remove();
586         }
587     }
588 
589     //-----------------------------------------------------------------------
590     @Override
591     public String toString() {
592         StringBuilder buf = new StringBuilder(64);
593         buf.append(fieldValues).append(',').append(chrono);
594         if (zone != null) {
595             buf.append(',').append(zone);
596         }
597         if (date != null || time != null) {
598             buf.append(" resolved to ");
599             if (date != null) {
600                 buf.append(date);
601                 if (time != null) {
602                     buf.append('T').append(time);
603                 }
604             } else {
605                 buf.append(time);
606             }
607         }
608         return buf.toString();
609     }
610 
611 }