Path: blob/master/src/java.base/share/classes/java/time/format/Parsed.java
67848 views
/*1* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* Copyright (c) 2008-2013, Stephen Colebourne & Michael Nascimento Santos32*33* All rights reserved.34*35* Redistribution and use in source and binary forms, with or without36* modification, are permitted provided that the following conditions are met:37*38* * Redistributions of source code must retain the above copyright notice,39* this list of conditions and the following disclaimer.40*41* * Redistributions in binary form must reproduce the above copyright notice,42* this list of conditions and the following disclaimer in the documentation43* and/or other materials provided with the distribution.44*45* * Neither the name of JSR-310 nor the names of its contributors46* may be used to endorse or promote products derived from this software47* without specific prior written permission.48*49* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS50* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT51* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR52* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR53* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,54* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,55* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR56* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF57* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING58* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS59* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.60*/61package java.time.format;6263import static java.time.format.DateTimeFormatterBuilder.DayPeriod;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;8182import 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;105106/**107* A store of parsed data.108* <p>109* This class is used during parsing to collect the data. Part of the parsing process110* involves handling optional blocks and multiple copies of the data get created to111* 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* @implSpec117* 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 class119* is automatically created for each parse and parsing is single-threaded120*121* @since 1.8122*/123final class Parsed implements TemporalAccessor {124// some fields are accessed using package scope from DateTimeParseContext125126/**127* The parsed fields.128*/129final Map<TemporalField, Long> fieldValues = new HashMap<>();130/**131* The parsed zone.132*/133ZoneId zone;134/**135* The parsed chronology.136*/137Chronology chrono;138/**139* Whether a leap-second is parsed.140*/141boolean leapSecond;142/**143* The resolver style to use.144*/145private ResolverStyle resolverStyle;146/**147* The resolved date.148*/149private ChronoLocalDate date;150/**151* The resolved time.152*/153private LocalTime time;154/**155* The excess period from time-only parsing.156*/157Period excessDays = Period.ZERO;158/**159* The parsed day period.160*/161DayPeriod dayPeriod;162163/**164* Creates an instance.165*/166Parsed() {167}168169/**170* Creates a copy.171*/172Parsed copy() {173// only copy fields used in parsing stage174Parsed cloned = new Parsed();175cloned.fieldValues.putAll(this.fieldValues);176cloned.zone = this.zone;177cloned.chrono = this.chrono;178cloned.leapSecond = this.leapSecond;179cloned.dayPeriod = this.dayPeriod;180return cloned;181}182183//-----------------------------------------------------------------------184@Override185public boolean isSupported(TemporalField field) {186if (fieldValues.containsKey(field) ||187(date != null && date.isSupported(field)) ||188(time != null && time.isSupported(field))) {189return true;190}191return field != null && (!(field instanceof ChronoField)) && field.isSupportedBy(this);192}193194@Override195public long getLong(TemporalField field) {196Objects.requireNonNull(field, "field");197Long value = fieldValues.get(field);198if (value != null) {199return value;200}201if (date != null && date.isSupported(field)) {202return date.getLong(field);203}204if (time != null && time.isSupported(field)) {205return time.getLong(field);206}207if (field instanceof ChronoField) {208throw new UnsupportedTemporalTypeException("Unsupported field: " + field);209}210return field.getFrom(this);211}212213@SuppressWarnings("unchecked")214@Override215public <R> R query(TemporalQuery<R> query) {216if (query == TemporalQueries.zoneId()) {217return (R) zone;218} else if (query == TemporalQueries.chronology()) {219return (R) chrono;220} else if (query == TemporalQueries.localDate()) {221return (R) (date != null ? LocalDate.from(date) : null);222} else if (query == TemporalQueries.localTime()) {223return (R) time;224} else if (query == TemporalQueries.offset()) {225Long offsetSecs = fieldValues.get(OFFSET_SECONDS);226if (offsetSecs != null) {227return (R) ZoneOffset.ofTotalSeconds(offsetSecs.intValue());228}229if (zone instanceof ZoneOffset) {230return (R)zone;231}232return query.queryFrom(this);233} else if (query == TemporalQueries.zone()) {234return query.queryFrom(this);235} else if (query == TemporalQueries.precision()) {236return null; // not a complete date/time237}238// inline TemporalAccessor.super.query(query) as an optimization239// non-JDK classes are not permitted to make this optimization240return query.queryFrom(this);241}242243//-----------------------------------------------------------------------244/**245* Resolves the fields in this context.246*247* @param resolverStyle the resolver style, not null248* @param resolverFields the fields to use for resolving, null for all fields249* @return this, for method chaining250* @throws DateTimeException if resolving one field results in a value for251* another field that is in conflict252*/253TemporalAccessor resolve(ResolverStyle resolverStyle, Set<TemporalField> resolverFields) {254if (resolverFields != null) {255fieldValues.keySet().retainAll(resolverFields);256}257this.resolverStyle = resolverStyle;258resolveFields();259resolveTimeLenient();260crossCheck();261resolvePeriod();262resolveFractional();263resolveInstant();264return this;265}266267//-----------------------------------------------------------------------268private void resolveFields() {269// resolve ChronoField270resolveInstantFields();271resolveDateFields();272resolveTimeFields();273274// if any other fields, handle them275// any lenient date resolution should return epoch-day276if (fieldValues.size() > 0) {277int changedCount = 0;278outer:279while (changedCount < 50) {280for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) {281TemporalField targetField = entry.getKey();282TemporalAccessor resolvedObject = targetField.resolve(fieldValues, this, resolverStyle);283if (resolvedObject != null) {284if (resolvedObject instanceof ChronoZonedDateTime<?> czdt) {285if (zone == null) {286zone = czdt.getZone();287} else if (zone.equals(czdt.getZone()) == false) {288throw new DateTimeException("ChronoZonedDateTime must use the effective parsed zone: " + zone);289}290resolvedObject = czdt.toLocalDateTime();291}292if (resolvedObject instanceof ChronoLocalDateTime<?> cldt) {293updateCheckConflict(cldt.toLocalTime(), Period.ZERO);294updateCheckConflict(cldt.toLocalDate());295changedCount++;296continue outer; // have to restart to avoid concurrent modification297}298if (resolvedObject instanceof ChronoLocalDate) {299updateCheckConflict((ChronoLocalDate) resolvedObject);300changedCount++;301continue outer; // have to restart to avoid concurrent modification302}303if (resolvedObject instanceof LocalTime) {304updateCheckConflict((LocalTime) resolvedObject, Period.ZERO);305changedCount++;306continue outer; // have to restart to avoid concurrent modification307}308throw new DateTimeException("Method resolve() can only return ChronoZonedDateTime, " +309"ChronoLocalDateTime, ChronoLocalDate or LocalTime");310} else if (fieldValues.containsKey(targetField) == false) {311changedCount++;312continue outer; // have to restart to avoid concurrent modification313}314}315break;316}317if (changedCount == 50) { // catch infinite loops318throw new DateTimeException("One of the parsed fields has an incorrectly implemented resolve method");319}320// if something changed then have to redo ChronoField resolve321if (changedCount > 0) {322resolveInstantFields();323resolveDateFields();324resolveTimeFields();325}326}327}328329private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) {330Long old = fieldValues.put(changeField, changeValue);331if (old != null && old.longValue() != changeValue.longValue()) {332throw new DateTimeException("Conflict found: " + changeField + " " + old +333" differs from " + changeField + " " + changeValue +334" while resolving " + targetField);335}336}337338339//-----------------------------------------------------------------------340private void resolveInstantFields() {341// resolve parsed instant seconds to date and time if zone available342if (fieldValues.containsKey(INSTANT_SECONDS)) {343if (zone != null) {344resolveInstantFields0(zone);345} else {346Long offsetSecs = fieldValues.get(OFFSET_SECONDS);347if (offsetSecs != null) {348ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue());349resolveInstantFields0(offset);350}351}352}353}354355private void resolveInstantFields0(ZoneId selectedZone) {356Instant instant = Instant.ofEpochSecond(fieldValues.get(INSTANT_SECONDS));357ChronoZonedDateTime<?> zdt = chrono.zonedDateTime(instant, selectedZone);358updateCheckConflict(zdt.toLocalDate());359updateCheckConflict(INSTANT_SECONDS, SECOND_OF_DAY, (long) zdt.toLocalTime().toSecondOfDay());360updateCheckConflict(INSTANT_SECONDS, OFFSET_SECONDS, (long) zdt.getOffset().getTotalSeconds());361}362363//-----------------------------------------------------------------------364private void resolveDateFields() {365updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle));366}367368private void updateCheckConflict(ChronoLocalDate cld) {369if (date != null) {370if (cld != null && date.equals(cld) == false) {371throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld);372}373} else if (cld != null) {374if (chrono.equals(cld.getChronology()) == false) {375throw new DateTimeException("ChronoLocalDate must use the effective parsed chronology: " + chrono);376}377date = cld;378}379}380381//-----------------------------------------------------------------------382private void resolveTimeFields() {383// simplify fields384if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) {385// lenient allows anything, smart allows 0-24, strict allows 1-24386long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY);387if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) {388CLOCK_HOUR_OF_DAY.checkValidValue(ch);389}390updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch);391}392if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) {393// lenient allows anything, smart allows 0-12, strict allows 1-12394long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM);395if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) {396CLOCK_HOUR_OF_AMPM.checkValidValue(ch);397}398updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch);399}400if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) {401long ap = fieldValues.remove(AMPM_OF_DAY);402long hap = fieldValues.remove(HOUR_OF_AMPM);403if (resolverStyle == ResolverStyle.LENIENT) {404updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, Math.addExact(Math.multiplyExact(ap, 12), hap));405} else { // STRICT or SMART406AMPM_OF_DAY.checkValidValue(ap);407HOUR_OF_AMPM.checkValidValue(hap);408updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap);409}410}411if (fieldValues.containsKey(NANO_OF_DAY)) {412long nod = fieldValues.remove(NANO_OF_DAY);413if (resolverStyle != ResolverStyle.LENIENT) {414NANO_OF_DAY.checkValidValue(nod);415}416updateCheckConflict(NANO_OF_DAY, HOUR_OF_DAY, nod / 3600_000_000_000L);417updateCheckConflict(NANO_OF_DAY, MINUTE_OF_HOUR, (nod / 60_000_000_000L) % 60);418updateCheckConflict(NANO_OF_DAY, SECOND_OF_MINUTE, (nod / 1_000_000_000L) % 60);419updateCheckConflict(NANO_OF_DAY, NANO_OF_SECOND, nod % 1_000_000_000L);420}421if (fieldValues.containsKey(MICRO_OF_DAY)) {422long cod = fieldValues.remove(MICRO_OF_DAY);423if (resolverStyle != ResolverStyle.LENIENT) {424MICRO_OF_DAY.checkValidValue(cod);425}426updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1_000_000L);427updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1_000_000L);428}429if (fieldValues.containsKey(MILLI_OF_DAY)) {430long lod = fieldValues.remove(MILLI_OF_DAY);431if (resolverStyle != ResolverStyle.LENIENT) {432MILLI_OF_DAY.checkValidValue(lod);433}434updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1_000);435updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1_000);436}437if (fieldValues.containsKey(SECOND_OF_DAY)) {438long sod = fieldValues.remove(SECOND_OF_DAY);439if (resolverStyle != ResolverStyle.LENIENT) {440SECOND_OF_DAY.checkValidValue(sod);441}442updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600);443updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60);444updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60);445}446if (fieldValues.containsKey(MINUTE_OF_DAY)) {447long mod = fieldValues.remove(MINUTE_OF_DAY);448if (resolverStyle != ResolverStyle.LENIENT) {449MINUTE_OF_DAY.checkValidValue(mod);450}451updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60);452updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60);453}454455// combine partial second fields strictly, leaving lenient expansion to later456if (fieldValues.containsKey(NANO_OF_SECOND)) {457long nos = fieldValues.get(NANO_OF_SECOND);458if (resolverStyle != ResolverStyle.LENIENT) {459NANO_OF_SECOND.checkValidValue(nos);460}461if (fieldValues.containsKey(MICRO_OF_SECOND)) {462long cos = fieldValues.remove(MICRO_OF_SECOND);463if (resolverStyle != ResolverStyle.LENIENT) {464MICRO_OF_SECOND.checkValidValue(cos);465}466nos = cos * 1000 + (nos % 1000);467updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, nos);468}469if (fieldValues.containsKey(MILLI_OF_SECOND)) {470long los = fieldValues.remove(MILLI_OF_SECOND);471if (resolverStyle != ResolverStyle.LENIENT) {472MILLI_OF_SECOND.checkValidValue(los);473}474updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, los * 1_000_000L + (nos % 1_000_000L));475}476}477478if (dayPeriod != null && fieldValues.containsKey(HOUR_OF_AMPM)) {479long hoap = fieldValues.remove(HOUR_OF_AMPM);480if (resolverStyle != ResolverStyle.LENIENT) {481HOUR_OF_AMPM.checkValidValue(hoap);482}483Long mohObj = fieldValues.get(MINUTE_OF_HOUR);484long moh = mohObj != null ? Math.floorMod(mohObj, 60) : 0;485long excessHours = dayPeriod.includes((Math.floorMod(hoap, 12) + 12) * 60 + moh) ? 12 : 0;486long hod = Math.addExact(hoap, excessHours);487updateCheckConflict(HOUR_OF_AMPM, HOUR_OF_DAY, hod);488dayPeriod = null;489}490491// convert to time if all four fields available (optimization)492if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) &&493fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) {494long hod = fieldValues.remove(HOUR_OF_DAY);495long moh = fieldValues.remove(MINUTE_OF_HOUR);496long som = fieldValues.remove(SECOND_OF_MINUTE);497long nos = fieldValues.remove(NANO_OF_SECOND);498resolveTime(hod, moh, som, nos);499}500}501502private void resolveTimeLenient() {503// leniently create a time from incomplete information504// done after everything else as it creates information from nothing505// which would break updateCheckConflict(field)506507if (time == null) {508// NANO_OF_SECOND merged with MILLI/MICRO above509if (fieldValues.containsKey(MILLI_OF_SECOND)) {510long los = fieldValues.remove(MILLI_OF_SECOND);511if (fieldValues.containsKey(MICRO_OF_SECOND)) {512// merge milli-of-second and micro-of-second for better error message513long cos = los * 1_000 + (fieldValues.get(MICRO_OF_SECOND) % 1_000);514updateCheckConflict(MILLI_OF_SECOND, MICRO_OF_SECOND, cos);515fieldValues.remove(MICRO_OF_SECOND);516fieldValues.put(NANO_OF_SECOND, cos * 1_000L);517} else {518// convert milli-of-second to nano-of-second519fieldValues.put(NANO_OF_SECOND, los * 1_000_000L);520}521} else if (fieldValues.containsKey(MICRO_OF_SECOND)) {522// convert micro-of-second to nano-of-second523long cos = fieldValues.remove(MICRO_OF_SECOND);524fieldValues.put(NANO_OF_SECOND, cos * 1_000L);525}526527// Set the hour-of-day, if not exist and not in STRICT, to the mid point of the day period or am/pm.528if (!fieldValues.containsKey(HOUR_OF_DAY) &&529!fieldValues.containsKey(MINUTE_OF_HOUR) &&530!fieldValues.containsKey(SECOND_OF_MINUTE) &&531!fieldValues.containsKey(NANO_OF_SECOND) &&532resolverStyle != ResolverStyle.STRICT) {533if (dayPeriod != null) {534long midpoint = dayPeriod.mid();535resolveTime(midpoint / 60, midpoint % 60, 0, 0);536dayPeriod = null;537} else if (fieldValues.containsKey(AMPM_OF_DAY)) {538long ap = fieldValues.remove(AMPM_OF_DAY);539if (resolverStyle == ResolverStyle.LENIENT) {540resolveTime(Math.addExact(Math.multiplyExact(ap, 12), 6), 0, 0, 0);541} else { // SMART542AMPM_OF_DAY.checkValidValue(ap);543resolveTime(ap * 12 + 6, 0, 0, 0);544}545}546}547548// merge hour/minute/second/nano leniently549Long hod = fieldValues.get(HOUR_OF_DAY);550if (hod != null) {551Long moh = fieldValues.get(MINUTE_OF_HOUR);552Long som = fieldValues.get(SECOND_OF_MINUTE);553Long nos = fieldValues.get(NANO_OF_SECOND);554555// check for invalid combinations that cannot be defaulted556if ((moh == null && (som != null || nos != null)) ||557(moh != null && som == null && nos != null)) {558return;559}560561// default as necessary and build time562long mohVal = (moh != null ? moh : 0);563long somVal = (som != null ? som : 0);564long nosVal = (nos != null ? nos : 0);565566if (dayPeriod != null && resolverStyle != ResolverStyle.LENIENT) {567// Check whether the hod/mohVal is within the day period568if (!dayPeriod.includes(hod * 60 + mohVal)) {569throw new DateTimeException("Conflict found: Resolved time %02d:%02d".formatted(hod, mohVal) +570" conflicts with " + dayPeriod);571}572}573574resolveTime(hod, mohVal, somVal, nosVal);575fieldValues.remove(HOUR_OF_DAY);576fieldValues.remove(MINUTE_OF_HOUR);577fieldValues.remove(SECOND_OF_MINUTE);578fieldValues.remove(NANO_OF_SECOND);579}580}581582// validate remaining583if (resolverStyle != ResolverStyle.LENIENT && fieldValues.size() > 0) {584for (Entry<TemporalField, Long> entry : fieldValues.entrySet()) {585TemporalField field = entry.getKey();586if (field instanceof ChronoField && field.isTimeBased()) {587((ChronoField) field).checkValidValue(entry.getValue());588}589}590}591}592593private void resolveTime(long hod, long moh, long som, long nos) {594if (resolverStyle == ResolverStyle.LENIENT) {595long totalNanos = Math.multiplyExact(hod, 3600_000_000_000L);596totalNanos = Math.addExact(totalNanos, Math.multiplyExact(moh, 60_000_000_000L));597totalNanos = Math.addExact(totalNanos, Math.multiplyExact(som, 1_000_000_000L));598totalNanos = Math.addExact(totalNanos, nos);599int excessDays = (int) Math.floorDiv(totalNanos, 86400_000_000_000L); // safe int cast600long nod = Math.floorMod(totalNanos, 86400_000_000_000L);601updateCheckConflict(LocalTime.ofNanoOfDay(nod), Period.ofDays(excessDays));602} else { // STRICT or SMART603int mohVal = MINUTE_OF_HOUR.checkValidIntValue(moh);604int nosVal = NANO_OF_SECOND.checkValidIntValue(nos);605// handle 24:00 end of day606if (resolverStyle == ResolverStyle.SMART && hod == 24 && mohVal == 0 && som == 0 && nosVal == 0) {607updateCheckConflict(LocalTime.MIDNIGHT, Period.ofDays(1));608} else {609int hodVal = HOUR_OF_DAY.checkValidIntValue(hod);610int somVal = SECOND_OF_MINUTE.checkValidIntValue(som);611updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal), Period.ZERO);612}613}614}615616private void resolvePeriod() {617// add whole days if we have both date and time618if (date != null && time != null && excessDays.isZero() == false) {619date = date.plus(excessDays);620excessDays = Period.ZERO;621}622}623624private void resolveFractional() {625// ensure fractional seconds available as ChronoField requires626// resolveTimeLenient() will have merged MICRO_OF_SECOND/MILLI_OF_SECOND to NANO_OF_SECOND627if (time == null &&628(fieldValues.containsKey(INSTANT_SECONDS) ||629fieldValues.containsKey(SECOND_OF_DAY) ||630fieldValues.containsKey(SECOND_OF_MINUTE))) {631if (fieldValues.containsKey(NANO_OF_SECOND)) {632long nos = fieldValues.get(NANO_OF_SECOND);633fieldValues.put(MICRO_OF_SECOND, nos / 1000);634fieldValues.put(MILLI_OF_SECOND, nos / 1000000);635} else {636fieldValues.put(NANO_OF_SECOND, 0L);637fieldValues.put(MICRO_OF_SECOND, 0L);638fieldValues.put(MILLI_OF_SECOND, 0L);639}640}641}642643private void resolveInstant() {644// add instant seconds (if not present) if we have date, time and zone645// Offset (if present) will be given priority over the zone.646if (!fieldValues.containsKey(INSTANT_SECONDS) && date != null && time != null) {647Long offsetSecs = fieldValues.get(OFFSET_SECONDS);648if (offsetSecs != null) {649ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue());650long instant = date.atTime(time).atZone(offset).toEpochSecond();651fieldValues.put(INSTANT_SECONDS, instant);652} else {653if (zone != null) {654long instant = date.atTime(time).atZone(zone).toEpochSecond();655fieldValues.put(INSTANT_SECONDS, instant);656}657}658}659}660661private void updateCheckConflict(LocalTime timeToSet, Period periodToSet) {662if (time != null) {663if (time.equals(timeToSet) == false) {664throw new DateTimeException("Conflict found: Fields resolved to different times: " + time + " " + timeToSet);665}666if (excessDays.isZero() == false && periodToSet.isZero() == false && excessDays.equals(periodToSet) == false) {667throw new DateTimeException("Conflict found: Fields resolved to different excess periods: " + excessDays + " " + periodToSet);668} else {669excessDays = periodToSet;670}671} else {672time = timeToSet;673excessDays = periodToSet;674}675}676677//-----------------------------------------------------------------------678private void crossCheck() {679// only cross-check date, time and date-time680// avoid object creation if possible681if (date != null) {682crossCheck(date);683}684if (time != null) {685crossCheck(time);686if (date != null && fieldValues.size() > 0) {687crossCheck(date.atTime(time));688}689}690}691692private void crossCheck(TemporalAccessor target) {693for (Iterator<Entry<TemporalField, Long>> it = fieldValues.entrySet().iterator(); it.hasNext(); ) {694Entry<TemporalField, Long> entry = it.next();695TemporalField field = entry.getKey();696if (target.isSupported(field)) {697long val1;698try {699val1 = target.getLong(field);700} catch (RuntimeException ex) {701continue;702}703long val2 = entry.getValue();704if (val1 != val2) {705throw new DateTimeException("Conflict found: Field " + field + " " + val1 +706" differs from " + field + " " + val2 + " derived from " + target);707}708it.remove();709}710}711}712713//-----------------------------------------------------------------------714@Override715public String toString() {716StringBuilder buf = new StringBuilder(64);717buf.append(fieldValues).append(',').append(chrono);718if (zone != null) {719buf.append(',').append(zone);720}721if (date != null || time != null) {722buf.append(" resolved to ");723if (date != null) {724buf.append(date);725if (time != null) {726buf.append('T').append(time);727}728} else {729buf.append(time);730}731}732return buf.toString();733}734735}736737738