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