[New] Added PeriodDuration.toMillis() with tests, etc.
This commit is contained in:
parent
704dc2b702
commit
bd0ea5cd27
|
@ -32,6 +32,7 @@
|
|||
package li.strolch.utils.time;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.*;
|
||||
import static li.strolch.utils.time.PeriodHelper.daysIn;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.*;
|
||||
|
@ -40,28 +41,27 @@ import java.time.chrono.IsoChronology;
|
|||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An amount of time in the ISO-8601 calendar system that combines a period and a duration.
|
||||
* <p>
|
||||
* This class models a quantity or amount of time in terms of a {@code Period} and {@code Duration}.
|
||||
* A period is a date-based amount of time, consisting of years, months and days.
|
||||
* A duration is a time-based amount of time, consisting of seconds and nanoseconds.
|
||||
* See the {@link Period} and {@link Duration} classes for more details.
|
||||
* This class models a quantity or amount of time in terms of a {@code Period} and {@code Duration}. A period is a
|
||||
* date-based amount of time, consisting of years, months and days. A duration is a time-based amount of time,
|
||||
* consisting of seconds and nanoseconds. See the {@link Period} and {@link Duration} classes for more details.
|
||||
* <p>
|
||||
* The days in a period take account of daylight saving changes (23 or 25 hour days).
|
||||
* When performing calculations, the period is added first, then the duration.
|
||||
* The days in a period take account of daylight saving changes (23 or 25 hour days). When performing calculations, the
|
||||
* period is added first, then the duration.
|
||||
* <p>
|
||||
* The model is of a directed amount, meaning that the amount may be negative.
|
||||
*
|
||||
* <h3>Implementation Requirements:</h3>
|
||||
* This class is immutable and thread-safe.
|
||||
* <p>
|
||||
* This class must be treated as a value type. Do not synchronize, rely on the
|
||||
* identity hash code or use the distinction between equals() and ==.
|
||||
* This class must be treated as a value type. Do not synchronize, rely on the identity hash code or use the distinction
|
||||
* between equals() and ==.
|
||||
*/
|
||||
public final class PeriodDuration
|
||||
implements TemporalAmount, Serializable {
|
||||
public final class PeriodDuration implements TemporalAmount, Serializable, Comparable<PeriodDuration> {
|
||||
|
||||
/**
|
||||
* A constant for a duration of zero.
|
||||
|
@ -75,8 +75,8 @@ public final class PeriodDuration
|
|||
/**
|
||||
* The supported units.
|
||||
*/
|
||||
private static final List<TemporalUnit> SUPPORTED_UNITS =
|
||||
Collections.unmodifiableList(Arrays.<TemporalUnit>asList(YEARS, MONTHS, DAYS, SECONDS, NANOS));
|
||||
private static final List<TemporalUnit> SUPPORTED_UNITS = Collections
|
||||
.unmodifiableList(Arrays.<TemporalUnit>asList(YEARS, MONTHS, DAYS, SECONDS, NANOS));
|
||||
/**
|
||||
* The number of seconds per day.
|
||||
*/
|
||||
|
@ -92,13 +92,17 @@ public final class PeriodDuration
|
|||
private final Duration duration;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Obtains an instance based on a period and duration.
|
||||
* <p>
|
||||
* The total amount of time of the resulting instance is the period plus the duration.
|
||||
*
|
||||
* @param period the period, not null
|
||||
* @param duration the duration, not null
|
||||
* @param period
|
||||
* the period, not null
|
||||
* @param duration
|
||||
* the duration, not null
|
||||
*
|
||||
* @return the combined period-duration, not null
|
||||
*/
|
||||
public static PeriodDuration of(Period period, Duration duration) {
|
||||
|
@ -112,7 +116,9 @@ public final class PeriodDuration
|
|||
* <p>
|
||||
* The duration will be zero.
|
||||
*
|
||||
* @param period the period, not null
|
||||
* @param period
|
||||
* the period, not null
|
||||
*
|
||||
* @return the combined period-duration, not null
|
||||
*/
|
||||
public static PeriodDuration of(Period period) {
|
||||
|
@ -125,7 +131,9 @@ public final class PeriodDuration
|
|||
* <p>
|
||||
* The period will be zero.
|
||||
*
|
||||
* @param duration the duration, not null
|
||||
* @param duration
|
||||
* the duration, not null
|
||||
*
|
||||
* @return the combined period-duration, not null
|
||||
*/
|
||||
public static PeriodDuration of(Duration duration) {
|
||||
|
@ -134,24 +142,28 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Obtains an instance from a temporal amount.
|
||||
* <p>
|
||||
* This obtains an instance based on the specified amount.
|
||||
* A {@code TemporalAmount} represents an amount of time which this factory
|
||||
* extracts to a {@code PeriodDuration}.
|
||||
* This obtains an instance based on the specified amount. A {@code TemporalAmount} represents an amount of time
|
||||
* which this factory extracts to a {@code PeriodDuration}.
|
||||
* <p>
|
||||
* The result is calculated by looping around each unit in the specified amount.
|
||||
* Any amount that is zero is ignore.
|
||||
* If a unit has an exact duration, it will be totalled using {@link Duration#plus(Duration)}.
|
||||
* If the unit is days or weeks, it will be totalled into the days part of the period.
|
||||
* If the unit is months or quarters, it will be totalled into the months part of the period.
|
||||
* If the unit is years, decades, centuries or millennia, it will be totalled into the years part of the period.
|
||||
* The result is calculated by looping around each unit in the specified amount. Any amount that is zero is ignore.
|
||||
* If a unit has an exact duration, it will be totalled using {@link Duration#plus(Duration)}. If the unit is days
|
||||
* or weeks, it will be totalled into the days part of the period. If the unit is months or quarters, it will be
|
||||
* totalled into the months part of the period. If the unit is years, decades, centuries or millennia, it will be
|
||||
* totalled into the years part of the period.
|
||||
*
|
||||
* @param amount
|
||||
* the temporal amount to convert, not null
|
||||
*
|
||||
* @param amount the temporal amount to convert, not null
|
||||
* @return the equivalent duration, not null
|
||||
* @throws DateTimeException if unable to convert to a {@code Duration}
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*
|
||||
* @throws DateTimeException
|
||||
* if unable to convert to a {@code Duration}
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs
|
||||
*/
|
||||
public static PeriodDuration from(TemporalAmount amount) {
|
||||
if (amount instanceof PeriodDuration) {
|
||||
|
@ -207,29 +219,23 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Obtains an instance from a text string such as {@code PnYnMnDTnHnMnS}.
|
||||
* <p>
|
||||
* This will parse the string produced by {@code toString()} which is
|
||||
* based on the ISO-8601 period formats {@code PnYnMnDTnHnMnS} and {@code PnW}.
|
||||
* This will parse the string produced by {@code toString()} which is based on the ISO-8601 period formats {@code
|
||||
* PnYnMnDTnHnMnS} and {@code PnW}.
|
||||
* <p>
|
||||
* The string starts with an optional sign, denoted by the ASCII negative
|
||||
* or positive symbol. If negative, the whole amount is negated.
|
||||
* The ASCII letter "P" is next in upper or lower case.
|
||||
* There are then a number of sections, each consisting of a number and a suffix.
|
||||
* At least one of the sections must be present.
|
||||
* The sections have suffixes in ASCII of "Y" for years, "M" for months,
|
||||
* "W" for weeks, "D" for days, "H" for hours, "M" for minutes, "S" for seconds,
|
||||
* accepted in upper or lower case. Note that the ASCII letter "T" separates
|
||||
* the date and time parts and must be present if any time part is present.
|
||||
* The suffixes must occur in order.
|
||||
* The number part of each section must consist of ASCII digits.
|
||||
* The number may be prefixed by the ASCII negative or positive symbol.
|
||||
* The number must parse to an {@code int}.
|
||||
* Any week-based input is multiplied by 7 and treated as a number of days.
|
||||
* The string starts with an optional sign, denoted by the ASCII negative or positive symbol. If negative, the whole
|
||||
* amount is negated. The ASCII letter "P" is next in upper or lower case. There are then a number of sections, each
|
||||
* consisting of a number and a suffix. At least one of the sections must be present. The sections have suffixes in
|
||||
* ASCII of "Y" for years, "M" for months, "W" for weeks, "D" for days, "H" for hours, "M" for minutes, "S" for
|
||||
* seconds, accepted in upper or lower case. Note that the ASCII letter "T" separates the date and time parts and
|
||||
* must be present if any time part is present. The suffixes must occur in order. The number part of each section
|
||||
* must consist of ASCII digits. The number may be prefixed by the ASCII negative or positive symbol. The number
|
||||
* must parse to an {@code int}. Any week-based input is multiplied by 7 and treated as a number of days.
|
||||
* <p>
|
||||
* The leading plus/minus sign, and negative values for weeks and days are
|
||||
* not part of the ISO-8601 standard.
|
||||
* The leading plus/minus sign, and negative values for weeks and days are not part of the ISO-8601 standard.
|
||||
* <p>
|
||||
* Note that the date style format {@code PYYYY-MM-DDTHH:MM:SS} is not supported.
|
||||
* <p>
|
||||
|
@ -246,9 +252,13 @@ public final class PeriodDuration
|
|||
* "-P1Y2M" -- PeriodDuration.of(Period.of(-1, -2, 0))
|
||||
* </pre>
|
||||
*
|
||||
* @param text the text to parse, not null
|
||||
* @param text
|
||||
* the text to parse, not null
|
||||
*
|
||||
* @return the parsed period, not null
|
||||
* @throws DateTimeParseException if the text cannot be parsed to a period
|
||||
*
|
||||
* @throws DateTimeParseException
|
||||
* if the text cannot be parsed to a period
|
||||
*/
|
||||
public static PeriodDuration parse(CharSequence text) {
|
||||
Objects.requireNonNull(text, "text");
|
||||
|
@ -276,19 +286,22 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Obtains an instance consisting of the amount of time between two temporals.
|
||||
* <p>
|
||||
* The start is included, but the end is not.
|
||||
* The result of this method can be negative if the end is before the start.
|
||||
* The start is included, but the end is not. The result of this method can be negative if the end is before the
|
||||
* start.
|
||||
* <p>
|
||||
* The calculation examines the temporals and extracts {@link LocalDate} and {@link LocalTime}.
|
||||
* If the time is missing, it will be defaulted to midnight.
|
||||
* If one date is missing, it will be defaulted to the other date.
|
||||
* It then finds the amount of time between the two dates and between the two times.
|
||||
* The calculation examines the temporals and extracts {@link LocalDate} and {@link LocalTime}. If the time is
|
||||
* missing, it will be defaulted to midnight. If one date is missing, it will be defaulted to the other date. It
|
||||
* then finds the amount of time between the two dates and between the two times.
|
||||
*
|
||||
* @param startInclusive
|
||||
* the start, inclusive, not null
|
||||
* @param endExclusive
|
||||
* the end, exclusive, not null
|
||||
*
|
||||
* @param startInclusive the start, inclusive, not null
|
||||
* @param endExclusive the end, exclusive, not null
|
||||
* @return the number of days between this date and the end date, not null
|
||||
*/
|
||||
public static PeriodDuration between(Temporal startInclusive, Temporal endExclusive) {
|
||||
|
@ -307,11 +320,14 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param period the period
|
||||
* @param duration the duration
|
||||
* @param period
|
||||
* the period
|
||||
* @param duration
|
||||
* the duration
|
||||
*/
|
||||
private PeriodDuration(Period period, Duration duration) {
|
||||
this.period = period;
|
||||
|
@ -328,18 +344,21 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets the value of the requested unit.
|
||||
* <p>
|
||||
* This returns a value for the supported units - {@link ChronoUnit#YEARS},
|
||||
* {@link ChronoUnit#MONTHS}, {@link ChronoUnit#DAYS}, {@link ChronoUnit#SECONDS}
|
||||
* and {@link ChronoUnit#NANOS}.
|
||||
* All other units throw an exception.
|
||||
* This returns a value for the supported units - {@link ChronoUnit#YEARS}, {@link ChronoUnit#MONTHS}, {@link
|
||||
* ChronoUnit#DAYS}, {@link ChronoUnit#SECONDS} and {@link ChronoUnit#NANOS}. All other units throw an exception.
|
||||
* Note that hours and minutes throw an exception.
|
||||
*
|
||||
* @param unit the {@code TemporalUnit} for which to return the value
|
||||
* @param unit
|
||||
* the {@code TemporalUnit} for which to return the value
|
||||
*
|
||||
* @return the long value of the unit
|
||||
* @throws UnsupportedTemporalTypeException if the unit is not supported
|
||||
*
|
||||
* @throws UnsupportedTemporalTypeException
|
||||
* if the unit is not supported
|
||||
*/
|
||||
@Override
|
||||
public long get(TemporalUnit unit) {
|
||||
|
@ -365,11 +384,10 @@ public final class PeriodDuration
|
|||
/**
|
||||
* Gets the set of units supported by this amount.
|
||||
* <p>
|
||||
* This returns the list {@link ChronoUnit#YEARS}, {@link ChronoUnit#MONTHS},
|
||||
* {@link ChronoUnit#DAYS}, {@link ChronoUnit#SECONDS} and {@link ChronoUnit#NANOS}.
|
||||
* This returns the list {@link ChronoUnit#YEARS}, {@link ChronoUnit#MONTHS}, {@link ChronoUnit#DAYS}, {@link
|
||||
* ChronoUnit#SECONDS} and {@link ChronoUnit#NANOS}.
|
||||
* <p>
|
||||
* This set can be used in conjunction with {@link #get(TemporalUnit)}
|
||||
* to access the entire state of the amount.
|
||||
* This set can be used in conjunction with {@link #get(TemporalUnit)} to access the entire state of the amount.
|
||||
*
|
||||
* @return a list containing the days unit, not null
|
||||
*/
|
||||
|
@ -379,6 +397,7 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets the period part.
|
||||
*
|
||||
|
@ -393,7 +412,9 @@ public final class PeriodDuration
|
|||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param period the new period
|
||||
* @param period
|
||||
* the new period
|
||||
*
|
||||
* @return the updated period-duration
|
||||
*/
|
||||
public PeriodDuration withPeriod(Period period) {
|
||||
|
@ -414,7 +435,9 @@ public final class PeriodDuration
|
|||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param duration the new duration
|
||||
* @param duration
|
||||
* the new duration
|
||||
*
|
||||
* @return the updated period-duration
|
||||
*/
|
||||
public PeriodDuration withDuration(Duration duration) {
|
||||
|
@ -422,11 +445,11 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks if all parts of this amount are zero.
|
||||
* <p>
|
||||
* This returns true if both {@link Period#isZero()} and {@link Duration#isZero()}
|
||||
* return true.
|
||||
* This returns true if both {@link Period#isZero()} and {@link Duration#isZero()} return true.
|
||||
*
|
||||
* @return true if this period is zero-length
|
||||
*/
|
||||
|
@ -435,18 +458,24 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a copy of this amount with the specified amount added.
|
||||
* <p>
|
||||
* The parameter is converted using {@link PeriodDuration#from(TemporalAmount)}.
|
||||
* The period and duration are combined separately.
|
||||
* The parameter is converted using {@link PeriodDuration#from(TemporalAmount)}. The period and duration are
|
||||
* combined separately.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param amountToAdd the amount to add, not null
|
||||
* @param amountToAdd
|
||||
* the amount to add, not null
|
||||
*
|
||||
* @return a {@code Days} based on this instance with the requested amount added, not null
|
||||
* @throws DateTimeException if the specified amount contains an invalid unit
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*
|
||||
* @throws DateTimeException
|
||||
* if the specified amount contains an invalid unit
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs
|
||||
*/
|
||||
public PeriodDuration plus(TemporalAmount amountToAdd) {
|
||||
PeriodDuration other = PeriodDuration.from(amountToAdd);
|
||||
|
@ -454,18 +483,24 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a copy of this amount with the specified amount subtracted.
|
||||
* <p>
|
||||
* The parameter is converted using {@link PeriodDuration#from(TemporalAmount)}.
|
||||
* The period and duration are combined separately.
|
||||
* The parameter is converted using {@link PeriodDuration#from(TemporalAmount)}. The period and duration are
|
||||
* combined separately.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param amountToAdd the amount to add, not null
|
||||
* @param amountToAdd
|
||||
* the amount to add, not null
|
||||
*
|
||||
* @return a {@code Days} based on this instance with the requested amount subtracted, not null
|
||||
* @throws DateTimeException if the specified amount contains an invalid unit
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*
|
||||
* @throws DateTimeException
|
||||
* if the specified amount contains an invalid unit
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs
|
||||
*/
|
||||
public PeriodDuration minus(TemporalAmount amountToAdd) {
|
||||
PeriodDuration other = PeriodDuration.from(amountToAdd);
|
||||
|
@ -473,14 +508,19 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns an instance with the amount multiplied by the specified scalar.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param scalar the scalar to multiply by, not null
|
||||
* @param scalar
|
||||
* the scalar to multiply by, not null
|
||||
*
|
||||
* @return the amount multiplied by the specified scalar, not null
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs
|
||||
*/
|
||||
public PeriodDuration multipliedBy(int scalar) {
|
||||
if (scalar == 1) {
|
||||
|
@ -495,34 +535,36 @@ public final class PeriodDuration
|
|||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @return the negated amount, not null
|
||||
* @throws ArithmeticException if numeric overflow occurs, which only happens if
|
||||
* the amount is {@code Long.MIN_VALUE}
|
||||
*
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs, which only happens if the amount is {@code Long.MIN_VALUE}
|
||||
*/
|
||||
public PeriodDuration negated() {
|
||||
return multipliedBy(-1);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a copy of this instance with the years and months exactly normalized.
|
||||
* <p>
|
||||
* This normalizes the years and months units, leaving the days unit unchanged.
|
||||
* The result is exact, always representing the same amount of time.
|
||||
* This normalizes the years and months units, leaving the days unit unchanged. The result is exact, always
|
||||
* representing the same amount of time.
|
||||
* <p>
|
||||
* The months unit is adjusted to have an absolute value less than 11,
|
||||
* with the years unit being adjusted to compensate. For example, a period of
|
||||
* "1 year and 15 months" will be normalized to "2 years and 3 months".
|
||||
* The months unit is adjusted to have an absolute value less than 11, with the years unit being adjusted to
|
||||
* compensate. For example, a period of "1 year and 15 months" will be normalized to "2 years and 3 months".
|
||||
* <p>
|
||||
* The sign of the years and months units will be the same after normalization.
|
||||
* For example, a period of "1 year and -25 months" will be normalized to
|
||||
* "-1 year and -1 month".
|
||||
* The sign of the years and months units will be the same after normalization. For example, a period of "1 year and
|
||||
* -25 months" will be normalized to "-1 year and -1 month".
|
||||
* <p>
|
||||
* Note that no normalization is performed on the days or duration.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @return a {@code PeriodDuration} based on this one with excess months normalized to years, not null
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs
|
||||
*/
|
||||
public PeriodDuration normalizedYears() {
|
||||
return withPeriod(period.normalized());
|
||||
|
@ -531,23 +573,23 @@ public final class PeriodDuration
|
|||
/**
|
||||
* Returns a copy of this instance with the days and duration normalized using the standard day of 24 hours.
|
||||
* <p>
|
||||
* This normalizes the days and duration, leaving the years and months unchanged.
|
||||
* The result uses a standard day length of 24 hours.
|
||||
* This normalizes the days and duration, leaving the years and months unchanged. The result uses a standard day
|
||||
* length of 24 hours.
|
||||
* <p>
|
||||
* This combines the duration seconds with the number of days and shares the total
|
||||
* seconds between the two fields. For example, a period of
|
||||
* "2 days and 86401 seconds" will be normalized to "3 days and 1 second".
|
||||
* This combines the duration seconds with the number of days and shares the total seconds between the two fields.
|
||||
* For example, a period of "2 days and 86401 seconds" will be normalized to "3 days and 1 second".
|
||||
* <p>
|
||||
* The sign of the days and duration will be the same after normalization.
|
||||
* For example, a period of "1 day and -172801 seconds" will be normalized to
|
||||
* "-1 day and -1 second".
|
||||
* The sign of the days and duration will be the same after normalization. For example, a period of "1 day and
|
||||
* -172801 seconds" will be normalized to "-1 day and -1 second".
|
||||
* <p>
|
||||
* Note that no normalization is performed on the years or months.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @return a {@code PeriodDuration} based on this one with excess duration normalized to days, not null
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs
|
||||
*/
|
||||
public PeriodDuration normalizedStandardDays() {
|
||||
long totalSecs = period.getDays() * SECONDS_PER_DAY + duration.getSeconds();
|
||||
|
@ -560,19 +602,26 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Adds this amount to the specified temporal object.
|
||||
* <p>
|
||||
* This returns a temporal object of the same observable type as the input
|
||||
* with this amount added. This simply adds the period and duration to the temporal.
|
||||
* This returns a temporal object of the same observable type as the input with this amount added. This simply adds
|
||||
* the period and duration to the temporal.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param temporal the temporal object to adjust, not null
|
||||
* @param temporal
|
||||
* the temporal object to adjust, not null
|
||||
*
|
||||
* @return an object of the same type with the adjustment made, not null
|
||||
* @throws DateTimeException if unable to add
|
||||
* @throws UnsupportedTemporalTypeException if the DAYS unit is not supported
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*
|
||||
* @throws DateTimeException
|
||||
* if unable to add
|
||||
* @throws UnsupportedTemporalTypeException
|
||||
* if the DAYS unit is not supported
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs
|
||||
*/
|
||||
@Override
|
||||
public Temporal addTo(Temporal temporal) {
|
||||
|
@ -582,16 +631,22 @@ public final class PeriodDuration
|
|||
/**
|
||||
* Subtracts this amount from the specified temporal object.
|
||||
* <p>
|
||||
* This returns a temporal object of the same observable type as the input
|
||||
* with this amount subtracted. This simply subtracts the period and duration from the temporal.
|
||||
* This returns a temporal object of the same observable type as the input with this amount subtracted. This simply
|
||||
* subtracts the period and duration from the temporal.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param temporal the temporal object to adjust, not null
|
||||
* @param temporal
|
||||
* the temporal object to adjust, not null
|
||||
*
|
||||
* @return an object of the same type with the adjustment made, not null
|
||||
* @throws DateTimeException if unable to subtract
|
||||
* @throws UnsupportedTemporalTypeException if the DAYS unit is not supported
|
||||
* @throws ArithmeticException if numeric overflow occurs
|
||||
*
|
||||
* @throws DateTimeException
|
||||
* if unable to subtract
|
||||
* @throws UnsupportedTemporalTypeException
|
||||
* if the DAYS unit is not supported
|
||||
* @throws ArithmeticException
|
||||
* if numeric overflow occurs
|
||||
*/
|
||||
@Override
|
||||
public Temporal subtractFrom(Temporal temporal) {
|
||||
|
@ -599,12 +654,15 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks if this amount is equal to the specified {@code PeriodDuration}.
|
||||
* <p>
|
||||
* The comparison is based on the underlying period and duration.
|
||||
*
|
||||
* @param otherAmount the other amount, null returns false
|
||||
* @param otherAmount
|
||||
* the other amount, null returns false
|
||||
*
|
||||
* @return true if the other amount is equal to this one
|
||||
*/
|
||||
@Override
|
||||
|
@ -630,10 +688,10 @@ public final class PeriodDuration
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a string representation of the amount.
|
||||
* This will be in the format 'PnYnMnDTnHnMnS', with sections omitted as necessary.
|
||||
* An empty amount will return "PT0S".
|
||||
* Returns a string representation of the amount. This will be in the format 'PnYnMnDTnHnMnS', with sections omitted
|
||||
* as necessary. An empty amount will return "PT0S".
|
||||
*
|
||||
* @return the period in ISO-8601 string format
|
||||
*/
|
||||
|
@ -648,4 +706,18 @@ public final class PeriodDuration
|
|||
return period.toString() + duration.toString().substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PeriodDuration o) {
|
||||
return Double.compare(daysIn(this), daysIn(o));
|
||||
}
|
||||
|
||||
public int periodPartToDays() {
|
||||
return (getPeriod().getYears() * 365) + (getPeriod().getMonths() * 30) + getPeriod().getDays();
|
||||
}
|
||||
|
||||
public long toMillis() {
|
||||
if (this.period.isZero())
|
||||
return this.duration.toMillis();
|
||||
return TimeUnit.DAYS.toMillis(periodPartToDays()) + this.duration.toMillis();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package li.strolch.utils.time;
|
||||
|
||||
import static java.time.Period.between;
|
||||
|
||||
import java.time.Period;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import li.strolch.utils.dbc.DBC;
|
||||
|
||||
public class PeriodHelper {
|
||||
|
||||
public static double daysIn(PeriodDuration periodDuration) {
|
||||
return (daysIn(periodDuration.getPeriod()) + (periodDuration.getDuration().toHours() / 24.0));
|
||||
}
|
||||
|
||||
public static double daysIn(Period period) {
|
||||
return (period.getYears() * 365.0) + (period.getMonths() * 30.0) + period.getDays();
|
||||
}
|
||||
|
||||
/**
|
||||
* This special function allows us to shift a date by a multiple of the given {@link PeriodDuration} so that is
|
||||
* before the given to date. It does multiple tries to get as close as possible, due to the inexactness of 30 days
|
||||
* being one month, and 365 days being one year.
|
||||
*
|
||||
* @param date
|
||||
* the date to shift
|
||||
* @param to
|
||||
* the date before which to stop shifting
|
||||
* @param periodDuration
|
||||
* the period shift in multiples by
|
||||
*
|
||||
* @return the shifted date
|
||||
*/
|
||||
public static ZonedDateTime shiftByMultipleOfPeriod(ZonedDateTime date, ZonedDateTime to,
|
||||
PeriodDuration periodDuration) {
|
||||
DBC.PRE.assertTrue("date must be before to!", date.isBefore(to));
|
||||
DBC.PRE.assertFalse("period duration may not be null!", periodDuration.isZero());
|
||||
Period between = between(date.toLocalDate(), to.toLocalDate());
|
||||
double daysInBetween = daysIn(between);
|
||||
double daysInPeriod = daysIn(periodDuration);
|
||||
long shifts = (long) (daysInBetween / daysInPeriod);
|
||||
if (shifts < 0)
|
||||
return date;
|
||||
|
||||
ZonedDateTime shiftedDate = date.plusDays((long) (shifts * daysInPeriod));
|
||||
|
||||
// see if we are close enough now
|
||||
between = between(shiftedDate.toLocalDate(), to.toLocalDate());
|
||||
daysInBetween = daysIn(between);
|
||||
shifts = (long) (daysInBetween / daysInPeriod);
|
||||
if (shifts < 2)
|
||||
return shiftedDate;
|
||||
|
||||
return shiftByMultipleOfPeriod(shiftedDate, to, periodDuration);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package li.strolch.utils.time;
|
||||
|
||||
import static java.time.ZoneId.systemDefault;
|
||||
import static li.strolch.utils.time.PeriodHelper.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PeriodHelperTest {
|
||||
|
||||
@Test
|
||||
public void shouldCalcHalfADay() {
|
||||
assertEquals(0.5, daysIn(PeriodDuration.parse("PT12H")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc1Day1() {
|
||||
assertEquals(1.0, daysIn(PeriodDuration.parse("P1D")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc1Day2() {
|
||||
assertEquals(1.0, daysIn(PeriodDuration.parse("PT24H")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc1AndAHalfDays() {
|
||||
assertEquals(1.5, daysIn(PeriodDuration.parse("PT36H")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc2Days1() {
|
||||
assertEquals(2.0, daysIn(PeriodDuration.parse("P2D")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc2Days2() {
|
||||
assertEquals(2.0, daysIn(PeriodDuration.parse("PT48H")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc2Days3() {
|
||||
assertEquals(2.0, daysIn(PeriodDuration.parse("P1DT24H")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc2AndAHalfDays0() {
|
||||
assertEquals(2.5, daysIn(PeriodDuration.parse("P1DT36H")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc3Days() {
|
||||
assertEquals(3.0, daysIn(PeriodDuration.parse("PT71H60M")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalc2Days23H() {
|
||||
assertEquals(2.958333333, daysIn(PeriodDuration.parse("PT71H")), 0.00001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalcDays() {
|
||||
assertEquals(30, daysIn(PeriodDuration.parse("P1M")), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalcShiftDays1() {
|
||||
ZonedDateTime past = ZonedDateTime.now().minusDays(35);
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
PeriodDuration periodDuration = PeriodDuration.parse("P1M");
|
||||
ZonedDateTime shiftedDate = shiftByMultipleOfPeriod(past, now, periodDuration);
|
||||
|
||||
assertTrue(shiftedDate.isAfter(now.minusDays(29)));
|
||||
assertTrue(shiftedDate.isBefore(now));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalcShiftDays2() {
|
||||
ZonedDateTime past = ZonedDateTime
|
||||
.parse("2007-12-03T10:15:30+01:00", DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(systemDefault()));
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
PeriodDuration periodDuration = PeriodDuration.parse("P1M");
|
||||
ZonedDateTime shiftedDate = shiftByMultipleOfPeriod(past, now, periodDuration);
|
||||
|
||||
// since P1M ist = 28 days, we have a rather inexact match, but it must certainly be before now() - P1M
|
||||
assertTrue(shiftedDate.isAfter(now.minusDays(56)));
|
||||
assertTrue(shiftedDate.isBefore(now.minusDays(28)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalcShiftDays3() {
|
||||
ZonedDateTime past = ZonedDateTime.now().minusDays(20);
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
PeriodDuration periodDuration = PeriodDuration.parse("P7D");
|
||||
ZonedDateTime shiftedDate = shiftByMultipleOfPeriod(past, now, periodDuration);
|
||||
|
||||
assertTrue(shiftedDate.isAfter(now.minusDays(9)));
|
||||
assertTrue(shiftedDate.isBefore(now));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCalcShiftDays4() {
|
||||
ZonedDateTime past = ZonedDateTime
|
||||
.parse("2007-12-03T10:15:30+01:00", DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(systemDefault()));
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
PeriodDuration periodDuration = PeriodDuration.parse("P7D");
|
||||
ZonedDateTime shiftedDate = shiftByMultipleOfPeriod(past, now, periodDuration);
|
||||
|
||||
// since we are many years and months before now, and a year is 356 days and a month is 28 days, we inexact, but at least we must be a more than P7D before now
|
||||
assertTrue(shiftedDate.isBefore(now));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue