Path: blob/master/src/java.base/share/classes/java/time/temporal/IsoFields.java
41159 views
/*1* Copyright (c) 2012, 2019, 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.CalendarDataUtility;84import sun.util.locale.provider.LocaleProviderAdapter;85import sun.util.locale.provider.LocaleResources;8687/**88* Fields and units specific to the ISO-8601 calendar system,89* including quarter-of-year and week-based-year.90* <p>91* This class defines fields and units that are specific to the ISO calendar system.92*93* <h2>Quarter of year</h2>94* The ISO-8601 standard is based on the standard civic 12 month year.95* This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4.96* <p>97* January, February and March are in Q1.98* April, May and June are in Q2.99* July, August and September are in Q3.100* October, November and December are in Q4.101* <p>102* The complete date is expressed using three fields:103* <ul>104* <li>{@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92105* <li>{@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the quarter within the year, from 1 to 4106* <li>{@link ChronoField#YEAR YEAR} - the standard ISO year107* </ul>108*109* <h2>Week based years</h2>110* The ISO-8601 standard was originally intended as a data interchange format,111* defining a string format for dates and times. However, it also defines an112* alternate way of expressing the date, based on the concept of week-based-year.113* <p>114* The date is expressed using three fields:115* <ul>116* <li>{@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the117* day-of-week from Monday (1) to Sunday (7)118* <li>{@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year119* <li>{@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year120* </ul>121* The week-based-year itself is defined relative to the standard ISO proleptic year.122* It differs from the standard year in that it always starts on a Monday.123* <p>124* The first week of a week-based-year is the first Monday-based week of the standard125* ISO year that has at least 4 days in the new year.126* <ul>127* <li>If January 1st is Monday then week 1 starts on January 1st128* <li>If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year129* <li>If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year130* <li>If January 1st is Thursday then week 1 starts on December 29th of the previous standard year131* <li>If January 1st is Friday then week 1 starts on January 4th132* <li>If January 1st is Saturday then week 1 starts on January 3rd133* <li>If January 1st is Sunday then week 1 starts on January 2nd134* </ul>135* There are 52 weeks in most week-based years, however on occasion there are 53 weeks.136* <p>137* For example:138*139* <table class=striped style="text-align: left">140* <caption>Examples of Week based Years</caption>141* <thead>142* <tr><th scope="col">Date</th><th scope="col">Day-of-week</th><th scope="col">Field values</th></tr>143* </thead>144* <tbody>145* <tr><th scope="row">2008-12-28</th><td>Sunday</td><td>Week 52 of week-based-year 2008</td></tr>146* <tr><th scope="row">2008-12-29</th><td>Monday</td><td>Week 1 of week-based-year 2009</td></tr>147* <tr><th scope="row">2008-12-31</th><td>Wednesday</td><td>Week 1 of week-based-year 2009</td></tr>148* <tr><th scope="row">2009-01-01</th><td>Thursday</td><td>Week 1 of week-based-year 2009</td></tr>149* <tr><th scope="row">2009-01-04</th><td>Sunday</td><td>Week 1 of week-based-year 2009</td></tr>150* <tr><th scope="row">2009-01-05</th><td>Monday</td><td>Week 2 of week-based-year 2009</td></tr>151* </tbody>152* </table>153*154* @implSpec155* <p>156* This class is immutable and thread-safe.157*158* @since 1.8159*/160public final class IsoFields {161162/**163* The field that represents the day-of-quarter.164* <p>165* This field allows the day-of-quarter value to be queried and set.166* The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91167* in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4.168* <p>169* The day-of-quarter can only be calculated if the day-of-year, month-of-year and year170* are available.171* <p>172* When setting this field, the value is allowed to be partially lenient, taking any173* value from 1 to 92. If the quarter has less than 92 days, then day 92, and174* potentially day 91, is in the following quarter.175* <p>176* In the resolving phase of parsing, a date can be created from a year,177* quarter-of-year and day-of-quarter.178* <p>179* In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are180* validated against their range of valid values. The day-of-quarter field181* is validated from 1 to 90, 91 or 92 depending on the year and quarter.182* <p>183* In {@linkplain ResolverStyle#SMART smart mode}, all three fields are184* validated against their range of valid values. The day-of-quarter field is185* validated between 1 and 92, ignoring the actual range based on the year and quarter.186* If the day-of-quarter exceeds the actual range by one day, then the resulting date187* is one day later. If the day-of-quarter exceeds the actual range by two days,188* then the resulting date is two days later.189* <p>190* In {@linkplain ResolverStyle#LENIENT lenient mode}, only the year is validated191* against the range of valid values. The resulting date is calculated equivalent to192* the following three stage approach. First, create a date on the first of January193* in the requested year. Then take the quarter-of-year, subtract one, and add the194* amount in quarters to the date. Finally, take the day-of-quarter, subtract one,195* and add the amount in days to the date.196* <p>197* This unit is an immutable and thread-safe singleton.198*/199public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER;200/**201* The field that represents the quarter-of-year.202* <p>203* This field allows the quarter-of-year value to be queried and set.204* The quarter-of-year has values from 1 to 4.205* <p>206* The quarter-of-year can only be calculated if the month-of-year is available.207* <p>208* In the resolving phase of parsing, a date can be created from a year,209* quarter-of-year and day-of-quarter.210* See {@link #DAY_OF_QUARTER} for details.211* <p>212* This unit is an immutable and thread-safe singleton.213*/214public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR;215/**216* The field that represents the week-of-week-based-year.217* <p>218* This field allows the week of the week-based-year value to be queried and set.219* The week-of-week-based-year has values from 1 to 52, or 53 if the220* week-based-year has 53 weeks.221* <p>222* In the resolving phase of parsing, a date can be created from a223* week-based-year, week-of-week-based-year and day-of-week.224* <p>225* In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are226* validated against their range of valid values. The week-of-week-based-year227* field is validated from 1 to 52 or 53 depending on the week-based-year.228* <p>229* In {@linkplain ResolverStyle#SMART smart mode}, all three fields are230* validated against their range of valid values. The week-of-week-based-year231* field is validated between 1 and 53, ignoring the week-based-year.232* If the week-of-week-based-year is 53, but the week-based-year only has233* 52 weeks, then the resulting date is in week 1 of the following week-based-year.234* <p>235* In {@linkplain ResolverStyle#LENIENT lenient mode}, only the week-based-year236* is validated against the range of valid values. If the day-of-week is outside237* the range 1 to 7, then the resulting date is adjusted by a suitable number of238* weeks to reduce the day-of-week to the range 1 to 7. If the week-of-week-based-year239* value is outside the range 1 to 52, then any excess weeks are added or subtracted240* from the resulting date.241* <p>242* This unit is an immutable and thread-safe singleton.243*/244public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR;245/**246* The field that represents the week-based-year.247* <p>248* This field allows the week-based-year value to be queried and set.249* <p>250* The field has a range that matches {@link LocalDate#MAX} and {@link LocalDate#MIN}.251* <p>252* In the resolving phase of parsing, a date can be created from a253* week-based-year, week-of-week-based-year and day-of-week.254* See {@link #WEEK_OF_WEEK_BASED_YEAR} for details.255* <p>256* This unit is an immutable and thread-safe singleton.257*/258public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR;259/**260* The unit that represents week-based-years for the purpose of addition and subtraction.261* <p>262* This allows a number of week-based-years to be added to, or subtracted from, a date.263* The unit is equal to either 52 or 53 weeks.264* The estimated duration of a week-based-year is the same as that of a standard ISO265* year at {@code 365.2425 Days}.266* <p>267* The rules for addition add the number of week-based-years to the existing value268* for the week-based-year field. If the resulting week-based-year only has 52 weeks,269* then the date will be in week 1 of the following week-based-year.270* <p>271* This unit is an immutable and thread-safe singleton.272*/273public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS;274/**275* Unit that represents the concept of a quarter-year.276* For the ISO calendar system, it is equal to 3 months.277* The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}.278* <p>279* This unit is an immutable and thread-safe singleton.280*/281public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS;282283/**284* Restricted constructor.285*/286private IsoFields() {287throw new AssertionError("Not instantiable");288}289290//-----------------------------------------------------------------------291/**292* Implementation of the field.293*/294private static enum Field implements TemporalField {295DAY_OF_QUARTER {296@Override297public TemporalUnit getBaseUnit() {298return DAYS;299}300@Override301public TemporalUnit getRangeUnit() {302return QUARTER_YEARS;303}304@Override305public ValueRange range() {306return ValueRange.of(1, 90, 92);307}308@Override309public boolean isSupportedBy(TemporalAccessor temporal) {310return temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) &&311temporal.isSupported(YEAR) && isIso(temporal);312}313@Override314public ValueRange rangeRefinedBy(TemporalAccessor temporal) {315if (isSupportedBy(temporal) == false) {316throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");317}318long qoy = temporal.getLong(QUARTER_OF_YEAR);319if (qoy == 1) {320long year = temporal.getLong(YEAR);321return (IsoChronology.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90));322} else if (qoy == 2) {323return ValueRange.of(1, 91);324} else if (qoy == 3 || qoy == 4) {325return ValueRange.of(1, 92);326} // else value not from 1 to 4, so drop through327return range();328}329@Override330public long getFrom(TemporalAccessor temporal) {331if (isSupportedBy(temporal) == false) {332throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");333}334int doy = temporal.get(DAY_OF_YEAR);335int moy = temporal.get(MONTH_OF_YEAR);336long year = temporal.getLong(YEAR);337return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year) ? 4 : 0)];338}339@SuppressWarnings("unchecked")340@Override341public <R extends Temporal> R adjustInto(R temporal, long newValue) {342// calls getFrom() to check if supported343long curValue = getFrom(temporal);344range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check345return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue));346}347@Override348public ChronoLocalDate resolve(349Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {350Long yearLong = fieldValues.get(YEAR);351Long qoyLong = fieldValues.get(QUARTER_OF_YEAR);352if (yearLong == null || qoyLong == null) {353return null;354}355int y = YEAR.checkValidIntValue(yearLong); // always validate356long doq = fieldValues.get(DAY_OF_QUARTER);357ensureIso(partialTemporal);358LocalDate date;359if (resolverStyle == ResolverStyle.LENIENT) {360date = LocalDate.of(y, 1, 1).plusMonths(Math.multiplyExact(Math.subtractExact(qoyLong, 1), 3));361doq = Math.subtractExact(doq, 1);362} else {363int qoy = QUARTER_OF_YEAR.range().checkValidIntValue(qoyLong, QUARTER_OF_YEAR); // validated364date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1);365if (doq < 1 || doq > 90) {366if (resolverStyle == ResolverStyle.STRICT) {367rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range368} else { // SMART369range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter370}371}372doq--;373}374fieldValues.remove(this);375fieldValues.remove(YEAR);376fieldValues.remove(QUARTER_OF_YEAR);377return date.plusDays(doq);378}379@Override380public String toString() {381return "DayOfQuarter";382}383},384QUARTER_OF_YEAR {385@Override386public TemporalUnit getBaseUnit() {387return QUARTER_YEARS;388}389@Override390public TemporalUnit getRangeUnit() {391return YEARS;392}393@Override394public ValueRange range() {395return ValueRange.of(1, 4);396}397@Override398public boolean isSupportedBy(TemporalAccessor temporal) {399return temporal.isSupported(MONTH_OF_YEAR) && isIso(temporal);400}401@Override402public long getFrom(TemporalAccessor temporal) {403if (isSupportedBy(temporal) == false) {404throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear");405}406long moy = temporal.getLong(MONTH_OF_YEAR);407return ((moy + 2) / 3);408}409public ValueRange rangeRefinedBy(TemporalAccessor temporal) {410if (isSupportedBy(temporal) == false) {411throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear");412}413return super.rangeRefinedBy(temporal);414}415@SuppressWarnings("unchecked")416@Override417public <R extends Temporal> R adjustInto(R temporal, long newValue) {418// calls getFrom() to check if supported419long curValue = getFrom(temporal);420range().checkValidValue(newValue, this); // strictly check from 1 to 4421return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3);422}423@Override424public String toString() {425return "QuarterOfYear";426}427},428WEEK_OF_WEEK_BASED_YEAR {429@Override430public String getDisplayName(Locale locale) {431Objects.requireNonNull(locale, "locale");432LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()433.getLocaleResources(434CalendarDataUtility435.findRegionOverride(locale));436ResourceBundle rb = lr.getJavaTimeFormatData();437return rb.containsKey("field.week") ? rb.getString("field.week") : toString();438}439440@Override441public TemporalUnit getBaseUnit() {442return WEEKS;443}444@Override445public TemporalUnit getRangeUnit() {446return WEEK_BASED_YEARS;447}448@Override449public ValueRange range() {450return ValueRange.of(1, 52, 53);451}452@Override453public boolean isSupportedBy(TemporalAccessor temporal) {454return temporal.isSupported(EPOCH_DAY) && isIso(temporal);455}456@Override457public ValueRange rangeRefinedBy(TemporalAccessor temporal) {458if (isSupportedBy(temporal) == false) {459throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear");460}461return getWeekRange(LocalDate.from(temporal));462}463@Override464public long getFrom(TemporalAccessor temporal) {465if (isSupportedBy(temporal) == false) {466throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear");467}468return getWeek(LocalDate.from(temporal));469}470@SuppressWarnings("unchecked")471@Override472public <R extends Temporal> R adjustInto(R temporal, long newValue) {473// calls getFrom() to check if supported474range().checkValidValue(newValue, this); // lenient range475return (R) temporal.plus(Math.subtractExact(newValue, getFrom(temporal)), WEEKS);476}477@Override478public ChronoLocalDate resolve(479Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {480Long wbyLong = fieldValues.get(WEEK_BASED_YEAR);481Long dowLong = fieldValues.get(DAY_OF_WEEK);482if (wbyLong == null || dowLong == null) {483return null;484}485int wby = WEEK_BASED_YEAR.range().checkValidIntValue(wbyLong, WEEK_BASED_YEAR); // always validate486long wowby = fieldValues.get(WEEK_OF_WEEK_BASED_YEAR);487ensureIso(partialTemporal);488LocalDate date = LocalDate.of(wby, 1, 4);489if (resolverStyle == ResolverStyle.LENIENT) {490long dow = dowLong; // unvalidated491if (dow > 7) {492date = date.plusWeeks((dow - 1) / 7);493dow = ((dow - 1) % 7) + 1;494} else if (dow < 1) {495date = date.plusWeeks(Math.subtractExact(dow, 7) / 7);496dow = ((dow + 6) % 7) + 1;497}498date = date.plusWeeks(Math.subtractExact(wowby, 1)).with(DAY_OF_WEEK, dow);499} else {500int dow = DAY_OF_WEEK.checkValidIntValue(dowLong); // validated501if (wowby < 1 || wowby > 52) {502if (resolverStyle == ResolverStyle.STRICT) {503getWeekRange(date).checkValidValue(wowby, this); // only allow exact range504} else { // SMART505range().checkValidValue(wowby, this); // allow 1-53 rolling into next year506}507}508date = date.plusWeeks(wowby - 1).with(DAY_OF_WEEK, dow);509}510fieldValues.remove(this);511fieldValues.remove(WEEK_BASED_YEAR);512fieldValues.remove(DAY_OF_WEEK);513return date;514}515@Override516public String toString() {517return "WeekOfWeekBasedYear";518}519},520WEEK_BASED_YEAR {521@Override522public TemporalUnit getBaseUnit() {523return WEEK_BASED_YEARS;524}525@Override526public TemporalUnit getRangeUnit() {527return FOREVER;528}529@Override530public ValueRange range() {531return YEAR.range();532}533@Override534public boolean isSupportedBy(TemporalAccessor temporal) {535return temporal.isSupported(EPOCH_DAY) && isIso(temporal);536}537@Override538public long getFrom(TemporalAccessor temporal) {539if (isSupportedBy(temporal) == false) {540throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");541}542return getWeekBasedYear(LocalDate.from(temporal));543}544public ValueRange rangeRefinedBy(TemporalAccessor temporal) {545if (isSupportedBy(temporal) == false) {546throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");547}548return super.rangeRefinedBy(temporal);549}550@SuppressWarnings("unchecked")551@Override552public <R extends Temporal> R adjustInto(R temporal, long newValue) {553if (isSupportedBy(temporal) == false) {554throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");555}556int newWby = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check557LocalDate date = LocalDate.from(temporal);558int dow = date.get(DAY_OF_WEEK);559int week = getWeek(date);560if (week == 53 && getWeekRange(newWby) == 52) {561week = 52;562}563LocalDate resolved = LocalDate.of(newWby, 1, 4); // 4th is guaranteed to be in week one564int days = (dow - resolved.get(DAY_OF_WEEK)) + ((week - 1) * 7);565resolved = resolved.plusDays(days);566return (R) temporal.with(resolved);567}568@Override569public String toString() {570return "WeekBasedYear";571}572};573574@Override575public boolean isDateBased() {576return true;577}578579@Override580public boolean isTimeBased() {581return false;582}583584@Override585public ValueRange rangeRefinedBy(TemporalAccessor temporal) {586return range();587}588589//-------------------------------------------------------------------------590private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274};591592593private static void ensureIso(TemporalAccessor temporal) {594if (isIso(temporal) == false) {595throw new DateTimeException("Resolve requires IsoChronology");596}597}598599private static ValueRange getWeekRange(LocalDate date) {600int wby = getWeekBasedYear(date);601return ValueRange.of(1, getWeekRange(wby));602}603604private static int getWeekRange(int wby) {605LocalDate date = LocalDate.of(wby, 1, 1);606// 53 weeks if standard year starts on Thursday, or Wed in a leap year607if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) {608return 53;609}610return 52;611}612613private static int getWeek(LocalDate date) {614int dow0 = date.getDayOfWeek().ordinal();615int doy0 = date.getDayOfYear() - 1;616int doyThu0 = doy0 + (3 - dow0); // adjust to mid-week Thursday (which is 3 indexed from zero)617int alignedWeek = doyThu0 / 7;618int firstThuDoy0 = doyThu0 - (alignedWeek * 7);619int firstMonDoy0 = firstThuDoy0 - 3;620if (firstMonDoy0 < -3) {621firstMonDoy0 += 7;622}623if (doy0 < firstMonDoy0) {624return (int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum();625}626int week = ((doy0 - firstMonDoy0) / 7) + 1;627if (week == 53) {628if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false) {629week = 1;630}631}632return week;633}634635private static int getWeekBasedYear(LocalDate date) {636int year = date.getYear();637int doy = date.getDayOfYear();638if (doy <= 3) {639int dow = date.getDayOfWeek().ordinal();640if (doy - dow < -2) {641year--;642}643} else if (doy >= 363) {644int dow = date.getDayOfWeek().ordinal();645doy = doy - 363 - (date.isLeapYear() ? 1 : 0);646if (doy - dow >= 0) {647year++;648}649}650return year;651}652}653654//-----------------------------------------------------------------------655/**656* Implementation of the unit.657*/658private static enum Unit implements TemporalUnit {659660/**661* Unit that represents the concept of a week-based-year.662*/663WEEK_BASED_YEARS("WeekBasedYears", Duration.ofSeconds(31556952L)),664/**665* Unit that represents the concept of a quarter-year.666*/667QUARTER_YEARS("QuarterYears", Duration.ofSeconds(31556952L / 4));668669private final String name;670private final Duration duration;671672private Unit(String name, Duration estimatedDuration) {673this.name = name;674this.duration = estimatedDuration;675}676677@Override678public Duration getDuration() {679return duration;680}681682@Override683public boolean isDurationEstimated() {684return true;685}686687@Override688public boolean isDateBased() {689return true;690}691692@Override693public boolean isTimeBased() {694return false;695}696697@Override698public boolean isSupportedBy(Temporal temporal) {699return temporal.isSupported(EPOCH_DAY) && isIso(temporal);700}701702@SuppressWarnings("unchecked")703@Override704public <R extends Temporal> R addTo(R temporal, long amount) {705switch (this) {706case WEEK_BASED_YEARS:707return (R) temporal.with(WEEK_BASED_YEAR,708Math.addExact(temporal.get(WEEK_BASED_YEAR), amount));709case QUARTER_YEARS:710return (R) temporal.plus(amount / 4, YEARS)711.plus((amount % 4) * 3, MONTHS);712default:713throw new IllegalStateException("Unreachable");714}715}716717@Override718public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) {719if (temporal1Inclusive.getClass() != temporal2Exclusive.getClass()) {720return temporal1Inclusive.until(temporal2Exclusive, this);721}722switch(this) {723case WEEK_BASED_YEARS:724return Math.subtractExact(temporal2Exclusive.getLong(WEEK_BASED_YEAR),725temporal1Inclusive.getLong(WEEK_BASED_YEAR));726case QUARTER_YEARS:727return temporal1Inclusive.until(temporal2Exclusive, MONTHS) / 3;728default:729throw new IllegalStateException("Unreachable");730}731}732733@Override734public String toString() {735return name;736}737}738739static boolean isIso(TemporalAccessor temporal) {740return Chronology.from(temporal).equals(IsoChronology.INSTANCE);741}742}743744745