Period.java
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
******************************************************************************
* Copyright (C) 2007, International Business Machines Corporation and *
* others. All Rights Reserved. *
******************************************************************************
*/
package com.ibm.icu.impl.duration;
import com.ibm.icu.impl.duration.impl.DataRecord.ETimeLimit;
/**
* Represents an approximate duration in multiple TimeUnits. Each unit, if set, has a count (which
* can be fractional and must be non-negative). In addition Period can either represent the duration
* as being into the past or future, and as being more or less than the defined value.
*
* <p>Use a PeriodFormatter to convert a Period to a String.
*
* <p>Periods are immutable. Mutating operations return the new result leaving the original
* unchanged.
*
* <p>Example:
*
* <pre>
* Period p1 = Period.at(3, WEEK).and(2, DAY).inFuture();
* Period p2 = p1.and(12, HOUR);</pre>
*/
public final class Period {
final byte timeLimit;
final boolean inFuture;
final int[] counts;
/**
* Constructs a Period representing a duration of count units extending into the past.
*
* @param count the number of units, must be non-negative
* @param unit the unit
* @return the new Period
*/
public static Period at(float count, TimeUnit unit) {
checkCount(count);
return new Period(ETimeLimit.NOLIMIT, false, count, unit);
}
/**
* Constructs a Period representing a duration more than count units extending into the past.
*
* @param count the number of units. must be non-negative
* @param unit the unit
* @return the new Period
*/
public static Period moreThan(float count, TimeUnit unit) {
checkCount(count);
return new Period(ETimeLimit.MT, false, count, unit);
}
/**
* Constructs a Period representing a duration less than count units extending into the past.
*
* @param count the number of units. must be non-negative
* @param unit the unit
* @return the new Period
*/
public static Period lessThan(float count, TimeUnit unit) {
checkCount(count);
return new Period(ETimeLimit.LT, false, count, unit);
}
/**
* Set the given unit to have the given count. Marks the unit as having been set. This can be
* used to set multiple units, or to reset a unit to have a new count. This does <b>not</b> add
* the count to an existing count for this unit.
*
* @param count the number of units. must be non-negative
* @param unit the unit
* @return the new Period
*/
public Period and(float count, TimeUnit unit) {
checkCount(count);
return setTimeUnitValue(unit, count);
}
/**
* Mark the given unit as not being set.
*
* @param unit the unit to unset
* @return the new Period
*/
public Period omit(TimeUnit unit) {
return setTimeUnitInternalValue(unit, 0);
}
/**
* Mark the duration as being at the defined duration.
*
* @return the new Period
*/
public Period at() {
return setTimeLimit(ETimeLimit.NOLIMIT);
}
/**
* Mark the duration as being more than the defined duration.
*
* @return the new Period
*/
public Period moreThan() {
return setTimeLimit(ETimeLimit.MT);
}
/**
* Mark the duration as being less than the defined duration.
*
* @return the new Period
*/
public Period lessThan() {
return setTimeLimit(ETimeLimit.LT);
}
/**
* Mark the time as being in the future.
*
* @return the new Period
*/
public Period inFuture() {
return setFuture(true);
}
/**
* Mark the duration as extending into the past.
*
* @return the new Period
*/
public Period inPast() {
return setFuture(false);
}
/**
* Mark the duration as extending into the future if future is true, and into the past
* otherwise.
*
* @param future true if the time is in the future
* @return the new Period
*/
public Period inFuture(boolean future) {
return setFuture(future);
}
/**
* Mark the duration as extending into the past if past is true, and into the future otherwise.
*
* @param past true if the time is in the past
* @return the new Period
*/
public Period inPast(boolean past) {
return setFuture(!past);
}
/**
* Returns true if any unit is set.
*
* @return true if any unit is set
*/
public boolean isSet() {
for (int i = 0; i < counts.length; ++i) {
if (counts[i] != 0) {
return true;
}
}
return false;
}
/**
* Returns true if the given unit is set.
*
* @param unit the unit to test
* @return true if the given unit is set.
*/
public boolean isSet(TimeUnit unit) {
return counts[unit.ordinal] > 0;
}
/**
* Returns the count for the specified unit. If the unit is not set, returns 0.
*
* @param unit the unit to test
* @return the count
*/
public float getCount(TimeUnit unit) {
int ord = unit.ordinal;
if (counts[ord] == 0) {
return 0;
}
return (counts[ord] - 1) / 1000f;
}
/**
* Returns true if this represents a duration into the future.
*
* @return true if this represents a duration into the future.
*/
public boolean isInFuture() {
return inFuture;
}
/**
* Returns true if this represents a duration into the past
*
* @return true if this represents a duration into the past
*/
public boolean isInPast() {
return !inFuture;
}
/**
* Returns true if this represents a duration in excess of the defined duration.
*
* @return true if this represents a duration in excess of the defined duration.
*/
public boolean isMoreThan() {
return timeLimit == ETimeLimit.MT;
}
/**
* Returns true if this represents a duration less than the defined duration.
*
* @return true if this represents a duration less than the defined duration.
*/
public boolean isLessThan() {
return timeLimit == ETimeLimit.LT;
}
/**
* Returns true if rhs extends Period and the two Periods are equal.
*
* @param rhs the object to compare to
* @return true if rhs is a Period and is equal to this
*/
@Override
public boolean equals(Object rhs) {
try {
return equals((Period) rhs);
} catch (ClassCastException e) {
return false;
}
}
/**
* Returns true if the same units are defined with the same counts, both extend into the future
* or both into the past, and if the limits (at, more than, less than) are the same. Note that
* this means that a period of 1000ms and a period of 1sec will not compare equal.
*
* @param rhs the period to compare to
* @return true if the two periods are equal
*/
public boolean equals(Period rhs) {
if (rhs != null && this.timeLimit == rhs.timeLimit && this.inFuture == rhs.inFuture) {
for (int i = 0; i < counts.length; ++i) {
if (counts[i] != rhs.counts[i]) {
return false;
}
}
return true;
}
return false;
}
/**
* Returns the hashCode.
*
* @return the hashCode
*/
@Override
public int hashCode() {
int hc = (timeLimit << 1) | (inFuture ? 1 : 0);
for (int i = 0; i < counts.length; ++i) {
hc = (hc << 2) ^ counts[i];
}
return hc;
}
/** Private constructor used by static factory methods. */
private Period(int limit, boolean future, float count, TimeUnit unit) {
this.timeLimit = (byte) limit;
this.inFuture = future;
this.counts = new int[TimeUnit.units.length];
this.counts[unit.ordinal] = (int) (count * 1000) + 1;
}
/** Package private constructor used by setters and factory. */
Period(int timeLimit, boolean inFuture, int[] counts) {
this.timeLimit = (byte) timeLimit;
this.inFuture = inFuture;
this.counts = counts;
}
/** Set the unit's internal value, converting from float to int. */
private Period setTimeUnitValue(TimeUnit unit, float value) {
if (value < 0) {
throw new IllegalArgumentException("value: " + value);
}
return setTimeUnitInternalValue(unit, (int) (value * 1000) + 1);
}
/**
* Sets the period to have the provided value, 1/1000 of the unit plus 1. Thus unset values are
* '0', 1' is the set value '0', 2 is the set value '1/1000', 3 is the set value '2/1000' etc.
*
* @param p the period to change
* @param value the int value as described above.
* @eturn the new Period object.
*/
private Period setTimeUnitInternalValue(TimeUnit unit, int value) {
int ord = unit.ordinal;
if (counts[ord] != value) {
int[] newCounts = new int[counts.length];
for (int i = 0; i < counts.length; ++i) {
newCounts[i] = counts[i];
}
newCounts[ord] = value;
return new Period(timeLimit, inFuture, newCounts);
}
return this;
}
/**
* Sets whether this defines a future time.
*
* @param future true if the time is in the future
* @return the new Period
*/
private Period setFuture(boolean future) {
if (this.inFuture != future) {
return new Period(timeLimit, future, counts);
}
return this;
}
/**
* Sets whether this is more than, less than, or 'about' the specified time.
*
* @param limit the kind of limit
* @return the new Period
*/
private Period setTimeLimit(byte limit) {
if (this.timeLimit != limit) {
return new Period(limit, inFuture, counts);
}
return this;
}
/** Validate count. */
private static void checkCount(float count) {
if (count < 0) {
throw new IllegalArgumentException("count (" + count + ") cannot be negative");
}
}
}