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.DateTimeException;
  73 import java.time.Duration;
  74 import java.time.LocalDate;
  75 import java.time.format.DateTimeBuilder;
  76 
  77 /**
  78  * Fields and units specific to the ISO-8601 calendar system,
  79  * including quarter-of-year and week-based-year.
  80  * <p>
  81  * This class defines fields and units that are specific to the ISO calendar system.
  82  *
  83  * <h3>Quarter of year</h3>
  84  * The ISO-8601 standard is based on the standard civic 12 month year.
  85  * This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4.
  86  * <p>
  87  * January, February and March are in Q1.
  88  * April, May and June are in Q2.
  89  * July, August and September are in Q3.
  90  * October, November and December are in Q4.
  91  * <p>
  92  * The complete date is expressed using three fields:
  93  * <p><ul>
  94  * <li>{@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92
  95  * <li>{@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the week within the week-based-year
  96  * <li>{@link ChronoField#YEAR YEAR} - the standard ISO year
  97  * </ul><p>
  98  *
  99  * <h3>Week based years</h3>
 100  * The ISO-8601 standard was originally intended as a data interchange format,
 101  * defining a string format for dates and times. However, it also defines an
 102  * alternate way of expressing the date, based on the concept of week-based-year.
 103  * <p>
 104  * The date is expressed using three fields:
 105  * <p><ul>
 106  * <li>{@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the
 107  *  day-of-week from Monday (1) to Sunday (7)
 108  * <li>{@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year
 109  * <li>{@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year
 110  * </ul><p>
 111  * The week-based-year itself is defined relative to the standard ISO proleptic year.
 112  * It differs from the standard year in that it always starts on a Monday.
 113  * <p>
 114  * The first week of a week-based-year is the first Monday-based week of the standard
 115  * ISO year that has at least 4 days in the new year.
 116  * <p><ul>
 117  * <li>If January 1st is Monday then week 1 starts on January 1st
 118  * <li>If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year
 119  * <li>If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year
 120  * <li>If January 1st is Thursday then week 1 starts on December 29th of the previous standard year
 121  * <li>If January 1st is Friday then week 1 starts on January 4th
 122  * <li>If January 1st is Saturday then week 1 starts on January 3rd
 123  * <li>If January 1st is Sunday then week 1 starts on January 2nd
 124  * </ul><p>
 125  * There are 52 weeks in most week-based years, however on occasion there are 53 weeks.
 126  * <p>
 127  * For example:
 128  * <p>
 129  * <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;">
 130  * <caption>Examples of Week based Years</caption>
 131  * <tr><th>Date</th><th>Day-of-week</th><th>Field values</th></tr>
 132  * <tr><th>2008-12-28</th><td>Sunday</td><td>Week 52 of week-based-year 2008</td></tr>
 133  * <tr><th>2008-12-29</th><td>Monday</td><td>Week 1 of week-based-year 2009</td></tr>
 134  * <tr><th>2008-12-31</th><td>Wednesday</td><td>Week 1 of week-based-year 2009</td></tr>
 135  * <tr><th>2009-01-01</th><td>Thursday</td><td>Week 1 of week-based-year 2009</td></tr>
 136  * <tr><th>2009-01-04</th><td>Sunday</td><td>Week 1 of week-based-year 2009</td></tr>
 137  * <tr><th>2009-01-05</th><td>Monday</td><td>Week 2 of week-based-year 2009</td></tr>
 138  * </table>
 139  *
 140  * <h3>Specification for implementors</h3>
 141  * <p>
 142  * This class is immutable and thread-safe.
 143  *
 144  * @since 1.8
 145  */
 146 public final class ISOFields {
 147 
 148     /**
 149      * The field that represents the day-of-quarter.
 150      * <p>
 151      * This field allows the day-of-quarter value to be queried and set.
 152      * The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91
 153      * in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4.
 154      * <p>
 155      * The day-of-quarter can only be calculated if the day-of-year, month-of-year and year
 156      * are available.
 157      * <p>
 158      * When setting this field, the value is allowed to be partially lenient, taking any
 159      * value from 1 to 92. If the quarter has less than 92 days, then day 92, and
 160      * potentially day 91, is in the following quarter.
 161      * <p>
 162      * This unit is an immutable and thread-safe singleton.
 163      */
 164     public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER;
 165     /**
 166      * The field that represents the quarter-of-year.
 167      * <p>
 168      * This field allows the quarter-of-year value to be queried and set.
 169      * The quarter-of-year has values from 1 to 4.
 170      * <p>
 171      * The day-of-quarter can only be calculated if the month-of-year is available.
 172      * <p>
 173      * This unit is an immutable and thread-safe singleton.
 174      */
 175     public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR;
 176     /**
 177      * The field that represents the week-of-week-based-year.
 178      * <p>
 179      * This field allows the week of the week-based-year value to be queried and set.
 180      * <p>
 181      * This unit is an immutable and thread-safe singleton.
 182      */
 183     public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR;
 184     /**
 185      * The field that represents the week-based-year.
 186      * <p>
 187      * This field allows the week-based-year value to be queried and set.
 188      * <p>
 189      * This unit is an immutable and thread-safe singleton.
 190      */
 191     public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR;
 192     /**
 193      * The unit that represents week-based-years for the purpose of addition and subtraction.
 194      * <p>
 195      * This allows a number of week-based-years to be added to, or subtracted from, a date.
 196      * The unit is equal to either 52 or 53 weeks.
 197      * The estimated duration of a week-based-year is the same as that of a standard ISO
 198      * year at {@code 365.2425 Days}.
 199      * <p>
 200      * The rules for addition add the number of week-based-years to the existing value
 201      * for the week-based-year field. If the resulting week-based-year only has 52 weeks,
 202      * then the date will be in week 1 of the following week-based-year.
 203      * <p>
 204      * This unit is an immutable and thread-safe singleton.
 205      */
 206     public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS;
 207     /**
 208      * Unit that represents the concept of a quarter-year.
 209      * For the ISO calendar system, it is equal to 3 months.
 210      * The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}.
 211      * <p>
 212      * This unit is an immutable and thread-safe singleton.
 213      */
 214     public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS;
 215 
 216     /**
 217      * Restricted constructor.
 218      */
 219     private ISOFields() {
 220         throw new AssertionError("Not instantiable");
 221     }
 222 
 223     //-----------------------------------------------------------------------
 224     /**
 225      * Implementation of the field.
 226      */
 227     private static enum Field implements TemporalField {
 228         DAY_OF_QUARTER {
 229             @Override
 230             public String getName() {
 231                 return "DayOfQuarter";
 232             }
 233             @Override
 234             public TemporalUnit getBaseUnit() {
 235                 return DAYS;
 236             }
 237             @Override
 238             public TemporalUnit getRangeUnit() {
 239                 return QUARTER_YEARS;
 240             }
 241             @Override
 242             public ValueRange range() {
 243                 return ValueRange.of(1, 90, 92);
 244             }
 245             @Override
 246             public boolean doIsSupported(TemporalAccessor temporal) {
 247                 return temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) &&
 248                         temporal.isSupported(YEAR) && Chrono.from(temporal).equals(ISOChrono.INSTANCE);
 249             }
 250             @Override
 251             public ValueRange doRange(TemporalAccessor temporal) {
 252                 if (doIsSupported(temporal) == false) {
 253                     throw new DateTimeException("Unsupported field: DayOfQuarter");
 254                 }
 255                 long qoy = temporal.getLong(QUARTER_OF_YEAR);
 256                 if (qoy == 1) {
 257                     long year = temporal.getLong(YEAR);
 258                     return (ISOChrono.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90));
 259                 } else if (qoy == 2) {
 260                     return ValueRange.of(1, 91);
 261                 } else if (qoy == 3 || qoy == 4) {
 262                     return ValueRange.of(1, 92);
 263                 } // else value not from 1 to 4, so drop through
 264                 return range();
 265             }
 266             @Override
 267             public long doGet(TemporalAccessor temporal) {
 268                 if (doIsSupported(temporal) == false) {
 269                     throw new DateTimeException("Unsupported field: DayOfQuarter");
 270                 }
 271                 int doy = temporal.get(DAY_OF_YEAR);
 272                 int moy = temporal.get(MONTH_OF_YEAR);
 273                 long year = temporal.getLong(YEAR);
 274                 return doy - QUARTER_DAYS[((moy - 1) / 3) + (ISOChrono.INSTANCE.isLeapYear(year) ? 4 : 0)];
 275             }
 276             @Override
 277             public <R extends Temporal> R doWith(R temporal, long newValue) {
 278                 long curValue = doGet(temporal);
 279                 range().checkValidValue(newValue, this);
 280                 return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue));
 281             }
 282         },
 283         QUARTER_OF_YEAR {
 284             @Override
 285             public String getName() {
 286                 return "QuarterOfYear";
 287             }
 288             @Override
 289             public TemporalUnit getBaseUnit() {
 290                 return QUARTER_YEARS;
 291             }
 292             @Override
 293             public TemporalUnit getRangeUnit() {
 294                 return YEARS;
 295             }
 296             @Override
 297             public ValueRange range() {
 298                 return ValueRange.of(1, 4);
 299             }
 300             @Override
 301             public boolean doIsSupported(TemporalAccessor temporal) {
 302                 return temporal.isSupported(MONTH_OF_YEAR) && Chrono.from(temporal).equals(ISOChrono.INSTANCE);
 303             }
 304             @Override
 305             public long doGet(TemporalAccessor temporal) {
 306                 if (doIsSupported(temporal) == false) {
 307                     throw new DateTimeException("Unsupported field: DayOfQuarter");
 308                 }
 309                 long moy = temporal.getLong(MONTH_OF_YEAR);
 310                 return ((moy + 2) / 3);
 311             }
 312             @Override
 313             public <R extends Temporal> R doWith(R temporal, long newValue) {
 314                 long curValue = doGet(temporal);
 315                 range().checkValidValue(newValue, this);
 316                 return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3);
 317             }
 318             @Override
 319             public boolean resolve(DateTimeBuilder builder, long value) {
 320                 Long[] values = builder.queryFieldValues(YEAR, QUARTER_OF_YEAR, DAY_OF_QUARTER);
 321                 if (values[0] != null && values[1] != null && values[2] != null) {
 322                     int y = YEAR.range().checkValidIntValue(values[0], YEAR);
 323                     int qoy = QUARTER_OF_YEAR.range().checkValidIntValue(values[1], QUARTER_OF_YEAR);
 324                     int doq = DAY_OF_QUARTER.range().checkValidIntValue(values[2], DAY_OF_QUARTER);
 325                     LocalDate date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1).plusDays(doq - 1);
 326                     builder.addFieldValue(EPOCH_DAY, date.toEpochDay());
 327                     builder.removeFieldValues(QUARTER_OF_YEAR, DAY_OF_QUARTER);
 328                 }
 329                 return false;
 330             }
 331         },
 332         WEEK_OF_WEEK_BASED_YEAR {
 333             @Override
 334             public String getName() {
 335                 return "WeekOfWeekBasedYear";
 336             }
 337             @Override
 338             public TemporalUnit getBaseUnit() {
 339                 return WEEKS;
 340             }
 341             @Override
 342             public TemporalUnit getRangeUnit() {
 343                 return WEEK_BASED_YEARS;
 344             }
 345             @Override
 346             public ValueRange range() {
 347                 return ValueRange.of(1, 52, 53);
 348             }
 349             @Override
 350             public boolean doIsSupported(TemporalAccessor temporal) {
 351                 return temporal.isSupported(EPOCH_DAY);
 352             }
 353             @Override
 354             public ValueRange doRange(TemporalAccessor temporal) {
 355                 return getWeekRange(LocalDate.from(temporal));
 356             }
 357             @Override
 358             public long doGet(TemporalAccessor temporal) {
 359                 return getWeek(LocalDate.from(temporal));
 360             }
 361             @Override
 362             public <R extends Temporal> R doWith(R temporal, long newValue) {
 363                 ValueRange.of(1, 53).checkValidValue(newValue, this);
 364                 return (R) temporal.plus(Math.subtractExact(newValue, doGet(temporal)), WEEKS);
 365             }
 366         },
 367         WEEK_BASED_YEAR {
 368             @Override
 369             public String getName() {
 370                 return "WeekBasedYear";
 371             }
 372             @Override
 373             public TemporalUnit getBaseUnit() {
 374                 return WEEK_BASED_YEARS;
 375             }
 376             @Override
 377             public TemporalUnit getRangeUnit() {
 378                 return FOREVER;
 379             }
 380             @Override
 381             public ValueRange range() {
 382                 return YEAR.range();
 383             }
 384             @Override
 385             public boolean doIsSupported(TemporalAccessor temporal) {
 386                 return temporal.isSupported(EPOCH_DAY);
 387             }
 388             @Override
 389             public long doGet(TemporalAccessor temporal) {
 390                 return getWeekBasedYear(LocalDate.from(temporal));
 391             }
 392             @Override
 393             public <R extends Temporal> R doWith(R temporal, long newValue) {
 394                 int newVal = range().checkValidIntValue(newValue, WEEK_BASED_YEAR);
 395                 LocalDate date = LocalDate.from(temporal);
 396                 int week = getWeek(date);
 397                 date = date.withDayOfYear(180).withYear(newVal).with(WEEK_OF_WEEK_BASED_YEAR, week);
 398                 return (R) date.with(date);
 399             }
 400             @Override
 401             public boolean resolve(DateTimeBuilder builder, long value) {
 402                 Long[] values = builder.queryFieldValues(WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK);
 403                 if (values[0] != null && values[1] != null && values[2] != null) {
 404                     int wby = WEEK_BASED_YEAR.range().checkValidIntValue(values[0], WEEK_BASED_YEAR);
 405                     int week = WEEK_OF_WEEK_BASED_YEAR.range().checkValidIntValue(values[1], WEEK_OF_WEEK_BASED_YEAR);
 406                     int dow = DAY_OF_WEEK.range().checkValidIntValue(values[2], DAY_OF_WEEK);
 407                     LocalDate date = LocalDate.of(wby, 2, 1).with(WEEK_OF_WEEK_BASED_YEAR, week).with(DAY_OF_WEEK, dow);
 408                     builder.addFieldValue(EPOCH_DAY, date.toEpochDay());
 409                     builder.removeFieldValues(WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK);
 410                 }
 411                 return false;
 412             }
 413         };
 414 
 415         @Override
 416         public ValueRange doRange(TemporalAccessor temporal) {
 417             return range();
 418         }
 419 
 420         @Override
 421         public boolean resolve(DateTimeBuilder builder, long value) {
 422             return false;
 423         }
 424 
 425         @Override
 426         public String toString() {
 427             return getName();
 428         }
 429 
 430         //-------------------------------------------------------------------------
 431         private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274};
 432 
 433         private static ValueRange getWeekRange(LocalDate date) {
 434             int wby = getWeekBasedYear(date);
 435             date = date.withDayOfYear(1).withYear(wby);
 436             // 53 weeks if standard year starts on Thursday, or Wed in a leap year
 437             if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) {
 438                 return ValueRange.of(1, 53);
 439             }
 440             return ValueRange.of(1, 52);
 441         }
 442 
 443         private static int getWeek(LocalDate date) {
 444             int dow0 = date.getDayOfWeek().ordinal();
 445             int doy0 = date.getDayOfYear() - 1;
 446             int doyThu0 = doy0 + (3 - dow0);  // adjust to mid-week Thursday (which is 3 indexed from zero)
 447             int alignedWeek = doyThu0 / 7;
 448             int firstThuDoy0 = doyThu0 - (alignedWeek * 7);
 449             int firstMonDoy0 = firstThuDoy0 - 3;
 450             if (firstMonDoy0 < -3) {
 451                 firstMonDoy0 += 7;
 452             }
 453             if (doy0 < firstMonDoy0) {
 454                 return (int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum();
 455             }
 456             int week = ((doy0 - firstMonDoy0) / 7) + 1;
 457             if (week == 53) {
 458                 if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false) {
 459                     week = 1;
 460                 }
 461             }
 462             return week;
 463         }
 464 
 465         private static int getWeekBasedYear(LocalDate date) {
 466             int year = date.getYear();
 467             int doy = date.getDayOfYear();
 468             if (doy <= 3) {
 469                 int dow = date.getDayOfWeek().ordinal();
 470                 if (doy - dow < -2) {
 471                     year--;
 472                 }
 473             } else if (doy >= 363) {
 474                 int dow = date.getDayOfWeek().ordinal();
 475                 doy = doy - 363 - (date.isLeapYear() ? 1 : 0);
 476                 if (doy - dow >= 0) {
 477                     year++;
 478                 }
 479             }
 480             return year;
 481         }
 482     }
 483 
 484     //-----------------------------------------------------------------------
 485     /**
 486      * Implementation of the period unit.
 487      */
 488     private static enum Unit implements TemporalUnit {
 489 
 490         /**
 491          * Unit that represents the concept of a week-based-year.
 492          */
 493         WEEK_BASED_YEARS("WeekBasedYears", Duration.ofSeconds(31556952L)),
 494         /**
 495          * Unit that represents the concept of a quarter-year.
 496          */
 497         QUARTER_YEARS("QuarterYears", Duration.ofSeconds(31556952L / 4));
 498 
 499         private final String name;
 500         private final Duration duration;
 501 
 502         private Unit(String name, Duration estimatedDuration) {
 503             this.name = name;
 504             this.duration = estimatedDuration;
 505         }
 506 
 507         @Override
 508         public String getName() {
 509             return name;
 510         }
 511 
 512         @Override
 513         public Duration getDuration() {
 514             return duration;
 515         }
 516 
 517         @Override
 518         public boolean isDurationEstimated() {
 519             return true;
 520         }
 521 
 522         @Override
 523         public boolean isSupported(Temporal temporal) {
 524             return temporal.isSupported(EPOCH_DAY);
 525         }
 526 
 527         @Override
 528         public <R extends Temporal> R doPlus(R dateTime, long periodToAdd) {
 529             switch(this) {
 530                 case WEEK_BASED_YEARS:
 531                     return (R) dateTime.with(WEEK_BASED_YEAR,
 532                             Math.addExact(dateTime.get(WEEK_BASED_YEAR), periodToAdd));
 533                 case QUARTER_YEARS:
 534                     // no overflow (256 is multiple of 4)
 535                     return (R) dateTime.plus(periodToAdd / 256, YEARS)
 536                             .plus((periodToAdd % 256) * 3, MONTHS);
 537                 default:
 538                     throw new IllegalStateException("Unreachable");
 539             }
 540         }
 541 
 542         @Override
 543         public <R extends Temporal> SimplePeriod between(R dateTime1, R dateTime2) {
 544             switch(this) {
 545                 case WEEK_BASED_YEARS:
 546                     long period = Math.subtractExact(dateTime2.getLong(WEEK_BASED_YEAR),
 547                             dateTime1.getLong(WEEK_BASED_YEAR));
 548                     return new SimplePeriod(period, WEEK_BASED_YEARS);
 549                 case QUARTER_YEARS:
 550                     long period2 = Math.subtractExact(dateTime2.getLong(QUARTER_OF_YEAR),
 551                             dateTime1.getLong(QUARTER_OF_YEAR));
 552                     return new SimplePeriod(period2, QUARTER_YEARS);
 553                 default:
 554                     throw new IllegalStateException("Unreachable");
 555             }
 556         }
 557 
 558         @Override
 559         public String toString() {
 560             return getName();
 561 
 562         }
 563     }
 564 }