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