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