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