|
13 | 13 | package org.openhab.core.library.types; |
14 | 14 |
|
15 | 15 | import java.time.DateTimeException; |
| 16 | +import java.time.Duration; |
16 | 17 | import java.time.Instant; |
17 | 18 | import java.time.LocalDateTime; |
18 | 19 | import java.time.OffsetDateTime; |
| 20 | +import java.time.Period; |
19 | 21 | import java.time.ZoneId; |
20 | 22 | import java.time.ZoneOffset; |
21 | 23 | import java.time.ZonedDateTime; |
| 24 | +import java.time.chrono.ChronoZonedDateTime; |
22 | 25 | import java.time.format.DateTimeFormatter; |
23 | 26 | import java.time.format.DateTimeParseException; |
| 27 | +import java.time.temporal.ChronoUnit; |
24 | 28 | import java.time.temporal.Temporal; |
| 29 | +import java.time.temporal.TemporalAccessor; |
| 30 | +import java.time.temporal.TemporalAmount; |
| 31 | +import java.time.temporal.TemporalUnit; |
| 32 | +import java.time.temporal.UnsupportedTemporalTypeException; |
25 | 33 | import java.time.zone.ZoneRulesException; |
26 | 34 | import java.util.IllegalFormatException; |
27 | 35 | import java.util.Locale; |
@@ -463,6 +471,274 @@ public String format(Locale locale, @Nullable String pattern, ZoneId zoneId) |
463 | 471 | return String.format(locale, pattern, zonedDateTime); |
464 | 472 | } |
465 | 473 |
|
| 474 | + /** |
| 475 | + * Truncation returns a copy of this {@code DateTimeType} with fields smaller than the specified unit set to zero. |
| 476 | + * For example, truncating with the {@link ChronoUnit#MINUTES minutes} unit will set the second-of-minute and |
| 477 | + * nano-of-second field to zero. |
| 478 | + * <p> |
| 479 | + * The unit must have a {@linkplain TemporalUnit#getDuration() duration} that divides into the length of a standard |
| 480 | + * day without remainder. This includes all supplied time units on {@link ChronoUnit} and {@link ChronoUnit#DAYS |
| 481 | + * DAYS}. Other units throw an exception. |
| 482 | + * <p> |
| 483 | + * This operates on the local time-line, {@link LocalDateTime#truncatedTo(TemporalUnit) truncating}, which is then |
| 484 | + * converted back to a {@link DateTimeType} using the zone ID to obtain the offset. |
| 485 | + * <p> |
| 486 | + * When converting back to {@code DateTimeType}, if the local date-time is in an overlap, then the offset will be |
| 487 | + * retained if possible, otherwise the earlier offset will be used. If in a gap, the local date-time will be |
| 488 | + * adjusted forward by the length of the gap. |
| 489 | + * |
| 490 | + * @param unit the unit to truncate to. |
| 491 | + * @return The resulting {@code DateTimeType}. |
| 492 | + * @throws DateTimeException If unable to truncate. |
| 493 | + * @throws UnsupportedTemporalTypeException If the unit is not supported. |
| 494 | + */ |
| 495 | + public DateTimeType truncatedTo(TemporalUnit unit) throws DateTimeException, UnsupportedTemporalTypeException { |
| 496 | + return new DateTimeType(getZonedDateTime().truncatedTo(unit), authoritativeZone); |
| 497 | + } |
| 498 | + |
| 499 | + /** |
| 500 | + * Calculate the amount of time between this and a {@link Temporal} object in terms of a single |
| 501 | + * {@code TemporalUnit}. The start and end points are {@code this} and the specified date-time. The result will be |
| 502 | + * negative if the end is before the start. |
| 503 | + * <p> |
| 504 | + * The {@code Temporal} passed to this method is converted to a {@code ZonedDateTime} using |
| 505 | + * {@link #from(TemporalAccessor)}. If the time-zone differs between the two zoned date-times, the specified end |
| 506 | + * date-time is normalized to have the same zone as this {@code ZonedDateTime}. |
| 507 | + * <p> |
| 508 | + * The calculation returns a whole number, representing the number of complete units between the two date-times. For |
| 509 | + * example, the amount in months between {@code 2012-06-15T00:00Z} and {@code 2012-08-14T23:59Z} will only be one |
| 510 | + * month as it is one minute short of two months. |
| 511 | + * <p> |
| 512 | + * The calculation is implemented in this method for {@link ChronoUnit}. The units {@code NANOS}, {@code MICROS}, |
| 513 | + * {@code MILLIS}, {@code SECONDS}, {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS}, {@code DAYS}, |
| 514 | + * {@code WEEKS}, {@code MONTHS}, {@code YEARS}, {@code DECADES}, {@code CENTURIES}, {@code MILLENNIA} and |
| 515 | + * {@code ERAS} are supported. Other {@code ChronoUnit} values will throw an exception. |
| 516 | + * <p> |
| 517 | + * The calculation for date and time units differ. |
| 518 | + * <p> |
| 519 | + * Date units operate on the local time-line, using the local date-time. For example, the period from noon on day 1 |
| 520 | + * to noon the following day in days will always be counted as exactly one day, irrespective of whether there was a |
| 521 | + * daylight savings change or not. |
| 522 | + * <p> |
| 523 | + * Time units operate on the instant time-line. The calculation effectively converts both zoned date-times to |
| 524 | + * instants and then calculates the period between the instants. For example, the period from noon on day 1 to noon |
| 525 | + * the following day in hours may be 23, 24 or 25 hours (or some other amount) depending on whether there was a |
| 526 | + * daylight savings change or not. |
| 527 | + * <p> |
| 528 | + * If the unit is not a {@code ChronoUnit}, then the result of this method is obtained by invoking |
| 529 | + * {@code TemporalUnit.between(Temporal, Temporal)} passing {@link #getZonedDateTime()} as the first argument and |
| 530 | + * the converted input temporal as the second argument. |
| 531 | + * |
| 532 | + * @param endExclusive the end date-time, exclusive. |
| 533 | + * @param unit the unit to measure the amount in. |
| 534 | + * @return the amount of time between this {@link DateTimeType} and the end date-time. |
| 535 | + * @throws ArithmeticException If numeric overflow occurs. |
| 536 | + * @throws DateTimeException If the amount cannot be calculated, or the end temporal cannot be converted to a |
| 537 | + * {@code ZonedDateTime}, or if the result exceeds the supported range. |
| 538 | + * @throws UnsupportedTemporalTypeException If the unit is not supported. |
| 539 | + */ |
| 540 | + public long until(Temporal endExclusive, TemporalUnit unit) |
| 541 | + throws ArithmeticException, DateTimeException, UnsupportedTemporalTypeException { |
| 542 | + if (unit instanceof ChronoUnit && !unit.isDateBased()) { |
| 543 | + return instant.until(endExclusive, unit); |
| 544 | + } |
| 545 | + return getZonedDateTime().until(endExclusive, unit); |
| 546 | + } |
| 547 | + |
| 548 | + /** |
| 549 | + * Calculate the amount of time between this and another {@link DateTimeType} in terms of a single |
| 550 | + * {@code TemporalUnit}. The start and end points are {@code this} and the specified {@link DateTimeType}. The |
| 551 | + * result will be negative if the end is before the start. |
| 552 | + * <p> |
| 553 | + * The {@link DateTimeType} passed to this method is converted to a {@code ZonedDateTime} using |
| 554 | + * {@link #getZonedDateTime()}. If the time-zone differs between the two zoned {@link DateTimeType}s, the specified |
| 555 | + * end {@code DateTimeType} is normalized to have the same zone as this {@code DateTimeType}. |
| 556 | + * <p> |
| 557 | + * The calculation returns a whole number, representing the number of complete units between the two |
| 558 | + * {@link DateTimeType}s. For example, the amount in months between {@code 2012-06-15T00:00Z} and |
| 559 | + * {@code 2012-08-14T23:59Z} will only be one month as it is one minute short of two months. |
| 560 | + * <p> |
| 561 | + * The calculation is implemented in this method for {@link ChronoUnit}. The units {@code NANOS}, {@code MICROS}, |
| 562 | + * {@code MILLIS}, {@code SECONDS}, {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS}, {@code DAYS}, |
| 563 | + * {@code WEEKS}, {@code MONTHS}, {@code YEARS}, {@code DECADES}, {@code CENTURIES}, {@code MILLENNIA} and |
| 564 | + * {@code ERAS} are supported. Other {@code ChronoUnit} values will throw an exception. |
| 565 | + * <p> |
| 566 | + * The calculation for date and time units differ. |
| 567 | + * <p> |
| 568 | + * Date units operate on the local time-line, using the local date-time. For example, the period from noon on day 1 |
| 569 | + * to noon the following day in days will always be counted as exactly one day, irrespective of whether there was a |
| 570 | + * daylight savings change or not. |
| 571 | + * <p> |
| 572 | + * Time units operate on the instant time-line. The calculation effectively converts both {@link DateTimeType}s to |
| 573 | + * instants and then calculates the period between the instants. For example, the period from noon on day 1 to noon |
| 574 | + * the following day in hours may be 23, 24 or 25 hours (or some other amount) depending on whether there was a |
| 575 | + * daylight savings change or not. |
| 576 | + * <p> |
| 577 | + * If the unit is not a {@code ChronoUnit}, then the result of this method is obtained by invoking |
| 578 | + * {@code TemporalUnit.between(Temporal, Temporal)} passing {@link #getZonedDateTime()} as the first argument and |
| 579 | + * {@code endExclusive.getZonedDateTime()} as the second argument. |
| 580 | + * |
| 581 | + * @param endExclusive the end {@link DateTimeType}, exclusive. |
| 582 | + * @param unit the unit to measure the amount in. |
| 583 | + * @return the amount of time between this and the other {@link DateTimeType}s. |
| 584 | + * @throws ArithmeticException If numeric overflow occurs. |
| 585 | + * @throws DateTimeException If the amount cannot be calculated, or the end temporal cannot be converted to a |
| 586 | + * {@code ZonedDateTime}, or if the result exceeds the supported range. |
| 587 | + * @throws UnsupportedTemporalTypeException If the unit is not supported. |
| 588 | + */ |
| 589 | + public long until(DateTimeType endExclusive, TemporalUnit unit) |
| 590 | + throws ArithmeticException, DateTimeException, UnsupportedTemporalTypeException { |
| 591 | + return until(endExclusive.getZonedDateTime(), unit); |
| 592 | + } |
| 593 | + |
| 594 | + /** |
| 595 | + * Returns a new {@link DateTimeType}, based on this one, with the specified amount added. The amount is typically |
| 596 | + * {@link Period} or {@link Duration} but may be any other type implementing the {@link TemporalAmount} |
| 597 | + * interface. |
| 598 | + * |
| 599 | + * @param amountToAdd the amount to add. |
| 600 | + * @return The resulting {@code DateTimeType}. |
| 601 | + * @throws ArithmeticException If numeric overflow occurs. |
| 602 | + * @throws DateTimeException If the addition cannot be made or if the result exceeds the supported range. |
| 603 | + */ |
| 604 | + public DateTimeType plus(TemporalAmount amountToAdd) throws ArithmeticException, DateTimeException { |
| 605 | + return new DateTimeType(getZonedDateTime().plus(amountToAdd), authoritativeZone); |
| 606 | + } |
| 607 | + |
| 608 | + /** |
| 609 | + * Return a new {@link DateTimeType}, based on this one, with the amount in terms of the unit added. If it is |
| 610 | + * not possible to add the amount, because the unit is not supported or for some other reason, an exception is |
| 611 | + * thrown. |
| 612 | + * <p> |
| 613 | + * The calculation for date and time units differ. Date units operate on the local time-line. The period is first |
| 614 | + * added to the local date-time, then converted back to a zoned date-time using the zone ID. |
| 615 | + * <p> |
| 616 | + * Time units operate on the instant time-line. |
| 617 | + * <p> |
| 618 | + * If the field is not a {@code ChronoUnit}, then the result of this method is obtained by invoking |
| 619 | + * {@code TemporalUnit.addTo(Temporal, long)}. In this case, the unit determines whether and how to perform the |
| 620 | + * addition. |
| 621 | + * |
| 622 | + * @param amountToAdd the amount of the unit to add to the result, may be negative. |
| 623 | + * @param unit the unit of the amount to add. |
| 624 | + * @return The resulting {@code DateTimeType}. |
| 625 | + * @throws ArithmeticException If numeric overflow occurs. |
| 626 | + * @throws DateTimeException If the addition cannot be made or if the result exceeds the supported range. |
| 627 | + * @throws UnsupportedTemporalTypeException If the unit is not supported. |
| 628 | + * @throws ZoneRulesException If no rules are available for the zone ID. |
| 629 | + */ |
| 630 | + public DateTimeType plus(long amountToAdd, TemporalUnit unit) |
| 631 | + throws ArithmeticException, DateTimeException, UnsupportedTemporalTypeException, ZoneRulesException { |
| 632 | + if (unit instanceof ChronoUnit && !unit.isDateBased()) { |
| 633 | + return new DateTimeType(instant.plus(amountToAdd, unit), zoneId, authoritativeZone); |
| 634 | + } |
| 635 | + return new DateTimeType(getZonedDateTime().plus(amountToAdd, unit), authoritativeZone); |
| 636 | + } |
| 637 | + |
| 638 | + /** |
| 639 | + * Return a new {@link DateTimeType}, based on this one, with the specified amount subtracted. The amount is |
| 640 | + * typically {@link Period} or {@link Duration} but may be any other type implementing the {@link TemporalAmount} |
| 641 | + * interface. |
| 642 | + * |
| 643 | + * @param amountToSubtract the amount to subtract. |
| 644 | + * @return The resulting {@code DateTimeType}. |
| 645 | + * @throws ArithmeticException If numeric overflow occurs. |
| 646 | + * @throws DateTimeException If the subtraction cannot be made or if the result exceeds the supported range. |
| 647 | + */ |
| 648 | + public DateTimeType minus(TemporalAmount amountToSubtract) throws ArithmeticException, DateTimeException { |
| 649 | + return new DateTimeType(getZonedDateTime().minus(amountToSubtract), authoritativeZone); |
| 650 | + } |
| 651 | + |
| 652 | + /** |
| 653 | + * Return a new {@link DateTimeType}, based on this one, with the amount in terms of the unit subtracted. If |
| 654 | + * it is not possible to subtract the amount, because the unit is not supported or for some other reason, an |
| 655 | + * exception is thrown. |
| 656 | + * <p> |
| 657 | + * The calculation for date and time units differ. Date units operate on the local time-line. The period is first |
| 658 | + * subtracted from the local date-time, then converted back to a zoned date-time using the zone ID. |
| 659 | + * <p> |
| 660 | + * Time units operate on the instant time-line. |
| 661 | + * <p> |
| 662 | + * This method is equivalent to {@link #plus(long, TemporalUnit)} with the amount negated. |
| 663 | + * |
| 664 | + * @param amountToSubtract the amount of the unit to subtract. |
| 665 | + * @param unit the unit of the amount to subtract. |
| 666 | + * @return The resulting {@code DateTimeType}. |
| 667 | + * @throws ArithmeticException If numeric overflow occurs. |
| 668 | + * @throws DateTimeException If the subtracted cannot be made or if the result exceeds the supported range. |
| 669 | + * @throws UnsupportedTemporalTypeException If the unit is not supported. |
| 670 | + * @throws ZoneRulesException If no rules are available for the zone ID. |
| 671 | + */ |
| 672 | + public DateTimeType minus(long amountToSubtract, TemporalUnit unit) |
| 673 | + throws ArithmeticException, DateTimeException, UnsupportedTemporalTypeException, ZoneRulesException { |
| 674 | + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) |
| 675 | + : plus(-amountToSubtract, unit)); |
| 676 | + } |
| 677 | + |
| 678 | + /** |
| 679 | + * Check whether this {@link DateTimeType} is after the specified {@link DateTimeType}. |
| 680 | + * |
| 681 | + * @param moment the {@link DateTimeType} to compare to. |
| 682 | + * @return {@code true} if this is after the specified moment, {@code false} otherwise. |
| 683 | + * @throws DateTimeException If the result exceeds the supported range. |
| 684 | + */ |
| 685 | + public boolean isAfter(DateTimeType moment) throws DateTimeException { |
| 686 | + return getZonedDateTime().isAfter(moment.getZonedDateTime()); |
| 687 | + } |
| 688 | + |
| 689 | + /** |
| 690 | + * Check whether this {@link DateTimeType} is after the specified {@link ChronoZonedDateTime}. |
| 691 | + * |
| 692 | + * @param moment the zoned date-time to compare to. |
| 693 | + * @return {@code true} if this is after the specified moment, {@code false} otherwise. |
| 694 | + * @throws DateTimeException If the result exceeds the supported range. |
| 695 | + */ |
| 696 | + public boolean isAfter(ChronoZonedDateTime<?> moment) throws DateTimeException { |
| 697 | + return getZonedDateTime().isAfter(moment); |
| 698 | + } |
| 699 | + |
| 700 | + /** |
| 701 | + * Check whether this {@link DateTimeType} is after the specified {@link Instant}. |
| 702 | + * |
| 703 | + * @param instant the moment in time to compare to. |
| 704 | + * @return {@code true} if this is after the specified instant, {@code false} otherwise. |
| 705 | + */ |
| 706 | + public boolean isAfter(Instant instant) { |
| 707 | + return this.instant.isAfter(instant); |
| 708 | + } |
| 709 | + |
| 710 | + /** |
| 711 | + * Check whether this {@link DateTimeType} is before the specified {@link DateTimeType}. |
| 712 | + * |
| 713 | + * @param moment the {@link DateTimeType} to compare to. |
| 714 | + * @return {@code true} if this is before the specified moment, {@code false} otherwise. |
| 715 | + * @throws DateTimeException If the result exceeds the supported range. |
| 716 | + */ |
| 717 | + public boolean isBefore(DateTimeType moment) throws DateTimeException { |
| 718 | + return getZonedDateTime().isBefore(moment.getZonedDateTime()); |
| 719 | + } |
| 720 | + |
| 721 | + /** |
| 722 | + * Check whether this {@link DateTimeType} is before the specified {@link ChronoZonedDateTime}. |
| 723 | + * |
| 724 | + * @param moment the zoned date-time to compare to. |
| 725 | + * @return {@code true} if this is before the specified moment, {@code false} otherwise. |
| 726 | + * @throws DateTimeException If the result exceeds the supported range. |
| 727 | + */ |
| 728 | + public boolean isBefore(ChronoZonedDateTime<?> moment) throws DateTimeException { |
| 729 | + return getZonedDateTime().isBefore(moment); |
| 730 | + } |
| 731 | + |
| 732 | + /** |
| 733 | + * Check whether this {@link DateTimeType} is before the specified {@link Instant}. |
| 734 | + * |
| 735 | + * @param instant the moment in time to compare to. |
| 736 | + * @return {@code true} if this is before the specified instant, {@code false} otherwise. |
| 737 | + */ |
| 738 | + public boolean isBefore(Instant instant) { |
| 739 | + return this.instant.isBefore(instant); |
| 740 | + } |
| 741 | + |
466 | 742 | /** |
467 | 743 | * Return a {@link DateTimeType} with a fixed offset zone. If this instance already has a fixed offset zone it is |
468 | 744 | * returned unchanged, otherwise a new instance with the current offset is returned. |
|
0 commit comments