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 */
62package java.time.format;
63
64import static java.time.temporal.ChronoField.AMPM_OF_DAY;
65import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
66import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY;
67import static java.time.temporal.ChronoField.HOUR_OF_AMPM;
68import static java.time.temporal.ChronoField.HOUR_OF_DAY;
69import static java.time.temporal.ChronoField.INSTANT_SECONDS;
70import static java.time.temporal.ChronoField.MICRO_OF_DAY;
71import static java.time.temporal.ChronoField.MICRO_OF_SECOND;
72import static java.time.temporal.ChronoField.MILLI_OF_DAY;
73import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
74import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
75import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
76import static java.time.temporal.ChronoField.NANO_OF_DAY;
77import static java.time.temporal.ChronoField.NANO_OF_SECOND;
78import static java.time.temporal.ChronoField.OFFSET_SECONDS;
79import static java.time.temporal.ChronoField.SECOND_OF_DAY;
80import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
81
82import java.time.DateTimeException;
83import java.time.Instant;
84import java.time.LocalDate;
85import java.time.LocalTime;
86import java.time.Period;
87import java.time.ZoneId;
88import java.time.ZoneOffset;
89import java.time.chrono.ChronoLocalDate;
90import java.time.chrono.ChronoLocalDateTime;
91import java.time.chrono.ChronoZonedDateTime;
92import java.time.chrono.Chronology;
93import java.time.temporal.ChronoField;
94import java.time.temporal.TemporalAccessor;
95import java.time.temporal.TemporalField;
96import java.time.temporal.TemporalQueries;
97import java.time.temporal.TemporalQuery;
98import java.time.temporal.UnsupportedTemporalTypeException;
99import java.util.HashMap;
100import java.util.Iterator;
101import java.util.Map;
102import java.util.Map.Entry;
103import java.util.Objects;
104import 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 */
123final 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        // 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
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    //-----------------------------------------------------------------------
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
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
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