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