       1 /*
       2  * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 /*
      27  * This file is available under and governed by the GNU General Public
      28  * License version 2 only, as published by the Free Software Foundation.
      29  * However, the following notice accompanied the original version of this
      30  * file:
      31  *
      32  * Copyright (c) 2011-2012, Stephen Colebourne & Michael Nascimento Santos
      33  *
      34  * All rights reserved.
      35  *
      36  * Redistribution and use in source and binary forms, with or without
      37  * modification, are permitted provided that the following conditions are met:
      38  *
      39  *  * Redistributions of source code must retain the above copyright notice,
      40  *    this list of conditions and the following disclaimer.
      41  *
      42  *  * Redistributions in binary form must reproduce the above copyright notice,
      43  *    this list of conditions and the following disclaimer in the documentation
      44  *    and/or other materials provided with the distribution.
      45  *
      46  *  * Neither the name of JSR-310 nor the names of its contributors
      47  *    may be used to endorse or promote products derived from this software
      48  *    without specific prior written permission.
      49  *
      50  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      51  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      52  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      53  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      54  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
      55  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
      56  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
      57  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
      58  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
      59  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
      60  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      61  */
      62 package java.time.temporal;
      63
      64 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
      65 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
      66 import static java.time.temporal.ChronoField.DAY_OF_YEAR;
      67 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
      68 import static java.time.temporal.ChronoField.YEAR;
      69 import static java.time.temporal.ChronoUnit.DAYS;
      70 import static java.time.temporal.ChronoUnit.FOREVER;
      71 import static java.time.temporal.ChronoUnit.MONTHS;
      72 import static java.time.temporal.ChronoUnit.WEEKS;
      73 import static java.time.temporal.ChronoUnit.YEARS;
      74
      75 import java.io.IOException;
      76 import java.io.InvalidObjectException;
      77 import java.io.ObjectInputStream;
      78 import java.io.Serializable;
      79 import java.time.DateTimeException;
      80 import java.time.DayOfWeek;
      81 import java.time.chrono.ChronoLocalDate;
      82 import java.time.chrono.Chronology;
      83 import java.time.format.ResolverStyle;
      84 import java.util.Locale;
      85 import java.util.Map;
      86 import java.util.Objects;
      87 import java.util.ResourceBundle;
      88 import java.util.concurrent.ConcurrentHashMap;
      89 import java.util.concurrent.ConcurrentMap;
      90 import sun.util.locale.provider.CalendarDataUtility;
      91 import sun.util.locale.provider.LocaleProviderAdapter;
      92 import sun.util.locale.provider.LocaleResources;
      93
      94 /**
      95  * Localized definitions of the day-of-week, week-of-month and week-of-year fields.
      96  * <p>
      97  * A standard week is seven days long, but cultures have different definitions for some
      98  * other aspects of a week. This class represents the definition of the week, for the
      99  * purpose of providing {@link TemporalField} instances.
     100  * <p>
     101  * WeekFields provides five fields,
     102  * {@link #dayOfWeek()}, {@link #weekOfMonth()}, {@link #weekOfYear()},
     103  * {@link #weekOfWeekBasedYear()}, and {@link #weekBasedYear()}
     104  * that provide access to the values from any {@linkplain Temporal temporal object}.
     105  * <p>
     106  * The computations for day-of-week, week-of-month, and week-of-year are based
     107  * on the  {@linkplain ChronoField#YEAR proleptic-year},
     108  * {@linkplain ChronoField#MONTH_OF_YEAR month-of-year},
     109  * {@linkplain ChronoField#DAY_OF_MONTH day-of-month}, and
     110  * {@linkplain ChronoField#DAY_OF_WEEK ISO day-of-week} which are based on the
     111  * {@linkplain ChronoField#EPOCH_DAY epoch-day} and the chronology.
     112  * The values may not be aligned with the {@linkplain ChronoField#YEAR_OF_ERA year-of-Era}
     113  * depending on the Chronology.
     114  * <p>A week is defined by:
     115  * <ul>
     116  * <li>The first day-of-week.
     117  * For example, the ISO-8601 standard considers Monday to be the first day-of-week.
     118  * <li>The minimal number of days in the first week.
     119  * For example, the ISO-8601 standard counts the first week as needing at least 4 days.
     120  * </ul>
     121  * Together these two values allow a year or month to be divided into weeks.
     122  *
     123  * <h2>Week of Month</h2>
     124  * One field is used: week-of-month.
     125  * The calculation ensures that weeks never overlap a month boundary.
     126  * The month is divided into periods where each period starts on the defined first day-of-week.
     127  * The earliest period is referred to as week 0 if it has less than the minimal number of days
     128  * and week 1 if it has at least the minimal number of days.
     129  *
     130  * <table class=striped style="text-align: left">
     131  * <caption>Examples of WeekFields</caption>
     132  * <thead>
     133  * <tr><th scope="col">Date</th><th scope="col">Day-of-week</th>
     134  *  <th scope="col">First day: Monday<br>Minimal days: 4</th><th scope="col">First day: Monday<br>Minimal days: 5</th></tr>
     135  * </thead>
     136  * <tbody>
     137  * <tr><th scope="row">2008-12-31</th><td>Wednesday</td>
     138  *  <td>Week 5 of December 2008</td><td>Week 5 of December 2008</td></tr>
     139  * <tr><th scope="row">2009-01-01</th><td>Thursday</td>
     140  *  <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr>
     141  * <tr><th scope="row">2009-01-04</th><td>Sunday</td>
     142  *  <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr>
     143  * <tr><th scope="row">2009-01-05</th><td>Monday</td>
     144  *  <td>Week 2 of January 2009</td><td>Week 1 of January 2009</td></tr>
     145  * </tbody>
     146  * </table>
     147  *
     148  * <h2>Week of Year</h2>
     149  * One field is used: week-of-year.
     150  * The calculation ensures that weeks never overlap a year boundary.
     151  * The year is divided into periods where each period starts on the defined first day-of-week.
     152  * The earliest period is referred to as week 0 if it has less than the minimal number of days
     153  * and week 1 if it has at least the minimal number of days.
     154  *
     155  * <h2>Week Based Year</h2>
     156  * Two fields are used for week-based-year, one for the
     157  * {@link #weekOfWeekBasedYear() week-of-week-based-year} and one for
     158  * {@link #weekBasedYear() week-based-year}.  In a week-based-year, each week
     159  * belongs to only a single year.  Week 1 of a year is the first week that
     160  * starts on the first day-of-week and has at least the minimum number of days.
     161  * The first and last weeks of a year may contain days from the
     162  * previous calendar year or next calendar year respectively.
     163  *
     164  * <table class=striped style="text-align: left;">
     165  * <caption>Examples of WeekFields for week-based-year</caption>
     166  * <thead>
     167  * <tr><th scope="col">Date</th><th scope="col">Day-of-week</th>
     168  *  <th scope="col">First day: Monday<br>Minimal days: 4</th><th scope="col">First day: Monday<br>Minimal days: 5</th></tr>
     169  * </thead>
     170  * <tbody>
     171  * <tr><th scope="row">2008-12-31</th><td>Wednesday</td>
     172  *  <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr>
     173  * <tr><th scope="row">2009-01-01</th><td>Thursday</td>
     174  *  <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr>
     175  * <tr><th scope="row">2009-01-04</th><td>Sunday</td>
     176  *  <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr>
     177  * <tr><th scope="row">2009-01-05</th><td>Monday</td>
     178  *  <td>Week 2 of 2009</td><td>Week 1 of 2009</td></tr>
     179  * </tbody>
     180  * </table>
     181  *
     182  * @implSpec
     183  * This class is immutable and thread-safe.
     184  *
     185  * @since 1.8
     186  */
     187 public final class WeekFields implements Serializable {
     188     // implementation notes
     189     // querying week-of-month or week-of-year should return the week value bound within the month/year
     190     // however, setting the week value should be lenient (use plus/minus weeks)
     191     // allow week-of-month outer range [0 to 6]
     192     // allow week-of-year outer range [0 to 54]
     193     // this is because callers shouldn't be expected to know the details of validity
     194
     195     /**
     196      * The cache of rules by firstDayOfWeek plus minimalDays.
     197      * Initialized first to be available for definition of ISO, etc.
     198      */
     199     private static final ConcurrentMap<String, WeekFields> CACHE = new ConcurrentHashMap<>(4, 0.75f, 2);
     200
     201     /**
     202      * The ISO-8601 definition, where a week starts on Monday and the first week
     203      * has a minimum of 4 days.
     204      * <p>
     205      * The ISO-8601 standard defines a calendar system based on weeks.
     206      * It uses the week-based-year and week-of-week-based-year concepts to split
     207      * up the passage of days instead of the standard year/month/day.
     208      * <p>
     209      * Note that the first week may start in the previous calendar year.
     210      * Note also that the first few days of a calendar year may be in the
     211      * week-based-year corresponding to the previous calendar year.
     212      */
     213     public static final WeekFields ISO = WeekFields.of(DayOfWeek.MONDAY, 4);
     214
     215     /**
     216      * The common definition of a week that starts on Sunday and the first week
     217      * has a minimum of 1 day.
     218      * <p>
     219      * Defined as starting on Sunday and with a minimum of 1 day in the month.
     220      * This week definition is in use in the US and other European countries.
     221      */
     222     public static final WeekFields SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1);
     223
     224     /**
     225      * The unit that represents week-based-years for the purpose of addition and subtraction.
     226      * <p>
     227      * This allows a number of week-based-years to be added to, or subtracted from, a date.
     228      * The unit is equal to either 52 or 53 weeks.
     229      * The estimated duration of a week-based-year is the same as that of a standard ISO
     230      * year at {@code 365.2425 Days}.
     231      * <p>
     232      * The rules for addition add the number of week-based-years to the existing value
     233      * for the week-based-year field retaining the week-of-week-based-year
     234      * and day-of-week, unless the week number it too large for the target year.
     235      * In that case, the week is set to the last week of the year
     236      * with the same day-of-week.
     237      * <p>
     238      * This unit is an immutable and thread-safe singleton.
     239      */
     240     public static final TemporalUnit WEEK_BASED_YEARS = IsoFields.WEEK_BASED_YEARS;
     241
     242     /**
     243      * Serialization version.
     244      */
     245     @java.io.Serial
     246     private static final long serialVersionUID = -1177360819670808121L;
     247
     248     /**
     249      * The first day-of-week.
     250      */
     251     private final DayOfWeek firstDayOfWeek;
     252     /**
     253      * The minimal number of days in the first week.
     254      */
     255     private final int minimalDays;
     256     /**
     257      * The field used to access the computed DayOfWeek.
     258      */
     259     private final transient TemporalField dayOfWeek = ComputedDayOfField.ofDayOfWeekField(this);
     260     /**
     261      * The field used to access the computed WeekOfMonth.
     262      */
     263     private final transient TemporalField weekOfMonth = ComputedDayOfField.ofWeekOfMonthField(this);
     264     /**
     265      * The field used to access the computed WeekOfYear.
     266      */
     267     private final transient TemporalField weekOfYear = ComputedDayOfField.ofWeekOfYearField(this);
     268     /**
     269      * The field that represents the week-of-week-based-year.
     270      * <p>
     271      * This field allows the week of the week-based-year value to be queried and set.
     272      * <p>
     273      * This unit is an immutable and thread-safe singleton.
     274      */
     275     private final transient TemporalField weekOfWeekBasedYear = ComputedDayOfField.ofWeekOfWeekBasedYearField(this);
     276     /**
     277      * The field that represents the week-based-year.
     278      * <p>
     279      * This field allows the week-based-year value to be queried and set.
     280      * <p>
     281      * This unit is an immutable and thread-safe singleton.
     282      */
     283     private final transient TemporalField weekBasedYear = ComputedDayOfField.ofWeekBasedYearField(this);
     284
     285     //-----------------------------------------------------------------------
     286     /**
     287      * Obtains an instance of {@code WeekFields} appropriate for a locale.
     288      * <p>
     289      * This will look up appropriate values from the provider of localization data.
     290      * If the locale contains "fw" (First day of week) and/or "rg"
     291      * (Region Override) <a href="../../util/Locale.html#def_locale_extension">
     292      * Unicode extensions</a>, returned instance will reflect the values specified with
     293      * those extensions. If both "fw" and "rg" are specified, the value from
     294      * the "fw" extension supersedes the implicit one from the "rg" extension.
     295      *
     296      * @param locale  the locale to use, not null
     297      * @return the week-definition, not null
     298      */
     299     public static WeekFields of(Locale locale) {
     300         Objects.requireNonNull(locale, "locale");
     301
     302         int calDow = CalendarDataUtility.retrieveFirstDayOfWeek(locale);
     303         DayOfWeek dow = DayOfWeek.SUNDAY.plus(calDow - 1);
     304         int minDays = CalendarDataUtility.retrieveMinimalDaysInFirstWeek(locale);
     305         return WeekFields.of(dow, minDays);
     306     }
     307
     308     /**
     309      * Obtains an instance of {@code WeekFields} from the first day-of-week and minimal days.
     310      * <p>
     311      * The first day-of-week defines the ISO {@code DayOfWeek} that is day 1 of the week.
     312      * The minimal number of days in the first week defines how many days must be present
     313      * in a month or year, starting from the first day-of-week, before the week is counted
     314      * as the first week. A value of 1 will count the first day of the month or year as part
     315      * of the first week, whereas a value of 7 will require the whole seven days to be in
     316      * the new month or year.
     317      * <p>
     318      * WeekFields instances are singletons; for each unique combination
     319      * of {@code firstDayOfWeek} and {@code minimalDaysInFirstWeek}
     320      * the same instance will be returned.
     321      *
     322      * @param firstDayOfWeek  the first day of the week, not null
     323      * @param minimalDaysInFirstWeek  the minimal number of days in the first week, from 1 to 7
     324      * @return the week-definition, not null
     325      * @throws IllegalArgumentException if the minimal days value is less than one
     326      *      or greater than 7
     327      */
     328     public static WeekFields of(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) {
     329         String key = firstDayOfWeek.toString() + minimalDaysInFirstWeek;
     330         WeekFields rules = CACHE.get(key);
     331         if (rules == null) {
     332             rules = new WeekFields(firstDayOfWeek, minimalDaysInFirstWeek);
     333             CACHE.putIfAbsent(key, rules);
     334             rules = CACHE.get(key);
     335         }
     336         return rules;
     337     }
     338
     339     //-----------------------------------------------------------------------
     340     /**
     341      * Creates an instance of the definition.
     342      *
     343      * @param firstDayOfWeek  the first day of the week, not null
     344      * @param minimalDaysInFirstWeek  the minimal number of days in the first week, from 1 to 7
     345      * @throws IllegalArgumentException if the minimal days value is invalid
     346      */
     347     private WeekFields(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) {
     348         Objects.requireNonNull(firstDayOfWeek, "firstDayOfWeek");
     349         if (minimalDaysInFirstWeek < 1 || minimalDaysInFirstWeek > 7) {
     350             throw new IllegalArgumentException("Minimal number of days is invalid");
     351         }
     352         this.firstDayOfWeek = firstDayOfWeek;
     353         this.minimalDays = minimalDaysInFirstWeek;
     354     }
     355
     356     //-----------------------------------------------------------------------
     357     /**
     358      * Restore the state of a WeekFields from the stream.
     359      * Check that the values are valid.
     360      *
     361      * @param s the stream to read
     362      * @throws IOException if an I/O error occurs
     363      * @throws InvalidObjectException if the serialized object has an invalid
     364      *     value for firstDayOfWeek or minimalDays.
     365      * @throws ClassNotFoundException if a class cannot be resolved
     366      */
     367     @java.io.Serial
     368     private void readObject(ObjectInputStream s)
     369          throws IOException, ClassNotFoundException, InvalidObjectException
     370     {
     371         s.defaultReadObject();
     372         if (firstDayOfWeek == null) {
     373             throw new InvalidObjectException("firstDayOfWeek is null");
     374         }
     375
     376         if (minimalDays < 1 || minimalDays > 7) {
     377             throw new InvalidObjectException("Minimal number of days is invalid");
     378         }
     379     }
     380
     381     /**
     382      * Return the singleton WeekFields associated with the
     383      * {@code firstDayOfWeek} and {@code minimalDays}.
     384      * @return the singleton WeekFields for the firstDayOfWeek and minimalDays.
     385      * @throws InvalidObjectException if the serialized object has invalid
     386      *     values for firstDayOfWeek or minimalDays.
     387      */
     388     @java.io.Serial
     389     private Object readResolve() throws InvalidObjectException {
     390         try {
     391             return WeekFields.of(firstDayOfWeek, minimalDays);
     392         } catch (IllegalArgumentException iae) {
     393             throw new InvalidObjectException("Invalid serialized WeekFields: " + iae.getMessage());
     394         }
     395     }
     396
     397     //-----------------------------------------------------------------------
     398     /**
     399      * Gets the first day-of-week.
     400      * <p>
     401      * The first day-of-week varies by culture.
     402      * For example, the US uses Sunday, while France and the ISO-8601 standard use Monday.
     403      * This method returns the first day using the standard {@code DayOfWeek} enum.
     404      *
     405      * @return the first day-of-week, not null
     406      */
     407     public DayOfWeek getFirstDayOfWeek() {
     408         return firstDayOfWeek;
     409     }
     410
     411     /**
     412      * Gets the minimal number of days in the first week.
     413      * <p>
     414      * The number of days considered to define the first week of a month or year
     415      * varies by culture.
     416      * For example, the ISO-8601 requires 4 days (more than half a week) to
     417      * be present before counting the first week.
     418      *
     419      * @return the minimal number of days in the first week of a month or year, from 1 to 7
     420      */
     421     public int getMinimalDaysInFirstWeek() {
     422         return minimalDays;
     423     }
     424
     425     //-----------------------------------------------------------------------
     426     /**
     427      * Returns a field to access the day of week based on this {@code WeekFields}.
     428      * <p>
     429      * This is similar to {@link ChronoField#DAY_OF_WEEK} but uses values for
     430      * the day-of-week based on this {@code WeekFields}.
     431      * The days are numbered from 1 to 7 where the
     432      * {@link #getFirstDayOfWeek() first day-of-week} is assigned the value 1.
     433      * <p>
     434      * For example, if the first day-of-week is Sunday, then that will have the
     435      * value 1, with other days ranging from Monday as 2 to Saturday as 7.
     436      * <p>
     437      * In the resolving phase of parsing, a localized day-of-week will be converted
     438      * to a standardized {@code ChronoField} day-of-week.
     439      * The day-of-week must be in the valid range 1 to 7.
     440      * Other fields in this class build dates using the standardized day-of-week.
     441      *
     442      * @return a field providing access to the day-of-week with localized numbering, not null
     443      */
     444     public TemporalField dayOfWeek() {
     445         return dayOfWeek;
     446     }
     447
     448     /**
     449      * Returns a field to access the week of month based on this {@code WeekFields}.
     450      * <p>
     451      * This represents the concept of the count of weeks within the month where weeks
     452      * start on a fixed day-of-week, such as Monday.
     453      * This field is typically used with {@link WeekFields#dayOfWeek()}.
     454      * <p>
     455      * Week one (1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}
     456      * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the month.
     457      * Thus, week one may start up to {@code minDays} days before the start of the month.
     458      * If the first week starts after the start of the month then the period before is week zero (0).
     459      * <p>
     460      * For example:<br>
     461      * - if the 1st day of the month is a Monday, week one starts on the 1st and there is no week zero<br>
     462      * - if the 2nd day of the month is a Monday, week one starts on the 2nd and the 1st is in week zero<br>
     463      * - 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>
     464      * - 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>
     465      * <p>
     466      * This field can be used with any calendar system.
     467      * <p>
     468      * In the resolving phase of parsing, a date can be created from a year,
     469      * week-of-month, month-of-year and day-of-week.
     470      * <p>
     471      * In {@linkplain ResolverStyle#STRICT strict mode}, all four fields are
     472      * validated against their range of valid values. The week-of-month field
     473      * is validated to ensure that the resulting month is the month requested.
     474      * <p>
     475      * In {@linkplain ResolverStyle#SMART smart mode}, all four fields are
     476      * validated against their range of valid values. The week-of-month field
     477      * is validated from 0 to 6, meaning that the resulting date can be in a
     478      * different month to that specified.
     479      * <p>
     480      * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
     481      * are validated against the range of valid values. The resulting date is calculated
     482      * equivalent to the following four stage approach.
     483      * First, create a date on the first day of the first week of January in the requested year.
     484      * Then take the month-of-year, subtract one, and add the amount in months to the date.
     485      * Then take the week-of-month, subtract one, and add the amount in weeks to the date.
     486      * Finally, adjust to the correct day-of-week within the localized week.
     487      *
     488      * @return a field providing access to the week-of-month, not null
     489      */
     490     public TemporalField weekOfMonth() {
     491         return weekOfMonth;
     492     }
     493
     494     /**
     495      * Returns a field to access the week of year based on this {@code WeekFields}.
     496      * <p>
     497      * This represents the concept of the count of weeks within the year where weeks
     498      * start on a fixed day-of-week, such as Monday.
     499      * This field is typically used with {@link WeekFields#dayOfWeek()}.
     500      * <p>
     501      * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}
     502      * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year.
     503      * Thus, week one may start up to {@code minDays} days before the start of the year.
     504      * If the first week starts after the start of the year then the period before is week zero (0).
     505      * <p>
     506      * For example:<br>
     507      * - if the 1st day of the year is a Monday, week one starts on the 1st and there is no week zero<br>
     508      * - if the 2nd day of the year is a Monday, week one starts on the 2nd and the 1st is in week zero<br>
     509      * - 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>
     510      * - 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>
     511      * <p>
     512      * This field can be used with any calendar system.
     513      * <p>
     514      * In the resolving phase of parsing, a date can be created from a year,
     515      * week-of-year and day-of-week.
     516      * <p>
     517      * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
     518      * validated against their range of valid values. The week-of-year field
     519      * is validated to ensure that the resulting year is the year requested.
     520      * <p>
     521      * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
     522      * validated against their range of valid values. The week-of-year field
     523      * is validated from 0 to 54, meaning that the resulting date can be in a
     524      * different year to that specified.
     525      * <p>
     526      * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
     527      * are validated against the range of valid values. The resulting date is calculated
     528      * equivalent to the following three stage approach.
     529      * First, create a date on the first day of the first week in the requested year.
     530      * Then take the week-of-year, subtract one, and add the amount in weeks to the date.
     531      * Finally, adjust to the correct day-of-week within the localized week.
     532      *
     533      * @return a field providing access to the week-of-year, not null
     534      */
     535     public TemporalField weekOfYear() {
     536         return weekOfYear;
     537     }
     538
     539     /**
     540      * Returns a field to access the week of a week-based-year based on this {@code WeekFields}.
     541      * <p>
     542      * This represents the concept of the count of weeks within the year where weeks
     543      * start on a fixed day-of-week, such as Monday and each week belongs to exactly one year.
     544      * This field is typically used with {@link WeekFields#dayOfWeek()} and
     545      * {@link WeekFields#weekBasedYear()}.
     546      * <p>
     547      * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}
     548      * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year.
     549      * If the first week starts after the start of the year then the period before
     550      * is in the last week of the previous year.
     551      * <p>
     552      * For example:<br>
     553      * - if the 1st day of the year is a Monday, week one starts on the 1st<br>
     554      * - if the 2nd day of the year is a Monday, week one starts on the 2nd and
     555      *   the 1st is in the last week of the previous year<br>
     556      * - if the 4th day of the year is a Monday, week one starts on the 4th and
     557      *   the 1st to 3rd is in the last week of the previous year<br>
     558      * - if the 5th day of the year is a Monday, week two starts on the 5th and
     559      *   the 1st to 4th is in week one<br>
     560      * <p>
     561      * This field can be used with any calendar system.
     562      * <p>
     563      * In the resolving phase of parsing, a date can be created from a week-based-year,
     564      * week-of-year and day-of-week.
     565      * <p>
     566      * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
     567      * validated against their range of valid values. The week-of-year field
     568      * is validated to ensure that the resulting week-based-year is the
     569      * week-based-year requested.
     570      * <p>
     571      * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
     572      * validated against their range of valid values. The week-of-week-based-year field
     573      * is validated from 1 to 53, meaning that the resulting date can be in the
     574      * following week-based-year to that specified.
     575      * <p>
     576      * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
     577      * are validated against the range of valid values. The resulting date is calculated
     578      * equivalent to the following three stage approach.
     579      * First, create a date on the first day of the first week in the requested week-based-year.
     580      * Then take the week-of-week-based-year, subtract one, and add the amount in weeks to the date.
     581      * Finally, adjust to the correct day-of-week within the localized week.
     582      *
     583      * @return a field providing access to the week-of-week-based-year, not null
     584      */
     585     public TemporalField weekOfWeekBasedYear() {
     586         return weekOfWeekBasedYear;
     587     }
     588
     589     /**
     590      * Returns a field to access the year of a week-based-year based on this {@code WeekFields}.
     591      * <p>
     592      * This represents the concept of the year where weeks start on a fixed day-of-week,
     593      * such as Monday and each week belongs to exactly one year.
     594      * This field is typically used with {@link WeekFields#dayOfWeek()} and
     595      * {@link WeekFields#weekOfWeekBasedYear()}.
     596      * <p>
     597      * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}
     598      * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year.
     599      * Thus, week one may start before the start of the year.
     600      * If the first week starts after the start of the year then the period before
     601      * is in the last week of the previous year.
     602      * <p>
     603      * This field can be used with any calendar system.
     604      * <p>
     605      * In the resolving phase of parsing, a date can be created from a week-based-year,
     606      * week-of-year and day-of-week.
     607      * <p>
     608      * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
     609      * validated against their range of valid values. The week-of-year field
     610      * is validated to ensure that the resulting week-based-year is the
     611      * week-based-year requested.
     612      * <p>
     613      * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
     614      * validated against their range of valid values. The week-of-week-based-year field
     615      * is validated from 1 to 53, meaning that the resulting date can be in the
     616      * following week-based-year to that specified.
     617      * <p>
     618      * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
     619      * are validated against the range of valid values. The resulting date is calculated
     620      * equivalent to the following three stage approach.
     621      * First, create a date on the first day of the first week in the requested week-based-year.
     622      * Then take the week-of-week-based-year, subtract one, and add the amount in weeks to the date.
     623      * Finally, adjust to the correct day-of-week within the localized week.
     624      *
     625      * @return a field providing access to the week-based-year, not null
     626      */
     627     public TemporalField weekBasedYear() {
     628         return weekBasedYear;
     629     }
     630
     631     //-----------------------------------------------------------------------
     632     /**
     633      * Checks if this {@code WeekFields} is equal to the specified object.
     634      * <p>
     635      * The comparison is based on the entire state of the rules, which is
     636      * the first day-of-week and minimal days.
     637      *
     638      * @param object  the other rules to compare to, null returns false
     639      * @return true if this is equal to the specified rules
     640      */
     641     @Override
     642     public boolean equals(Object object) {
     643         if (this == object) {
     644             return true;
     645         }
     646         if (object instanceof WeekFields) {
     647             return hashCode() == object.hashCode();
     648         }
     649         return false;
     650     }
     651
     652     /**
     653      * A hash code for this {@code WeekFields}.
     654      *
     655      * @return a suitable hash code
     656      */
     657     @Override
     658     public int hashCode() {
     659         return firstDayOfWeek.ordinal() * 7 + minimalDays;
     660     }
     661
     662     //-----------------------------------------------------------------------
     663     /**
     664      * A string representation of this {@code WeekFields} instance.
     665      *
     666      * @return the string representation, not null
     667      */
     668     @Override
     669     public String toString() {
     670         return "WeekFields[" + firstDayOfWeek + ',' + minimalDays + ']';
     671     }
     672
     673     //-----------------------------------------------------------------------
     674     /**
     675      * Field type that computes DayOfWeek, WeekOfMonth, and WeekOfYear
     676      * based on a WeekFields.
     677      * A separate Field instance is required for each different WeekFields;
     678      * combination of start of week and minimum number of days.
     679      * Constructors are provided to create fields for DayOfWeek, WeekOfMonth,
     680      * and WeekOfYear.
     681      */
     682     static class ComputedDayOfField implements TemporalField {
     683
     684         /**
     685          * Returns a field to access the day of week,
     686          * computed based on a WeekFields.
     687          * <p>
     688          * The WeekDefintion of the first day of the week is used with
     689          * the ISO DAY_OF_WEEK field to compute week boundaries.
     690          */
     691         static ComputedDayOfField ofDayOfWeekField(WeekFields weekDef) {
     692             return new ComputedDayOfField("DayOfWeek", weekDef, DAYS, WEEKS, DAY_OF_WEEK_RANGE);
     693         }
     694
     695         /**
     696          * Returns a field to access the week of month,
     697          * computed based on a WeekFields.
     698          * @see WeekFields#weekOfMonth()
     699          */
     700         static ComputedDayOfField ofWeekOfMonthField(WeekFields weekDef) {
     701             return new ComputedDayOfField("WeekOfMonth", weekDef, WEEKS, MONTHS, WEEK_OF_MONTH_RANGE);
     702         }
     703
     704         /**
     705          * Returns a field to access the week of year,
     706          * computed based on a WeekFields.
     707          * @see WeekFields#weekOfYear()
     708          */
     709         static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) {
     710             return new ComputedDayOfField("WeekOfYear", weekDef, WEEKS, YEARS, WEEK_OF_YEAR_RANGE);
     711         }
     712
     713         /**
     714          * Returns a field to access the week of week-based-year,
     715          * computed based on a WeekFields.
     716          * @see WeekFields#weekOfWeekBasedYear()
     717          */
     718         static ComputedDayOfField ofWeekOfWeekBasedYearField(WeekFields weekDef) {
     719             return new ComputedDayOfField("WeekOfWeekBasedYear", weekDef, WEEKS, IsoFields.WEEK_BASED_YEARS, WEEK_OF_WEEK_BASED_YEAR_RANGE);
     720         }
     721
     722         /**
     723          * Returns a field to access the week of week-based-year,
     724          * computed based on a WeekFields.
     725          * @see WeekFields#weekBasedYear()
     726          */
     727         static ComputedDayOfField ofWeekBasedYearField(WeekFields weekDef) {
     728             return new ComputedDayOfField("WeekBasedYear", weekDef, IsoFields.WEEK_BASED_YEARS, FOREVER, ChronoField.YEAR.range());
     729         }
     730
     731         /**
     732          * Return a new week-based-year date of the Chronology, year, week-of-year,
     733          * and dow of week.
     734          * @param chrono The chronology of the new date
     735          * @param yowby the year of the week-based-year
     736          * @param wowby the week of the week-based-year
     737          * @param dow the day of the week
     738          * @return a ChronoLocalDate for the requested year, week of year, and day of week
     739          */
     740         private ChronoLocalDate ofWeekBasedYear(Chronology chrono,
     741                 int yowby, int wowby, int dow) {
     742             ChronoLocalDate date = chrono.date(yowby, 1, 1);
     743             int ldow = localizedDayOfWeek(date);
     744             int offset = startOfWeekOffset(1, ldow);
     745
     746             // Clamp the week of year to keep it in the same year
     747             int yearLen = date.lengthOfYear();
     748             int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
     749             wowby = Math.min(wowby, newYearWeek - 1);
     750
     751             int days = -offset + (dow - 1) + (wowby - 1) * 7;
     752             return date.plus(days, DAYS);
     753         }
     754
     755         private final String name;
     756         private final WeekFields weekDef;
     757         private final TemporalUnit baseUnit;
     758         private final TemporalUnit rangeUnit;
     759         private final ValueRange range;
     760
     761         private ComputedDayOfField(String name, WeekFields weekDef, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) {
     762             this.name = name;
     763             this.weekDef = weekDef;
     764             this.baseUnit = baseUnit;
     765             this.rangeUnit = rangeUnit;
     766             this.range = range;
     767         }
     768
     769         private static final ValueRange DAY_OF_WEEK_RANGE = ValueRange.of(1, 7);
     770         private static final ValueRange WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 6);
     771         private static final ValueRange WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 54);
     772         private static final ValueRange WEEK_OF_WEEK_BASED_YEAR_RANGE = ValueRange.of(1, 52, 53);
     773
     774         @Override
     775         public long getFrom(TemporalAccessor temporal) {
     776             if (rangeUnit == WEEKS) {  // day-of-week
     777                 return localizedDayOfWeek(temporal);
     778             } else if (rangeUnit == MONTHS) {  // week-of-month
     779                 return localizedWeekOfMonth(temporal);
     780             } else if (rangeUnit == YEARS) {  // week-of-year
     781                 return localizedWeekOfYear(temporal);
     782             } else if (rangeUnit == WEEK_BASED_YEARS) {
     783                 return localizedWeekOfWeekBasedYear(temporal);
     784             } else if (rangeUnit == FOREVER) {
     785                 return localizedWeekBasedYear(temporal);
     786             } else {
     787                 throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this);
     788             }
     789         }
     790
     791         private int localizedDayOfWeek(TemporalAccessor temporal) {
     792             int sow = weekDef.getFirstDayOfWeek().getValue();
     793             int isoDow = temporal.get(DAY_OF_WEEK);
     794             return Math.floorMod(isoDow - sow, 7) + 1;
     795         }
     796
     797         private int localizedDayOfWeek(int isoDow) {
     798             int sow = weekDef.getFirstDayOfWeek().getValue();
     799             return Math.floorMod(isoDow - sow, 7) + 1;
     800         }
     801
     802         private long localizedWeekOfMonth(TemporalAccessor temporal) {
     803             int dow = localizedDayOfWeek(temporal);
     804             int dom = temporal.get(DAY_OF_MONTH);
     805             int offset = startOfWeekOffset(dom, dow);
     806             return computeWeek(offset, dom);
     807         }
     808
     809         private long localizedWeekOfYear(TemporalAccessor temporal) {
     810             int dow = localizedDayOfWeek(temporal);
     811             int doy = temporal.get(DAY_OF_YEAR);
     812             int offset = startOfWeekOffset(doy, dow);
     813             return computeWeek(offset, doy);
     814         }
     815
     816         /**
     817          * Returns the year of week-based-year for the temporal.
     818          * The year can be the previous year, the current year, or the next year.
     819          * @param temporal a date of any chronology, not null
     820          * @return the year of week-based-year for the date
     821          */
     822         private int localizedWeekBasedYear(TemporalAccessor temporal) {
     823             int dow = localizedDayOfWeek(temporal);
     824             int year = temporal.get(YEAR);
     825             int doy = temporal.get(DAY_OF_YEAR);
     826             int offset = startOfWeekOffset(doy, dow);
     827             int week = computeWeek(offset, doy);
     828             if (week == 0) {
     829                 // Day is in end of week of previous year; return the previous year
     830                 return year - 1;
     831             } else {
     832                 // If getting close to end of year, use higher precision logic
     833                 // Check if date of year is in partial week associated with next year
     834                 ValueRange dayRange = temporal.range(DAY_OF_YEAR);
     835                 int yearLen = (int)dayRange.getMaximum();
     836                 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
     837                 if (week >= newYearWeek) {
     838                     return year + 1;
     839                 }
     840             }
     841             return year;
     842         }
     843
     844         /**
     845          * Returns the week of week-based-year for the temporal.
     846          * The week can be part of the previous year, the current year,
     847          * or the next year depending on the week start and minimum number
     848          * of days.
     849          * @param temporal  a date of any chronology
     850          * @return the week of the year
     851          * @see #localizedWeekBasedYear(java.time.temporal.TemporalAccessor)
     852          */
     853         private int localizedWeekOfWeekBasedYear(TemporalAccessor temporal) {
     854             int dow = localizedDayOfWeek(temporal);
     855             int doy = temporal.get(DAY_OF_YEAR);
     856             int offset = startOfWeekOffset(doy, dow);
     857             int week = computeWeek(offset, doy);
     858             if (week == 0) {
     859                 // Day is in end of week of previous year
     860                 // Recompute from the last day of the previous year
     861                 ChronoLocalDate date = Chronology.from(temporal).date(temporal);
     862                 date = date.minus(doy, DAYS);   // Back down into previous year
     863                 return localizedWeekOfWeekBasedYear(date);
     864             } else if (week > 50) {
     865                 // If getting close to end of year, use higher precision logic
     866                 // Check if date of year is in partial week associated with next year
     867                 ValueRange dayRange = temporal.range(DAY_OF_YEAR);
     868                 int yearLen = (int)dayRange.getMaximum();
     869                 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
     870                 if (week >= newYearWeek) {
     871                     // Overlaps with week of following year; reduce to week in following year
     872                     week = week - newYearWeek + 1;
     873                 }
     874             }
     875             return week;
     876         }
     877
     878         /**
     879          * Returns an offset to align week start with a day of month or day of year.
     880          *
     881          * @param day  the day; 1 through infinity
     882          * @param dow  the day of the week of that day; 1 through 7
     883          * @return  an offset in days to align a day with the start of the first 'full' week
     884          */
     885         private int startOfWeekOffset(int day, int dow) {
     886             // offset of first day corresponding to the day of week in first 7 days (zero origin)
     887             int weekStart = Math.floorMod(day - dow, 7);
     888             int offset = -weekStart;
     889             if (weekStart + 1 > weekDef.getMinimalDaysInFirstWeek()) {
     890                 // The previous week has the minimum days in the current month to be a 'week'
     891                 offset = 7 - weekStart;
     892             }
     893             return offset;
     894         }
     895
     896         /**
     897          * Returns the week number computed from the reference day and reference dayOfWeek.
     898          *
     899          * @param offset the offset to align a date with the start of week
     900          *     from {@link #startOfWeekOffset}.
     901          * @param day  the day for which to compute the week number
     902          * @return the week number where zero is used for a partial week and 1 for the first full week
     903          */
     904         private int computeWeek(int offset, int day) {
     905             return ((7 + offset + (day - 1)) / 7);
     906         }
     907
     908         @SuppressWarnings("unchecked")
     909         @Override
     910         public <R extends Temporal> R adjustInto(R temporal, long newValue) {
     911             // Check the new value and get the old value of the field
     912             int newVal = range.checkValidIntValue(newValue, this);  // lenient check range
     913             int currentVal = temporal.get(this);
     914             if (newVal == currentVal) {
     915                 return temporal;
     916             }
     917
     918             if (rangeUnit == FOREVER) {     // replace year of WeekBasedYear
     919                 // Create a new date object with the same chronology,
     920                 // the desired year and the same week and dow.
     921                 int idow = temporal.get(weekDef.dayOfWeek);
     922                 int wowby = temporal.get(weekDef.weekOfWeekBasedYear);
     923                 return (R) ofWeekBasedYear(Chronology.from(temporal), (int)newValue, wowby, idow);
     924             } else {
     925                 // Compute the difference and add that using the base unit of the field
     926                 return (R) temporal.plus(newVal - currentVal, baseUnit);
     927             }
     928         }
     929
     930         @Override
     931         public ChronoLocalDate resolve(
     932                 Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
     933             final long value = fieldValues.get(this);
     934             final int newValue = Math.toIntExact(value);  // broad limit makes overflow checking lighter
     935             // first convert localized day-of-week to ISO day-of-week
     936             // doing this first handles case where both ISO and localized were parsed and might mismatch
     937             // day-of-week is always strict as two different day-of-week values makes lenient complex
     938             if (rangeUnit == WEEKS) {  // day-of-week
     939                 final int checkedValue = range.checkValidIntValue(value, this);  // no leniency as too complex
     940                 final int startDow = weekDef.getFirstDayOfWeek().getValue();
     941                 long isoDow = Math.floorMod((startDow - 1) + (checkedValue - 1), 7) + 1;
     942                 fieldValues.remove(this);
     943                 fieldValues.put(DAY_OF_WEEK, isoDow);
     944                 return null;
     945             }
     946
     947             // can only build date if ISO day-of-week is present
     948             if (fieldValues.containsKey(DAY_OF_WEEK) == false) {
     949                 return null;
     950             }
     951             int isoDow = DAY_OF_WEEK.checkValidIntValue(fieldValues.get(DAY_OF_WEEK));
     952             int dow = localizedDayOfWeek(isoDow);
     953
     954             // build date
     955             Chronology chrono = Chronology.from(partialTemporal);
     956             if (fieldValues.containsKey(YEAR)) {
     957                 int year = YEAR.checkValidIntValue(fieldValues.get(YEAR));  // validate
     958                 if (rangeUnit == MONTHS && fieldValues.containsKey(MONTH_OF_YEAR)) {  // week-of-month
     959                     long month = fieldValues.get(MONTH_OF_YEAR);  // not validated yet
     960                     return resolveWoM(fieldValues, chrono, year, month, newValue, dow, resolverStyle);
     961                 }
     962                 if (rangeUnit == YEARS) {  // week-of-year
     963                     return resolveWoY(fieldValues, chrono, year, newValue, dow, resolverStyle);
     964                 }
     965             } else if ((rangeUnit == WEEK_BASED_YEARS || rangeUnit == FOREVER) &&
     966                     fieldValues.containsKey(weekDef.weekBasedYear) &&
     967                     fieldValues.containsKey(weekDef.weekOfWeekBasedYear)) { // week-of-week-based-year and year-of-week-based-year
     968                 return resolveWBY(fieldValues, chrono, dow, resolverStyle);
     969             }
     970             return null;
     971         }
     972
     973         private ChronoLocalDate resolveWoM(
     974                 Map<TemporalField, Long> fieldValues, Chronology chrono, int year, long month, long wom, int localDow, ResolverStyle resolverStyle) {
     975             ChronoLocalDate date;
     976             if (resolverStyle == ResolverStyle.LENIENT) {
     977                 date = chrono.date(year, 1, 1).plus(Math.subtractExact(month, 1), MONTHS);
     978                 long weeks = Math.subtractExact(wom, localizedWeekOfMonth(date));
     979                 int days = localDow - localizedDayOfWeek(date);  // safe from overflow
     980                 date = date.plus(Math.addExact(Math.multiplyExact(weeks, 7), days), DAYS);
     981             } else {
     982                 int monthValid = MONTH_OF_YEAR.checkValidIntValue(month);  // validate
     983                 date = chrono.date(year, monthValid, 1);
     984                 int womInt = range.checkValidIntValue(wom, this);  // validate
     985                 int weeks = (int) (womInt - localizedWeekOfMonth(date));  // safe from overflow
     986                 int days = localDow - localizedDayOfWeek(date);  // safe from overflow
     987                 date = date.plus(weeks * 7 + days, DAYS);
     988                 if (resolverStyle == ResolverStyle.STRICT && date.getLong(MONTH_OF_YEAR) != month) {
     989                     throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");
     990                 }
     991             }
     992             fieldValues.remove(this);
     993             fieldValues.remove(YEAR);
     994             fieldValues.remove(MONTH_OF_YEAR);
     995             fieldValues.remove(DAY_OF_WEEK);
     996             return date;
     997         }
     998
     999         private ChronoLocalDate resolveWoY(
    1000                 Map<TemporalField, Long> fieldValues, Chronology chrono, int year, long woy, int localDow, ResolverStyle resolverStyle) {
    1001             ChronoLocalDate date = chrono.date(year, 1, 1);
    1002             if (resolverStyle == ResolverStyle.LENIENT) {
    1003                 long weeks = Math.subtractExact(woy, localizedWeekOfYear(date));
    1004                 int days = localDow - localizedDayOfWeek(date);  // safe from overflow
    1005                 date = date.plus(Math.addExact(Math.multiplyExact(weeks, 7), days), DAYS);
    1006             } else {
    1007                 int womInt = range.checkValidIntValue(woy, this);  // validate
    1008                 int weeks = (int) (womInt - localizedWeekOfYear(date));  // safe from overflow
    1009                 int days = localDow - localizedDayOfWeek(date);  // safe from overflow
    1010                 date = date.plus(weeks * 7 + days, DAYS);
    1011                 if (resolverStyle == ResolverStyle.STRICT && date.getLong(YEAR) != year) {
    1012                     throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");
    1013                 }
    1014             }
    1015             fieldValues.remove(this);
    1016             fieldValues.remove(YEAR);
    1017             fieldValues.remove(DAY_OF_WEEK);
    1018             return date;
    1019         }
    1020
    1021         private ChronoLocalDate resolveWBY(
    1022                 Map<TemporalField, Long> fieldValues, Chronology chrono, int localDow, ResolverStyle resolverStyle) {
    1023             int yowby = weekDef.weekBasedYear.range().checkValidIntValue(
    1024                     fieldValues.get(weekDef.weekBasedYear), weekDef.weekBasedYear);
    1025             ChronoLocalDate date;
    1026             if (resolverStyle == ResolverStyle.LENIENT) {
    1027                 date = ofWeekBasedYear(chrono, yowby, 1, localDow);
    1028                 long wowby = fieldValues.get(weekDef.weekOfWeekBasedYear);
    1029                 long weeks = Math.subtractExact(wowby, 1);
    1030                 date = date.plus(weeks, WEEKS);
    1031             } else {
    1032                 int wowby = weekDef.weekOfWeekBasedYear.range().checkValidIntValue(
    1033                         fieldValues.get(weekDef.weekOfWeekBasedYear), weekDef.weekOfWeekBasedYear);  // validate
    1034                 date = ofWeekBasedYear(chrono, yowby, wowby, localDow);
    1035                 if (resolverStyle == ResolverStyle.STRICT && localizedWeekBasedYear(date) != yowby) {
    1036                     throw new DateTimeException("Strict mode rejected resolved date as it is in a different week-based-year");
    1037                 }
    1038             }
    1039             fieldValues.remove(this);
    1040             fieldValues.remove(weekDef.weekBasedYear);
    1041             fieldValues.remove(weekDef.weekOfWeekBasedYear);
    1042             fieldValues.remove(DAY_OF_WEEK);
    1043             return date;
    1044         }
    1045
    1046         //-----------------------------------------------------------------------
    1047         @Override
    1048         public String getDisplayName(Locale locale) {
    1049             Objects.requireNonNull(locale, "locale");
    1050             if (rangeUnit == YEARS) {  // only have values for week-of-year
    1051                 LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
    1052                         .getLocaleResources(
    1053                             CalendarDataUtility.findRegionOverride(locale));
    1054                 ResourceBundle rb = lr.getJavaTimeFormatData();
    1055                 return rb.containsKey("field.week") ? rb.getString("field.week") : name;
    1056             }
    1057             return name;
    1058         }
    1059
    1060         @Override
    1061         public TemporalUnit getBaseUnit() {
    1062             return baseUnit;
    1063         }
    1064
    1065         @Override
    1066         public TemporalUnit getRangeUnit() {
    1067             return rangeUnit;
    1068         }
    1069
    1070         @Override
    1071         public boolean isDateBased() {
    1072             return true;
    1073         }
    1074
    1075         @Override
    1076         public boolean isTimeBased() {
    1077             return false;
    1078         }
    1079
    1080         @Override
    1081         public ValueRange range() {
    1082             return range;
    1083         }
    1084
    1085         //-----------------------------------------------------------------------
    1086         @Override
    1087         public boolean isSupportedBy(TemporalAccessor temporal) {
    1088             if (temporal.isSupported(DAY_OF_WEEK)) {
    1089                 if (rangeUnit == WEEKS) {  // day-of-week
    1090                     return true;
    1091                 } else if (rangeUnit == MONTHS) {  // week-of-month
    1092                     return temporal.isSupported(DAY_OF_MONTH);
    1093                 } else if (rangeUnit == YEARS) {  // week-of-year
    1094                     return temporal.isSupported(DAY_OF_YEAR);
    1095                 } else if (rangeUnit == WEEK_BASED_YEARS) {
    1096                     return temporal.isSupported(DAY_OF_YEAR);
    1097                 } else if (rangeUnit == FOREVER) {
    1098                     return temporal.isSupported(YEAR);
    1099                 }
    1100             }
    1101             return false;
    1102         }
    1103
    1104         @Override
    1105         public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
    1106             if (rangeUnit == ChronoUnit.WEEKS) {  // day-of-week
    1107                 return range;
    1108             } else if (rangeUnit == MONTHS) {  // week-of-month
    1109                 return rangeByWeek(temporal, DAY_OF_MONTH);
    1110             } else if (rangeUnit == YEARS) {  // week-of-year
    1111                 return rangeByWeek(temporal, DAY_OF_YEAR);
    1112             } else if (rangeUnit == WEEK_BASED_YEARS) {
    1113                 return rangeWeekOfWeekBasedYear(temporal);
    1114             } else if (rangeUnit == FOREVER) {
    1115                 return YEAR.range();
    1116             } else {
    1117                 throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this);
    1118             }
    1119         }
    1120
    1121         /**
    1122          * Map the field range to a week range
    1123          * @param temporal the temporal
    1124          * @param field the field to get the range of
    1125          * @return the ValueRange with the range adjusted to weeks.
    1126          */
    1127         private ValueRange rangeByWeek(TemporalAccessor temporal, TemporalField field) {
    1128             int dow = localizedDayOfWeek(temporal);
    1129             int offset = startOfWeekOffset(temporal.get(field), dow);
    1130             ValueRange fieldRange = temporal.range(field);
    1131             return ValueRange.of(computeWeek(offset, (int) fieldRange.getMinimum()),
    1132                     computeWeek(offset, (int) fieldRange.getMaximum()));
    1133         }
    1134
    1135         /**
    1136          * Map the field range to a week range of a week year.
    1137          * @param temporal  the temporal
    1138          * @return the ValueRange with the range adjusted to weeks.
    1139          */
    1140         private ValueRange rangeWeekOfWeekBasedYear(TemporalAccessor temporal) {
    1141             if (!temporal.isSupported(DAY_OF_YEAR)) {
    1142                 return WEEK_OF_YEAR_RANGE;
    1143             }
    1144             int dow = localizedDayOfWeek(temporal);
    1145             int doy = temporal.get(DAY_OF_YEAR);
    1146             int offset = startOfWeekOffset(doy, dow);
    1147             int week = computeWeek(offset, doy);
    1148             if (week == 0) {
    1149                 // Day is in end of week of previous year
    1150                 // Recompute from the last day of the previous year
    1151                 ChronoLocalDate date = Chronology.from(temporal).date(temporal);
    1152                 date = date.minus(doy + 7, DAYS);   // Back down into previous year
    1153                 return rangeWeekOfWeekBasedYear(date);
    1154             }
    1155             // Check if day of year is in partial week associated with next year
    1156             ValueRange dayRange = temporal.range(DAY_OF_YEAR);
    1157             int yearLen = (int)dayRange.getMaximum();
    1158             int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
    1159
    1160             if (week >= newYearWeek) {
    1161                 // Overlaps with weeks of following year; recompute from a week in following year
    1162                 ChronoLocalDate date = Chronology.from(temporal).date(temporal);
    1163                 date = date.plus(yearLen - doy + 1 + 7, ChronoUnit.DAYS);
    1164                 return rangeWeekOfWeekBasedYear(date);
    1165             }
    1166             return ValueRange.of(1, newYearWeek-1);
    1167         }
    1168
    1169         //-----------------------------------------------------------------------
    1170         @Override
    1171         public String toString() {
    1172             return name + "[" + weekDef.toString() + "]";
    1173         }
    1174     }
    1175 }
    --- EOF ---