/* * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * This file is available under and governed by the GNU General Public * License version 2 only, as published by the Free Software Foundation. * However, the following notice accompanied the original version of this * file: * * Copyright (c) 2008-2013, Stephen Colebourne & Michael Nascimento Santos * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of JSR-310 nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package java.time.format; import static java.time.temporal.ChronoField.AMPM_OF_DAY; import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; import static java.time.temporal.ChronoField.HOUR_OF_AMPM; import static java.time.temporal.ChronoField.HOUR_OF_DAY; import static java.time.temporal.ChronoField.INSTANT_SECONDS; import static java.time.temporal.ChronoField.MICRO_OF_DAY; import static java.time.temporal.ChronoField.MICRO_OF_SECOND; import static java.time.temporal.ChronoField.MILLI_OF_DAY; import static java.time.temporal.ChronoField.MILLI_OF_SECOND; import static java.time.temporal.ChronoField.MINUTE_OF_DAY; import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; import static java.time.temporal.ChronoField.NANO_OF_DAY; import static java.time.temporal.ChronoField.NANO_OF_SECOND; import static java.time.temporal.ChronoField.OFFSET_SECONDS; import static java.time.temporal.ChronoField.SECOND_OF_DAY; import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; import java.time.DateTimeException; import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.time.Period; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.chrono.ChronoLocalDate; import java.time.chrono.ChronoLocalDateTime; import java.time.chrono.ChronoZonedDateTime; import java.time.chrono.Chronology; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.TemporalQueries; import java.time.temporal.TemporalQuery; import java.time.temporal.UnsupportedTemporalTypeException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; /** * A store of parsed data. *

* This class is used during parsing to collect the data. Part of the parsing process * involves handling optional blocks and multiple copies of the data get created to * support the necessary backtracking. *

* Once parsing is completed, this class can be used as the resultant {@code TemporalAccessor}. * In most cases, it is only exposed once the fields have been resolved. * * @implSpec * This class is a mutable context intended for use from a single thread. * Usage of the class is thread-safe within standard parsing as a new instance of this class * is automatically created for each parse and parsing is single-threaded * * @since 1.8 */ final class Parsed implements TemporalAccessor { // some fields are accessed using package scope from DateTimeParseContext /** * The parsed fields. */ final Map fieldValues = new HashMap<>(); /** * The parsed zone. */ ZoneId zone; /** * The parsed chronology. */ Chronology chrono; /** * Whether a leap-second is parsed. */ boolean leapSecond; /** * The resolver style to use. */ private ResolverStyle resolverStyle; /** * The resolved date. */ private ChronoLocalDate date; /** * The resolved time. */ private LocalTime time; /** * The excess period from time-only parsing. */ Period excessDays = Period.ZERO; /** * Creates an instance. */ Parsed() { } /** * Creates a copy. */ Parsed copy() { // only copy fields used in parsing stage Parsed cloned = new Parsed(); cloned.fieldValues.putAll(this.fieldValues); cloned.zone = this.zone; cloned.chrono = this.chrono; cloned.leapSecond = this.leapSecond; return cloned; } //----------------------------------------------------------------------- @Override public boolean isSupported(TemporalField field) { if (fieldValues.containsKey(field) || (date != null && date.isSupported(field)) || (time != null && time.isSupported(field))) { return true; } return field != null && (field instanceof ChronoField == false) && field.isSupportedBy(this); } @Override public long getLong(TemporalField field) { Objects.requireNonNull(field, "field"); Long value = fieldValues.get(field); if (value != null) { return value; } if (date != null && date.isSupported(field)) { return date.getLong(field); } if (time != null && time.isSupported(field)) { return time.getLong(field); } if (field instanceof ChronoField) { throw new UnsupportedTemporalTypeException("Unsupported field: " + field); } return field.getFrom(this); } @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { if (query == TemporalQueries.zoneId()) { return (R) zone; } else if (query == TemporalQueries.chronology()) { return (R) chrono; } else if (query == TemporalQueries.localDate()) { return (R) (date != null ? LocalDate.from(date) : null); } else if (query == TemporalQueries.localTime()) { return (R) time; } else if (query == TemporalQueries.offset()) { Long offsetSecs = fieldValues.get(OFFSET_SECONDS); if (offsetSecs != null) { return (R) ZoneOffset.ofTotalSeconds(offsetSecs.intValue()); } if (zone instanceof ZoneOffset) { return (R)zone; } return query.queryFrom(this); } else if (query == TemporalQueries.zone()) { return query.queryFrom(this); } else if (query == TemporalQueries.precision()) { return null; // not a complete date/time } // inline TemporalAccessor.super.query(query) as an optimization // non-JDK classes are not permitted to make this optimization return query.queryFrom(this); } //----------------------------------------------------------------------- /** * Resolves the fields in this context. * * @param resolverStyle the resolver style, not null * @param resolverFields the fields to use for resolving, null for all fields * @return this, for method chaining * @throws DateTimeException if resolving one field results in a value for * another field that is in conflict */ TemporalAccessor resolve(ResolverStyle resolverStyle, Set resolverFields) { if (resolverFields != null) { fieldValues.keySet().retainAll(resolverFields); } this.resolverStyle = resolverStyle; resolveFields(); resolveTimeLenient(); crossCheck(); resolvePeriod(); resolveFractional(); resolveInstant(); return this; } //----------------------------------------------------------------------- private void resolveFields() { // resolve ChronoField resolveInstantFields(); resolveDateFields(); resolveTimeFields(); // if any other fields, handle them // any lenient date resolution should return epoch-day if (fieldValues.size() > 0) { int changedCount = 0; outer: while (changedCount < 50) { for (Map.Entry entry : fieldValues.entrySet()) { TemporalField targetField = entry.getKey(); TemporalAccessor resolvedObject = targetField.resolve(fieldValues, this, resolverStyle); if (resolvedObject != null) { if (resolvedObject instanceof ChronoZonedDateTime) { ChronoZonedDateTime czdt = (ChronoZonedDateTime) resolvedObject; if (zone == null) { zone = czdt.getZone(); } else if (zone.equals(czdt.getZone()) == false) { throw new DateTimeException("ChronoZonedDateTime must use the effective parsed zone: " + zone); } resolvedObject = czdt.toLocalDateTime(); } if (resolvedObject instanceof ChronoLocalDateTime) { ChronoLocalDateTime cldt = (ChronoLocalDateTime) resolvedObject; updateCheckConflict(cldt.toLocalTime(), Period.ZERO); updateCheckConflict(cldt.toLocalDate()); changedCount++; continue outer; // have to restart to avoid concurrent modification } if (resolvedObject instanceof ChronoLocalDate) { updateCheckConflict((ChronoLocalDate) resolvedObject); changedCount++; continue outer; // have to restart to avoid concurrent modification } if (resolvedObject instanceof LocalTime) { updateCheckConflict((LocalTime) resolvedObject, Period.ZERO); changedCount++; continue outer; // have to restart to avoid concurrent modification } throw new DateTimeException("Method resolve() can only return ChronoZonedDateTime, " + "ChronoLocalDateTime, ChronoLocalDate or LocalTime"); } else if (fieldValues.containsKey(targetField) == false) { changedCount++; continue outer; // have to restart to avoid concurrent modification } } break; } if (changedCount == 50) { // catch infinite loops throw new DateTimeException("One of the parsed fields has an incorrectly implemented resolve method"); } // if something changed then have to redo ChronoField resolve if (changedCount > 0) { resolveInstantFields(); resolveDateFields(); resolveTimeFields(); } } } private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) { Long old = fieldValues.put(changeField, changeValue); if (old != null && old.longValue() != changeValue.longValue()) { throw new DateTimeException("Conflict found: " + changeField + " " + old + " differs from " + changeField + " " + changeValue + " while resolving " + targetField); } } //----------------------------------------------------------------------- private void resolveInstantFields() { // resolve parsed instant seconds to date and time if zone available if (fieldValues.containsKey(INSTANT_SECONDS)) { if (zone != null) { resolveInstantFields0(zone); } else { Long offsetSecs = fieldValues.get(OFFSET_SECONDS); if (offsetSecs != null) { ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue()); resolveInstantFields0(offset); } } } } private void resolveInstantFields0(ZoneId selectedZone) { Instant instant = Instant.ofEpochSecond(fieldValues.remove(INSTANT_SECONDS)); ChronoZonedDateTime zdt = chrono.zonedDateTime(instant, selectedZone); updateCheckConflict(zdt.toLocalDate()); updateCheckConflict(INSTANT_SECONDS, SECOND_OF_DAY, (long) zdt.toLocalTime().toSecondOfDay()); } //----------------------------------------------------------------------- private void resolveDateFields() { updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle)); } private void updateCheckConflict(ChronoLocalDate cld) { if (date != null) { if (cld != null && date.equals(cld) == false) { throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld); } } else if (cld != null) { if (chrono.equals(cld.getChronology()) == false) { throw new DateTimeException("ChronoLocalDate must use the effective parsed chronology: " + chrono); } date = cld; } } //----------------------------------------------------------------------- private void resolveTimeFields() { // simplify fields if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) { // lenient allows anything, smart allows 0-24, strict allows 1-24 long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY); if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) { CLOCK_HOUR_OF_DAY.checkValidValue(ch); } updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch); } if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) { // lenient allows anything, smart allows 0-12, strict allows 1-12 long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM); if (resolverStyle == ResolverStyle.STRICT || (resolverStyle == ResolverStyle.SMART && ch != 0)) { CLOCK_HOUR_OF_AMPM.checkValidValue(ch); } updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch); } if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) { long ap = fieldValues.remove(AMPM_OF_DAY); long hap = fieldValues.remove(HOUR_OF_AMPM); if (resolverStyle == ResolverStyle.LENIENT) { updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, Math.addExact(Math.multiplyExact(ap, 12), hap)); } else { // STRICT or SMART AMPM_OF_DAY.checkValidValue(ap); HOUR_OF_AMPM.checkValidValue(hap); updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap); } } if (fieldValues.containsKey(NANO_OF_DAY)) { long nod = fieldValues.remove(NANO_OF_DAY); if (resolverStyle != ResolverStyle.LENIENT) { NANO_OF_DAY.checkValidValue(nod); } updateCheckConflict(NANO_OF_DAY, HOUR_OF_DAY, nod / 3600_000_000_000L); updateCheckConflict(NANO_OF_DAY, MINUTE_OF_HOUR, (nod / 60_000_000_000L) % 60); updateCheckConflict(NANO_OF_DAY, SECOND_OF_MINUTE, (nod / 1_000_000_000L) % 60); updateCheckConflict(NANO_OF_DAY, NANO_OF_SECOND, nod % 1_000_000_000L); } if (fieldValues.containsKey(MICRO_OF_DAY)) { long cod = fieldValues.remove(MICRO_OF_DAY); if (resolverStyle != ResolverStyle.LENIENT) { MICRO_OF_DAY.checkValidValue(cod); } updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1_000_000L); updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1_000_000L); } if (fieldValues.containsKey(MILLI_OF_DAY)) { long lod = fieldValues.remove(MILLI_OF_DAY); if (resolverStyle != ResolverStyle.LENIENT) { MILLI_OF_DAY.checkValidValue(lod); } updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1_000); updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1_000); } if (fieldValues.containsKey(SECOND_OF_DAY)) { long sod = fieldValues.remove(SECOND_OF_DAY); if (resolverStyle != ResolverStyle.LENIENT) { SECOND_OF_DAY.checkValidValue(sod); } updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600); updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60); updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60); } if (fieldValues.containsKey(MINUTE_OF_DAY)) { long mod = fieldValues.remove(MINUTE_OF_DAY); if (resolverStyle != ResolverStyle.LENIENT) { MINUTE_OF_DAY.checkValidValue(mod); } updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60); updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60); } // combine partial second fields strictly, leaving lenient expansion to later if (fieldValues.containsKey(NANO_OF_SECOND)) { long nos = fieldValues.get(NANO_OF_SECOND); if (resolverStyle != ResolverStyle.LENIENT) { NANO_OF_SECOND.checkValidValue(nos); } if (fieldValues.containsKey(MICRO_OF_SECOND)) { long cos = fieldValues.remove(MICRO_OF_SECOND); if (resolverStyle != ResolverStyle.LENIENT) { MICRO_OF_SECOND.checkValidValue(cos); } nos = cos * 1000 + (nos % 1000); updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, nos); } if (fieldValues.containsKey(MILLI_OF_SECOND)) { long los = fieldValues.remove(MILLI_OF_SECOND); if (resolverStyle != ResolverStyle.LENIENT) { MILLI_OF_SECOND.checkValidValue(los); } updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, los * 1_000_000L + (nos % 1_000_000L)); } } // convert to time if all four fields available (optimization) if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) && fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) { long hod = fieldValues.remove(HOUR_OF_DAY); long moh = fieldValues.remove(MINUTE_OF_HOUR); long som = fieldValues.remove(SECOND_OF_MINUTE); long nos = fieldValues.remove(NANO_OF_SECOND); resolveTime(hod, moh, som, nos); } } private void resolveTimeLenient() { // leniently create a time from incomplete information // done after everything else as it creates information from nothing // which would break updateCheckConflict(field) if (time == null) { // NANO_OF_SECOND merged with MILLI/MICRO above if (fieldValues.containsKey(MILLI_OF_SECOND)) { long los = fieldValues.remove(MILLI_OF_SECOND); if (fieldValues.containsKey(MICRO_OF_SECOND)) { // merge milli-of-second and micro-of-second for better error message long cos = los * 1_000 + (fieldValues.get(MICRO_OF_SECOND) % 1_000); updateCheckConflict(MILLI_OF_SECOND, MICRO_OF_SECOND, cos); fieldValues.remove(MICRO_OF_SECOND); fieldValues.put(NANO_OF_SECOND, cos * 1_000L); } else { // convert milli-of-second to nano-of-second fieldValues.put(NANO_OF_SECOND, los * 1_000_000L); } } else if (fieldValues.containsKey(MICRO_OF_SECOND)) { // convert micro-of-second to nano-of-second long cos = fieldValues.remove(MICRO_OF_SECOND); fieldValues.put(NANO_OF_SECOND, cos * 1_000L); } // merge hour/minute/second/nano leniently Long hod = fieldValues.get(HOUR_OF_DAY); if (hod != null) { Long moh = fieldValues.get(MINUTE_OF_HOUR); Long som = fieldValues.get(SECOND_OF_MINUTE); Long nos = fieldValues.get(NANO_OF_SECOND); // check for invalid combinations that cannot be defaulted if ((moh == null && (som != null || nos != null)) || (moh != null && som == null && nos != null)) { return; } // default as necessary and build time long mohVal = (moh != null ? moh : 0); long somVal = (som != null ? som : 0); long nosVal = (nos != null ? nos : 0); resolveTime(hod, mohVal, somVal, nosVal); fieldValues.remove(HOUR_OF_DAY); fieldValues.remove(MINUTE_OF_HOUR); fieldValues.remove(SECOND_OF_MINUTE); fieldValues.remove(NANO_OF_SECOND); } } // validate remaining if (resolverStyle != ResolverStyle.LENIENT && fieldValues.size() > 0) { for (Entry entry : fieldValues.entrySet()) { TemporalField field = entry.getKey(); if (field instanceof ChronoField && field.isTimeBased()) { ((ChronoField) field).checkValidValue(entry.getValue()); } } } } private void resolveTime(long hod, long moh, long som, long nos) { if (resolverStyle == ResolverStyle.LENIENT) { long totalNanos = Math.multiplyExact(hod, 3600_000_000_000L); totalNanos = Math.addExact(totalNanos, Math.multiplyExact(moh, 60_000_000_000L)); totalNanos = Math.addExact(totalNanos, Math.multiplyExact(som, 1_000_000_000L)); totalNanos = Math.addExact(totalNanos, nos); int excessDays = (int) Math.floorDiv(totalNanos, 86400_000_000_000L); // safe int cast long nod = Math.floorMod(totalNanos, 86400_000_000_000L); updateCheckConflict(LocalTime.ofNanoOfDay(nod), Period.ofDays(excessDays)); } else { // STRICT or SMART int mohVal = MINUTE_OF_HOUR.checkValidIntValue(moh); int nosVal = NANO_OF_SECOND.checkValidIntValue(nos); // handle 24:00 end of day if (resolverStyle == ResolverStyle.SMART && hod == 24 && mohVal == 0 && som == 0 && nosVal == 0) { updateCheckConflict(LocalTime.MIDNIGHT, Period.ofDays(1)); } else { int hodVal = HOUR_OF_DAY.checkValidIntValue(hod); int somVal = SECOND_OF_MINUTE.checkValidIntValue(som); updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal), Period.ZERO); } } } private void resolvePeriod() { // add whole days if we have both date and time if (date != null && time != null && excessDays.isZero() == false) { date = date.plus(excessDays); excessDays = Period.ZERO; } } private void resolveFractional() { // ensure fractional seconds available as ChronoField requires // resolveTimeLenient() will have merged MICRO_OF_SECOND/MILLI_OF_SECOND to NANO_OF_SECOND if (time == null && (fieldValues.containsKey(INSTANT_SECONDS) || fieldValues.containsKey(SECOND_OF_DAY) || fieldValues.containsKey(SECOND_OF_MINUTE))) { if (fieldValues.containsKey(NANO_OF_SECOND)) { long nos = fieldValues.get(NANO_OF_SECOND); fieldValues.put(MICRO_OF_SECOND, nos / 1000); fieldValues.put(MILLI_OF_SECOND, nos / 1000000); } else { fieldValues.put(NANO_OF_SECOND, 0L); fieldValues.put(MICRO_OF_SECOND, 0L); fieldValues.put(MILLI_OF_SECOND, 0L); } } } private void resolveInstant() { // add instant seconds if we have date, time and zone // Offset (if present) will be given priority over the zone. if (date != null && time != null) { Long offsetSecs = fieldValues.get(OFFSET_SECONDS); if (offsetSecs != null) { ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSecs.intValue()); long instant = date.atTime(time).atZone(offset).toEpochSecond(); fieldValues.put(INSTANT_SECONDS, instant); } else { if (zone != null) { long instant = date.atTime(time).atZone(zone).toEpochSecond(); fieldValues.put(INSTANT_SECONDS, instant); } } } } private void updateCheckConflict(LocalTime timeToSet, Period periodToSet) { if (time != null) { if (time.equals(timeToSet) == false) { throw new DateTimeException("Conflict found: Fields resolved to different times: " + time + " " + timeToSet); } if (excessDays.isZero() == false && periodToSet.isZero() == false && excessDays.equals(periodToSet) == false) { throw new DateTimeException("Conflict found: Fields resolved to different excess periods: " + excessDays + " " + periodToSet); } else { excessDays = periodToSet; } } else { time = timeToSet; excessDays = periodToSet; } } //----------------------------------------------------------------------- private void crossCheck() { // only cross-check date, time and date-time // avoid object creation if possible if (date != null) { crossCheck(date); } if (time != null) { crossCheck(time); if (date != null && fieldValues.size() > 0) { crossCheck(date.atTime(time)); } } } private void crossCheck(TemporalAccessor target) { for (Iterator> it = fieldValues.entrySet().iterator(); it.hasNext(); ) { Entry entry = it.next(); TemporalField field = entry.getKey(); if (target.isSupported(field)) { long val1; try { val1 = target.getLong(field); } catch (RuntimeException ex) { continue; } long val2 = entry.getValue(); if (val1 != val2) { throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + target); } it.remove(); } } } //----------------------------------------------------------------------- @Override public String toString() { StringBuilder buf = new StringBuilder(64); buf.append(fieldValues).append(',').append(chrono); if (zone != null) { buf.append(',').append(zone); } if (date != null || time != null) { buf.append(" resolved to "); if (date != null) { buf.append(date); if (time != null) { buf.append('T').append(time); } } else { buf.append(time); } } return buf.toString(); } }