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