1 /*
2 ******************************************************************************
3 * Copyright (C) 2007, International Business Machines Corporation and   *
4 * others. All Rights Reserved.                                               *
5 ******************************************************************************
6 */
7 
8 package com.ibm.icu.impl.duration;
9 
10 import com.ibm.icu.impl.duration.impl.DataRecord.ETimeLimit;
11 
12 /**
13  * Represents an approximate duration in multiple TimeUnits.  Each unit,
14  * if set, has a count (which can be fractional and must be non-negative).
15  * In addition Period can either represent the duration as being into the past
16  * or future, and as being more or less than the defined value.
17  * <p>
18  * Use a PeriodFormatter to convert a Period to a String.
19  * <p>
20  * Periods are immutable.  Mutating operations return the new
21  * result leaving the original unchanged.
22  * <p>
23  * Example:<pre>
24  * Period p1 = Period.at(3, WEEK).and(2, DAY).inFuture();
25  * Period p2 = p1.and(12, HOUR);</pre>
26  */
27 public final class Period {
28   final byte timeLimit;
29   final boolean inFuture;
30   final int[] counts;
31 
32   /**
33    * Constructs a Period representing a duration of
34    * count units extending into the past.
35    * @param count the number of units, must be non-negative
36    * @param unit the unit
37    * @return the new Period
38    */
at(float count, TimeUnit unit)39   public static Period at(float count, TimeUnit unit) {
40     checkCount(count);
41     return new Period(ETimeLimit.NOLIMIT, false, count, unit);
42   }
43 
44   /**
45    * Constructs a Period representing a duration more than
46    * count units extending into the past.
47    * @param count the number of units. must be non-negative
48    * @param unit the unit
49    * @return the new Period
50    */
moreThan(float count, TimeUnit unit)51   public static Period moreThan(float count, TimeUnit unit) {
52     checkCount(count);
53     return new Period(ETimeLimit.MT, false, count, unit);
54   }
55 
56   /**
57    * Constructs a Period representing a duration
58    * less than count units extending into the past.
59    * @param count the number of units. must be non-negative
60    * @param unit the unit
61    * @return the new Period
62    */
lessThan(float count, TimeUnit unit)63   public static Period lessThan(float count, TimeUnit unit) {
64     checkCount(count);
65     return new Period(ETimeLimit.LT, false, count, unit);
66   }
67 
68   /**
69    * Set the given unit to have the given count.  Marks the
70    * unit as having been set.  This can be used to set
71    * multiple units, or to reset a unit to have a new count.
72    * This does <b>not</b> add the count to an existing count
73    * for this unit.
74    *
75    * @param count the number of units.  must be non-negative
76    * @param unit the unit
77    * @return the new Period
78    */
and(float count, TimeUnit unit)79   public Period and(float count, TimeUnit unit) {
80     checkCount(count);
81     return setTimeUnitValue(unit, count);
82   }
83 
84   /**
85    * Mark the given unit as not being set.
86    *
87    * @param unit the unit to unset
88    * @return the new Period
89    */
omit(TimeUnit unit)90   public Period omit(TimeUnit unit) {
91     return setTimeUnitInternalValue(unit, 0);
92   }
93 
94   /**
95    * Mark the duration as being at the defined duration.
96    *
97    * @return the new Period
98    */
at()99   public Period at() {
100     return setTimeLimit(ETimeLimit.NOLIMIT);
101   }
102 
103   /**
104    * Mark the duration as being more than the defined duration.
105    *
106    * @return the new Period
107    */
moreThan()108   public Period moreThan() {
109     return setTimeLimit(ETimeLimit.MT);
110   }
111 
112   /**
113    * Mark the duration as being less than the defined duration.
114    *
115    * @return the new Period
116    */
lessThan()117   public Period lessThan() {
118     return setTimeLimit(ETimeLimit.LT);
119   }
120 
121   /**
122    * Mark the time as being in the future.
123    *
124    * @return the new Period
125    */
inFuture()126   public Period inFuture() {
127     return setFuture(true);
128   }
129 
130   /**
131    * Mark the duration as extending into the past.
132    *
133    * @return the new Period
134    */
inPast()135   public Period inPast() {
136     return setFuture(false);
137   }
138 
139   /**
140    * Mark the duration as extending into the future if
141    * future is true, and into the past otherwise.
142    *
143    * @param future true if the time is in the future
144    * @return the new Period
145    */
inFuture(boolean future)146   public Period inFuture(boolean future) {
147     return setFuture(future);
148   }
149 
150   /**
151    * Mark the duration as extending into the past if
152    * past is true, and into the future otherwise.
153    *
154    * @param past true if the time is in the past
155    * @return the new Period
156    */
inPast(boolean past)157   public Period inPast(boolean past) {
158     return setFuture(!past);
159   }
160 
161   /**
162    * Returns true if any unit is set.
163    * @return true if any unit is set
164    */
isSet()165   public boolean isSet() {
166     for (int i = 0; i < counts.length; ++i) {
167       if (counts[i] != 0) {
168         return true;
169       }
170     }
171     return false;
172   }
173 
174   /**
175    * Returns true if the given unit is set.
176    * @param unit the unit to test
177    * @return true if the given unit is set.
178    */
isSet(TimeUnit unit)179   public boolean isSet(TimeUnit unit) {
180     return counts[unit.ordinal] > 0;
181   }
182 
183   /**
184    * Returns the count for the specified unit.  If the
185    * unit is not set, returns 0.
186    * @param unit the unit to test
187    * @return the count
188    */
getCount(TimeUnit unit)189   public float getCount(TimeUnit unit) {
190     int ord = unit.ordinal;
191     if (counts[ord] == 0) {
192       return 0;
193     }
194     return (counts[ord] - 1)/1000f;
195   }
196 
197   /**
198    * Returns true if this represents a
199    * duration into the future.
200    * @return true if this represents a
201    * duration into the future.
202    */
isInFuture()203   public boolean isInFuture() {
204     return inFuture;
205   }
206 
207   /**
208    * Returns true if this represents a
209    * duration into the past
210    * @return true if this represents a
211    * duration into the past
212    */
isInPast()213   public boolean isInPast  () {
214     return !inFuture;
215   }
216 
217   /**
218    * Returns true if this represents a duration in
219    * excess of the defined duration.
220    * @return true if this represents a duration in
221    * excess of the defined duration.
222    */
isMoreThan()223   public boolean isMoreThan() {
224     return timeLimit == ETimeLimit.MT;
225   }
226 
227   /**
228    * Returns true if this represents a duration
229    * less than the defined duration.
230    * @return true if this represents a duration
231    * less than the defined duration.
232    */
isLessThan()233   public boolean isLessThan() {
234     return timeLimit == ETimeLimit.LT;
235   }
236 
237   /**
238    * Returns true if rhs extends Period and
239    * the two Periods are equal.
240    * @param rhs the object to compare to
241    * @return true if rhs is a Period and is equal to this
242    */
equals(Object rhs)243   public boolean equals(Object rhs) {
244     try {
245       return equals((Period)rhs);
246     }
247     catch (ClassCastException e) {
248       return false;
249     }
250   }
251 
252   /**
253    * Returns true if the same units are defined with
254    * the same counts, both extend into the future or both into the
255    * past, and if the limits (at, more than, less than) are the same.
256    * Note that this means that a period of 1000ms and a period of 1sec
257    * will not compare equal.
258    *
259    * @param rhs the period to compare to
260    * @return true if the two periods are equal
261    */
equals(Period rhs)262   public boolean equals(Period rhs) {
263     if (rhs != null &&
264         this.timeLimit == rhs.timeLimit &&
265         this.inFuture == rhs.inFuture) {
266       for (int i = 0; i < counts.length; ++i) {
267         if (counts[i] != rhs.counts[i]) {
268           return false;
269         }
270       }
271       return true;
272     }
273     return false;
274   }
275 
276   /**
277    * Returns the hashCode.
278    * @return the hashCode
279    */
hashCode()280   public int hashCode() {
281     int hc = (timeLimit << 1) | (inFuture ? 1 : 0);
282     for (int i = 0; i < counts.length; ++i) {
283       hc = (hc << 2) ^ counts[i];
284     }
285     return hc;
286   }
287 
288   /**
289    * Private constructor used by static factory methods.
290    */
Period(int limit, boolean future, float count, TimeUnit unit)291   private Period(int limit, boolean future, float count, TimeUnit unit) {
292     this.timeLimit = (byte) limit;
293     this.inFuture = future;
294     this.counts = new int[TimeUnit.units.length];
295     this.counts[unit.ordinal] = (int)(count * 1000) + 1;
296   }
297 
298   /**
299    * Package private constructor used by setters and factory.
300    */
Period(int timeLimit, boolean inFuture, int[] counts)301   Period(int timeLimit, boolean inFuture, int[] counts) {
302     this.timeLimit = (byte) timeLimit;
303     this.inFuture = inFuture;
304     this.counts = counts;
305   }
306 
307   /**
308    * Set the unit's internal value, converting from float to int.
309    */
setTimeUnitValue(TimeUnit unit, float value)310   private Period setTimeUnitValue(TimeUnit unit, float value) {
311     if (value < 0) {
312       throw new IllegalArgumentException("value: " + value);
313     }
314     return setTimeUnitInternalValue(unit, (int)(value * 1000) + 1);
315   }
316 
317   /**
318    * Sets the period to have the provided value, 1/1000 of the
319    * unit plus 1.  Thus unset values are '0', 1' is the set value '0',
320    * 2 is the set value '1/1000', 3 is the set value '2/1000' etc.
321    * @param p the period to change
322    * @param value the int value as described above.
323    * @eturn the new Period object.
324    */
setTimeUnitInternalValue(TimeUnit unit, int value)325   private Period setTimeUnitInternalValue(TimeUnit unit, int value) {
326     int ord = unit.ordinal;
327     if (counts[ord] != value) {
328       int[] newCounts = new int[counts.length];
329       for (int i = 0; i < counts.length; ++i) {
330         newCounts[i] = counts[i];
331       }
332       newCounts[ord] = value;
333       return new Period(timeLimit, inFuture, newCounts);
334     }
335     return this;
336   }
337 
338   /**
339    * Sets whether this defines a future time.
340    * @param future true if the time is in the future
341    * @return  the new Period
342    */
setFuture(boolean future)343   private Period setFuture(boolean future) {
344     if (this.inFuture != future) {
345       return new Period(timeLimit, future, counts);
346     }
347     return this;
348   }
349 
350   /**
351    * Sets whether this is more than, less than, or
352    * 'about' the specified time.
353    * @param limit the kind of limit
354    * @return the new Period
355    */
setTimeLimit(byte limit)356   private Period setTimeLimit(byte limit) {
357     if (this.timeLimit != limit) {
358       return new Period(limit, inFuture, counts);
359 
360     }
361     return this;
362   }
363 
364   /**
365    * Validate count.
366    */
checkCount(float count)367   private static void checkCount(float count) {
368     if (count < 0) {
369       throw new IllegalArgumentException("count (" + count +
370                                          ") cannot be negative");
371     }
372   }
373 }
374