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