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