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 * Copyright (c) 2011-2012, Stephen Colebourne & Michael Nascimento Santos 28 * 29 * All rights reserved. 30 * 31 * Redistribution and use in source and binary forms, with or without 32 * modification, are permitted provided that the following conditions are met: 33 * 34 * * Redistributions of source code must retain the above copyright notice, 35 * this list of conditions and the following disclaimer. 36 * 37 * * Redistributions in binary form must reproduce the above copyright notice, 38 * this list of conditions and the following disclaimer in the documentation 39 * and/or other materials provided with the distribution. 40 * 41 * * Neither the name of JSR-310 nor the names of its contributors 42 * may be used to endorse or promote products derived from this software 43 * without specific prior written permission. 44 * 45 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 46 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 47 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 48 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 49 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 50 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 51 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 52 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 53 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 54 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 55 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 56 */ 57 package java.time.temporal; 58 59 import static java.time.DayOfWeek.THURSDAY; 60 import static java.time.DayOfWeek.WEDNESDAY; 61 import static java.time.temporal.ChronoField.DAY_OF_WEEK; 62 import static java.time.temporal.ChronoField.DAY_OF_YEAR; 63 import static java.time.temporal.ChronoField.EPOCH_DAY; 64 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 65 import static java.time.temporal.ChronoField.YEAR; 66 import static java.time.temporal.ChronoUnit.DAYS; 67 import static java.time.temporal.ChronoUnit.FOREVER; 68 import static java.time.temporal.ChronoUnit.MONTHS; 69 import static java.time.temporal.ChronoUnit.WEEKS; 70 import static java.time.temporal.ChronoUnit.YEARS; 71 72 import java.time.Duration; 73 import java.time.LocalDate; 74 import java.time.chrono.Chronology; 75 import java.time.chrono.IsoChronology; 76 import java.util.HashMap; 77 import java.util.Locale; 78 import java.util.Map; 79 import java.util.Objects; 80 import java.util.ResourceBundle; 81 import sun.util.locale.provider.LocaleProviderAdapter; 82 import sun.util.locale.provider.LocaleResources; 83 84 /** 85 * Fields and units specific to the ISO-8601 calendar system, 86 * including quarter-of-year and week-based-year. 87 * <p> 88 * This class defines fields and units that are specific to the ISO calendar system. 89 * 90 * <h3>Quarter of year</h3> 91 * The ISO-8601 standard is based on the standard civic 12 month year. 92 * This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4. 93 * <p> 94 * January, February and March are in Q1. 95 * April, May and June are in Q2. 96 * July, August and September are in Q3. 97 * October, November and December are in Q4. 98 * <p> 99 * The complete date is expressed using three fields: 100 * <p><ul> 101 * <li>{@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92 102 * <li>{@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the week within the week-based-year 103 * <li>{@link ChronoField#YEAR YEAR} - the standard ISO year 104 * </ul><p> 105 * 106 * <h3>Week based years</h3> 107 * The ISO-8601 standard was originally intended as a data interchange format, 108 * defining a string format for dates and times. However, it also defines an 109 * alternate way of expressing the date, based on the concept of week-based-year. 110 * <p> 111 * The date is expressed using three fields: 112 * <p><ul> 113 * <li>{@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the 114 * day-of-week from Monday (1) to Sunday (7) 115 * <li>{@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year 116 * <li>{@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year 117 * </ul><p> 118 * The week-based-year itself is defined relative to the standard ISO proleptic year. 119 * It differs from the standard year in that it always starts on a Monday. 120 * <p> 121 * The first week of a week-based-year is the first Monday-based week of the standard 122 * ISO year that has at least 4 days in the new year. 123 * <p><ul> 124 * <li>If January 1st is Monday then week 1 starts on January 1st 125 * <li>If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year 126 * <li>If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year 127 * <li>If January 1st is Thursday then week 1 starts on December 29th of the previous standard year 128 * <li>If January 1st is Friday then week 1 starts on January 4th 129 * <li>If January 1st is Saturday then week 1 starts on January 3rd 130 * <li>If January 1st is Sunday then week 1 starts on January 2nd 131 * </ul><p> 132 * There are 52 weeks in most week-based years, however on occasion there are 53 weeks. 133 * <p> 134 * For example: 135 * <p> 136 * <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;"> 137 * <caption>Examples of Week based Years</caption> 138 * <tr><th>Date</th><th>Day-of-week</th><th>Field values</th></tr> 139 * <tr><th>2008-12-28</th><td>Sunday</td><td>Week 52 of week-based-year 2008</td></tr> 140 * <tr><th>2008-12-29</th><td>Monday</td><td>Week 1 of week-based-year 2009</td></tr> 141 * <tr><th>2008-12-31</th><td>Wednesday</td><td>Week 1 of week-based-year 2009</td></tr> 142 * <tr><th>2009-01-01</th><td>Thursday</td><td>Week 1 of week-based-year 2009</td></tr> 143 * <tr><th>2009-01-04</th><td>Sunday</td><td>Week 1 of week-based-year 2009</td></tr> 144 * <tr><th>2009-01-05</th><td>Monday</td><td>Week 2 of week-based-year 2009</td></tr> 145 * </table> 146 * 147 * <h3>Specification for implementors</h3> 148 * <p> 149 * This class is immutable and thread-safe. 150 * 151 * @since 1.8 152 */ 153 public final class IsoFields { 154 155 /** 156 * The field that represents the day-of-quarter. 157 * <p> 158 * This field allows the day-of-quarter value to be queried and set. 159 * The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91 160 * in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4. 161 * <p> 162 * The day-of-quarter can only be calculated if the day-of-year, month-of-year and year 163 * are available. 164 * <p> 165 * When setting this field, the value is allowed to be partially lenient, taking any 166 * value from 1 to 92. If the quarter has less than 92 days, then day 92, and 167 * potentially day 91, is in the following quarter. 168 * <p> 169 * This unit is an immutable and thread-safe singleton. 170 */ 171 public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER; 172 /** 173 * The field that represents the quarter-of-year. 174 * <p> 175 * This field allows the quarter-of-year value to be queried and set. 176 * The quarter-of-year has values from 1 to 4. 177 * <p> 178 * The day-of-quarter can only be calculated if the month-of-year is available. 179 * <p> 180 * This unit is an immutable and thread-safe singleton. 181 */ 182 public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR; 183 /** 184 * The field that represents the week-of-week-based-year. 185 * <p> 186 * This field allows the week of the week-based-year value to be queried and set. 187 * <p> 188 * This unit is an immutable and thread-safe singleton. 189 */ 190 public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR; 191 /** 192 * The field that represents the week-based-year. 193 * <p> 194 * This field allows the week-based-year value to be queried and set. 195 * <p> 196 * This unit is an immutable and thread-safe singleton. 197 */ 198 public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR; 199 /** 200 * The unit that represents week-based-years for the purpose of addition and subtraction. 201 * <p> 202 * This allows a number of week-based-years to be added to, or subtracted from, a date. 203 * The unit is equal to either 52 or 53 weeks. 204 * The estimated duration of a week-based-year is the same as that of a standard ISO 205 * year at {@code 365.2425 Days}. 206 * <p> 207 * The rules for addition add the number of week-based-years to the existing value 208 * for the week-based-year field. If the resulting week-based-year only has 52 weeks, 209 * then the date will be in week 1 of the following week-based-year. 210 * <p> 211 * This unit is an immutable and thread-safe singleton. 212 */ 213 public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS; 214 /** 215 * Unit that represents the concept of a quarter-year. 216 * For the ISO calendar system, it is equal to 3 months. 217 * The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}. 218 * <p> 219 * This unit is an immutable and thread-safe singleton. 220 */ 221 public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS; 222 223 /** 224 * Restricted constructor. 225 */ 226 private IsoFields() { 227 throw new AssertionError("Not instantiable"); 228 } 229 230 //----------------------------------------------------------------------- 231 /** 232 * Implementation of the field. 233 */ 234 private static enum Field implements TemporalField { 235 DAY_OF_QUARTER { 236 @Override 237 public String getName() { 238 return "DayOfQuarter"; 239 } 240 @Override 241 public TemporalUnit getBaseUnit() { 242 return DAYS; 243 } 244 @Override 245 public TemporalUnit getRangeUnit() { 246 return QUARTER_YEARS; 247 } 248 @Override 249 public ValueRange range() { 250 return ValueRange.of(1, 90, 92); 251 } 252 @Override 253 public boolean isSupportedBy(TemporalAccessor temporal) { 254 return temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) && 255 temporal.isSupported(YEAR) && isIso(temporal); 256 } 257 @Override 258 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 259 if (isSupportedBy(temporal) == false) { 260 throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter"); 261 } 262 long qoy = temporal.getLong(QUARTER_OF_YEAR); 263 if (qoy == 1) { 264 long year = temporal.getLong(YEAR); 265 return (IsoChronology.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90)); 266 } else if (qoy == 2) { 267 return ValueRange.of(1, 91); 268 } else if (qoy == 3 || qoy == 4) { 269 return ValueRange.of(1, 92); 270 } // else value not from 1 to 4, so drop through 271 return range(); 272 } 273 @Override 274 public long getFrom(TemporalAccessor temporal) { 275 if (isSupportedBy(temporal) == false) { 276 throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter"); 277 } 278 int doy = temporal.get(DAY_OF_YEAR); 279 int moy = temporal.get(MONTH_OF_YEAR); 280 long year = temporal.getLong(YEAR); 281 return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year) ? 4 : 0)]; 282 } 283 @SuppressWarnings("unchecked") 284 @Override 285 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 286 // calls getFrom() to check if supported 287 long curValue = getFrom(temporal); 288 range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check 289 return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue)); 290 } 291 @Override 292 public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value) { 293 if ((temporal.isSupported(YEAR) && temporal.isSupported(QUARTER_OF_YEAR)) == false) { 294 return null; 295 } 296 int y = temporal.get(YEAR); 297 int qoy = temporal.get(QUARTER_OF_YEAR); 298 range().checkValidValue(value, this); // leniently check from 1 to 92 TODO: check 299 LocalDate date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1).plusDays(value - 1); 300 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); 301 result.put(EPOCH_DAY, date.toEpochDay()); 302 result.put(YEAR, null); 303 result.put(QUARTER_OF_YEAR, null); 304 return result; 305 } 306 }, 307 QUARTER_OF_YEAR { 308 @Override 309 public String getName() { 310 return "QuarterOfYear"; 311 } 312 @Override 313 public TemporalUnit getBaseUnit() { 314 return QUARTER_YEARS; 315 } 316 @Override 317 public TemporalUnit getRangeUnit() { 318 return YEARS; 319 } 320 @Override 321 public ValueRange range() { 322 return ValueRange.of(1, 4); 323 } 324 @Override 325 public boolean isSupportedBy(TemporalAccessor temporal) { 326 return temporal.isSupported(MONTH_OF_YEAR) && isIso(temporal); 327 } 328 @Override 329 public long getFrom(TemporalAccessor temporal) { 330 if (isSupportedBy(temporal) == false) { 331 throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear"); 332 } 333 long moy = temporal.getLong(MONTH_OF_YEAR); 334 return ((moy + 2) / 3); 335 } 336 @SuppressWarnings("unchecked") 337 @Override 338 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 339 // calls getFrom() to check if supported 340 long curValue = getFrom(temporal); 341 range().checkValidValue(newValue, this); // strictly check from 1 to 4 342 return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3); 343 } 344 }, 345 WEEK_OF_WEEK_BASED_YEAR { 346 @Override 347 public String getName() { 348 return "WeekOfWeekBasedYear"; 349 } 350 351 @Override 352 public String getDisplayName(Locale locale) { 353 Objects.requireNonNull(locale, "locale"); 354 LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() 355 .getLocaleResources(locale); 356 ResourceBundle rb = lr.getFormatData(); 357 return rb.containsKey("field.week") ? rb.getString("field.week") : getName(); 358 } 359 360 @Override 361 public TemporalUnit getBaseUnit() { 362 return WEEKS; 363 } 364 @Override 365 public TemporalUnit getRangeUnit() { 366 return WEEK_BASED_YEARS; 367 } 368 @Override 369 public ValueRange range() { 370 return ValueRange.of(1, 52, 53); 371 } 372 @Override 373 public boolean isSupportedBy(TemporalAccessor temporal) { 374 return temporal.isSupported(EPOCH_DAY) && isIso(temporal); 375 } 376 @Override 377 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 378 if (isSupportedBy(temporal) == false) { 379 throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear"); 380 } 381 return getWeekRange(LocalDate.from(temporal)); 382 } 383 @Override 384 public long getFrom(TemporalAccessor temporal) { 385 if (isSupportedBy(temporal) == false) { 386 throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear"); 387 } 388 return getWeek(LocalDate.from(temporal)); 389 } 390 @SuppressWarnings("unchecked") 391 @Override 392 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 393 // calls getFrom() to check if supported 394 range().checkValidValue(newValue, this); // lenient range 395 return (R) temporal.plus(Math.subtractExact(newValue, getFrom(temporal)), WEEKS); 396 } 397 @Override 398 public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value) { 399 if ((temporal.isSupported(WEEK_BASED_YEAR) && temporal.isSupported(DAY_OF_WEEK)) == false) { 400 return null; 401 } 402 int wby = temporal.get(WEEK_BASED_YEAR); 403 int dow = temporal.get(DAY_OF_WEEK); 404 range().checkValidValue(value, this); // lenient range 405 LocalDate date = LocalDate.of(wby, 1, 4).plusWeeks(value - 1).with(DAY_OF_WEEK, dow); 406 Map<TemporalField, Long> result = new HashMap<>(2, 1.0f); 407 result.put(EPOCH_DAY, date.toEpochDay()); 408 result.put(WEEK_BASED_YEAR, null); 409 result.put(DAY_OF_WEEK, null); 410 return result; 411 } 412 }, 413 WEEK_BASED_YEAR { 414 @Override 415 public String getName() { 416 return "WeekBasedYear"; 417 } 418 @Override 419 public TemporalUnit getBaseUnit() { 420 return WEEK_BASED_YEARS; 421 } 422 @Override 423 public TemporalUnit getRangeUnit() { 424 return FOREVER; 425 } 426 @Override 427 public ValueRange range() { 428 return YEAR.range(); 429 } 430 @Override 431 public boolean isSupportedBy(TemporalAccessor temporal) { 432 return temporal.isSupported(EPOCH_DAY) && isIso(temporal); 433 } 434 @Override 435 public long getFrom(TemporalAccessor temporal) { 436 if (isSupportedBy(temporal) == false) { 437 throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear"); 438 } 439 return getWeekBasedYear(LocalDate.from(temporal)); 440 } 441 @SuppressWarnings("unchecked") 442 @Override 443 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 444 if (isSupportedBy(temporal) == false) { 445 throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear"); 446 } 447 int newVal = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check 448 LocalDate date = LocalDate.from(temporal); 449 int week = getWeek(date); 450 date = date.withDayOfYear(180).withYear(newVal).with(WEEK_OF_WEEK_BASED_YEAR, week); 451 return (R) date.with(date); 452 } 453 }; 454 455 @Override 456 public boolean isDateBased() { 457 return true; 458 } 459 460 @Override 461 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 462 return range(); 463 } 464 465 @Override 466 public String toString() { 467 return getName(); 468 } 469 470 //------------------------------------------------------------------------- 471 private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274}; 472 473 private static boolean isIso(TemporalAccessor temporal) { 474 return Chronology.from(temporal).equals(IsoChronology.INSTANCE); 475 } 476 477 private static ValueRange getWeekRange(LocalDate date) { 478 int wby = getWeekBasedYear(date); 479 date = date.withDayOfYear(1).withYear(wby); 480 // 53 weeks if standard year starts on Thursday, or Wed in a leap year 481 if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) { 482 return ValueRange.of(1, 53); 483 } 484 return ValueRange.of(1, 52); 485 } 486 487 private static int getWeek(LocalDate date) { 488 int dow0 = date.getDayOfWeek().ordinal(); 489 int doy0 = date.getDayOfYear() - 1; 490 int doyThu0 = doy0 + (3 - dow0); // adjust to mid-week Thursday (which is 3 indexed from zero) 491 int alignedWeek = doyThu0 / 7; 492 int firstThuDoy0 = doyThu0 - (alignedWeek * 7); 493 int firstMonDoy0 = firstThuDoy0 - 3; 494 if (firstMonDoy0 < -3) { 495 firstMonDoy0 += 7; 496 } 497 if (doy0 < firstMonDoy0) { 498 return (int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum(); 499 } 500 int week = ((doy0 - firstMonDoy0) / 7) + 1; 501 if (week == 53) { 502 if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false) { 503 week = 1; 504 } 505 } 506 return week; 507 } 508 509 private static int getWeekBasedYear(LocalDate date) { 510 int year = date.getYear(); 511 int doy = date.getDayOfYear(); 512 if (doy <= 3) { 513 int dow = date.getDayOfWeek().ordinal(); 514 if (doy - dow < -2) { 515 year--; 516 } 517 } else if (doy >= 363) { 518 int dow = date.getDayOfWeek().ordinal(); 519 doy = doy - 363 - (date.isLeapYear() ? 1 : 0); 520 if (doy - dow >= 0) { 521 year++; 522 } 523 } 524 return year; 525 } 526 } 527 528 //----------------------------------------------------------------------- 529 /** 530 * Implementation of the period unit. 531 */ 532 private static enum Unit implements TemporalUnit { 533 534 /** 535 * Unit that represents the concept of a week-based-year. 536 */ 537 WEEK_BASED_YEARS("WeekBasedYears", Duration.ofSeconds(31556952L)), 538 /** 539 * Unit that represents the concept of a quarter-year. 540 */ 541 QUARTER_YEARS("QuarterYears", Duration.ofSeconds(31556952L / 4)); 542 543 private final String name; 544 private final Duration duration; 545 546 private Unit(String name, Duration estimatedDuration) { 547 this.name = name; 548 this.duration = estimatedDuration; 549 } 550 551 @Override 552 public String getName() { 553 return name; 554 } 555 556 @Override 557 public Duration getDuration() { 558 return duration; 559 } 560 561 @Override 562 public boolean isDurationEstimated() { 563 return true; 564 } 565 566 @Override 567 public boolean isSupportedBy(Temporal temporal) { 568 return temporal.isSupported(EPOCH_DAY); 569 } 570 571 @SuppressWarnings("unchecked") 572 @Override 573 public <R extends Temporal> R addTo(R temporal, long amount) { 574 switch(this) { 575 case WEEK_BASED_YEARS: 576 return (R) temporal.with(WEEK_BASED_YEAR, 577 Math.addExact(temporal.get(WEEK_BASED_YEAR), amount)); 578 case QUARTER_YEARS: 579 // no overflow (256 is multiple of 4) 580 return (R) temporal.plus(amount / 256, YEARS) 581 .plus((amount % 256) * 3, MONTHS); 582 default: 583 throw new IllegalStateException("Unreachable"); 584 } 585 } 586 587 @Override 588 public long between(Temporal temporal1, Temporal temporal2) { 589 switch(this) { 590 case WEEK_BASED_YEARS: 591 return Math.subtractExact(temporal2.getLong(WEEK_BASED_YEAR), 592 temporal1.getLong(WEEK_BASED_YEAR)); 593 case QUARTER_YEARS: 594 return temporal1.periodUntil(temporal2, MONTHS) / 3; 595 default: 596 throw new IllegalStateException("Unreachable"); 597 } 598 } 599 600 @Override 601 public String toString() { 602 return getName(); 603 604 } 605 } 606 }