1 /*
   2  * Copyright (c) 2012, 2018, 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) 2012, 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.chrono;
  63 
  64 import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH;
  65 import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR;
  66 import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH;
  67 import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR;
  68 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
  69 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
  70 import static java.time.temporal.ChronoField.DAY_OF_YEAR;
  71 import static java.time.temporal.ChronoField.EPOCH_DAY;
  72 import static java.time.temporal.ChronoField.ERA;
  73 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
  74 import static java.time.temporal.ChronoField.PROLEPTIC_MONTH;
  75 import static java.time.temporal.ChronoField.YEAR;
  76 import static java.time.temporal.ChronoField.YEAR_OF_ERA;
  77 import static java.time.temporal.ChronoUnit.DAYS;
  78 import static java.time.temporal.ChronoUnit.MONTHS;
  79 import static java.time.temporal.ChronoUnit.WEEKS;
  80 import static java.time.temporal.TemporalAdjusters.nextOrSame;
  81 
  82 import java.io.DataInput;
  83 import java.io.DataOutput;
  84 import java.io.IOException;
  85 import java.io.InvalidObjectException;
  86 import java.io.ObjectInputStream;
  87 import java.io.ObjectStreamException;
  88 import java.io.Serializable;
  89 import java.time.DateTimeException;
  90 import java.time.DayOfWeek;
  91 import java.time.format.ResolverStyle;
  92 import java.time.temporal.ChronoField;
  93 import java.time.temporal.TemporalAdjusters;
  94 import java.time.temporal.TemporalField;
  95 import java.time.temporal.ValueRange;
  96 import java.util.Comparator;
  97 import java.util.HashSet;
  98 import java.util.List;
  99 import java.util.Locale;
 100 import java.util.Map;
 101 import java.util.Objects;
 102 import java.util.ServiceLoader;
 103 import java.util.Set;
 104 import java.util.concurrent.ConcurrentHashMap;
 105 
 106 import sun.util.logging.PlatformLogger;
 107 
 108 /**
 109  * An abstract implementation of a calendar system, used to organize and identify dates.
 110  * <p>
 111  * The main date and time API is built on the ISO calendar system.
 112  * The chronology operates behind the scenes to represent the general concept of a calendar system.
 113  * <p>
 114  * See {@link Chronology} for more details.
 115  *
 116  * @implSpec
 117  * This class is separated from the {@code Chronology} interface so that the static methods
 118  * are not inherited. While {@code Chronology} can be implemented directly, it is strongly
 119  * recommended to extend this abstract class instead.
 120  * <p>
 121  * This class must be implemented with care to ensure other classes operate correctly.
 122  * All implementations that can be instantiated must be final, immutable and thread-safe.
 123  * Subclasses should be Serializable wherever possible.
 124  *
 125  * @since 1.8
 126  */
 127 public abstract class AbstractChronology implements Chronology {
 128 
 129     /**
 130      * Map of available calendars by ID.
 131      */
 132     private static final ConcurrentHashMap<String, Chronology> CHRONOS_BY_ID = new ConcurrentHashMap<>();
 133     /**
 134      * Map of available calendars by calendar type.
 135      */
 136     private static final ConcurrentHashMap<String, Chronology> CHRONOS_BY_TYPE = new ConcurrentHashMap<>();
 137 
 138     /**
 139      * Register a Chronology by its ID and type for lookup by {@link #of(String)}.
 140      * Chronologies must not be registered until they are completely constructed.
 141      * Specifically, not in the constructor of Chronology.
 142      *
 143      * @param chrono the chronology to register; not null
 144      * @return the already registered Chronology if any, may be null
 145      */
 146     static Chronology registerChrono(Chronology chrono) {
 147         return registerChrono(chrono, chrono.getId());
 148     }
 149 
 150     /**
 151      * Register a Chronology by ID and type for lookup by {@link #of(String)}.
 152      * Chronos must not be registered until they are completely constructed.
 153      * Specifically, not in the constructor of Chronology.
 154      *
 155      * @param chrono the chronology to register; not null
 156      * @param id the ID to register the chronology; not null
 157      * @return the already registered Chronology if any, may be null
 158      */
 159     static Chronology registerChrono(Chronology chrono, String id) {
 160         Chronology prev = CHRONOS_BY_ID.putIfAbsent(id, chrono);
 161         if (prev == null) {
 162             String type = chrono.getCalendarType();
 163             if (type != null) {
 164                 CHRONOS_BY_TYPE.putIfAbsent(type, chrono);
 165             }
 166         }
 167         return prev;
 168     }
 169 
 170     /**
 171      * Initialization of the maps from id and type to Chronology.
 172      * The ServiceLoader is used to find and register any implementations
 173      * of {@link java.time.chrono.AbstractChronology} found in the bootclass loader.
 174      * The built-in chronologies are registered explicitly.
 175      * Calendars configured via the Thread's context classloader are local
 176      * to that thread and are ignored.
 177      * <p>
 178      * The initialization is done only once using the registration
 179      * of the IsoChronology as the test and the final step.
 180      * Multiple threads may perform the initialization concurrently.
 181      * Only the first registration of each Chronology is retained by the
 182      * ConcurrentHashMap.
 183      * @return true if the cache was initialized
 184      */
 185     private static boolean initCache() {
 186         if (CHRONOS_BY_ID.get("ISO") == null) {
 187             // Initialization is incomplete
 188 
 189             // Register built-in Chronologies
 190             registerChrono(HijrahChronology.INSTANCE);
 191             registerChrono(JapaneseChronology.INSTANCE);
 192             registerChrono(MinguoChronology.INSTANCE);
 193             registerChrono(ThaiBuddhistChronology.INSTANCE);
 194 
 195             // Register Chronologies from the ServiceLoader
 196             @SuppressWarnings("rawtypes")
 197             ServiceLoader<AbstractChronology> loader =  ServiceLoader.load(AbstractChronology.class, null);
 198             for (AbstractChronology chrono : loader) {
 199                 String id = chrono.getId();
 200                 if (id.equals("ISO") || registerChrono(chrono) != null) {
 201                     // Log the attempt to replace an existing Chronology
 202                     PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
 203                     logger.warning("Ignoring duplicate Chronology, from ServiceLoader configuration "  + id);
 204                 }
 205             }
 206 
 207             // finally, register IsoChronology to mark initialization is complete
 208             registerChrono(IsoChronology.INSTANCE);
 209             return true;
 210         }
 211         return false;
 212     }
 213 
 214     //-----------------------------------------------------------------------
 215     /**
 216      * Obtains an instance of {@code Chronology} from a locale.
 217      * <p>
 218      * See {@link Chronology#ofLocale(Locale)}.
 219      *
 220      * @param locale  the locale to use to obtain the calendar system, not null
 221      * @return the calendar system associated with the locale, not null
 222      * @throws java.time.DateTimeException if the locale-specified calendar cannot be found
 223      */
 224     static Chronology ofLocale(Locale locale) {
 225         Objects.requireNonNull(locale, "locale");
 226         String type = locale.getUnicodeLocaleType("ca");
 227         if (type == null || "iso".equals(type) || "iso8601".equals(type)) {
 228             return IsoChronology.INSTANCE;
 229         }
 230         // Not pre-defined; lookup by the type
 231         do {
 232             Chronology chrono = CHRONOS_BY_TYPE.get(type);
 233             if (chrono != null) {
 234                 return chrono;
 235             }
 236             // If not found, do the initialization (once) and repeat the lookup
 237         } while (initCache());
 238 
 239         // Look for a Chronology using ServiceLoader of the Thread's ContextClassLoader
 240         // Application provided Chronologies must not be cached
 241         @SuppressWarnings("rawtypes")
 242         ServiceLoader<Chronology> loader = ServiceLoader.load(Chronology.class);
 243         for (Chronology chrono : loader) {
 244             if (type.equals(chrono.getCalendarType())) {
 245                 return chrono;
 246             }
 247         }
 248         throw new DateTimeException("Unknown calendar system: " + type);
 249     }
 250 
 251     //-----------------------------------------------------------------------
 252     /**
 253      * Obtains an instance of {@code Chronology} from a chronology ID or
 254      * calendar system type.
 255      * <p>
 256      * See {@link Chronology#of(String)}.
 257      *
 258      * @param id  the chronology ID or calendar system type, not null
 259      * @return the chronology with the identifier requested, not null
 260      * @throws java.time.DateTimeException if the chronology cannot be found
 261      */
 262     static Chronology of(String id) {
 263         Objects.requireNonNull(id, "id");
 264         do {
 265             Chronology chrono = of0(id);
 266             if (chrono != null) {
 267                 return chrono;
 268             }
 269             // If not found, do the initialization (once) and repeat the lookup
 270         } while (initCache());
 271 
 272         // Look for a Chronology using ServiceLoader of the Thread's ContextClassLoader
 273         // Application provided Chronologies must not be cached
 274         @SuppressWarnings("rawtypes")
 275         ServiceLoader<Chronology> loader = ServiceLoader.load(Chronology.class);
 276         for (Chronology chrono : loader) {
 277             if (id.equals(chrono.getId()) || id.equals(chrono.getCalendarType())) {
 278                 return chrono;
 279             }
 280         }
 281         throw new DateTimeException("Unknown chronology: " + id);
 282     }
 283 
 284     /**
 285      * Obtains an instance of {@code Chronology} from a chronology ID or
 286      * calendar system type.
 287      *
 288      * @param id  the chronology ID or calendar system type, not null
 289      * @return the chronology with the identifier requested, or {@code null} if not found
 290      */
 291     private static Chronology of0(String id) {
 292         Chronology chrono = CHRONOS_BY_ID.get(id);
 293         if (chrono == null) {
 294             chrono = CHRONOS_BY_TYPE.get(id);
 295         }
 296         return chrono;
 297     }
 298 
 299     /**
 300      * Returns the available chronologies.
 301      * <p>
 302      * Each returned {@code Chronology} is available for use in the system.
 303      * The set of chronologies includes the system chronologies and
 304      * any chronologies provided by the application via ServiceLoader
 305      * configuration.
 306      *
 307      * @return the independent, modifiable set of the available chronology IDs, not null
 308      */
 309     static Set<Chronology> getAvailableChronologies() {
 310         initCache();       // force initialization
 311         HashSet<Chronology> chronos = new HashSet<>(CHRONOS_BY_ID.values());
 312 
 313         /// Add in Chronologies from the ServiceLoader configuration
 314         @SuppressWarnings("rawtypes")
 315         ServiceLoader<Chronology> loader = ServiceLoader.load(Chronology.class);
 316         for (Chronology chrono : loader) {
 317             chronos.add(chrono);
 318         }
 319         return chronos;
 320     }
 321 
 322     //-----------------------------------------------------------------------
 323     /**
 324      * Creates an instance.
 325      */
 326     protected AbstractChronology() {
 327     }
 328 
 329     //-----------------------------------------------------------------------
 330     /**
 331      * Resolves parsed {@code ChronoField} values into a date during parsing.
 332      * <p>
 333      * Most {@code TemporalField} implementations are resolved using the
 334      * resolve method on the field. By contrast, the {@code ChronoField} class
 335      * defines fields that only have meaning relative to the chronology.
 336      * As such, {@code ChronoField} date fields are resolved here in the
 337      * context of a specific chronology.
 338      * <p>
 339      * {@code ChronoField} instances are resolved by this method, which may
 340      * be overridden in subclasses.
 341      * <ul>
 342      * <li>{@code EPOCH_DAY} - If present, this is converted to a date and
 343      *  all other date fields are then cross-checked against the date.
 344      * <li>{@code PROLEPTIC_MONTH} - If present, then it is split into the
 345      *  {@code YEAR} and {@code MONTH_OF_YEAR}. If the mode is strict or smart
 346      *  then the field is validated.
 347      * <li>{@code YEAR_OF_ERA} and {@code ERA} - If both are present, then they
 348      *  are combined to form a {@code YEAR}. In lenient mode, the {@code YEAR_OF_ERA}
 349      *  range is not validated, in smart and strict mode it is. The {@code ERA} is
 350      *  validated for range in all three modes. If only the {@code YEAR_OF_ERA} is
 351      *  present, and the mode is smart or lenient, then the last available era
 352      *  is assumed. In strict mode, no era is assumed and the {@code YEAR_OF_ERA} is
 353      *  left untouched. If only the {@code ERA} is present, then it is left untouched.
 354      * <li>{@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} -
 355      *  If all three are present, then they are combined to form a date.
 356      *  In all three modes, the {@code YEAR} is validated.
 357      *  If the mode is smart or strict, then the month and day are validated.
 358      *  If the mode is lenient, then the date is combined in a manner equivalent to
 359      *  creating a date on the first day of the first month in the requested year,
 360      *  then adding the difference in months, then the difference in days.
 361      *  If the mode is smart, and the day-of-month is greater than the maximum for
 362      *  the year-month, then the day-of-month is adjusted to the last day-of-month.
 363      *  If the mode is strict, then the three fields must form a valid date.
 364      * <li>{@code YEAR} and {@code DAY_OF_YEAR} -
 365      *  If both are present, then they are combined to form a date.
 366      *  In all three modes, the {@code YEAR} is validated.
 367      *  If the mode is lenient, then the date is combined in a manner equivalent to
 368      *  creating a date on the first day of the requested year, then adding
 369      *  the difference in days.
 370      *  If the mode is smart or strict, then the two fields must form a valid date.
 371      * <li>{@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and
 372      *  {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} -
 373      *  If all four are present, then they are combined to form a date.
 374      *  In all three modes, the {@code YEAR} is validated.
 375      *  If the mode is lenient, then the date is combined in a manner equivalent to
 376      *  creating a date on the first day of the first month in the requested year, then adding
 377      *  the difference in months, then the difference in weeks, then in days.
 378      *  If the mode is smart or strict, then the all four fields are validated to
 379      *  their outer ranges. The date is then combined in a manner equivalent to
 380      *  creating a date on the first day of the requested year and month, then adding
 381      *  the amount in weeks and days to reach their values. If the mode is strict,
 382      *  the date is additionally validated to check that the day and week adjustment
 383      *  did not change the month.
 384      * <li>{@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and
 385      *  {@code DAY_OF_WEEK} - If all four are present, then they are combined to
 386      *  form a date. The approach is the same as described above for
 387      *  years, months and weeks in {@code ALIGNED_DAY_OF_WEEK_IN_MONTH}.
 388      *  The day-of-week is adjusted as the next or same matching day-of-week once
 389      *  the years, months and weeks have been handled.
 390      * <li>{@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} -
 391      *  If all three are present, then they are combined to form a date.
 392      *  In all three modes, the {@code YEAR} is validated.
 393      *  If the mode is lenient, then the date is combined in a manner equivalent to
 394      *  creating a date on the first day of the requested year, then adding
 395      *  the difference in weeks, then in days.
 396      *  If the mode is smart or strict, then the all three fields are validated to
 397      *  their outer ranges. The date is then combined in a manner equivalent to
 398      *  creating a date on the first day of the requested year, then adding
 399      *  the amount in weeks and days to reach their values. If the mode is strict,
 400      *  the date is additionally validated to check that the day and week adjustment
 401      *  did not change the year.
 402      * <li>{@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code DAY_OF_WEEK} -
 403      *  If all three are present, then they are combined to form a date.
 404      *  The approach is the same as described above for years and weeks in
 405      *  {@code ALIGNED_DAY_OF_WEEK_IN_YEAR}. The day-of-week is adjusted as the
 406      *  next or same matching day-of-week once the years and weeks have been handled.
 407      * </ul>
 408      * <p>
 409      * The default implementation is suitable for most calendar systems.
 410      * If {@link java.time.temporal.ChronoField#YEAR_OF_ERA} is found without an {@link java.time.temporal.ChronoField#ERA}
 411      * then the last era in {@link #eras()} is used.
 412      * The implementation assumes a 7 day week, that the first day-of-month
 413      * has the value 1, that first day-of-year has the value 1, and that the
 414      * first of the month and year always exists.
 415      *
 416      * @param fieldValues  the map of fields to values, which can be updated, not null
 417      * @param resolverStyle  the requested type of resolve, not null
 418      * @return the resolved date, null if insufficient information to create a date
 419      * @throws java.time.DateTimeException if the date cannot be resolved, typically
 420      *  because of a conflict in the input data
 421      */
 422     @Override
 423     public ChronoLocalDate resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 424         // check epoch-day before inventing era
 425         if (fieldValues.containsKey(EPOCH_DAY)) {
 426             return dateEpochDay(fieldValues.remove(EPOCH_DAY));
 427         }
 428 
 429         // fix proleptic month before inventing era
 430         resolveProlepticMonth(fieldValues, resolverStyle);
 431 
 432         // invent era if necessary to resolve year-of-era
 433         ChronoLocalDate resolved = resolveYearOfEra(fieldValues, resolverStyle);
 434         if (resolved != null) {
 435             return resolved;
 436         }
 437 
 438         // build date
 439         if (fieldValues.containsKey(YEAR)) {
 440             if (fieldValues.containsKey(MONTH_OF_YEAR)) {
 441                 if (fieldValues.containsKey(DAY_OF_MONTH)) {
 442                     return resolveYMD(fieldValues, resolverStyle);
 443                 }
 444                 if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) {
 445                     if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
 446                         return resolveYMAA(fieldValues, resolverStyle);
 447                     }
 448                     if (fieldValues.containsKey(DAY_OF_WEEK)) {
 449                         return resolveYMAD(fieldValues, resolverStyle);
 450                     }
 451                 }
 452             }
 453             if (fieldValues.containsKey(DAY_OF_YEAR)) {
 454                 return resolveYD(fieldValues, resolverStyle);
 455             }
 456             if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) {
 457                 if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
 458                     return resolveYAA(fieldValues, resolverStyle);
 459                 }
 460                 if (fieldValues.containsKey(DAY_OF_WEEK)) {
 461                     return resolveYAD(fieldValues, resolverStyle);
 462                 }
 463             }
 464         }
 465         return null;
 466     }
 467 
 468     void resolveProlepticMonth(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 469         Long pMonth = fieldValues.remove(PROLEPTIC_MONTH);
 470         if (pMonth != null) {
 471             if (resolverStyle != ResolverStyle.LENIENT) {
 472                 PROLEPTIC_MONTH.checkValidValue(pMonth);
 473             }
 474             // first day-of-month is likely to be safest for setting proleptic-month
 475             // cannot add to year zero, as not all chronologies have a year zero
 476             ChronoLocalDate chronoDate = dateNow()
 477                     .with(DAY_OF_MONTH, 1).with(PROLEPTIC_MONTH, pMonth);
 478             addFieldValue(fieldValues, MONTH_OF_YEAR, chronoDate.get(MONTH_OF_YEAR));
 479             addFieldValue(fieldValues, YEAR, chronoDate.get(YEAR));
 480         }
 481     }
 482 
 483     ChronoLocalDate resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 484         Long yoeLong = fieldValues.remove(YEAR_OF_ERA);
 485         if (yoeLong != null) {
 486             Long eraLong = fieldValues.remove(ERA);
 487             int yoe;
 488             if (resolverStyle != ResolverStyle.LENIENT) {
 489                 yoe = range(YEAR_OF_ERA).checkValidIntValue(yoeLong, YEAR_OF_ERA);
 490             } else {
 491                 yoe = Math.toIntExact(yoeLong);
 492             }
 493             if (eraLong != null) {
 494                 Era eraObj = eraOf(range(ERA).checkValidIntValue(eraLong, ERA));
 495                 addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe));
 496             } else {
 497                 if (fieldValues.containsKey(YEAR)) {
 498                     int year = range(YEAR).checkValidIntValue(fieldValues.get(YEAR), YEAR);
 499                     ChronoLocalDate chronoDate = dateYearDay(year, 1);
 500                     addFieldValue(fieldValues, YEAR, prolepticYear(chronoDate.getEra(), yoe));
 501                 } else if (resolverStyle == ResolverStyle.STRICT) {
 502                     // do not invent era if strict
 503                     // reinstate the field removed earlier, no cross-check issues
 504                     fieldValues.put(YEAR_OF_ERA, yoeLong);
 505                 } else {
 506                     List<Era> eras = eras();
 507                     if (eras.isEmpty()) {
 508                         addFieldValue(fieldValues, YEAR, yoe);
 509                     } else {
 510                         Era eraObj = eras.get(eras.size() - 1);
 511                         addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe));
 512                     }
 513                 }
 514             }
 515         } else if (fieldValues.containsKey(ERA)) {
 516             range(ERA).checkValidValue(fieldValues.get(ERA), ERA);  // always validated
 517         }
 518         return null;
 519     }
 520 
 521     ChronoLocalDate resolveYMD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 522         int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
 523         if (resolverStyle == ResolverStyle.LENIENT) {
 524             long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
 525             long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1);
 526             return date(y, 1, 1).plus(months, MONTHS).plus(days, DAYS);
 527         }
 528         int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
 529         ValueRange domRange = range(DAY_OF_MONTH);
 530         int dom = domRange.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH), DAY_OF_MONTH);
 531         if (resolverStyle == ResolverStyle.SMART) {  // previous valid
 532             try {
 533                 return date(y, moy, dom);
 534             } catch (DateTimeException ex) {
 535                 return date(y, moy, 1).with(TemporalAdjusters.lastDayOfMonth());
 536             }
 537         }
 538         return date(y, moy, dom);
 539     }
 540 
 541     ChronoLocalDate resolveYD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 542         int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
 543         if (resolverStyle == ResolverStyle.LENIENT) {
 544             long days = Math.subtractExact(fieldValues.remove(DAY_OF_YEAR), 1);
 545             return dateYearDay(y, 1).plus(days, DAYS);
 546         }
 547         int doy = range(DAY_OF_YEAR).checkValidIntValue(fieldValues.remove(DAY_OF_YEAR), DAY_OF_YEAR);
 548         return dateYearDay(y, doy);  // smart is same as strict
 549     }
 550 
 551     ChronoLocalDate resolveYMAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 552         int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
 553         if (resolverStyle == ResolverStyle.LENIENT) {
 554             long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
 555             long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1);
 556             long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), 1);
 557             return date(y, 1, 1).plus(months, MONTHS).plus(weeks, WEEKS).plus(days, DAYS);
 558         }
 559         int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
 560         int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH);
 561         int ad = range(ALIGNED_DAY_OF_WEEK_IN_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), ALIGNED_DAY_OF_WEEK_IN_MONTH);
 562         ChronoLocalDate date = date(y, moy, 1).plus((aw - 1) * 7 + (ad - 1), DAYS);
 563         if (resolverStyle == ResolverStyle.STRICT && date.get(MONTH_OF_YEAR) != moy) {
 564             throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");
 565         }
 566         return date;
 567     }
 568 
 569     ChronoLocalDate resolveYMAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 570         int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
 571         if (resolverStyle == ResolverStyle.LENIENT) {
 572             long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
 573             long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1);
 574             long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1);
 575             return resolveAligned(date(y, 1, 1), months, weeks, dow);
 576         }
 577         int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
 578         int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH);
 579         int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK);
 580         ChronoLocalDate date = date(y, moy, 1).plus((aw - 1) * 7, DAYS).with(nextOrSame(DayOfWeek.of(dow)));
 581         if (resolverStyle == ResolverStyle.STRICT && date.get(MONTH_OF_YEAR) != moy) {
 582             throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");
 583         }
 584         return date;
 585     }
 586 
 587     ChronoLocalDate resolveYAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 588         int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
 589         if (resolverStyle == ResolverStyle.LENIENT) {
 590             long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1);
 591             long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), 1);
 592             return dateYearDay(y, 1).plus(weeks, WEEKS).plus(days, DAYS);
 593         }
 594         int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR);
 595         int ad = range(ALIGNED_DAY_OF_WEEK_IN_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), ALIGNED_DAY_OF_WEEK_IN_YEAR);
 596         ChronoLocalDate date = dateYearDay(y, 1).plus((aw - 1) * 7 + (ad - 1), DAYS);
 597         if (resolverStyle == ResolverStyle.STRICT && date.get(YEAR) != y) {
 598             throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");
 599         }
 600         return date;
 601     }
 602 
 603     ChronoLocalDate resolveYAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
 604         int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
 605         if (resolverStyle == ResolverStyle.LENIENT) {
 606             long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1);
 607             long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1);
 608             return resolveAligned(dateYearDay(y, 1), 0, weeks, dow);
 609         }
 610         int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR);
 611         int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK);
 612         ChronoLocalDate date = dateYearDay(y, 1).plus((aw - 1) * 7, DAYS).with(nextOrSame(DayOfWeek.of(dow)));
 613         if (resolverStyle == ResolverStyle.STRICT && date.get(YEAR) != y) {
 614             throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");
 615         }
 616         return date;
 617     }
 618 
 619     ChronoLocalDate resolveAligned(ChronoLocalDate base, long months, long weeks, long dow) {
 620         ChronoLocalDate date = base.plus(months, MONTHS).plus(weeks, WEEKS);
 621         if (dow > 7) {
 622             date = date.plus((dow - 1) / 7, WEEKS);
 623             dow = ((dow - 1) % 7) + 1;
 624         } else if (dow < 1) {
 625             date = date.plus(Math.subtractExact(dow,  7) / 7, WEEKS);
 626             dow = ((dow + 6) % 7) + 1;
 627         }
 628         return date.with(nextOrSame(DayOfWeek.of((int) dow)));
 629     }
 630 
 631     /**
 632      * Adds a field-value pair to the map, checking for conflicts.
 633      * <p>
 634      * If the field is not already present, then the field-value pair is added to the map.
 635      * If the field is already present and it has the same value as that specified, no action occurs.
 636      * If the field is already present and it has a different value to that specified, then
 637      * an exception is thrown.
 638      *
 639      * @param field  the field to add, not null
 640      * @param value  the value to add, not null
 641      * @throws java.time.DateTimeException if the field is already present with a different value
 642      */
 643     void addFieldValue(Map<TemporalField, Long> fieldValues, ChronoField field, long value) {
 644         Long old = fieldValues.get(field);  // check first for better error message
 645         if (old != null && old.longValue() != value) {
 646             throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value);
 647         }
 648         fieldValues.put(field, value);
 649     }
 650 
 651     //-----------------------------------------------------------------------
 652     /**
 653      * Compares this chronology to another chronology.
 654      * <p>
 655      * The comparison order first by the chronology ID string, then by any
 656      * additional information specific to the subclass.
 657      * It is "consistent with equals", as defined by {@link Comparable}.
 658      *
 659      * @implSpec
 660      * This implementation compares the chronology ID.
 661      * Subclasses must compare any additional state that they store.
 662      *
 663      * @param other  the other chronology to compare to, not null
 664      * @return the comparator value, negative if less, positive if greater
 665      */
 666     @Override
 667     public int compareTo(Chronology other) {
 668         return getId().compareTo(other.getId());
 669     }
 670 
 671     /**
 672      * Checks if this chronology is equal to another chronology.
 673      * <p>
 674      * The comparison is based on the entire state of the object.
 675      *
 676      * @implSpec
 677      * This implementation checks the type and calls
 678      * {@link #compareTo(java.time.chrono.Chronology)}.
 679      *
 680      * @param obj  the object to check, null returns false
 681      * @return true if this is equal to the other chronology
 682      */
 683     @Override
 684     public boolean equals(Object obj) {
 685         if (this == obj) {
 686            return true;
 687         }
 688         if (obj instanceof AbstractChronology) {
 689             return compareTo((AbstractChronology) obj) == 0;
 690         }
 691         return false;
 692     }
 693 
 694     /**
 695      * A hash code for this chronology.
 696      * <p>
 697      * The hash code should be based on the entire state of the object.
 698      *
 699      * @implSpec
 700      * This implementation is based on the chronology ID and class.
 701      * Subclasses should add any additional state that they store.
 702      *
 703      * @return a suitable hash code
 704      */
 705     @Override
 706     public int hashCode() {
 707         return getClass().hashCode() ^ getId().hashCode();
 708     }
 709 
 710     //-----------------------------------------------------------------------
 711     /**
 712      * Outputs this chronology as a {@code String}, using the chronology ID.
 713      *
 714      * @return a string representation of this chronology, not null
 715      */
 716     @Override
 717     public String toString() {
 718         return getId();
 719     }
 720 
 721     //-----------------------------------------------------------------------
 722     /**
 723      * Writes the Chronology using a
 724      * <a href="{@docRoot}/serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.
 725      * <pre>
 726      *  out.writeByte(1);  // identifies this as a Chronology
 727      *  out.writeUTF(getId());
 728      * </pre>
 729      *
 730      * @return the instance of {@code Ser}, not null
 731      */
 732     @java.io.Serial
 733     Object writeReplace() {
 734         return new Ser(Ser.CHRONO_TYPE, this);
 735     }
 736 
 737     /**
 738      * Defend against malicious streams.
 739      *
 740      * @param s the stream to read
 741      * @throws java.io.InvalidObjectException always
 742      */
 743     @java.io.Serial
 744     private void readObject(ObjectInputStream s) throws ObjectStreamException {
 745         throw new InvalidObjectException("Deserialization via serialization delegate");
 746     }
 747 
 748     void writeExternal(DataOutput out) throws IOException {
 749         out.writeUTF(getId());
 750     }
 751 
 752     static Chronology readExternal(DataInput in) throws IOException {
 753         String id = in.readUTF();
 754         return Chronology.of(id);
 755     }
 756 
 757 }