Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/time/temporal/WeekFields.java
38918 views
/*1* Copyright (c) 2012, 2013, 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* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* Copyright (c) 2011-2012, Stephen Colebourne & Michael Nascimento Santos32*33* All rights reserved.34*35* Redistribution and use in source and binary forms, with or without36* modification, are permitted provided that the following conditions are met:37*38* * Redistributions of source code must retain the above copyright notice,39* this list of conditions and the following disclaimer.40*41* * Redistributions in binary form must reproduce the above copyright notice,42* this list of conditions and the following disclaimer in the documentation43* and/or other materials provided with the distribution.44*45* * Neither the name of JSR-310 nor the names of its contributors46* may be used to endorse or promote products derived from this software47* without specific prior written permission.48*49* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS50* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT51* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR52* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR53* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,54* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,55* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR56* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF57* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING58* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS59* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.60*/61package java.time.temporal;6263import static java.time.temporal.ChronoField.DAY_OF_MONTH;64import static java.time.temporal.ChronoField.DAY_OF_WEEK;65import static java.time.temporal.ChronoField.DAY_OF_YEAR;66import static java.time.temporal.ChronoField.MONTH_OF_YEAR;67import static java.time.temporal.ChronoField.YEAR;68import static java.time.temporal.ChronoUnit.DAYS;69import static java.time.temporal.ChronoUnit.FOREVER;70import static java.time.temporal.ChronoUnit.MONTHS;71import static java.time.temporal.ChronoUnit.WEEKS;72import static java.time.temporal.ChronoUnit.YEARS;7374import java.io.IOException;75import java.io.InvalidObjectException;76import java.io.ObjectInputStream;77import java.io.Serializable;78import java.time.DateTimeException;79import java.time.DayOfWeek;80import java.time.chrono.ChronoLocalDate;81import java.time.chrono.Chronology;82import java.time.format.ResolverStyle;83import java.util.Locale;84import java.util.Map;85import java.util.Objects;86import java.util.ResourceBundle;87import java.util.concurrent.ConcurrentHashMap;88import java.util.concurrent.ConcurrentMap;89import sun.util.locale.provider.CalendarDataUtility;90import sun.util.locale.provider.LocaleProviderAdapter;91import sun.util.locale.provider.LocaleResources;9293/**94* Localized definitions of the day-of-week, week-of-month and week-of-year fields.95* <p>96* A standard week is seven days long, but cultures have different definitions for some97* other aspects of a week. This class represents the definition of the week, for the98* purpose of providing {@link TemporalField} instances.99* <p>100* WeekFields provides five fields,101* {@link #dayOfWeek()}, {@link #weekOfMonth()}, {@link #weekOfYear()},102* {@link #weekOfWeekBasedYear()}, and {@link #weekBasedYear()}103* that provide access to the values from any {@linkplain Temporal temporal object}.104* <p>105* The computations for day-of-week, week-of-month, and week-of-year are based106* on the {@linkplain ChronoField#YEAR proleptic-year},107* {@linkplain ChronoField#MONTH_OF_YEAR month-of-year},108* {@linkplain ChronoField#DAY_OF_MONTH day-of-month}, and109* {@linkplain ChronoField#DAY_OF_WEEK ISO day-of-week} which are based on the110* {@linkplain ChronoField#EPOCH_DAY epoch-day} and the chronology.111* The values may not be aligned with the {@linkplain ChronoField#YEAR_OF_ERA year-of-Era}112* depending on the Chronology.113* <p>A week is defined by:114* <ul>115* <li>The first day-of-week.116* For example, the ISO-8601 standard considers Monday to be the first day-of-week.117* <li>The minimal number of days in the first week.118* For example, the ISO-8601 standard counts the first week as needing at least 4 days.119* </ul>120* Together these two values allow a year or month to be divided into weeks.121*122* <h3>Week of Month</h3>123* One field is used: week-of-month.124* The calculation ensures that weeks never overlap a month boundary.125* The month is divided into periods where each period starts on the defined first day-of-week.126* The earliest period is referred to as week 0 if it has less than the minimal number of days127* and week 1 if it has at least the minimal number of days.128*129* <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;">130* <caption>Examples of WeekFields</caption>131* <tr><th>Date</th><td>Day-of-week</td>132* <td>First day: Monday<br>Minimal days: 4</td><td>First day: Monday<br>Minimal days: 5</td></tr>133* <tr><th>2008-12-31</th><td>Wednesday</td>134* <td>Week 5 of December 2008</td><td>Week 5 of December 2008</td></tr>135* <tr><th>2009-01-01</th><td>Thursday</td>136* <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr>137* <tr><th>2009-01-04</th><td>Sunday</td>138* <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr>139* <tr><th>2009-01-05</th><td>Monday</td>140* <td>Week 2 of January 2009</td><td>Week 1 of January 2009</td></tr>141* </table>142*143* <h3>Week of Year</h3>144* One field is used: week-of-year.145* The calculation ensures that weeks never overlap a year boundary.146* The year is divided into periods where each period starts on the defined first day-of-week.147* The earliest period is referred to as week 0 if it has less than the minimal number of days148* and week 1 if it has at least the minimal number of days.149*150* <h3>Week Based Year</h3>151* Two fields are used for week-based-year, one for the152* {@link #weekOfWeekBasedYear() week-of-week-based-year} and one for153* {@link #weekBasedYear() week-based-year}. In a week-based-year, each week154* belongs to only a single year. Week 1 of a year is the first week that155* starts on the first day-of-week and has at least the minimum number of days.156* The first and last weeks of a year may contain days from the157* previous calendar year or next calendar year respectively.158*159* <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;">160* <caption>Examples of WeekFields for week-based-year</caption>161* <tr><th>Date</th><td>Day-of-week</td>162* <td>First day: Monday<br>Minimal days: 4</td><td>First day: Monday<br>Minimal days: 5</td></tr>163* <tr><th>2008-12-31</th><td>Wednesday</td>164* <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr>165* <tr><th>2009-01-01</th><td>Thursday</td>166* <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr>167* <tr><th>2009-01-04</th><td>Sunday</td>168* <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr>169* <tr><th>2009-01-05</th><td>Monday</td>170* <td>Week 2 of 2009</td><td>Week 1 of 2009</td></tr>171* </table>172*173* @implSpec174* This class is immutable and thread-safe.175*176* @since 1.8177*/178public final class WeekFields implements Serializable {179// implementation notes180// querying week-of-month or week-of-year should return the week value bound within the month/year181// however, setting the week value should be lenient (use plus/minus weeks)182// allow week-of-month outer range [0 to 6]183// allow week-of-year outer range [0 to 54]184// this is because callers shouldn't be expected to know the details of validity185186/**187* The cache of rules by firstDayOfWeek plus minimalDays.188* Initialized first to be available for definition of ISO, etc.189*/190private static final ConcurrentMap<String, WeekFields> CACHE = new ConcurrentHashMap<>(4, 0.75f, 2);191192/**193* The ISO-8601 definition, where a week starts on Monday and the first week194* has a minimum of 4 days.195* <p>196* The ISO-8601 standard defines a calendar system based on weeks.197* It uses the week-based-year and week-of-week-based-year concepts to split198* up the passage of days instead of the standard year/month/day.199* <p>200* Note that the first week may start in the previous calendar year.201* Note also that the first few days of a calendar year may be in the202* week-based-year corresponding to the previous calendar year.203*/204public static final WeekFields ISO = new WeekFields(DayOfWeek.MONDAY, 4);205206/**207* The common definition of a week that starts on Sunday and the first week208* has a minimum of 1 day.209* <p>210* Defined as starting on Sunday and with a minimum of 1 day in the month.211* This week definition is in use in the US and other European countries.212*/213public static final WeekFields SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1);214215/**216* The unit that represents week-based-years for the purpose of addition and subtraction.217* <p>218* This allows a number of week-based-years to be added to, or subtracted from, a date.219* The unit is equal to either 52 or 53 weeks.220* The estimated duration of a week-based-year is the same as that of a standard ISO221* year at {@code 365.2425 Days}.222* <p>223* The rules for addition add the number of week-based-years to the existing value224* for the week-based-year field retaining the week-of-week-based-year225* and day-of-week, unless the week number it too large for the target year.226* In that case, the week is set to the last week of the year227* with the same day-of-week.228* <p>229* This unit is an immutable and thread-safe singleton.230*/231public static final TemporalUnit WEEK_BASED_YEARS = IsoFields.WEEK_BASED_YEARS;232233/**234* Serialization version.235*/236private static final long serialVersionUID = -1177360819670808121L;237238/**239* The first day-of-week.240*/241private final DayOfWeek firstDayOfWeek;242/**243* The minimal number of days in the first week.244*/245private final int minimalDays;246/**247* The field used to access the computed DayOfWeek.248*/249private final transient TemporalField dayOfWeek = ComputedDayOfField.ofDayOfWeekField(this);250/**251* The field used to access the computed WeekOfMonth.252*/253private final transient TemporalField weekOfMonth = ComputedDayOfField.ofWeekOfMonthField(this);254/**255* The field used to access the computed WeekOfYear.256*/257private final transient TemporalField weekOfYear = ComputedDayOfField.ofWeekOfYearField(this);258/**259* The field that represents the week-of-week-based-year.260* <p>261* This field allows the week of the week-based-year value to be queried and set.262* <p>263* This unit is an immutable and thread-safe singleton.264*/265private final transient TemporalField weekOfWeekBasedYear = ComputedDayOfField.ofWeekOfWeekBasedYearField(this);266/**267* The field that represents the week-based-year.268* <p>269* This field allows the week-based-year value to be queried and set.270* <p>271* This unit is an immutable and thread-safe singleton.272*/273private final transient TemporalField weekBasedYear = ComputedDayOfField.ofWeekBasedYearField(this);274275//-----------------------------------------------------------------------276/**277* Obtains an instance of {@code WeekFields} appropriate for a locale.278* <p>279* This will look up appropriate values from the provider of localization data.280*281* @param locale the locale to use, not null282* @return the week-definition, not null283*/284public static WeekFields of(Locale locale) {285Objects.requireNonNull(locale, "locale");286locale = new Locale(locale.getLanguage(), locale.getCountry()); // elminate variants287288int calDow = CalendarDataUtility.retrieveFirstDayOfWeek(locale);289DayOfWeek dow = DayOfWeek.SUNDAY.plus(calDow - 1);290int minDays = CalendarDataUtility.retrieveMinimalDaysInFirstWeek(locale);291return WeekFields.of(dow, minDays);292}293294/**295* Obtains an instance of {@code WeekFields} from the first day-of-week and minimal days.296* <p>297* The first day-of-week defines the ISO {@code DayOfWeek} that is day 1 of the week.298* The minimal number of days in the first week defines how many days must be present299* in a month or year, starting from the first day-of-week, before the week is counted300* as the first week. A value of 1 will count the first day of the month or year as part301* of the first week, whereas a value of 7 will require the whole seven days to be in302* the new month or year.303* <p>304* WeekFields instances are singletons; for each unique combination305* of {@code firstDayOfWeek} and {@code minimalDaysInFirstWeek} the306* the same instance will be returned.307*308* @param firstDayOfWeek the first day of the week, not null309* @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7310* @return the week-definition, not null311* @throws IllegalArgumentException if the minimal days value is less than one312* or greater than 7313*/314public static WeekFields of(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) {315String key = firstDayOfWeek.toString() + minimalDaysInFirstWeek;316WeekFields rules = CACHE.get(key);317if (rules == null) {318rules = new WeekFields(firstDayOfWeek, minimalDaysInFirstWeek);319CACHE.putIfAbsent(key, rules);320rules = CACHE.get(key);321}322return rules;323}324325//-----------------------------------------------------------------------326/**327* Creates an instance of the definition.328*329* @param firstDayOfWeek the first day of the week, not null330* @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7331* @throws IllegalArgumentException if the minimal days value is invalid332*/333private WeekFields(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) {334Objects.requireNonNull(firstDayOfWeek, "firstDayOfWeek");335if (minimalDaysInFirstWeek < 1 || minimalDaysInFirstWeek > 7) {336throw new IllegalArgumentException("Minimal number of days is invalid");337}338this.firstDayOfWeek = firstDayOfWeek;339this.minimalDays = minimalDaysInFirstWeek;340}341342//-----------------------------------------------------------------------343/**344* Restore the state of a WeekFields from the stream.345* Check that the values are valid.346*347* @param s the stream to read348* @throws InvalidObjectException if the serialized object has an invalid349* value for firstDayOfWeek or minimalDays.350* @throws ClassNotFoundException if a class cannot be resolved351*/352private void readObject(ObjectInputStream s)353throws IOException, ClassNotFoundException, InvalidObjectException354{355s.defaultReadObject();356if (firstDayOfWeek == null) {357throw new InvalidObjectException("firstDayOfWeek is null");358}359360if (minimalDays < 1 || minimalDays > 7) {361throw new InvalidObjectException("Minimal number of days is invalid");362}363}364365/**366* Return the singleton WeekFields associated with the367* {@code firstDayOfWeek} and {@code minimalDays}.368* @return the singleton WeekFields for the firstDayOfWeek and minimalDays.369* @throws InvalidObjectException if the serialized object has invalid370* values for firstDayOfWeek or minimalDays.371*/372private Object readResolve() throws InvalidObjectException {373try {374return WeekFields.of(firstDayOfWeek, minimalDays);375} catch (IllegalArgumentException iae) {376throw new InvalidObjectException("Invalid serialized WeekFields: " + iae.getMessage());377}378}379380//-----------------------------------------------------------------------381/**382* Gets the first day-of-week.383* <p>384* The first day-of-week varies by culture.385* For example, the US uses Sunday, while France and the ISO-8601 standard use Monday.386* This method returns the first day using the standard {@code DayOfWeek} enum.387*388* @return the first day-of-week, not null389*/390public DayOfWeek getFirstDayOfWeek() {391return firstDayOfWeek;392}393394/**395* Gets the minimal number of days in the first week.396* <p>397* The number of days considered to define the first week of a month or year398* varies by culture.399* For example, the ISO-8601 requires 4 days (more than half a week) to400* be present before counting the first week.401*402* @return the minimal number of days in the first week of a month or year, from 1 to 7403*/404public int getMinimalDaysInFirstWeek() {405return minimalDays;406}407408//-----------------------------------------------------------------------409/**410* Returns a field to access the day of week based on this {@code WeekFields}.411* <p>412* This is similar to {@link ChronoField#DAY_OF_WEEK} but uses values for413* the day-of-week based on this {@code WeekFields}.414* The days are numbered from 1 to 7 where the415* {@link #getFirstDayOfWeek() first day-of-week} is assigned the value 1.416* <p>417* For example, if the first day-of-week is Sunday, then that will have the418* value 1, with other days ranging from Monday as 2 to Saturday as 7.419* <p>420* In the resolving phase of parsing, a localized day-of-week will be converted421* to a standardized {@code ChronoField} day-of-week.422* The day-of-week must be in the valid range 1 to 7.423* Other fields in this class build dates using the standardized day-of-week.424*425* @return a field providing access to the day-of-week with localized numbering, not null426*/427public TemporalField dayOfWeek() {428return dayOfWeek;429}430431/**432* Returns a field to access the week of month based on this {@code WeekFields}.433* <p>434* This represents the concept of the count of weeks within the month where weeks435* start on a fixed day-of-week, such as Monday.436* This field is typically used with {@link WeekFields#dayOfWeek()}.437* <p>438* Week one (1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}439* where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the month.440* Thus, week one may start up to {@code minDays} days before the start of the month.441* If the first week starts after the start of the month then the period before is week zero (0).442* <p>443* For example:<br>444* - if the 1st day of the month is a Monday, week one starts on the 1st and there is no week zero<br>445* - if the 2nd day of the month is a Monday, week one starts on the 2nd and the 1st is in week zero<br>446* - if the 4th day of the month is a Monday, week one starts on the 4th and the 1st to 3rd is in week zero<br>447* - if the 5th day of the month is a Monday, week two starts on the 5th and the 1st to 4th is in week one<br>448* <p>449* This field can be used with any calendar system.450* <p>451* In the resolving phase of parsing, a date can be created from a year,452* week-of-month, month-of-year and day-of-week.453* <p>454* In {@linkplain ResolverStyle#STRICT strict mode}, all four fields are455* validated against their range of valid values. The week-of-month field456* is validated to ensure that the resulting month is the month requested.457* <p>458* In {@linkplain ResolverStyle#SMART smart mode}, all four fields are459* validated against their range of valid values. The week-of-month field460* is validated from 0 to 6, meaning that the resulting date can be in a461* different month to that specified.462* <p>463* In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week464* are validated against the range of valid values. The resulting date is calculated465* equivalent to the following four stage approach.466* First, create a date on the first day of the first week of January in the requested year.467* Then take the month-of-year, subtract one, and add the amount in months to the date.468* Then take the week-of-month, subtract one, and add the amount in weeks to the date.469* Finally, adjust to the correct day-of-week within the localized week.470*471* @return a field providing access to the week-of-month, not null472*/473public TemporalField weekOfMonth() {474return weekOfMonth;475}476477/**478* Returns a field to access the week of year based on this {@code WeekFields}.479* <p>480* This represents the concept of the count of weeks within the year where weeks481* start on a fixed day-of-week, such as Monday.482* This field is typically used with {@link WeekFields#dayOfWeek()}.483* <p>484* Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}485* where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year.486* Thus, week one may start up to {@code minDays} days before the start of the year.487* If the first week starts after the start of the year then the period before is week zero (0).488* <p>489* For example:<br>490* - if the 1st day of the year is a Monday, week one starts on the 1st and there is no week zero<br>491* - if the 2nd day of the year is a Monday, week one starts on the 2nd and the 1st is in week zero<br>492* - if the 4th day of the year is a Monday, week one starts on the 4th and the 1st to 3rd is in week zero<br>493* - if the 5th day of the year is a Monday, week two starts on the 5th and the 1st to 4th is in week one<br>494* <p>495* This field can be used with any calendar system.496* <p>497* In the resolving phase of parsing, a date can be created from a year,498* week-of-year and day-of-week.499* <p>500* In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are501* validated against their range of valid values. The week-of-year field502* is validated to ensure that the resulting year is the year requested.503* <p>504* In {@linkplain ResolverStyle#SMART smart mode}, all three fields are505* validated against their range of valid values. The week-of-year field506* is validated from 0 to 54, meaning that the resulting date can be in a507* different year to that specified.508* <p>509* In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week510* are validated against the range of valid values. The resulting date is calculated511* equivalent to the following three stage approach.512* First, create a date on the first day of the first week in the requested year.513* Then take the week-of-year, subtract one, and add the amount in weeks to the date.514* Finally, adjust to the correct day-of-week within the localized week.515*516* @return a field providing access to the week-of-year, not null517*/518public TemporalField weekOfYear() {519return weekOfYear;520}521522/**523* Returns a field to access the week of a week-based-year based on this {@code WeekFields}.524* <p>525* This represents the concept of the count of weeks within the year where weeks526* start on a fixed day-of-week, such as Monday and each week belongs to exactly one year.527* This field is typically used with {@link WeekFields#dayOfWeek()} and528* {@link WeekFields#weekBasedYear()}.529* <p>530* Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}531* where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year.532* If the first week starts after the start of the year then the period before533* is in the last week of the previous year.534* <p>535* For example:<br>536* - if the 1st day of the year is a Monday, week one starts on the 1st<br>537* - if the 2nd day of the year is a Monday, week one starts on the 2nd and538* the 1st is in the last week of the previous year<br>539* - if the 4th day of the year is a Monday, week one starts on the 4th and540* the 1st to 3rd is in the last week of the previous year<br>541* - if the 5th day of the year is a Monday, week two starts on the 5th and542* the 1st to 4th is in week one<br>543* <p>544* This field can be used with any calendar system.545* <p>546* In the resolving phase of parsing, a date can be created from a week-based-year,547* week-of-year and day-of-week.548* <p>549* In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are550* validated against their range of valid values. The week-of-year field551* is validated to ensure that the resulting week-based-year is the552* week-based-year requested.553* <p>554* In {@linkplain ResolverStyle#SMART smart mode}, all three fields are555* validated against their range of valid values. The week-of-week-based-year field556* is validated from 1 to 53, meaning that the resulting date can be in the557* following week-based-year to that specified.558* <p>559* In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week560* are validated against the range of valid values. The resulting date is calculated561* equivalent to the following three stage approach.562* First, create a date on the first day of the first week in the requested week-based-year.563* Then take the week-of-week-based-year, subtract one, and add the amount in weeks to the date.564* Finally, adjust to the correct day-of-week within the localized week.565*566* @return a field providing access to the week-of-week-based-year, not null567*/568public TemporalField weekOfWeekBasedYear() {569return weekOfWeekBasedYear;570}571572/**573* Returns a field to access the year of a week-based-year based on this {@code WeekFields}.574* <p>575* This represents the concept of the year where weeks start on a fixed day-of-week,576* such as Monday and each week belongs to exactly one year.577* This field is typically used with {@link WeekFields#dayOfWeek()} and578* {@link WeekFields#weekOfWeekBasedYear()}.579* <p>580* Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}581* where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year.582* Thus, week one may start before the start of the year.583* If the first week starts after the start of the year then the period before584* is in the last week of the previous year.585* <p>586* This field can be used with any calendar system.587* <p>588* In the resolving phase of parsing, a date can be created from a week-based-year,589* week-of-year and day-of-week.590* <p>591* In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are592* validated against their range of valid values. The week-of-year field593* is validated to ensure that the resulting week-based-year is the594* week-based-year requested.595* <p>596* In {@linkplain ResolverStyle#SMART smart mode}, all three fields are597* validated against their range of valid values. The week-of-week-based-year field598* is validated from 1 to 53, meaning that the resulting date can be in the599* following week-based-year to that specified.600* <p>601* In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week602* are validated against the range of valid values. The resulting date is calculated603* equivalent to the following three stage approach.604* First, create a date on the first day of the first week in the requested week-based-year.605* Then take the week-of-week-based-year, subtract one, and add the amount in weeks to the date.606* Finally, adjust to the correct day-of-week within the localized week.607*608* @return a field providing access to the week-based-year, not null609*/610public TemporalField weekBasedYear() {611return weekBasedYear;612}613614//-----------------------------------------------------------------------615/**616* Checks if this {@code WeekFields} is equal to the specified object.617* <p>618* The comparison is based on the entire state of the rules, which is619* the first day-of-week and minimal days.620*621* @param object the other rules to compare to, null returns false622* @return true if this is equal to the specified rules623*/624@Override625public boolean equals(Object object) {626if (this == object) {627return true;628}629if (object instanceof WeekFields) {630return hashCode() == object.hashCode();631}632return false;633}634635/**636* A hash code for this {@code WeekFields}.637*638* @return a suitable hash code639*/640@Override641public int hashCode() {642return firstDayOfWeek.ordinal() * 7 + minimalDays;643}644645//-----------------------------------------------------------------------646/**647* A string representation of this {@code WeekFields} instance.648*649* @return the string representation, not null650*/651@Override652public String toString() {653return "WeekFields[" + firstDayOfWeek + ',' + minimalDays + ']';654}655656//-----------------------------------------------------------------------657/**658* Field type that computes DayOfWeek, WeekOfMonth, and WeekOfYear659* based on a WeekFields.660* A separate Field instance is required for each different WeekFields;661* combination of start of week and minimum number of days.662* Constructors are provided to create fields for DayOfWeek, WeekOfMonth,663* and WeekOfYear.664*/665static class ComputedDayOfField implements TemporalField {666667/**668* Returns a field to access the day of week,669* computed based on a WeekFields.670* <p>671* The WeekDefintion of the first day of the week is used with672* the ISO DAY_OF_WEEK field to compute week boundaries.673*/674static ComputedDayOfField ofDayOfWeekField(WeekFields weekDef) {675return new ComputedDayOfField("DayOfWeek", weekDef, DAYS, WEEKS, DAY_OF_WEEK_RANGE);676}677678/**679* Returns a field to access the week of month,680* computed based on a WeekFields.681* @see WeekFields#weekOfMonth()682*/683static ComputedDayOfField ofWeekOfMonthField(WeekFields weekDef) {684return new ComputedDayOfField("WeekOfMonth", weekDef, WEEKS, MONTHS, WEEK_OF_MONTH_RANGE);685}686687/**688* Returns a field to access the week of year,689* computed based on a WeekFields.690* @see WeekFields#weekOfYear()691*/692static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) {693return new ComputedDayOfField("WeekOfYear", weekDef, WEEKS, YEARS, WEEK_OF_YEAR_RANGE);694}695696/**697* Returns a field to access the week of week-based-year,698* computed based on a WeekFields.699* @see WeekFields#weekOfWeekBasedYear()700*/701static ComputedDayOfField ofWeekOfWeekBasedYearField(WeekFields weekDef) {702return new ComputedDayOfField("WeekOfWeekBasedYear", weekDef, WEEKS, IsoFields.WEEK_BASED_YEARS, WEEK_OF_WEEK_BASED_YEAR_RANGE);703}704705/**706* Returns a field to access the week of week-based-year,707* computed based on a WeekFields.708* @see WeekFields#weekBasedYear()709*/710static ComputedDayOfField ofWeekBasedYearField(WeekFields weekDef) {711return new ComputedDayOfField("WeekBasedYear", weekDef, IsoFields.WEEK_BASED_YEARS, FOREVER, ChronoField.YEAR.range());712}713714/**715* Return a new week-based-year date of the Chronology, year, week-of-year,716* and dow of week.717* @param chrono The chronology of the new date718* @param yowby the year of the week-based-year719* @param wowby the week of the week-based-year720* @param dow the day of the week721* @return a ChronoLocalDate for the requested year, week of year, and day of week722*/723private ChronoLocalDate ofWeekBasedYear(Chronology chrono,724int yowby, int wowby, int dow) {725ChronoLocalDate date = chrono.date(yowby, 1, 1);726int ldow = localizedDayOfWeek(date);727int offset = startOfWeekOffset(1, ldow);728729// Clamp the week of year to keep it in the same year730int yearLen = date.lengthOfYear();731int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());732wowby = Math.min(wowby, newYearWeek - 1);733734int days = -offset + (dow - 1) + (wowby - 1) * 7;735return date.plus(days, DAYS);736}737738private final String name;739private final WeekFields weekDef;740private final TemporalUnit baseUnit;741private final TemporalUnit rangeUnit;742private final ValueRange range;743744private ComputedDayOfField(String name, WeekFields weekDef, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) {745this.name = name;746this.weekDef = weekDef;747this.baseUnit = baseUnit;748this.rangeUnit = rangeUnit;749this.range = range;750}751752private static final ValueRange DAY_OF_WEEK_RANGE = ValueRange.of(1, 7);753private static final ValueRange WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 6);754private static final ValueRange WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 54);755private static final ValueRange WEEK_OF_WEEK_BASED_YEAR_RANGE = ValueRange.of(1, 52, 53);756757@Override758public long getFrom(TemporalAccessor temporal) {759if (rangeUnit == WEEKS) { // day-of-week760return localizedDayOfWeek(temporal);761} else if (rangeUnit == MONTHS) { // week-of-month762return localizedWeekOfMonth(temporal);763} else if (rangeUnit == YEARS) { // week-of-year764return localizedWeekOfYear(temporal);765} else if (rangeUnit == WEEK_BASED_YEARS) {766return localizedWeekOfWeekBasedYear(temporal);767} else if (rangeUnit == FOREVER) {768return localizedWeekBasedYear(temporal);769} else {770throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this);771}772}773774private int localizedDayOfWeek(TemporalAccessor temporal) {775int sow = weekDef.getFirstDayOfWeek().getValue();776int isoDow = temporal.get(DAY_OF_WEEK);777return Math.floorMod(isoDow - sow, 7) + 1;778}779780private int localizedDayOfWeek(int isoDow) {781int sow = weekDef.getFirstDayOfWeek().getValue();782return Math.floorMod(isoDow - sow, 7) + 1;783}784785private long localizedWeekOfMonth(TemporalAccessor temporal) {786int dow = localizedDayOfWeek(temporal);787int dom = temporal.get(DAY_OF_MONTH);788int offset = startOfWeekOffset(dom, dow);789return computeWeek(offset, dom);790}791792private long localizedWeekOfYear(TemporalAccessor temporal) {793int dow = localizedDayOfWeek(temporal);794int doy = temporal.get(DAY_OF_YEAR);795int offset = startOfWeekOffset(doy, dow);796return computeWeek(offset, doy);797}798799/**800* Returns the year of week-based-year for the temporal.801* The year can be the previous year, the current year, or the next year.802* @param temporal a date of any chronology, not null803* @return the year of week-based-year for the date804*/805private int localizedWeekBasedYear(TemporalAccessor temporal) {806int dow = localizedDayOfWeek(temporal);807int year = temporal.get(YEAR);808int doy = temporal.get(DAY_OF_YEAR);809int offset = startOfWeekOffset(doy, dow);810int week = computeWeek(offset, doy);811if (week == 0) {812// Day is in end of week of previous year; return the previous year813return year - 1;814} else {815// If getting close to end of year, use higher precision logic816// Check if date of year is in partial week associated with next year817ValueRange dayRange = temporal.range(DAY_OF_YEAR);818int yearLen = (int)dayRange.getMaximum();819int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());820if (week >= newYearWeek) {821return year + 1;822}823}824return year;825}826827/**828* Returns the week of week-based-year for the temporal.829* The week can be part of the previous year, the current year,830* or the next year depending on the week start and minimum number831* of days.832* @param temporal a date of any chronology833* @return the week of the year834* @see #localizedWeekBasedYear(java.time.temporal.TemporalAccessor)835*/836private int localizedWeekOfWeekBasedYear(TemporalAccessor temporal) {837int dow = localizedDayOfWeek(temporal);838int doy = temporal.get(DAY_OF_YEAR);839int offset = startOfWeekOffset(doy, dow);840int week = computeWeek(offset, doy);841if (week == 0) {842// Day is in end of week of previous year843// Recompute from the last day of the previous year844ChronoLocalDate date = Chronology.from(temporal).date(temporal);845date = date.minus(doy, DAYS); // Back down into previous year846return localizedWeekOfWeekBasedYear(date);847} else if (week > 50) {848// If getting close to end of year, use higher precision logic849// Check if date of year is in partial week associated with next year850ValueRange dayRange = temporal.range(DAY_OF_YEAR);851int yearLen = (int)dayRange.getMaximum();852int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());853if (week >= newYearWeek) {854// Overlaps with week of following year; reduce to week in following year855week = week - newYearWeek + 1;856}857}858return week;859}860861/**862* Returns an offset to align week start with a day of month or day of year.863*864* @param day the day; 1 through infinity865* @param dow the day of the week of that day; 1 through 7866* @return an offset in days to align a day with the start of the first 'full' week867*/868private int startOfWeekOffset(int day, int dow) {869// offset of first day corresponding to the day of week in first 7 days (zero origin)870int weekStart = Math.floorMod(day - dow, 7);871int offset = -weekStart;872if (weekStart + 1 > weekDef.getMinimalDaysInFirstWeek()) {873// The previous week has the minimum days in the current month to be a 'week'874offset = 7 - weekStart;875}876return offset;877}878879/**880* Returns the week number computed from the reference day and reference dayOfWeek.881*882* @param offset the offset to align a date with the start of week883* from {@link #startOfWeekOffset}.884* @param day the day for which to compute the week number885* @return the week number where zero is used for a partial week and 1 for the first full week886*/887private int computeWeek(int offset, int day) {888return ((7 + offset + (day - 1)) / 7);889}890891@SuppressWarnings("unchecked")892@Override893public <R extends Temporal> R adjustInto(R temporal, long newValue) {894// Check the new value and get the old value of the field895int newVal = range.checkValidIntValue(newValue, this); // lenient check range896int currentVal = temporal.get(this);897if (newVal == currentVal) {898return temporal;899}900901if (rangeUnit == FOREVER) { // replace year of WeekBasedYear902// Create a new date object with the same chronology,903// the desired year and the same week and dow.904int idow = temporal.get(weekDef.dayOfWeek);905int wowby = temporal.get(weekDef.weekOfWeekBasedYear);906return (R) ofWeekBasedYear(Chronology.from(temporal), (int)newValue, wowby, idow);907} else {908// Compute the difference and add that using the base unit of the field909return (R) temporal.plus(newVal - currentVal, baseUnit);910}911}912913@Override914public ChronoLocalDate resolve(915Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {916final long value = fieldValues.get(this);917final int newValue = Math.toIntExact(value); // broad limit makes overflow checking lighter918// first convert localized day-of-week to ISO day-of-week919// doing this first handles case where both ISO and localized were parsed and might mismatch920// day-of-week is always strict as two different day-of-week values makes lenient complex921if (rangeUnit == WEEKS) { // day-of-week922final int checkedValue = range.checkValidIntValue(value, this); // no leniency as too complex923final int startDow = weekDef.getFirstDayOfWeek().getValue();924long isoDow = Math.floorMod((startDow - 1) + (checkedValue - 1), 7) + 1;925fieldValues.remove(this);926fieldValues.put(DAY_OF_WEEK, isoDow);927return null;928}929930// can only build date if ISO day-of-week is present931if (fieldValues.containsKey(DAY_OF_WEEK) == false) {932return null;933}934int isoDow = DAY_OF_WEEK.checkValidIntValue(fieldValues.get(DAY_OF_WEEK));935int dow = localizedDayOfWeek(isoDow);936937// build date938Chronology chrono = Chronology.from(partialTemporal);939if (fieldValues.containsKey(YEAR)) {940int year = YEAR.checkValidIntValue(fieldValues.get(YEAR)); // validate941if (rangeUnit == MONTHS && fieldValues.containsKey(MONTH_OF_YEAR)) { // week-of-month942long month = fieldValues.get(MONTH_OF_YEAR); // not validated yet943return resolveWoM(fieldValues, chrono, year, month, newValue, dow, resolverStyle);944}945if (rangeUnit == YEARS) { // week-of-year946return resolveWoY(fieldValues, chrono, year, newValue, dow, resolverStyle);947}948} else if ((rangeUnit == WEEK_BASED_YEARS || rangeUnit == FOREVER) &&949fieldValues.containsKey(weekDef.weekBasedYear) &&950fieldValues.containsKey(weekDef.weekOfWeekBasedYear)) { // week-of-week-based-year and year-of-week-based-year951return resolveWBY(fieldValues, chrono, dow, resolverStyle);952}953return null;954}955956private ChronoLocalDate resolveWoM(957Map<TemporalField, Long> fieldValues, Chronology chrono, int year, long month, long wom, int localDow, ResolverStyle resolverStyle) {958ChronoLocalDate date;959if (resolverStyle == ResolverStyle.LENIENT) {960date = chrono.date(year, 1, 1).plus(Math.subtractExact(month, 1), MONTHS);961long weeks = Math.subtractExact(wom, localizedWeekOfMonth(date));962int days = localDow - localizedDayOfWeek(date); // safe from overflow963date = date.plus(Math.addExact(Math.multiplyExact(weeks, 7), days), DAYS);964} else {965int monthValid = MONTH_OF_YEAR.checkValidIntValue(month); // validate966date = chrono.date(year, monthValid, 1);967int womInt = range.checkValidIntValue(wom, this); // validate968int weeks = (int) (womInt - localizedWeekOfMonth(date)); // safe from overflow969int days = localDow - localizedDayOfWeek(date); // safe from overflow970date = date.plus(weeks * 7 + days, DAYS);971if (resolverStyle == ResolverStyle.STRICT && date.getLong(MONTH_OF_YEAR) != month) {972throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");973}974}975fieldValues.remove(this);976fieldValues.remove(YEAR);977fieldValues.remove(MONTH_OF_YEAR);978fieldValues.remove(DAY_OF_WEEK);979return date;980}981982private ChronoLocalDate resolveWoY(983Map<TemporalField, Long> fieldValues, Chronology chrono, int year, long woy, int localDow, ResolverStyle resolverStyle) {984ChronoLocalDate date = chrono.date(year, 1, 1);985if (resolverStyle == ResolverStyle.LENIENT) {986long weeks = Math.subtractExact(woy, localizedWeekOfYear(date));987int days = localDow - localizedDayOfWeek(date); // safe from overflow988date = date.plus(Math.addExact(Math.multiplyExact(weeks, 7), days), DAYS);989} else {990int womInt = range.checkValidIntValue(woy, this); // validate991int weeks = (int) (womInt - localizedWeekOfYear(date)); // safe from overflow992int days = localDow - localizedDayOfWeek(date); // safe from overflow993date = date.plus(weeks * 7 + days, DAYS);994if (resolverStyle == ResolverStyle.STRICT && date.getLong(YEAR) != year) {995throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");996}997}998fieldValues.remove(this);999fieldValues.remove(YEAR);1000fieldValues.remove(DAY_OF_WEEK);1001return date;1002}10031004private ChronoLocalDate resolveWBY(1005Map<TemporalField, Long> fieldValues, Chronology chrono, int localDow, ResolverStyle resolverStyle) {1006int yowby = weekDef.weekBasedYear.range().checkValidIntValue(1007fieldValues.get(weekDef.weekBasedYear), weekDef.weekBasedYear);1008ChronoLocalDate date;1009if (resolverStyle == ResolverStyle.LENIENT) {1010date = ofWeekBasedYear(chrono, yowby, 1, localDow);1011long wowby = fieldValues.get(weekDef.weekOfWeekBasedYear);1012long weeks = Math.subtractExact(wowby, 1);1013date = date.plus(weeks, WEEKS);1014} else {1015int wowby = weekDef.weekOfWeekBasedYear.range().checkValidIntValue(1016fieldValues.get(weekDef.weekOfWeekBasedYear), weekDef.weekOfWeekBasedYear); // validate1017date = ofWeekBasedYear(chrono, yowby, wowby, localDow);1018if (resolverStyle == ResolverStyle.STRICT && localizedWeekBasedYear(date) != yowby) {1019throw new DateTimeException("Strict mode rejected resolved date as it is in a different week-based-year");1020}1021}1022fieldValues.remove(this);1023fieldValues.remove(weekDef.weekBasedYear);1024fieldValues.remove(weekDef.weekOfWeekBasedYear);1025fieldValues.remove(DAY_OF_WEEK);1026return date;1027}10281029//-----------------------------------------------------------------------1030@Override1031public String getDisplayName(Locale locale) {1032Objects.requireNonNull(locale, "locale");1033if (rangeUnit == YEARS) { // only have values for week-of-year1034LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()1035.getLocaleResources(locale);1036ResourceBundle rb = lr.getJavaTimeFormatData();1037return rb.containsKey("field.week") ? rb.getString("field.week") : name;1038}1039return name;1040}10411042@Override1043public TemporalUnit getBaseUnit() {1044return baseUnit;1045}10461047@Override1048public TemporalUnit getRangeUnit() {1049return rangeUnit;1050}10511052@Override1053public boolean isDateBased() {1054return true;1055}10561057@Override1058public boolean isTimeBased() {1059return false;1060}10611062@Override1063public ValueRange range() {1064return range;1065}10661067//-----------------------------------------------------------------------1068@Override1069public boolean isSupportedBy(TemporalAccessor temporal) {1070if (temporal.isSupported(DAY_OF_WEEK)) {1071if (rangeUnit == WEEKS) { // day-of-week1072return true;1073} else if (rangeUnit == MONTHS) { // week-of-month1074return temporal.isSupported(DAY_OF_MONTH);1075} else if (rangeUnit == YEARS) { // week-of-year1076return temporal.isSupported(DAY_OF_YEAR);1077} else if (rangeUnit == WEEK_BASED_YEARS) {1078return temporal.isSupported(DAY_OF_YEAR);1079} else if (rangeUnit == FOREVER) {1080return temporal.isSupported(YEAR);1081}1082}1083return false;1084}10851086@Override1087public ValueRange rangeRefinedBy(TemporalAccessor temporal) {1088if (rangeUnit == ChronoUnit.WEEKS) { // day-of-week1089return range;1090} else if (rangeUnit == MONTHS) { // week-of-month1091return rangeByWeek(temporal, DAY_OF_MONTH);1092} else if (rangeUnit == YEARS) { // week-of-year1093return rangeByWeek(temporal, DAY_OF_YEAR);1094} else if (rangeUnit == WEEK_BASED_YEARS) {1095return rangeWeekOfWeekBasedYear(temporal);1096} else if (rangeUnit == FOREVER) {1097return YEAR.range();1098} else {1099throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this);1100}1101}11021103/**1104* Map the field range to a week range1105* @param temporal the temporal1106* @param field the field to get the range of1107* @return the ValueRange with the range adjusted to weeks.1108*/1109private ValueRange rangeByWeek(TemporalAccessor temporal, TemporalField field) {1110int dow = localizedDayOfWeek(temporal);1111int offset = startOfWeekOffset(temporal.get(field), dow);1112ValueRange fieldRange = temporal.range(field);1113return ValueRange.of(computeWeek(offset, (int) fieldRange.getMinimum()),1114computeWeek(offset, (int) fieldRange.getMaximum()));1115}11161117/**1118* Map the field range to a week range of a week year.1119* @param temporal the temporal1120* @return the ValueRange with the range adjusted to weeks.1121*/1122private ValueRange rangeWeekOfWeekBasedYear(TemporalAccessor temporal) {1123if (!temporal.isSupported(DAY_OF_YEAR)) {1124return WEEK_OF_YEAR_RANGE;1125}1126int dow = localizedDayOfWeek(temporal);1127int doy = temporal.get(DAY_OF_YEAR);1128int offset = startOfWeekOffset(doy, dow);1129int week = computeWeek(offset, doy);1130if (week == 0) {1131// Day is in end of week of previous year1132// Recompute from the last day of the previous year1133ChronoLocalDate date = Chronology.from(temporal).date(temporal);1134date = date.minus(doy + 7, DAYS); // Back down into previous year1135return rangeWeekOfWeekBasedYear(date);1136}1137// Check if day of year is in partial week associated with next year1138ValueRange dayRange = temporal.range(DAY_OF_YEAR);1139int yearLen = (int)dayRange.getMaximum();1140int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());11411142if (week >= newYearWeek) {1143// Overlaps with weeks of following year; recompute from a week in following year1144ChronoLocalDate date = Chronology.from(temporal).date(temporal);1145date = date.plus(yearLen - doy + 1 + 7, ChronoUnit.DAYS);1146return rangeWeekOfWeekBasedYear(date);1147}1148return ValueRange.of(1, newYearWeek-1);1149}11501151//-----------------------------------------------------------------------1152@Override1153public String toString() {1154return name + "[" + weekDef.toString() + "]";1155}1156}1157}115811591160