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