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.EPOCH_DAY; 68 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 69 import static java.time.temporal.ChronoField.YEAR; 70 import static java.time.temporal.ChronoUnit.DAYS; 71 import static java.time.temporal.ChronoUnit.FOREVER; 72 import static java.time.temporal.ChronoUnit.MONTHS; 73 import static java.time.temporal.ChronoUnit.WEEKS; 74 import static java.time.temporal.ChronoUnit.YEARS; 75 76 import java.io.InvalidObjectException; 77 import java.io.Serializable; 78 import java.time.DayOfWeek; 79 import java.time.chrono.ChronoLocalDate; 80 import java.time.chrono.Chronology; 81 import java.util.Collections; 82 import java.util.HashMap; 83 import java.util.Locale; 84 import java.util.Map; 85 import java.util.Objects; 86 import java.util.ResourceBundle; 87 import java.util.concurrent.ConcurrentHashMap; 88 import java.util.concurrent.ConcurrentMap; 89 import sun.util.locale.provider.CalendarDataUtility; 90 import sun.util.locale.provider.LocaleProviderAdapter; 91 import sun.util.locale.provider.LocaleResources; 92 93 /** 94 * Localized definitions of the day-of-week, week-of-month and week-of-year fields. 95 * <p> 96 * A standard week is seven days long, but cultures have different definitions for some 97 * other aspects of a week. This class represents the definition of the week, for the 98 * purpose of providing {@link TemporalField} instances. 99 * <p> 100 * WeekFields provides five fields, 101 * {@link #dayOfWeek()}, {@link #weekOfMonth()}, {@link #weekOfYear()}, 102 * {@link #weekOfWeekBasedYear()}, and {@link #weekBasedYear()} 103 * that provide access to the values from any {@linkplain Temporal temporal object}. 104 * <p> 105 * The computations for day-of-week, week-of-month, and week-of-year are based 106 * on the {@linkplain ChronoField#YEAR proleptic-year}, 107 * {@linkplain ChronoField#MONTH_OF_YEAR month-of-year}, 108 * {@linkplain ChronoField#DAY_OF_MONTH day-of-month}, and 109 * {@linkplain ChronoField#DAY_OF_WEEK ISO day-of-week} which are based on the 110 * {@linkplain ChronoField#EPOCH_DAY epoch-day} and the chronology. 111 * The values may not be aligned with the {@linkplain ChronoField#YEAR_OF_ERA year-of-Era} 112 * depending on the Chronology. 113 * <p>A week is defined by: 114 * <ul> 115 * <li>The first day-of-week. 116 * For example, the ISO-8601 standard considers Monday to be the first day-of-week. 117 * <li>The minimal number of days in the first week. 118 * For example, the ISO-8601 standard counts the first week as needing at least 4 days. 119 * </ul><p> 120 * Together these two values allow a year or month to be divided into weeks. 121 * <p> 122 * <h3>Week of Month</h3> 123 * One field is used: week-of-month. 124 * The calculation ensures that weeks never overlap a month boundary. 125 * The month is divided into periods where each period starts on the defined first day-of-week. 126 * The earliest period is referred to as week 0 if it has less than the minimal number of days 127 * and week 1 if it has at least the minimal number of days. 128 * <p> 129 * <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;"> 130 * <caption>Examples of WeekFields</caption> 131 * <tr><th>Date</th><td>Day-of-week</td> 132 * <td>First day: Monday<br>Minimal days: 4</td><td>First day: Monday<br>Minimal days: 5</td></tr> 133 * <tr><th>2008-12-31</th><td>Wednesday</td> 134 * <td>Week 5 of December 2008</td><td>Week 5 of December 2008</td></tr> 135 * <tr><th>2009-01-01</th><td>Thursday</td> 136 * <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr> 137 * <tr><th>2009-01-04</th><td>Sunday</td> 138 * <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr> 139 * <tr><th>2009-01-05</th><td>Monday</td> 140 * <td>Week 2 of January 2009</td><td>Week 1 of January 2009</td></tr> 141 * </table> 142 * 143 * <h3>Week of Year</h3> 144 * One field is used: week-of-year. 145 * The calculation ensures that weeks never overlap a year boundary. 146 * The year is divided into periods where each period starts on the defined first day-of-week. 147 * The earliest period is referred to as week 0 if it has less than the minimal number of days 148 * and week 1 if it has at least the minimal number of days. 149 * 150 * <h3>Week Based Year</h3> 151 * Two fields are used for week-based-year, one for the 152 * {@link #weekOfWeekBasedYear() week-of-week-based-year} and one for 153 * {@link #weekBasedYear() week-based-year}. In a week-based-year, each week 154 * belongs to only a single year. Week 1 of a year is the first week that 155 * starts on the first day-of-week and has at least the minimum number of days. 156 * The first and last weeks of a year may contain days from the 157 * previous calendar year or next calendar year respectively. 158 * 159 * <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;"> 160 * <caption>Examples of WeekFields for week-based-year</caption> 161 * <tr><th>Date</th><td>Day-of-week</td> 162 * <td>First day: Monday<br>Minimal days: 4</td><td>First day: Monday<br>Minimal days: 5</td></tr> 163 * <tr><th>2008-12-31</th><td>Wednesday</td> 164 * <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr> 165 * <tr><th>2009-01-01</th><td>Thursday</td> 166 * <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr> 167 * <tr><th>2009-01-04</th><td>Sunday</td> 168 * <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr> 169 * <tr><th>2009-01-05</th><td>Monday</td> 170 * <td>Week 2 of 2009</td><td>Week 1 of 2009</td></tr> 171 * </table> 172 * <h3>Specification for implementors</h3> 173 * This class is immutable and thread-safe. 174 * 175 * @since 1.8 176 */ 177 public final class WeekFields implements Serializable { 178 // implementation notes 179 // querying week-of-month or week-of-year should return the week value bound within the month/year 180 // however, setting the week value should be lenient (use plus/minus weeks) 181 // allow week-of-month outer range [0 to 6] 182 // allow week-of-year outer range [0 to 54] 183 // this is because callers shouldn't be expected to know the details of validity 184 185 /** 186 * The cache of rules by firstDayOfWeek plus minimalDays. 187 * Initialized first to be available for definition of ISO, etc. 188 */ 189 private static final ConcurrentMap<String, WeekFields> CACHE = new ConcurrentHashMap<>(4, 0.75f, 2); 190 191 /** 192 * The ISO-8601 definition, where a week starts on Monday and the first week 193 * has a minimum of 4 days. 194 * <p> 195 * The ISO-8601 standard defines a calendar system based on weeks. 196 * It uses the week-based-year and week-of-week-based-year concepts to split 197 * up the passage of days instead of the standard year/month/day. 198 * <p> 199 * Note that the first week may start in the previous calendar year. 200 * Note also that the first few days of a calendar year may be in the 201 * week-based-year corresponding to the previous calendar year. 202 * <p> 203 * This field is an immutable and thread-safe singleton. 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 * <p> 214 * This field is an immutable and thread-safe singleton. 215 */ 216 public static final WeekFields SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1); 217 218 /** 219 * The unit that represents week-based-years for the purpose of addition and subtraction. 220 * <p> 221 * This allows a number of week-based-years to be added to, or subtracted from, a date. 222 * The unit is equal to either 52 or 53 weeks. 223 * The estimated duration of a week-based-year is the same as that of a standard ISO 224 * year at {@code 365.2425 Days}. 225 * <p> 226 * The rules for addition add the number of week-based-years to the existing value 227 * for the week-based-year field retaining the week-of-week-based-year 228 * and day-of-week, unless the week number it too large for the target year. 229 * In that case, the week is set to the last week of the year 230 * with the same day-of-week. 231 * <p> 232 * This field is an immutable and thread-safe singleton. 233 */ 234 public static final TemporalUnit WEEK_BASED_YEARS = IsoFields.WEEK_BASED_YEARS; 235 236 /** 237 * Serialization version. 238 */ 239 private static final long serialVersionUID = -1177360819670808121L; 240 241 /** 242 * The first day-of-week. 243 */ 244 private final DayOfWeek firstDayOfWeek; 245 /** 246 * The minimal number of days in the first week. 247 */ 248 private final int minimalDays; 249 250 /** 251 * The field used to access the computed DayOfWeek. 252 */ 253 private transient final TemporalField dayOfWeek = ComputedDayOfField.ofDayOfWeekField(this); 254 255 /** 256 * The field used to access the computed WeekOfMonth. 257 */ 258 private transient final TemporalField weekOfMonth = ComputedDayOfField.ofWeekOfMonthField(this); 259 260 /** 261 * The field used to access the computed WeekOfYear. 262 */ 263 private transient final TemporalField weekOfYear = ComputedDayOfField.ofWeekOfYearField(this); 264 265 /** 266 * The field that represents the week-of-week-based-year. 267 * <p> 268 * This field allows the week of the week-based-year value to be queried and set. 269 * <p> 270 * This unit is an immutable and thread-safe singleton. 271 */ 272 private transient final TemporalField weekOfWeekBasedYear = ComputedDayOfField.ofWeekOfWeekBasedYearField(this); 273 274 /** 275 * The field that represents the week-based-year. 276 * <p> 277 * This field allows the week-based-year value to be queried and set. 278 * <p> 279 * This unit is an immutable and thread-safe singleton. 280 */ 281 private transient final TemporalField weekBasedYear = ComputedDayOfField.ofWeekBasedYearField(this); 282 283 /** 284 * Obtains an instance of {@code WeekFields} appropriate for a locale. 285 * <p> 286 * This will look up appropriate values from the provider of localization data. 287 * 288 * @param locale the locale to use, not null 289 * @return the week-definition, not null 290 */ 291 public static WeekFields of(Locale locale) { 292 Objects.requireNonNull(locale, "locale"); 293 locale = new Locale(locale.getLanguage(), locale.getCountry()); // elminate variants 294 295 int calDow = CalendarDataUtility.retrieveFirstDayOfWeek(locale); 296 DayOfWeek dow = DayOfWeek.SUNDAY.plus(calDow - 1); 297 int minDays = CalendarDataUtility.retrieveMinimalDaysInFirstWeek(locale); 298 return WeekFields.of(dow, minDays); 299 } 300 301 /** 302 * Obtains an instance of {@code WeekFields} from the first day-of-week and minimal days. 303 * <p> 304 * The first day-of-week defines the ISO {@code DayOfWeek} that is day 1 of the week. 305 * The minimal number of days in the first week defines how many days must be present 306 * in a month or year, starting from the first day-of-week, before the week is counted 307 * as the first week. A value of 1 will count the first day of the month or year as part 308 * of the first week, whereas a value of 7 will require the whole seven days to be in 309 * the new month or year. 310 * <p> 311 * WeekFields instances are singletons; for each unique combination 312 * of {@code firstDayOfWeek} and {@code minimalDaysInFirstWeek} the 313 * the same instance will be returned. 314 * 315 * @param firstDayOfWeek the first day of the week, not null 316 * @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7 317 * @return the week-definition, not null 318 * @throws IllegalArgumentException if the minimal days value is less than one 319 * or greater than 7 320 */ 321 public static WeekFields of(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) { 322 String key = firstDayOfWeek.toString() + minimalDaysInFirstWeek; 323 WeekFields rules = CACHE.get(key); 324 if (rules == null) { 325 rules = new WeekFields(firstDayOfWeek, minimalDaysInFirstWeek); 326 CACHE.putIfAbsent(key, rules); 327 rules = CACHE.get(key); 328 } 329 return rules; 330 } 331 332 //----------------------------------------------------------------------- 333 /** 334 * Creates an instance of the definition. 335 * 336 * @param firstDayOfWeek the first day of the week, not null 337 * @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7 338 * @throws IllegalArgumentException if the minimal days value is invalid 339 */ 340 private WeekFields(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) { 341 Objects.requireNonNull(firstDayOfWeek, "firstDayOfWeek"); 342 if (minimalDaysInFirstWeek < 1 || minimalDaysInFirstWeek > 7) { 343 throw new IllegalArgumentException("Minimal number of days is invalid"); 344 } 345 this.firstDayOfWeek = firstDayOfWeek; 346 this.minimalDays = minimalDaysInFirstWeek; 347 } 348 349 //----------------------------------------------------------------------- 350 /** 351 * Return the singleton WeekFields associated with the 352 * {@code firstDayOfWeek} and {@code minimalDays}. 353 * @return the singleton WeekFields for the firstDayOfWeek and minimalDays. 354 * @throws InvalidObjectException if the serialized object has invalid 355 * values for firstDayOfWeek or minimalDays. 356 */ 357 private Object readResolve() throws InvalidObjectException { 358 try { 359 return WeekFields.of(firstDayOfWeek, minimalDays); 360 } catch (IllegalArgumentException iae) { 361 throw new InvalidObjectException("Invalid serialized WeekFields: " 362 + iae.getMessage()); 363 } 364 } 365 366 //----------------------------------------------------------------------- 367 /** 368 * Gets the first day-of-week. 369 * <p> 370 * The first day-of-week varies by culture. 371 * For example, the US uses Sunday, while France and the ISO-8601 standard use Monday. 372 * This method returns the first day using the standard {@code DayOfWeek} enum. 373 * 374 * @return the first day-of-week, not null 375 */ 376 public DayOfWeek getFirstDayOfWeek() { 377 return firstDayOfWeek; 378 } 379 380 /** 381 * Gets the minimal number of days in the first week. 382 * <p> 383 * The number of days considered to define the first week of a month or year 384 * varies by culture. 385 * For example, the ISO-8601 requires 4 days (more than half a week) to 386 * be present before counting the first week. 387 * 388 * @return the minimal number of days in the first week of a month or year, from 1 to 7 389 */ 390 public int getMinimalDaysInFirstWeek() { 391 return minimalDays; 392 } 393 394 //----------------------------------------------------------------------- 395 /** 396 * Returns a field to access the day of week, 397 * computed based on this WeekFields. 398 * <p> 399 * The days of week are numbered from 1 to 7. 400 * Day number 1 is the {@link #getFirstDayOfWeek() first day-of-week}. 401 * 402 * @return the field for day-of-week using this week definition, not null 403 */ 404 public TemporalField dayOfWeek() { 405 return dayOfWeek; 406 } 407 408 /** 409 * Returns a field to access the week of month, 410 * computed based on this WeekFields. 411 * <p> 412 * This represents the concept of the count of weeks within the month where weeks 413 * start on a fixed day-of-week, such as Monday. 414 * This field is typically used with {@link WeekFields#dayOfWeek()}. 415 * <p> 416 * Week one (1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 417 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the month. 418 * Thus, week one may start up to {@code minDays} days before the start of the month. 419 * If the first week starts after the start of the month then the period before is week zero (0). 420 * <p> 421 * For example:<br> 422 * - if the 1st day of the month is a Monday, week one starts on the 1st and there is no week zero<br> 423 * - if the 2nd day of the month is a Monday, week one starts on the 2nd and the 1st is in week zero<br> 424 * - 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> 425 * - 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> 426 * <p> 427 * This field can be used with any calendar system. 428 * @return a TemporalField to access the WeekOfMonth, not null 429 */ 430 public TemporalField weekOfMonth() { 431 return weekOfMonth; 432 } 433 434 /** 435 * Returns a field to access the week of year, 436 * computed based on this WeekFields. 437 * <p> 438 * This represents the concept of the count of weeks within the year where weeks 439 * start on a fixed day-of-week, such as Monday. 440 * This field is typically used with {@link WeekFields#dayOfWeek()}. 441 * <p> 442 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 443 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year. 444 * Thus, week one may start up to {@code minDays} days before the start of the year. 445 * If the first week starts after the start of the year then the period before is week zero (0). 446 * <p> 447 * For example:<br> 448 * - if the 1st day of the year is a Monday, week one starts on the 1st and there is no week zero<br> 449 * - if the 2nd day of the year is a Monday, week one starts on the 2nd and the 1st is in week zero<br> 450 * - 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> 451 * - 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> 452 * <p> 453 * This field can be used with any calendar system. 454 * @return a TemporalField to access the WeekOfYear, not null 455 */ 456 public TemporalField weekOfYear() { 457 return weekOfYear; 458 } 459 460 /** 461 * Returns a field to access the week of a week-based-year, 462 * computed based on this WeekFields. 463 * <p> 464 * This represents the concept of the count of weeks within the year where weeks 465 * start on a fixed day-of-week, such as Monday and each week belongs to exactly one year. 466 * This field is typically used with {@link WeekFields#dayOfWeek()} and 467 * {@link WeekFields#weekBasedYear()}. 468 * <p> 469 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 470 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year. 471 * If the first week starts after the start of the year then the period before 472 * is in the last week of the previous year. 473 * <p> 474 * For example:<br> 475 * - if the 1st day of the year is a Monday, week one starts on the 1st<br> 476 * - if the 2nd day of the year is a Monday, week one starts on the 2nd and 477 * the 1st is in the last week of the previous year<br> 478 * - if the 4th day of the year is a Monday, week one starts on the 4th and 479 * the 1st to 3rd is in the last week of the previous year<br> 480 * - if the 5th day of the year is a Monday, week two starts on the 5th and 481 * the 1st to 4th is in week one<br> 482 * <p> 483 * This field can be used with any calendar system. 484 * @return a TemporalField to access the week of week-based-year, not null 485 */ 486 public TemporalField weekOfWeekBasedYear() { 487 return weekOfWeekBasedYear; 488 } 489 490 /** 491 * Returns a field to access the year of a week-based-year, 492 * computed based on this WeekFields. 493 * <p> 494 * This represents the concept of the year where weeks start on a fixed day-of-week, 495 * such as Monday and each week belongs to exactly one year. 496 * This field is typically used with {@link WeekFields#dayOfWeek()} and 497 * {@link WeekFields#weekOfWeekBasedYear()}. 498 * <p> 499 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 500 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year. 501 * Thus, week one may start before the start of the year. 502 * If the first week starts after the start of the year then the period before 503 * is in the last week of the previous year. 504 * <p> 505 * This field can be used with any calendar system. 506 * @return a TemporalField to access the year of week-based-year, not null 507 */ 508 public TemporalField weekBasedYear() { 509 return weekBasedYear; 510 } 511 512 /** 513 * Checks if this WeekFields is equal to the specified object. 514 * <p> 515 * The comparison is based on the entire state of the rules, which is 516 * the first day-of-week and minimal days. 517 * 518 * @param object the other rules to compare to, null returns false 519 * @return true if this is equal to the specified rules 520 */ 521 @Override 522 public boolean equals(Object object) { 523 if (this == object) { 524 return true; 525 } 526 if (object instanceof WeekFields) { 527 return hashCode() == object.hashCode(); 528 } 529 return false; 530 } 531 532 /** 533 * A hash code for these rules. 534 * 535 * @return a suitable hash code 536 */ 537 @Override 538 public int hashCode() { 539 return firstDayOfWeek.ordinal() * 7 + minimalDays; 540 } 541 542 //----------------------------------------------------------------------- 543 /** 544 * A string representation of this definition. 545 * 546 * @return the string representation, not null 547 */ 548 @Override 549 public String toString() { 550 return "WeekFields[" + firstDayOfWeek + ',' + minimalDays + ']'; 551 } 552 553 //----------------------------------------------------------------------- 554 /** 555 * Field type that computes DayOfWeek, WeekOfMonth, and WeekOfYear 556 * based on a WeekFields. 557 * A separate Field instance is required for each different WeekFields; 558 * combination of start of week and minimum number of days. 559 * Constructors are provided to create fields for DayOfWeek, WeekOfMonth, 560 * and WeekOfYear. 561 */ 562 static class ComputedDayOfField implements TemporalField { 563 564 /** 565 * Returns a field to access the day of week, 566 * computed based on a WeekFields. 567 * <p> 568 * The WeekDefintion of the first day of the week is used with 569 * the ISO DAY_OF_WEEK field to compute week boundaries. 570 */ 571 static ComputedDayOfField ofDayOfWeekField(WeekFields weekDef) { 572 return new ComputedDayOfField("DayOfWeek", weekDef, DAYS, WEEKS, DAY_OF_WEEK_RANGE); 573 } 574 575 /** 576 * Returns a field to access the week of month, 577 * computed based on a WeekFields. 578 * @see WeekFields#weekOfMonth() 579 */ 580 static ComputedDayOfField ofWeekOfMonthField(WeekFields weekDef) { 581 return new ComputedDayOfField("WeekOfMonth", weekDef, WEEKS, MONTHS, WEEK_OF_MONTH_RANGE); 582 } 583 584 /** 585 * Returns a field to access the week of year, 586 * computed based on a WeekFields. 587 * @see WeekFields#weekOfYear() 588 */ 589 static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) { 590 return new ComputedDayOfField("WeekOfYear", weekDef, WEEKS, YEARS, WEEK_OF_YEAR_RANGE); 591 } 592 593 /** 594 * Returns a field to access the week of week-based-year, 595 * computed based on a WeekFields. 596 * @see WeekFields#weekOfWeekBasedYear() 597 */ 598 static ComputedDayOfField ofWeekOfWeekBasedYearField(WeekFields weekDef) { 599 return new ComputedDayOfField("WeekOfWeekBasedYear", weekDef, WEEKS, IsoFields.WEEK_BASED_YEARS, WEEK_OF_YEAR_RANGE); 600 } 601 602 /** 603 * Returns a field to access the week of week-based-year, 604 * computed based on a WeekFields. 605 * @see WeekFields#weekBasedYear() 606 */ 607 static ComputedDayOfField ofWeekBasedYearField(WeekFields weekDef) { 608 return new ComputedDayOfField("WeekBasedYear", weekDef, IsoFields.WEEK_BASED_YEARS, FOREVER, ChronoField.YEAR.range()); 609 } 610 611 /** 612 * Return a new week-based-year date of the Chronology, year, week-of-year, 613 * and dow of week. 614 * @param chrono The chronology of the new date 615 * @param yowby the year of the week-based-year 616 * @param wowby the week of the week-based-year 617 * @param dow the day of the week 618 * @return a ChronoLocalDate for the requested year, week of year, and day of week 619 */ 620 private ChronoLocalDate<?> ofWeekBasedYear(Chronology chrono, 621 int yowby, int wowby, int dow) { 622 ChronoLocalDate<?> date = chrono.date(yowby, 1, 1); 623 int ldow = localizedDayOfWeek(date); 624 int offset = startOfWeekOffset(1, ldow); 625 626 // Clamp the week of year to keep it in the same year 627 int yearLen = date.lengthOfYear(); 628 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); 629 wowby = Math.min(wowby, newYearWeek - 1); 630 631 int days = -offset + (dow - 1) + (wowby - 1) * 7; 632 return date.plus(days, DAYS); 633 } 634 635 private final String name; 636 private final WeekFields weekDef; 637 private final TemporalUnit baseUnit; 638 private final TemporalUnit rangeUnit; 639 private final ValueRange range; 640 641 private ComputedDayOfField(String name, WeekFields weekDef, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) { 642 this.name = name; 643 this.weekDef = weekDef; 644 this.baseUnit = baseUnit; 645 this.rangeUnit = rangeUnit; 646 this.range = range; 647 } 648 649 private static final ValueRange DAY_OF_WEEK_RANGE = ValueRange.of(1, 7); 650 private static final ValueRange WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 6); 651 private static final ValueRange WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 54); 652 653 @Override 654 public long getFrom(TemporalAccessor temporal) { 655 if (rangeUnit == WEEKS) { // day-of-week 656 return localizedDayOfWeek(temporal); 657 } else if (rangeUnit == MONTHS) { // week-of-month 658 return localizedWeekOfMonth(temporal); 659 } else if (rangeUnit == YEARS) { // week-of-year 660 return localizedWeekOfYear(temporal); 661 } else if (rangeUnit == WEEK_BASED_YEARS) { 662 return localizedWeekOfWeekBasedYear(temporal); 663 } else if (rangeUnit == FOREVER) { 664 return localizedWeekBasedYear(temporal); 665 } else { 666 throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this); 667 } 668 } 669 670 private int localizedDayOfWeek(TemporalAccessor temporal) { 671 int sow = weekDef.getFirstDayOfWeek().getValue(); 672 int isoDow = temporal.get(DAY_OF_WEEK); 673 return Math.floorMod(isoDow - sow, 7) + 1; 674 } 675 676 private long localizedWeekOfMonth(TemporalAccessor temporal) { 677 int dow = localizedDayOfWeek(temporal); 678 int dom = temporal.get(DAY_OF_MONTH); 679 int offset = startOfWeekOffset(dom, dow); 680 return computeWeek(offset, dom); 681 } 682 683 private long localizedWeekOfYear(TemporalAccessor temporal) { 684 int dow = localizedDayOfWeek(temporal); 685 int doy = temporal.get(DAY_OF_YEAR); 686 int offset = startOfWeekOffset(doy, dow); 687 return computeWeek(offset, doy); 688 } 689 690 /** 691 * Returns the year of week-based-year for the temporal. 692 * The year can be the previous year, the current year, or the next year. 693 * @param temporal a date of any chronology, not null 694 * @return the year of week-based-year for the date 695 */ 696 private int localizedWeekBasedYear(TemporalAccessor temporal) { 697 int dow = localizedDayOfWeek(temporal); 698 int year = temporal.get(YEAR); 699 int doy = temporal.get(DAY_OF_YEAR); 700 int offset = startOfWeekOffset(doy, dow); 701 int week = computeWeek(offset, doy); 702 if (week == 0) { 703 // Day is in end of week of previous year; return the previous year 704 return year - 1; 705 } else { 706 // If getting close to end of year, use higher precision logic 707 // Check if date of year is in partial week associated with next year 708 ValueRange dayRange = temporal.range(DAY_OF_YEAR); 709 int yearLen = (int)dayRange.getMaximum(); 710 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); 711 if (week >= newYearWeek) { 712 return year + 1; 713 } 714 } 715 return year; 716 } 717 718 /** 719 * Returns the week of week-based-year for the temporal. 720 * The week can be part of the previous year, the current year, 721 * or the next year depending on the week start and minimum number 722 * of days. 723 * @param temporal a date of any chronology 724 * @return the week of the year 725 * @see #localizedWeekBasedYear(java.time.temporal.TemporalAccessor) 726 */ 727 private int localizedWeekOfWeekBasedYear(TemporalAccessor temporal) { 728 int dow = localizedDayOfWeek(temporal); 729 int doy = temporal.get(DAY_OF_YEAR); 730 int offset = startOfWeekOffset(doy, dow); 731 int week = computeWeek(offset, doy); 732 if (week == 0) { 733 // Day is in end of week of previous year 734 // Recompute from the last day of the previous year 735 ChronoLocalDate date = Chronology.from(temporal).date(temporal); 736 date = date.minus(doy, DAYS); // Back down into previous year 737 return localizedWeekOfWeekBasedYear(date); 738 } else if (week > 50) { 739 // If getting close to end of year, use higher precision logic 740 // Check if date of year is in partial week associated with next year 741 ValueRange dayRange = temporal.range(DAY_OF_YEAR); 742 int yearLen = (int)dayRange.getMaximum(); 743 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); 744 if (week >= newYearWeek) { 745 // Overlaps with week of following year; reduce to week in following year 746 week = week - newYearWeek + 1; 747 } 748 } 749 return week; 750 } 751 752 /** 753 * Returns an offset to align week start with a day of month or day of year. 754 * 755 * @param day the day; 1 through infinity 756 * @param dow the day of the week of that day; 1 through 7 757 * @return an offset in days to align a day with the start of the first 'full' week 758 */ 759 private int startOfWeekOffset(int day, int dow) { 760 // offset of first day corresponding to the day of week in first 7 days (zero origin) 761 int weekStart = Math.floorMod(day - dow, 7); 762 int offset = -weekStart; 763 if (weekStart + 1 > weekDef.getMinimalDaysInFirstWeek()) { 764 // The previous week has the minimum days in the current month to be a 'week' 765 offset = 7 - weekStart; 766 } 767 return offset; 768 } 769 770 /** 771 * Returns the week number computed from the reference day and reference dayOfWeek. 772 * 773 * @param offset the offset to align a date with the start of week 774 * from {@link #startOfWeekOffset}. 775 * @param day the day for which to compute the week number 776 * @return the week number where zero is used for a partial week and 1 for the first full week 777 */ 778 private int computeWeek(int offset, int day) { 779 return ((7 + offset + (day - 1)) / 7); 780 } 781 782 @SuppressWarnings("unchecked") 783 @Override 784 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 785 // Check the new value and get the old value of the field 786 int newVal = range.checkValidIntValue(newValue, this); // lenient check range 787 int currentVal = temporal.get(this); 788 if (newVal == currentVal) { 789 return temporal; 790 } 791 792 if (rangeUnit == FOREVER) { // replace year of WeekBasedYear 793 // Create a new date object with the same chronology, 794 // the desired year and the same week and dow. 795 int idow = temporal.get(weekDef.dayOfWeek); 796 int wowby = temporal.get(weekDef.weekOfWeekBasedYear); 797 return (R) ofWeekBasedYear(Chronology.from(temporal), (int)newValue, wowby, idow); 798 } else { 799 // Compute the difference and add that using the base unit of the field 800 return (R) temporal.plus(newVal - currentVal, baseUnit); 801 } 802 } 803 804 @Override 805 public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value) { 806 int newValue = range.checkValidIntValue(value, this); 807 int sow = weekDef.getFirstDayOfWeek().getValue(); 808 if (rangeUnit == WEEKS) { // day-of-week 809 int isoDow = Math.floorMod((sow - 1) + (newValue - 1), 7) + 1; 810 return Collections.<TemporalField, Long>singletonMap(DAY_OF_WEEK, (long) isoDow); 811 } 812 if (temporal.isSupported(DAY_OF_WEEK) == false) { 813 return null; 814 } 815 Chronology chrono = Chronology.from(temporal); // defaults to ISO 816 int dow = localizedDayOfWeek(temporal); 817 if (temporal.isSupported(YEAR)) { 818 int year = temporal.get(YEAR); 819 if (rangeUnit == MONTHS) { // week-of-month 820 if (temporal.isSupported(MONTH_OF_YEAR) == false) { 821 return null; 822 } 823 int month = temporal.get(ChronoField.MONTH_OF_YEAR); 824 @SuppressWarnings("rawtypes") 825 ChronoLocalDate date = chrono.date(year, month, 1); 826 int dateDow = localizedDayOfWeek(date); 827 long weeks = newValue - localizedWeekOfMonth(date); 828 int days = dow - dateDow; 829 date = date.plus(weeks * 7 + days, DAYS); 830 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); 831 result.put(EPOCH_DAY, date.toEpochDay()); 832 result.put(YEAR, null); 833 result.put(MONTH_OF_YEAR, null); 834 result.put(DAY_OF_WEEK, null); 835 return result; 836 } else if (rangeUnit == YEARS) { // week-of-year 837 @SuppressWarnings("rawtypes") 838 ChronoLocalDate date = chrono.date(year, 1, 1); 839 int dateDow = localizedDayOfWeek(date); 840 long weeks = newValue - localizedWeekOfYear(date); 841 int days = dow - dateDow; 842 date = date.plus(weeks * 7 + days, DAYS); 843 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); 844 result.put(EPOCH_DAY, date.toEpochDay()); 845 result.put(YEAR, null); 846 result.put(DAY_OF_WEEK, null); 847 return result; 848 } 849 } else if (rangeUnit == WEEK_BASED_YEARS || rangeUnit == FOREVER) { 850 if (temporal.isSupported(weekDef.weekBasedYear) && 851 temporal.isSupported(weekDef.weekOfWeekBasedYear)) { 852 // week-of-week-based-year and year-of-week-based-year 853 int yowby = temporal.get(weekDef.weekBasedYear); 854 int wowby = temporal.get(weekDef.weekOfWeekBasedYear); 855 ChronoLocalDate<?> date = ofWeekBasedYear(Chronology.from(temporal), yowby, wowby, dow); 856 857 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); 858 result.put(EPOCH_DAY, date.toEpochDay()); 859 result.put(DAY_OF_WEEK, null); 860 result.put(weekDef.weekOfWeekBasedYear, null); 861 result.put(weekDef.weekBasedYear, null); 862 return result; 863 } 864 } 865 return null; 866 } 867 868 //----------------------------------------------------------------------- 869 @Override 870 public String getName() { 871 return name; 872 } 873 874 @Override 875 public String getDisplayName(Locale locale) { 876 Objects.requireNonNull(locale, "locale"); 877 if (rangeUnit == YEARS) { // only have values for week-of-year 878 LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() 879 .getLocaleResources(locale); 880 ResourceBundle rb = lr.getFormatData(); 881 return rb.containsKey("field.week") ? rb.getString("field.week") : getName(); 882 } 883 return getName(); 884 } 885 886 @Override 887 public TemporalUnit getBaseUnit() { 888 return baseUnit; 889 } 890 891 @Override 892 public TemporalUnit getRangeUnit() { 893 return rangeUnit; 894 } 895 896 @Override 897 public boolean isDateBased() { 898 return true; 899 } 900 901 @Override 902 public ValueRange range() { 903 return range; 904 } 905 906 //----------------------------------------------------------------------- 907 @Override 908 public boolean isSupportedBy(TemporalAccessor temporal) { 909 if (temporal.isSupported(DAY_OF_WEEK)) { 910 if (rangeUnit == WEEKS) { // day-of-week 911 return true; 912 } else if (rangeUnit == MONTHS) { // week-of-month 913 return temporal.isSupported(DAY_OF_MONTH); 914 } else if (rangeUnit == YEARS) { // week-of-year 915 return temporal.isSupported(DAY_OF_YEAR); 916 } else if (rangeUnit == WEEK_BASED_YEARS) { 917 return temporal.isSupported(DAY_OF_YEAR); 918 } else if (rangeUnit == FOREVER) { 919 return temporal.isSupported(YEAR); 920 } 921 } 922 return false; 923 } 924 925 @Override 926 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 927 if (rangeUnit == ChronoUnit.WEEKS) { // day-of-week 928 return range; 929 } else if (rangeUnit == MONTHS) { // week-of-month 930 return rangeByWeek(temporal, DAY_OF_MONTH); 931 } else if (rangeUnit == YEARS) { // week-of-year 932 return rangeByWeek(temporal, DAY_OF_YEAR); 933 } else if (rangeUnit == WEEK_BASED_YEARS) { 934 return rangeWeekOfWeekBasedYear(temporal); 935 } else if (rangeUnit == FOREVER) { 936 return YEAR.range(); 937 } else { 938 throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this); 939 } 940 } 941 942 /** 943 * Map the field range to a week range 944 * @param temporal the temporal 945 * @param field the field to get the range of 946 * @return the ValueRange with the range adjusted to weeks. 947 */ 948 private ValueRange rangeByWeek(TemporalAccessor temporal, TemporalField field) { 949 int dow = localizedDayOfWeek(temporal); 950 int offset = startOfWeekOffset(temporal.get(field), dow); 951 ValueRange fieldRange = temporal.range(field); 952 return ValueRange.of(computeWeek(offset, (int) fieldRange.getMinimum()), 953 computeWeek(offset, (int) fieldRange.getMaximum())); 954 } 955 956 /** 957 * Map the field range to a week range of a week year. 958 * @param temporal the temporal 959 * @param field the field to get the range of 960 * @return the ValueRange with the range adjusted to weeks. 961 */ 962 private ValueRange rangeWeekOfWeekBasedYear(TemporalAccessor temporal) { 963 if (!temporal.isSupported(DAY_OF_YEAR)) { 964 return WEEK_OF_YEAR_RANGE; 965 } 966 int dow = localizedDayOfWeek(temporal); 967 int doy = temporal.get(DAY_OF_YEAR); 968 int offset = startOfWeekOffset(doy, dow); 969 int week = computeWeek(offset, doy); 970 if (week == 0) { 971 // Day is in end of week of previous year 972 // Recompute from the last day of the previous year 973 ChronoLocalDate date = Chronology.from(temporal).date(temporal); 974 date = date.minus(doy + 7, DAYS); // Back down into previous year 975 return rangeWeekOfWeekBasedYear(date); 976 } 977 // Check if day of year is in partial week associated with next year 978 ValueRange dayRange = temporal.range(DAY_OF_YEAR); 979 int yearLen = (int)dayRange.getMaximum(); 980 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); 981 982 if (week >= newYearWeek) { 983 // Overlaps with weeks of following year; recompute from a week in following year 984 ChronoLocalDate date = Chronology.from(temporal).date(temporal); 985 date = date.plus(yearLen - doy + 1 + 7, ChronoUnit.DAYS); 986 return rangeWeekOfWeekBasedYear(date); 987 } 988 return ValueRange.of(1, newYearWeek-1); 989 } 990 991 //----------------------------------------------------------------------- 992 @Override 993 public String toString() { 994 return getName() + "[" + weekDef.toString() + "]"; 995 } 996 } 997 }