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