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