1 /* 2 * Copyright (c) 2012, 2019, 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 */ registerChrono(Chronology chrono)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 */ registerChrono(Chronology chrono, String id)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 */ initCache()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 */ ofLocale(Locale locale)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 */ of(String id)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 */ of0(String id)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 */ getAvailableChronologies()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 */ AbstractChronology()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 resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveProlepticMonth(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveYMD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveYD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveYMAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveYMAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveYAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveYAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle)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 resolveAligned(ChronoLocalDate base, long months, long weeks, long dow)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 */ addFieldValue(Map<TemporalField, Long> fieldValues, ChronoField field, long value)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 compareTo(Chronology other)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 equals(Object obj)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 hashCode()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 toString()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 writeReplace()733 Object writeReplace() { 734 return new Ser(Ser.CHRONO_TYPE, (Serializable)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 readObject(ObjectInputStream s)744 private void readObject(ObjectInputStream s) throws ObjectStreamException { 745 throw new InvalidObjectException("Deserialization via serialization delegate"); 746 } 747 writeExternal(DataOutput out)748 void writeExternal(DataOutput out) throws IOException { 749 out.writeUTF(getId()); 750 } 751 readExternal(DataInput in)752 static Chronology readExternal(DataInput in) throws IOException { 753 String id = in.readUTF(); 754 return Chronology.of(id); 755 } 756 757 } 758