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