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