Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/time/temporal/IsoFields.java
38918 views
/*1* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* Copyright (c) 2011-2012, Stephen Colebourne & Michael Nascimento Santos27*28* All rights reserved.29*30* Redistribution and use in source and binary forms, with or without31* modification, are permitted provided that the following conditions are met:32*33* * Redistributions of source code must retain the above copyright notice,34* this list of conditions and the following disclaimer.35*36* * Redistributions in binary form must reproduce the above copyright notice,37* this list of conditions and the following disclaimer in the documentation38* and/or other materials provided with the distribution.39*40* * Neither the name of JSR-310 nor the names of its contributors41* may be used to endorse or promote products derived from this software42* without specific prior written permission.43*44* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS45* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT46* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR47* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR48* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,49* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,50* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR51* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF52* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING53* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS54* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.55*/56package java.time.temporal;5758import static java.time.DayOfWeek.THURSDAY;59import static java.time.DayOfWeek.WEDNESDAY;60import static java.time.temporal.ChronoField.DAY_OF_WEEK;61import static java.time.temporal.ChronoField.DAY_OF_YEAR;62import static java.time.temporal.ChronoField.EPOCH_DAY;63import static java.time.temporal.ChronoField.MONTH_OF_YEAR;64import static java.time.temporal.ChronoField.YEAR;65import static java.time.temporal.ChronoUnit.DAYS;66import static java.time.temporal.ChronoUnit.FOREVER;67import static java.time.temporal.ChronoUnit.MONTHS;68import static java.time.temporal.ChronoUnit.WEEKS;69import static java.time.temporal.ChronoUnit.YEARS;7071import java.time.DateTimeException;72import java.time.Duration;73import java.time.LocalDate;74import java.time.chrono.ChronoLocalDate;75import java.time.chrono.Chronology;76import java.time.chrono.IsoChronology;77import java.time.format.ResolverStyle;78import java.util.Locale;79import java.util.Map;80import java.util.Objects;81import java.util.ResourceBundle;8283import sun.util.locale.provider.LocaleProviderAdapter;84import sun.util.locale.provider.LocaleResources;8586/**87* Fields and units specific to the ISO-8601 calendar system,88* including quarter-of-year and week-based-year.89* <p>90* This class defines fields and units that are specific to the ISO calendar system.91*92* <h3>Quarter of year</h3>93* The ISO-8601 standard is based on the standard civic 12 month year.94* This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4.95* <p>96* January, February and March are in Q1.97* April, May and June are in Q2.98* July, August and September are in Q3.99* October, November and December are in Q4.100* <p>101* The complete date is expressed using three fields:102* <ul>103* <li>{@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92104* <li>{@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the week within the week-based-year105* <li>{@link ChronoField#YEAR YEAR} - the standard ISO year106* </ul>107*108* <h3>Week based years</h3>109* The ISO-8601 standard was originally intended as a data interchange format,110* defining a string format for dates and times. However, it also defines an111* alternate way of expressing the date, based on the concept of week-based-year.112* <p>113* The date is expressed using three fields:114* <ul>115* <li>{@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the116* day-of-week from Monday (1) to Sunday (7)117* <li>{@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year118* <li>{@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year119* </ul>120* The week-based-year itself is defined relative to the standard ISO proleptic year.121* It differs from the standard year in that it always starts on a Monday.122* <p>123* The first week of a week-based-year is the first Monday-based week of the standard124* ISO year that has at least 4 days in the new year.125* <ul>126* <li>If January 1st is Monday then week 1 starts on January 1st127* <li>If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year128* <li>If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year129* <li>If January 1st is Thursday then week 1 starts on December 29th of the previous standard year130* <li>If January 1st is Friday then week 1 starts on January 4th131* <li>If January 1st is Saturday then week 1 starts on January 3rd132* <li>If January 1st is Sunday then week 1 starts on January 2nd133* </ul>134* There are 52 weeks in most week-based years, however on occasion there are 53 weeks.135* <p>136* For example:137*138* <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;">139* <caption>Examples of Week based Years</caption>140* <tr><th>Date</th><th>Day-of-week</th><th>Field values</th></tr>141* <tr><th>2008-12-28</th><td>Sunday</td><td>Week 52 of week-based-year 2008</td></tr>142* <tr><th>2008-12-29</th><td>Monday</td><td>Week 1 of week-based-year 2009</td></tr>143* <tr><th>2008-12-31</th><td>Wednesday</td><td>Week 1 of week-based-year 2009</td></tr>144* <tr><th>2009-01-01</th><td>Thursday</td><td>Week 1 of week-based-year 2009</td></tr>145* <tr><th>2009-01-04</th><td>Sunday</td><td>Week 1 of week-based-year 2009</td></tr>146* <tr><th>2009-01-05</th><td>Monday</td><td>Week 2 of week-based-year 2009</td></tr>147* </table>148*149* @implSpec150* <p>151* This class is immutable and thread-safe.152*153* @since 1.8154*/155public final class IsoFields {156157/**158* The field that represents the day-of-quarter.159* <p>160* This field allows the day-of-quarter value to be queried and set.161* The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91162* in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4.163* <p>164* The day-of-quarter can only be calculated if the day-of-year, month-of-year and year165* are available.166* <p>167* When setting this field, the value is allowed to be partially lenient, taking any168* value from 1 to 92. If the quarter has less than 92 days, then day 92, and169* potentially day 91, is in the following quarter.170* <p>171* In the resolving phase of parsing, a date can be created from a year,172* quarter-of-year and day-of-quarter.173* <p>174* In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are175* validated against their range of valid values. The day-of-quarter field176* is validated from 1 to 90, 91 or 92 depending on the year and quarter.177* <p>178* In {@linkplain ResolverStyle#SMART smart mode}, all three fields are179* validated against their range of valid values. The day-of-quarter field is180* validated between 1 and 92, ignoring the actual range based on the year and quarter.181* If the day-of-quarter exceeds the actual range by one day, then the resulting date182* is one day later. If the day-of-quarter exceeds the actual range by two days,183* then the resulting date is two days later.184* <p>185* In {@linkplain ResolverStyle#LENIENT lenient mode}, only the year is validated186* against the range of valid values. The resulting date is calculated equivalent to187* the following three stage approach. First, create a date on the first of January188* in the requested year. Then take the quarter-of-year, subtract one, and add the189* amount in quarters to the date. Finally, take the day-of-quarter, subtract one,190* and add the amount in days to the date.191* <p>192* This unit is an immutable and thread-safe singleton.193*/194public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER;195/**196* The field that represents the quarter-of-year.197* <p>198* This field allows the quarter-of-year value to be queried and set.199* The quarter-of-year has values from 1 to 4.200* <p>201* The quarter-of-year can only be calculated if the month-of-year is available.202* <p>203* In the resolving phase of parsing, a date can be created from a year,204* quarter-of-year and day-of-quarter.205* See {@link #DAY_OF_QUARTER} for details.206* <p>207* This unit is an immutable and thread-safe singleton.208*/209public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR;210/**211* The field that represents the week-of-week-based-year.212* <p>213* This field allows the week of the week-based-year value to be queried and set.214* The week-of-week-based-year has values from 1 to 52, or 53 if the215* week-based-year has 53 weeks.216* <p>217* In the resolving phase of parsing, a date can be created from a218* week-based-year, week-of-week-based-year and day-of-week.219* <p>220* In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are221* validated against their range of valid values. The week-of-week-based-year222* field is validated from 1 to 52 or 53 depending on the week-based-year.223* <p>224* In {@linkplain ResolverStyle#SMART smart mode}, all three fields are225* validated against their range of valid values. The week-of-week-based-year226* field is validated between 1 and 53, ignoring the week-based-year.227* If the week-of-week-based-year is 53, but the week-based-year only has228* 52 weeks, then the resulting date is in week 1 of the following week-based-year.229* <p>230* In {@linkplain ResolverStyle#LENIENT lenient mode}, only the week-based-year231* is validated against the range of valid values. If the day-of-week is outside232* the range 1 to 7, then the resulting date is adjusted by a suitable number of233* weeks to reduce the day-of-week to the range 1 to 7. If the week-of-week-based-year234* value is outside the range 1 to 52, then any excess weeks are added or subtracted235* from the resulting date.236* <p>237* This unit is an immutable and thread-safe singleton.238*/239public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR;240/**241* The field that represents the week-based-year.242* <p>243* This field allows the week-based-year value to be queried and set.244* <p>245* The field has a range that matches {@link LocalDate#MAX} and {@link LocalDate#MIN}.246* <p>247* In the resolving phase of parsing, a date can be created from a248* week-based-year, week-of-week-based-year and day-of-week.249* See {@link #WEEK_OF_WEEK_BASED_YEAR} for details.250* <p>251* This unit is an immutable and thread-safe singleton.252*/253public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR;254/**255* The unit that represents week-based-years for the purpose of addition and subtraction.256* <p>257* This allows a number of week-based-years to be added to, or subtracted from, a date.258* The unit is equal to either 52 or 53 weeks.259* The estimated duration of a week-based-year is the same as that of a standard ISO260* year at {@code 365.2425 Days}.261* <p>262* The rules for addition add the number of week-based-years to the existing value263* for the week-based-year field. If the resulting week-based-year only has 52 weeks,264* then the date will be in week 1 of the following week-based-year.265* <p>266* This unit is an immutable and thread-safe singleton.267*/268public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS;269/**270* Unit that represents the concept of a quarter-year.271* For the ISO calendar system, it is equal to 3 months.272* The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}.273* <p>274* This unit is an immutable and thread-safe singleton.275*/276public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS;277278/**279* Restricted constructor.280*/281private IsoFields() {282throw new AssertionError("Not instantiable");283}284285//-----------------------------------------------------------------------286/**287* Implementation of the field.288*/289private static enum Field implements TemporalField {290DAY_OF_QUARTER {291@Override292public TemporalUnit getBaseUnit() {293return DAYS;294}295@Override296public TemporalUnit getRangeUnit() {297return QUARTER_YEARS;298}299@Override300public ValueRange range() {301return ValueRange.of(1, 90, 92);302}303@Override304public boolean isSupportedBy(TemporalAccessor temporal) {305return temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) &&306temporal.isSupported(YEAR) && isIso(temporal);307}308@Override309public ValueRange rangeRefinedBy(TemporalAccessor temporal) {310if (isSupportedBy(temporal) == false) {311throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");312}313long qoy = temporal.getLong(QUARTER_OF_YEAR);314if (qoy == 1) {315long year = temporal.getLong(YEAR);316return (IsoChronology.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90));317} else if (qoy == 2) {318return ValueRange.of(1, 91);319} else if (qoy == 3 || qoy == 4) {320return ValueRange.of(1, 92);321} // else value not from 1 to 4, so drop through322return range();323}324@Override325public long getFrom(TemporalAccessor temporal) {326if (isSupportedBy(temporal) == false) {327throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");328}329int doy = temporal.get(DAY_OF_YEAR);330int moy = temporal.get(MONTH_OF_YEAR);331long year = temporal.getLong(YEAR);332return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year) ? 4 : 0)];333}334@SuppressWarnings("unchecked")335@Override336public <R extends Temporal> R adjustInto(R temporal, long newValue) {337// calls getFrom() to check if supported338long curValue = getFrom(temporal);339range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check340return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue));341}342@Override343public ChronoLocalDate resolve(344Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {345Long yearLong = fieldValues.get(YEAR);346Long qoyLong = fieldValues.get(QUARTER_OF_YEAR);347if (yearLong == null || qoyLong == null) {348return null;349}350int y = YEAR.checkValidIntValue(yearLong); // always validate351long doq = fieldValues.get(DAY_OF_QUARTER);352ensureIso(partialTemporal);353LocalDate date;354if (resolverStyle == ResolverStyle.LENIENT) {355date = LocalDate.of(y, 1, 1).plusMonths(Math.multiplyExact(Math.subtractExact(qoyLong, 1), 3));356doq = Math.subtractExact(doq, 1);357} else {358int qoy = QUARTER_OF_YEAR.range().checkValidIntValue(qoyLong, QUARTER_OF_YEAR); // validated359date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1);360if (doq < 1 || doq > 90) {361if (resolverStyle == ResolverStyle.STRICT) {362rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range363} else { // SMART364range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter365}366}367doq--;368}369fieldValues.remove(this);370fieldValues.remove(YEAR);371fieldValues.remove(QUARTER_OF_YEAR);372return date.plusDays(doq);373}374@Override375public String toString() {376return "DayOfQuarter";377}378},379QUARTER_OF_YEAR {380@Override381public TemporalUnit getBaseUnit() {382return QUARTER_YEARS;383}384@Override385public TemporalUnit getRangeUnit() {386return YEARS;387}388@Override389public ValueRange range() {390return ValueRange.of(1, 4);391}392@Override393public boolean isSupportedBy(TemporalAccessor temporal) {394return temporal.isSupported(MONTH_OF_YEAR) && isIso(temporal);395}396@Override397public long getFrom(TemporalAccessor temporal) {398if (isSupportedBy(temporal) == false) {399throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear");400}401long moy = temporal.getLong(MONTH_OF_YEAR);402return ((moy + 2) / 3);403}404@SuppressWarnings("unchecked")405@Override406public <R extends Temporal> R adjustInto(R temporal, long newValue) {407// calls getFrom() to check if supported408long curValue = getFrom(temporal);409range().checkValidValue(newValue, this); // strictly check from 1 to 4410return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3);411}412@Override413public String toString() {414return "QuarterOfYear";415}416},417WEEK_OF_WEEK_BASED_YEAR {418@Override419public String getDisplayName(Locale locale) {420Objects.requireNonNull(locale, "locale");421LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()422.getLocaleResources(locale);423ResourceBundle rb = lr.getJavaTimeFormatData();424return rb.containsKey("field.week") ? rb.getString("field.week") : toString();425}426427@Override428public TemporalUnit getBaseUnit() {429return WEEKS;430}431@Override432public TemporalUnit getRangeUnit() {433return WEEK_BASED_YEARS;434}435@Override436public ValueRange range() {437return ValueRange.of(1, 52, 53);438}439@Override440public boolean isSupportedBy(TemporalAccessor temporal) {441return temporal.isSupported(EPOCH_DAY) && isIso(temporal);442}443@Override444public ValueRange rangeRefinedBy(TemporalAccessor temporal) {445if (isSupportedBy(temporal) == false) {446throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear");447}448return getWeekRange(LocalDate.from(temporal));449}450@Override451public long getFrom(TemporalAccessor temporal) {452if (isSupportedBy(temporal) == false) {453throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear");454}455return getWeek(LocalDate.from(temporal));456}457@SuppressWarnings("unchecked")458@Override459public <R extends Temporal> R adjustInto(R temporal, long newValue) {460// calls getFrom() to check if supported461range().checkValidValue(newValue, this); // lenient range462return (R) temporal.plus(Math.subtractExact(newValue, getFrom(temporal)), WEEKS);463}464@Override465public ChronoLocalDate resolve(466Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {467Long wbyLong = fieldValues.get(WEEK_BASED_YEAR);468Long dowLong = fieldValues.get(DAY_OF_WEEK);469if (wbyLong == null || dowLong == null) {470return null;471}472int wby = WEEK_BASED_YEAR.range().checkValidIntValue(wbyLong, WEEK_BASED_YEAR); // always validate473long wowby = fieldValues.get(WEEK_OF_WEEK_BASED_YEAR);474ensureIso(partialTemporal);475LocalDate date = LocalDate.of(wby, 1, 4);476if (resolverStyle == ResolverStyle.LENIENT) {477long dow = dowLong; // unvalidated478if (dow > 7) {479date = date.plusWeeks((dow - 1) / 7);480dow = ((dow - 1) % 7) + 1;481} else if (dow < 1) {482date = date.plusWeeks(Math.subtractExact(dow, 7) / 7);483dow = ((dow + 6) % 7) + 1;484}485date = date.plusWeeks(Math.subtractExact(wowby, 1)).with(DAY_OF_WEEK, dow);486} else {487int dow = DAY_OF_WEEK.checkValidIntValue(dowLong); // validated488if (wowby < 1 || wowby > 52) {489if (resolverStyle == ResolverStyle.STRICT) {490getWeekRange(date).checkValidValue(wowby, this); // only allow exact range491} else { // SMART492range().checkValidValue(wowby, this); // allow 1-53 rolling into next year493}494}495date = date.plusWeeks(wowby - 1).with(DAY_OF_WEEK, dow);496}497fieldValues.remove(this);498fieldValues.remove(WEEK_BASED_YEAR);499fieldValues.remove(DAY_OF_WEEK);500return date;501}502@Override503public String toString() {504return "WeekOfWeekBasedYear";505}506},507WEEK_BASED_YEAR {508@Override509public TemporalUnit getBaseUnit() {510return WEEK_BASED_YEARS;511}512@Override513public TemporalUnit getRangeUnit() {514return FOREVER;515}516@Override517public ValueRange range() {518return YEAR.range();519}520@Override521public boolean isSupportedBy(TemporalAccessor temporal) {522return temporal.isSupported(EPOCH_DAY) && isIso(temporal);523}524@Override525public long getFrom(TemporalAccessor temporal) {526if (isSupportedBy(temporal) == false) {527throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");528}529return getWeekBasedYear(LocalDate.from(temporal));530}531@SuppressWarnings("unchecked")532@Override533public <R extends Temporal> R adjustInto(R temporal, long newValue) {534if (isSupportedBy(temporal) == false) {535throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");536}537int newWby = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check538LocalDate date = LocalDate.from(temporal);539int dow = date.get(DAY_OF_WEEK);540int week = getWeek(date);541if (week == 53 && getWeekRange(newWby) == 52) {542week = 52;543}544LocalDate resolved = LocalDate.of(newWby, 1, 4); // 4th is guaranteed to be in week one545int days = (dow - resolved.get(DAY_OF_WEEK)) + ((week - 1) * 7);546resolved = resolved.plusDays(days);547return (R) temporal.with(resolved);548}549@Override550public String toString() {551return "WeekBasedYear";552}553};554555@Override556public boolean isDateBased() {557return true;558}559560@Override561public boolean isTimeBased() {562return false;563}564565@Override566public ValueRange rangeRefinedBy(TemporalAccessor temporal) {567return range();568}569570//-------------------------------------------------------------------------571private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274};572573private static boolean isIso(TemporalAccessor temporal) {574return Chronology.from(temporal).equals(IsoChronology.INSTANCE);575}576577private static void ensureIso(TemporalAccessor temporal) {578if (isIso(temporal) == false) {579throw new DateTimeException("Resolve requires IsoChronology");580}581}582583private static ValueRange getWeekRange(LocalDate date) {584int wby = getWeekBasedYear(date);585return ValueRange.of(1, getWeekRange(wby));586}587588private static int getWeekRange(int wby) {589LocalDate date = LocalDate.of(wby, 1, 1);590// 53 weeks if standard year starts on Thursday, or Wed in a leap year591if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) {592return 53;593}594return 52;595}596597private static int getWeek(LocalDate date) {598int dow0 = date.getDayOfWeek().ordinal();599int doy0 = date.getDayOfYear() - 1;600int doyThu0 = doy0 + (3 - dow0); // adjust to mid-week Thursday (which is 3 indexed from zero)601int alignedWeek = doyThu0 / 7;602int firstThuDoy0 = doyThu0 - (alignedWeek * 7);603int firstMonDoy0 = firstThuDoy0 - 3;604if (firstMonDoy0 < -3) {605firstMonDoy0 += 7;606}607if (doy0 < firstMonDoy0) {608return (int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum();609}610int week = ((doy0 - firstMonDoy0) / 7) + 1;611if (week == 53) {612if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false) {613week = 1;614}615}616return week;617}618619private static int getWeekBasedYear(LocalDate date) {620int year = date.getYear();621int doy = date.getDayOfYear();622if (doy <= 3) {623int dow = date.getDayOfWeek().ordinal();624if (doy - dow < -2) {625year--;626}627} else if (doy >= 363) {628int dow = date.getDayOfWeek().ordinal();629doy = doy - 363 - (date.isLeapYear() ? 1 : 0);630if (doy - dow >= 0) {631year++;632}633}634return year;635}636}637638//-----------------------------------------------------------------------639/**640* Implementation of the unit.641*/642private static enum Unit implements TemporalUnit {643644/**645* Unit that represents the concept of a week-based-year.646*/647WEEK_BASED_YEARS("WeekBasedYears", Duration.ofSeconds(31556952L)),648/**649* Unit that represents the concept of a quarter-year.650*/651QUARTER_YEARS("QuarterYears", Duration.ofSeconds(31556952L / 4));652653private final String name;654private final Duration duration;655656private Unit(String name, Duration estimatedDuration) {657this.name = name;658this.duration = estimatedDuration;659}660661@Override662public Duration getDuration() {663return duration;664}665666@Override667public boolean isDurationEstimated() {668return true;669}670671@Override672public boolean isDateBased() {673return true;674}675676@Override677public boolean isTimeBased() {678return false;679}680681@Override682public boolean isSupportedBy(Temporal temporal) {683return temporal.isSupported(EPOCH_DAY);684}685686@SuppressWarnings("unchecked")687@Override688public <R extends Temporal> R addTo(R temporal, long amount) {689switch (this) {690case WEEK_BASED_YEARS:691return (R) temporal.with(WEEK_BASED_YEAR,692Math.addExact(temporal.get(WEEK_BASED_YEAR), amount));693case QUARTER_YEARS:694return (R) temporal.plus(amount / 4, YEARS)695.plus((amount % 4) * 3, MONTHS);696default:697throw new IllegalStateException("Unreachable");698}699}700701@Override702public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) {703if (temporal1Inclusive.getClass() != temporal2Exclusive.getClass()) {704return temporal1Inclusive.until(temporal2Exclusive, this);705}706switch(this) {707case WEEK_BASED_YEARS:708return Math.subtractExact(temporal2Exclusive.getLong(WEEK_BASED_YEAR),709temporal1Inclusive.getLong(WEEK_BASED_YEAR));710case QUARTER_YEARS:711return temporal1Inclusive.until(temporal2Exclusive, MONTHS) / 3;712default:713throw new IllegalStateException("Unreachable");714}715}716717@Override718public String toString() {719return name;720}721}722}723724725