Skip to content

Commit c16799c

Browse files
author
Ravi Nadahar
committed
Enhance DateTimeType with "convenience methods" for easier date/time manipulation
The following methods have been added: - truncatedTo(TemporalUnit unit) - until(Temporal endExclusive, TemporalUnit unit) - until(DateTimeType endExclusive, TemporalUnit unit) - plus(TemporalAmount amountToAdd) - plus(long amountToAdd, TemporalUnit unit) - minus(TemporalAmount amountToSubtract) - minus(long amountToSubtract, TemporalUnit unit) - isAfter(DateTimeType moment) - isAfter(ChronoZonedDateTime<?> moment) - isAfter(Instant instant) - isBefore(DateTimeType moment) - isBefore(ChronoZonedDateTime<?> moment) - isBefore(Instant instant) Corresponding tests have also been created. Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
1 parent 6be634c commit c16799c

File tree

2 files changed

+528
-0
lines changed

2 files changed

+528
-0
lines changed

bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/DateTimeType.java

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@
1313
package org.openhab.core.library.types;
1414

1515
import java.time.DateTimeException;
16+
import java.time.Duration;
1617
import java.time.Instant;
1718
import java.time.LocalDateTime;
1819
import java.time.OffsetDateTime;
20+
import java.time.Period;
1921
import java.time.ZoneId;
2022
import java.time.ZoneOffset;
2123
import java.time.ZonedDateTime;
24+
import java.time.chrono.ChronoZonedDateTime;
2225
import java.time.format.DateTimeFormatter;
2326
import java.time.format.DateTimeParseException;
27+
import java.time.temporal.ChronoUnit;
2428
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;
2533
import java.time.zone.ZoneRulesException;
2634
import java.util.IllegalFormatException;
2735
import java.util.Locale;
@@ -463,6 +471,274 @@ public String format(Locale locale, @Nullable String pattern, ZoneId zoneId)
463471
return String.format(locale, pattern, zonedDateTime);
464472
}
465473

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 ZonedDateTime#from(TemporalAccessor)}. If the time-zone differs between the two zoned date-times, the
506+
* specified end 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+
466742
/**
467743
* Return a {@link DateTimeType} with a fixed offset zone. If this instance already has a fixed offset zone it is
468744
* returned unchanged, otherwise a new instance with the current offset is returned.

0 commit comments

Comments
 (0)