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. 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"); 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) { 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 } | 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. 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"); 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) { 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 } |