blob: 427de068644f61f6618732e551cbbb652422dda8 [file] [log] [blame]
/*
******************************************************************************
* 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
*/
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
*/
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");
}
}
}