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