1 /* 2 * Copyright (c) 2012, 2017, 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 * Copyright (c) 2011-2012, Stephen Colebourne & Michael Nascimento Santos 28 * 29 * All rights reserved. 30 * 31 * Redistribution and use in source and binary forms, with or without 32 * modification, are permitted provided that the following conditions are met: 33 * 34 * * Redistributions of source code must retain the above copyright notice, 35 * this list of conditions and the following disclaimer. 36 * 37 * * Redistributions in binary form must reproduce the above copyright notice, 38 * this list of conditions and the following disclaimer in the documentation 39 * and/or other materials provided with the distribution. 40 * 41 * * Neither the name of JSR-310 nor the names of its contributors 42 * may be used to endorse or promote products derived from this software 43 * without specific prior written permission. 44 * 45 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 46 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 47 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 48 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 49 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 50 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 51 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 52 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 53 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 54 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 55 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 56 */ 57 package java.time.temporal; 58 59 import static java.time.DayOfWeek.THURSDAY; 60 import static java.time.DayOfWeek.WEDNESDAY; 61 import static java.time.temporal.ChronoField.DAY_OF_WEEK; 62 import static java.time.temporal.ChronoField.DAY_OF_YEAR; 63 import static java.time.temporal.ChronoField.EPOCH_DAY; 64 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 65 import static java.time.temporal.ChronoField.YEAR; 66 import static java.time.temporal.ChronoUnit.DAYS; 67 import static java.time.temporal.ChronoUnit.FOREVER; 68 import static java.time.temporal.ChronoUnit.MONTHS; 69 import static java.time.temporal.ChronoUnit.WEEKS; 70 import static java.time.temporal.ChronoUnit.YEARS; 71 72 import java.time.DateTimeException; 73 import java.time.Duration; 74 import java.time.LocalDate; 75 import java.time.chrono.ChronoLocalDate; 76 import java.time.chrono.Chronology; 77 import java.time.chrono.IsoChronology; 78 import java.time.format.ResolverStyle; 79 import java.util.Locale; 80 import java.util.Map; 81 import java.util.Objects; 82 import java.util.ResourceBundle; 83 84 import sun.util.locale.provider.LocaleProviderAdapter; 85 import sun.util.locale.provider.LocaleResources; 86 87 /** 88 * Fields and units specific to the ISO-8601 calendar system, 89 * including quarter-of-year and week-based-year. 90 * <p> 91 * This class defines fields and units that are specific to the ISO calendar system. 92 * 93 * <h3>Quarter of year</h3> 94 * The ISO-8601 standard is based on the standard civic 12 month year. 95 * This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4. 96 * <p> 97 * January, February and March are in Q1. 98 * April, May and June are in Q2. 99 * July, August and September are in Q3. 100 * October, November and December are in Q4. 101 * <p> 102 * The complete date is expressed using three fields: 103 * <ul> 104 * <li>{@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92 105 * <li>{@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the quarter within the year, from 1 to 4 106 * <li>{@link ChronoField#YEAR YEAR} - the standard ISO year 107 * </ul> 108 * 109 * <h3>Week based years</h3> 110 * The ISO-8601 standard was originally intended as a data interchange format, 111 * defining a string format for dates and times. However, it also defines an 112 * alternate way of expressing the date, based on the concept of week-based-year. 113 * <p> 114 * The date is expressed using three fields: 115 * <ul> 116 * <li>{@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the 117 * day-of-week from Monday (1) to Sunday (7) 118 * <li>{@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year 119 * <li>{@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year 120 * </ul> 121 * The week-based-year itself is defined relative to the standard ISO proleptic year. 122 * It differs from the standard year in that it always starts on a Monday. 123 * <p> 124 * The first week of a week-based-year is the first Monday-based week of the standard 125 * ISO year that has at least 4 days in the new year. 126 * <ul> 127 * <li>If January 1st is Monday then week 1 starts on January 1st 128 * <li>If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year 129 * <li>If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year 130 * <li>If January 1st is Thursday then week 1 starts on December 29th of the previous standard year 131 * <li>If January 1st is Friday then week 1 starts on January 4th 132 * <li>If January 1st is Saturday then week 1 starts on January 3rd 133 * <li>If January 1st is Sunday then week 1 starts on January 2nd 134 * </ul> 135 * There are 52 weeks in most week-based years, however on occasion there are 53 weeks. 136 * <p> 137 * For example: 138 * 139 * <table class=striped style="text-align: left"> 140 * <caption>Examples of Week based Years</caption> 141 * <thead> 142 * <tr><th scope="col">Date</th><th scope="col">Day-of-week</th><th scope="col">Field values</th></tr> 143 * </thead> 144 * <tbody> 145 * <tr><th scope="row">2008-12-28</th><td>Sunday</td><td>Week 52 of week-based-year 2008</td></tr> 146 * <tr><th scope="row">2008-12-29</th><td>Monday</td><td>Week 1 of week-based-year 2009</td></tr> 147 * <tr><th scope="row">2008-12-31</th><td>Wednesday</td><td>Week 1 of week-based-year 2009</td></tr> 148 * <tr><th scope="row">2009-01-01</th><td>Thursday</td><td>Week 1 of week-based-year 2009</td></tr> 149 * <tr><th scope="row">2009-01-04</th><td>Sunday</td><td>Week 1 of week-based-year 2009</td></tr> 150 * <tr><th scope="row">2009-01-05</th><td>Monday</td><td>Week 2 of week-based-year 2009</td></tr> 151 * </tbody> 152 * </table> 153 * 154 * @implSpec 155 * <p> 156 * This class is immutable and thread-safe. 157 * 158 * @since 1.8 159 */ 160 public final class IsoFields { 161 162 /** 163 * The field that represents the day-of-quarter. 164 * <p> 165 * This field allows the day-of-quarter value to be queried and set. 166 * The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91 167 * in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4. 168 * <p> 169 * The day-of-quarter can only be calculated if the day-of-year, month-of-year and year 170 * are available. 171 * <p> 172 * When setting this field, the value is allowed to be partially lenient, taking any 173 * value from 1 to 92. If the quarter has less than 92 days, then day 92, and 174 * potentially day 91, is in the following quarter. 175 * <p> 176 * In the resolving phase of parsing, a date can be created from a year, 177 * quarter-of-year and day-of-quarter. 178 * <p> 179 * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are 180 * validated against their range of valid values. The day-of-quarter field 181 * is validated from 1 to 90, 91 or 92 depending on the year and quarter. 182 * <p> 183 * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are 184 * validated against their range of valid values. The day-of-quarter field is 185 * validated between 1 and 92, ignoring the actual range based on the year and quarter. 186 * If the day-of-quarter exceeds the actual range by one day, then the resulting date 187 * is one day later. If the day-of-quarter exceeds the actual range by two days, 188 * then the resulting date is two days later. 189 * <p> 190 * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the year is validated 191 * against the range of valid values. The resulting date is calculated equivalent to 192 * the following three stage approach. First, create a date on the first of January 193 * in the requested year. Then take the quarter-of-year, subtract one, and add the 194 * amount in quarters to the date. Finally, take the day-of-quarter, subtract one, 195 * and add the amount in days to the date. 196 * <p> 197 * This unit is an immutable and thread-safe singleton. 198 */ 199 public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER; 200 /** 201 * The field that represents the quarter-of-year. 202 * <p> 203 * This field allows the quarter-of-year value to be queried and set. 204 * The quarter-of-year has values from 1 to 4. 205 * <p> 206 * The quarter-of-year can only be calculated if the month-of-year is available. 207 * <p> 208 * In the resolving phase of parsing, a date can be created from a year, 209 * quarter-of-year and day-of-quarter. 210 * See {@link #DAY_OF_QUARTER} for details. 211 * <p> 212 * This unit is an immutable and thread-safe singleton. 213 */ 214 public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR; 215 /** 216 * The field that represents the week-of-week-based-year. 217 * <p> 218 * This field allows the week of the week-based-year value to be queried and set. 219 * The week-of-week-based-year has values from 1 to 52, or 53 if the 220 * week-based-year has 53 weeks. 221 * <p> 222 * In the resolving phase of parsing, a date can be created from a 223 * week-based-year, week-of-week-based-year and day-of-week. 224 * <p> 225 * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are 226 * validated against their range of valid values. The week-of-week-based-year 227 * field is validated from 1 to 52 or 53 depending on the week-based-year. 228 * <p> 229 * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are 230 * validated against their range of valid values. The week-of-week-based-year 231 * field is validated between 1 and 53, ignoring the week-based-year. 232 * If the week-of-week-based-year is 53, but the week-based-year only has 233 * 52 weeks, then the resulting date is in week 1 of the following week-based-year. 234 * <p> 235 * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the week-based-year 236 * is validated against the range of valid values. If the day-of-week is outside 237 * the range 1 to 7, then the resulting date is adjusted by a suitable number of 238 * weeks to reduce the day-of-week to the range 1 to 7. If the week-of-week-based-year 239 * value is outside the range 1 to 52, then any excess weeks are added or subtracted 240 * from the resulting date. 241 * <p> 242 * This unit is an immutable and thread-safe singleton. 243 */ 244 public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR; 245 /** 246 * The field that represents the week-based-year. 247 * <p> 248 * This field allows the week-based-year value to be queried and set. 249 * <p> 250 * The field has a range that matches {@link LocalDate#MAX} and {@link LocalDate#MIN}. 251 * <p> 252 * In the resolving phase of parsing, a date can be created from a 253 * week-based-year, week-of-week-based-year and day-of-week. 254 * See {@link #WEEK_OF_WEEK_BASED_YEAR} for details. 255 * <p> 256 * This unit is an immutable and thread-safe singleton. 257 */ 258 public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR; 259 /** 260 * The unit that represents week-based-years for the purpose of addition and subtraction. 261 * <p> 262 * This allows a number of week-based-years to be added to, or subtracted from, a date. 263 * The unit is equal to either 52 or 53 weeks. 264 * The estimated duration of a week-based-year is the same as that of a standard ISO 265 * year at {@code 365.2425 Days}. 266 * <p> 267 * The rules for addition add the number of week-based-years to the existing value 268 * for the week-based-year field. If the resulting week-based-year only has 52 weeks, 269 * then the date will be in week 1 of the following week-based-year. 270 * <p> 271 * This unit is an immutable and thread-safe singleton. 272 */ 273 public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS; 274 /** 275 * Unit that represents the concept of a quarter-year. 276 * For the ISO calendar system, it is equal to 3 months. 277 * The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}. 278 * <p> 279 * This unit is an immutable and thread-safe singleton. 280 */ 281 public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS; 282 283 /** 284 * Restricted constructor. 285 */ 286 private IsoFields() { 287 throw new AssertionError("Not instantiable"); 288 } 289 290 //----------------------------------------------------------------------- 291 /** 292 * Implementation of the field. 293 */ 294 private static enum Field implements TemporalField { 295 DAY_OF_QUARTER { 296 @Override 297 public TemporalUnit getBaseUnit() { 298 return DAYS; 299 } 300 @Override 301 public TemporalUnit getRangeUnit() { 302 return QUARTER_YEARS; 303 } 304 @Override 305 public ValueRange range() { 306 return ValueRange.of(1, 90, 92); 307 } 308 @Override 309 public boolean isSupportedBy(TemporalAccessor temporal) { 310 return temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) && 311 temporal.isSupported(YEAR) && isIso(temporal); 312 } 313 @Override 314 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 315 if (isSupportedBy(temporal) == false) { 316 throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter"); 317 } 318 long qoy = temporal.getLong(QUARTER_OF_YEAR); 319 if (qoy == 1) { 320 long year = temporal.getLong(YEAR); 321 return (IsoChronology.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90)); 322 } else if (qoy == 2) { 323 return ValueRange.of(1, 91); 324 } else if (qoy == 3 || qoy == 4) { 325 return ValueRange.of(1, 92); 326 } // else value not from 1 to 4, so drop through 327 return range(); 328 } 329 @Override 330 public long getFrom(TemporalAccessor temporal) { 331 if (isSupportedBy(temporal) == false) { 332 throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter"); 333 } 334 int doy = temporal.get(DAY_OF_YEAR); 335 int moy = temporal.get(MONTH_OF_YEAR); 336 long year = temporal.getLong(YEAR); 337 return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year) ? 4 : 0)]; 338 } 339 @SuppressWarnings("unchecked") 340 @Override 341 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 342 // calls getFrom() to check if supported 343 long curValue = getFrom(temporal); 344 range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check 345 return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue)); 346 } 347 @Override 348 public ChronoLocalDate resolve( 349 Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) { 350 Long yearLong = fieldValues.get(YEAR); 351 Long qoyLong = fieldValues.get(QUARTER_OF_YEAR); 352 if (yearLong == null || qoyLong == null) { 353 return null; 354 } 355 int y = YEAR.checkValidIntValue(yearLong); // always validate 356 long doq = fieldValues.get(DAY_OF_QUARTER); 357 ensureIso(partialTemporal); 358 LocalDate date; 359 if (resolverStyle == ResolverStyle.LENIENT) { 360 date = LocalDate.of(y, 1, 1).plusMonths(Math.multiplyExact(Math.subtractExact(qoyLong, 1), 3)); 361 doq = Math.subtractExact(doq, 1); 362 } else { 363 int qoy = QUARTER_OF_YEAR.range().checkValidIntValue(qoyLong, QUARTER_OF_YEAR); // validated 364 date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1); 365 if (doq < 1 || doq > 90) { 366 if (resolverStyle == ResolverStyle.STRICT) { 367 rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range 368 } else { // SMART 369 range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter 370 } 371 } 372 doq--; 373 } 374 fieldValues.remove(this); 375 fieldValues.remove(YEAR); 376 fieldValues.remove(QUARTER_OF_YEAR); 377 return date.plusDays(doq); 378 } 379 @Override 380 public String toString() { 381 return "DayOfQuarter"; 382 } 383 }, 384 QUARTER_OF_YEAR { 385 @Override 386 public TemporalUnit getBaseUnit() { 387 return QUARTER_YEARS; 388 } 389 @Override 390 public TemporalUnit getRangeUnit() { 391 return YEARS; 392 } 393 @Override 394 public ValueRange range() { 395 return ValueRange.of(1, 4); 396 } 397 @Override 398 public boolean isSupportedBy(TemporalAccessor temporal) { 399 return temporal.isSupported(MONTH_OF_YEAR) && isIso(temporal); 400 } 401 @Override 402 public long getFrom(TemporalAccessor temporal) { 403 if (isSupportedBy(temporal) == false) { 404 throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear"); 405 } 406 long moy = temporal.getLong(MONTH_OF_YEAR); 407 return ((moy + 2) / 3); 408 } 409 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 410 if (isSupportedBy(temporal) == false) { 411 throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear"); 412 } 413 return super.rangeRefinedBy(temporal); 414 } 415 @SuppressWarnings("unchecked") 416 @Override 417 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 418 // calls getFrom() to check if supported 419 long curValue = getFrom(temporal); 420 range().checkValidValue(newValue, this); // strictly check from 1 to 4 421 return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3); 422 } 423 @Override 424 public String toString() { 425 return "QuarterOfYear"; 426 } 427 }, 428 WEEK_OF_WEEK_BASED_YEAR { 429 @Override 430 public String getDisplayName(Locale locale) { 431 Objects.requireNonNull(locale, "locale"); 432 LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() 433 .getLocaleResources(locale); 434 ResourceBundle rb = lr.getJavaTimeFormatData(); 435 return rb.containsKey("field.week") ? rb.getString("field.week") : toString(); 436 } 437 438 @Override 439 public TemporalUnit getBaseUnit() { 440 return WEEKS; 441 } 442 @Override 443 public TemporalUnit getRangeUnit() { 444 return WEEK_BASED_YEARS; 445 } 446 @Override 447 public ValueRange range() { 448 return ValueRange.of(1, 52, 53); 449 } 450 @Override 451 public boolean isSupportedBy(TemporalAccessor temporal) { 452 return temporal.isSupported(EPOCH_DAY) && isIso(temporal); 453 } 454 @Override 455 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 456 if (isSupportedBy(temporal) == false) { 457 throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear"); 458 } 459 return getWeekRange(LocalDate.from(temporal)); 460 } 461 @Override 462 public long getFrom(TemporalAccessor temporal) { 463 if (isSupportedBy(temporal) == false) { 464 throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear"); 465 } 466 return getWeek(LocalDate.from(temporal)); 467 } 468 @SuppressWarnings("unchecked") 469 @Override 470 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 471 // calls getFrom() to check if supported 472 range().checkValidValue(newValue, this); // lenient range 473 return (R) temporal.plus(Math.subtractExact(newValue, getFrom(temporal)), WEEKS); 474 } 475 @Override 476 public ChronoLocalDate resolve( 477 Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) { 478 Long wbyLong = fieldValues.get(WEEK_BASED_YEAR); 479 Long dowLong = fieldValues.get(DAY_OF_WEEK); 480 if (wbyLong == null || dowLong == null) { 481 return null; 482 } 483 int wby = WEEK_BASED_YEAR.range().checkValidIntValue(wbyLong, WEEK_BASED_YEAR); // always validate 484 long wowby = fieldValues.get(WEEK_OF_WEEK_BASED_YEAR); 485 ensureIso(partialTemporal); 486 LocalDate date = LocalDate.of(wby, 1, 4); 487 if (resolverStyle == ResolverStyle.LENIENT) { 488 long dow = dowLong; // unvalidated 489 if (dow > 7) { 490 date = date.plusWeeks((dow - 1) / 7); 491 dow = ((dow - 1) % 7) + 1; 492 } else if (dow < 1) { 493 date = date.plusWeeks(Math.subtractExact(dow, 7) / 7); 494 dow = ((dow + 6) % 7) + 1; 495 } 496 date = date.plusWeeks(Math.subtractExact(wowby, 1)).with(DAY_OF_WEEK, dow); 497 } else { 498 int dow = DAY_OF_WEEK.checkValidIntValue(dowLong); // validated 499 if (wowby < 1 || wowby > 52) { 500 if (resolverStyle == ResolverStyle.STRICT) { 501 getWeekRange(date).checkValidValue(wowby, this); // only allow exact range 502 } else { // SMART 503 range().checkValidValue(wowby, this); // allow 1-53 rolling into next year 504 } 505 } 506 date = date.plusWeeks(wowby - 1).with(DAY_OF_WEEK, dow); 507 } 508 fieldValues.remove(this); 509 fieldValues.remove(WEEK_BASED_YEAR); 510 fieldValues.remove(DAY_OF_WEEK); 511 return date; 512 } 513 @Override 514 public String toString() { 515 return "WeekOfWeekBasedYear"; 516 } 517 }, 518 WEEK_BASED_YEAR { 519 @Override 520 public TemporalUnit getBaseUnit() { 521 return WEEK_BASED_YEARS; 522 } 523 @Override 524 public TemporalUnit getRangeUnit() { 525 return FOREVER; 526 } 527 @Override 528 public ValueRange range() { 529 return YEAR.range(); 530 } 531 @Override 532 public boolean isSupportedBy(TemporalAccessor temporal) { 533 return temporal.isSupported(EPOCH_DAY) && isIso(temporal); 534 } 535 @Override 536 public long getFrom(TemporalAccessor temporal) { 537 if (isSupportedBy(temporal) == false) { 538 throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear"); 539 } 540 return getWeekBasedYear(LocalDate.from(temporal)); 541 } 542 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 543 if (isSupportedBy(temporal) == false) { 544 throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear"); 545 } 546 return super.rangeRefinedBy(temporal); 547 } 548 @SuppressWarnings("unchecked") 549 @Override 550 public <R extends Temporal> R adjustInto(R temporal, long newValue) { 551 if (isSupportedBy(temporal) == false) { 552 throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear"); 553 } 554 int newWby = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check 555 LocalDate date = LocalDate.from(temporal); 556 int dow = date.get(DAY_OF_WEEK); 557 int week = getWeek(date); 558 if (week == 53 && getWeekRange(newWby) == 52) { 559 week = 52; 560 } 561 LocalDate resolved = LocalDate.of(newWby, 1, 4); // 4th is guaranteed to be in week one 562 int days = (dow - resolved.get(DAY_OF_WEEK)) + ((week - 1) * 7); 563 resolved = resolved.plusDays(days); 564 return (R) temporal.with(resolved); 565 } 566 @Override 567 public String toString() { 568 return "WeekBasedYear"; 569 } 570 }; 571 572 @Override 573 public boolean isDateBased() { 574 return true; 575 } 576 577 @Override 578 public boolean isTimeBased() { 579 return false; 580 } 581 582 @Override 583 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { 584 return range(); 585 } 586 587 //------------------------------------------------------------------------- 588 private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274}; 589 590 591 private static void ensureIso(TemporalAccessor temporal) { 592 if (isIso(temporal) == false) { 593 throw new DateTimeException("Resolve requires IsoChronology"); 594 } 595 } 596 597 private static ValueRange getWeekRange(LocalDate date) { 598 int wby = getWeekBasedYear(date); 599 return ValueRange.of(1, getWeekRange(wby)); 600 } 601 602 private static int getWeekRange(int wby) { 603 LocalDate date = LocalDate.of(wby, 1, 1); 604 // 53 weeks if standard year starts on Thursday, or Wed in a leap year 605 if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) { 606 return 53; 607 } 608 return 52; 609 } 610 611 private static int getWeek(LocalDate date) { 612 int dow0 = date.getDayOfWeek().ordinal(); 613 int doy0 = date.getDayOfYear() - 1; 614 int doyThu0 = doy0 + (3 - dow0); // adjust to mid-week Thursday (which is 3 indexed from zero) 615 int alignedWeek = doyThu0 / 7; 616 int firstThuDoy0 = doyThu0 - (alignedWeek * 7); 617 int firstMonDoy0 = firstThuDoy0 - 3; 618 if (firstMonDoy0 < -3) { 619 firstMonDoy0 += 7; 620 } 621 if (doy0 < firstMonDoy0) { 622 return (int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum(); 623 } 624 int week = ((doy0 - firstMonDoy0) / 7) + 1; 625 if (week == 53) { 626 if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false) { 627 week = 1; 628 } 629 } 630 return week; 631 } 632 633 private static int getWeekBasedYear(LocalDate date) { 634 int year = date.getYear(); 635 int doy = date.getDayOfYear(); 636 if (doy <= 3) { 637 int dow = date.getDayOfWeek().ordinal(); 638 if (doy - dow < -2) { 639 year--; 640 } 641 } else if (doy >= 363) { 642 int dow = date.getDayOfWeek().ordinal(); 643 doy = doy - 363 - (date.isLeapYear() ? 1 : 0); 644 if (doy - dow >= 0) { 645 year++; 646 } 647 } 648 return year; 649 } 650 } 651 652 //----------------------------------------------------------------------- 653 /** 654 * Implementation of the unit. 655 */ 656 private static enum Unit implements TemporalUnit { 657 658 /** 659 * Unit that represents the concept of a week-based-year. 660 */ 661 WEEK_BASED_YEARS("WeekBasedYears", Duration.ofSeconds(31556952L)), 662 /** 663 * Unit that represents the concept of a quarter-year. 664 */ 665 QUARTER_YEARS("QuarterYears", Duration.ofSeconds(31556952L / 4)); 666 667 private final String name; 668 private final Duration duration; 669 670 private Unit(String name, Duration estimatedDuration) { 671 this.name = name; 672 this.duration = estimatedDuration; 673 } 674 675 @Override 676 public Duration getDuration() { 677 return duration; 678 } 679 680 @Override 681 public boolean isDurationEstimated() { 682 return true; 683 } 684 685 @Override 686 public boolean isDateBased() { 687 return true; 688 } 689 690 @Override 691 public boolean isTimeBased() { 692 return false; 693 } 694 695 @Override 696 public boolean isSupportedBy(Temporal temporal) { 697 return temporal.isSupported(EPOCH_DAY) && isIso(temporal); 698 } 699 700 @SuppressWarnings("unchecked") 701 @Override 702 public <R extends Temporal> R addTo(R temporal, long amount) { 703 switch (this) { 704 case WEEK_BASED_YEARS: 705 return (R) temporal.with(WEEK_BASED_YEAR, 706 Math.addExact(temporal.get(WEEK_BASED_YEAR), amount)); 707 case QUARTER_YEARS: 708 // no overflow (256 is multiple of 4) 709 return (R) temporal.plus(amount / 256, YEARS) 710 .plus((amount % 256) * 3, MONTHS); 711 default: 712 throw new IllegalStateException("Unreachable"); 713 } 714 } 715 716 @Override 717 public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) { 718 if (temporal1Inclusive.getClass() != temporal2Exclusive.getClass()) { 719 return temporal1Inclusive.until(temporal2Exclusive, this); 720 } 721 switch(this) { 722 case WEEK_BASED_YEARS: 723 return Math.subtractExact(temporal2Exclusive.getLong(WEEK_BASED_YEAR), 724 temporal1Inclusive.getLong(WEEK_BASED_YEAR)); 725 case QUARTER_YEARS: 726 return temporal1Inclusive.until(temporal2Exclusive, MONTHS) / 3; 727 default: 728 throw new IllegalStateException("Unreachable"); 729 } 730 } 731 732 @Override 733 public String toString() { 734 return name; 735 } 736 } 737 738 static boolean isIso(TemporalAccessor temporal) { 739 return Chronology.from(temporal).equals(IsoChronology.INSTANCE); 740 } 741 }