1 /* Copyright 2004 The Apache Software Foundation 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package org.apache.xmlbeans; 17 18 import java.math.BigDecimal; 19 20 /** 21 * Represents an XML Schema-compatible duration. 22 * <p> 23 * A duration is made up of a number of years, months, days, hours, 24 * minutes, seconds, and fractions of seconds. See the 25 * XML Schema specification 26 * <a target="_blank" href="http://www.w3.org/TR/xmlschema-2/#duration">section on xs:duration</a> 27 * for details on the rules for 28 * <a target="_blank" href="http://www.w3.org/TR/xmlschema-2/#duration-order">comparing durations</a> and 29 * <a target="_blank" href="http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes">adding durations to dates</a>. 30 */ 31 public final class GDuration implements GDurationSpecification, java.io.Serializable 32 { 33 private static final long serialVersionUID = 1L; 34 35 private int _sign; 36 private int _CY; 37 private int _M; 38 private int _D; 39 private int _h; 40 private int _m; 41 private int _s; 42 private BigDecimal _fs; 43 44 /** 45 * Constructs an empty GDuration representing zero seconds. 46 */ 47 public GDuration() 48 { 49 _sign = +1; 50 _fs = GDate._zero; 51 } 52 53 private static final int SEEN_NOTHING = 0; 54 private static final int SEEN_YEAR = 1; 55 private static final int SEEN_MONTH = 2; 56 private static final int SEEN_DAY = 3; 57 private static final int SEEN_HOUR = 4; 58 private static final int SEEN_MINUTE = 5; 59 private static final int SEEN_SECOND = 6; 60 61 /** 62 * Constructs a GDuration from a lexical 63 * representation. The lexical space contains the 64 * union of the lexical spaces of all the schema 65 * date/time types (except for duration). 66 */ 67 public GDuration(CharSequence str) 68 { 69 // Form: -PnYnMnDTnHnMnS 70 // (where each n may be preceded by a - for us, and the whole may be -) 71 72 // first trim XML whitespace 73 int len = str.length(); 74 int start = 0; 75 while (len > 0 && GDate.isSpace(str.charAt(len - 1))) 76 len -= 1; 77 while (start < len && GDate.isSpace(str.charAt(start))) 78 start += 1; 79 80 _sign = 1; 81 boolean tmark = false; 82 83 if (start < len && str.charAt(start) == '-') 84 { 85 _sign = -1; 86 start += 1; 87 } 88 89 if (start >= len || str.charAt(start) != 'P') 90 throw new IllegalArgumentException("duration must begin with P"); 91 92 start += 1; 93 94 int seen = SEEN_NOTHING; 95 _fs = GDate._zero; 96 97 for (;start < len; start += 1) 98 { 99 boolean negval = false; 100 char ch = str.charAt(start); 101 if (ch == 'T') 102 { 103 if (tmark) 104 throw new IllegalArgumentException("duration must have no more than one T'"); 105 if (seen > SEEN_DAY) 106 throw new IllegalArgumentException("T in duration must precede time fields"); 107 seen = SEEN_DAY; 108 tmark = true; 109 start += 1; 110 if (start >= len) 111 throw new IllegalArgumentException("illegal duration"); 112 ch = str.charAt(start); 113 } 114 if (ch == '-') 115 { 116 negval = true; 117 if (start == len) 118 throw new IllegalArgumentException("illegal duration"); 119 start += 1; 120 ch = str.charAt(start); 121 } 122 if (!GDate.isDigit(ch)) 123 throw new IllegalArgumentException("illegal duration"); 124 int value = GDate.digitVal(ch); 125 for (;;) 126 { 127 start += 1; 128 ch = (start < len) ? str.charAt(start) : '\0'; 129 if (!GDate.isDigit(ch)) 130 break; 131 value = value * 10 + GDate.digitVal(ch); 132 } 133 if (ch == '.') 134 { 135 int i = start; 136 do i += 1; 137 while (i < len && GDate.isDigit(ch = str.charAt(i))); 138 _fs = new BigDecimal(str.subSequence(start, i).toString()); 139 if (i >= len || ch != 'S') 140 throw new IllegalArgumentException("illegal duration"); 141 start = i; 142 } 143 if (negval) 144 value = -value; 145 switch (seen) 146 { 147 case SEEN_NOTHING: 148 if (ch == 'Y') 149 { 150 seen = SEEN_YEAR; 151 _CY = value; 152 break; 153 } // fallthrough 154 case SEEN_YEAR: 155 if (ch == 'M') 156 { 157 seen = SEEN_MONTH; 158 _M = value; 159 break; 160 } // fallthrough 161 case SEEN_MONTH: 162 if (ch == 'D') 163 { 164 seen = SEEN_DAY; 165 _D = value; 166 break; 167 } // fallthrough 168 case SEEN_DAY: 169 if (ch == 'H') 170 { 171 if (!tmark) 172 throw new IllegalArgumentException("time in duration must follow T"); 173 seen = SEEN_HOUR; 174 _h = value; 175 break; 176 } // fallthrough 177 case SEEN_HOUR: 178 if (ch == 'M') 179 { 180 if (!tmark) 181 throw new IllegalArgumentException("time in duration must follow T"); 182 seen = SEEN_MINUTE; 183 _m = value; 184 break; 185 } // fallthrough 186 case SEEN_MINUTE: 187 if (ch == 'S') 188 { 189 if (!tmark) 190 throw new IllegalArgumentException("time in duration must follow T"); 191 seen = SEEN_SECOND; 192 _s = value; 193 break; 194 } // fallthrough 195 default: 196 throw new IllegalArgumentException("duration must specify Y M D T H M S in order"); 197 } 198 } 199 200 if ( seen == SEEN_NOTHING ) 201 throw new IllegalArgumentException("duration must contain at least one number and its designator: " + str); 202 } 203 204 /** 205 * Constructs a GDuration with the specified sign, 206 * year, month, day, hours, minutes, seconds, and optional 207 * fractional seconds. 208 * @param sign +1 for a positive duration, -1 for a negative duration 209 * @throws java.lang.IllegalArgumentException if the sign is not 1 or -1 210 */ 211 public GDuration( 212 int sign, 213 int year, 214 int month, 215 int day, 216 int hour, 217 int minute, 218 int second, 219 BigDecimal fraction) 220 { 221 if (sign != 1 && sign != -1) 222 throw new IllegalArgumentException(); 223 _sign = sign; 224 _CY = year; 225 _M = month; 226 _D = day; 227 _h = hour; 228 _m = minute; 229 _s = second; 230 _fs = fraction == null ? GDate._zero : fraction; 231 } 232 233 /** 234 * Constructs a GDuration from another GDurationSpecification. 235 */ 236 public GDuration(GDurationSpecification gDuration) 237 { 238 _sign = gDuration.getSign(); 239 _CY = gDuration.getYear(); 240 _M = gDuration.getMonth(); 241 _D = gDuration.getDay(); 242 _h = gDuration.getHour(); 243 _m = gDuration.getMinute(); 244 _s = gDuration.getSecond(); 245 _fs = gDuration.getFraction(); 246 } 247 248 /** 249 * Builds another GDate with the same value 250 * as this one. 251 */ 252 public Object clone() 253 { 254 return new GDuration(this); 255 } 256 257 /** 258 * All GDuration instances return true. 259 */ 260 public final boolean isImmutable() 261 { 262 return true; 263 } 264 265 /** 266 * Returns the sign of the duration: +1 is forwards 267 * and -1 is backwards in time. 268 */ 269 public final int getSign() 270 { return _sign; } 271 272 /** 273 * Gets the year component. 274 */ 275 public final int getYear() 276 { return _CY; } 277 278 /** 279 * Gets the month-of-year component. 280 */ 281 public final int getMonth() 282 { return _M; } 283 284 /** 285 * Gets the day-of-month component. 286 */ 287 public final int getDay() 288 { return _D; } 289 290 /** 291 * Gets the hour-of-day component. 292 */ 293 public final int getHour() 294 { return _h; } 295 296 /** 297 * Gets the minute-of-hour component. 298 */ 299 public final int getMinute() 300 { return _m; } 301 302 /** 303 * Gets the second-of-minute component. 304 */ 305 public final int getSecond() 306 { return _s; } 307 308 309 /** 310 * Gets the fraction-of-second. Range from 0 (inclusive) to 1 (exclusive). 311 */ 312 public BigDecimal getFraction() 313 { return _fs; } 314 315 /** 316 * Returns true if all of the individual components 317 * of the duration are nonnegative. 318 */ 319 public boolean isValid() 320 { 321 return GDurationBuilder.isValidDuration(this); 322 } 323 324 /** 325 * Comparison to another GDuration. 326 * <ul> 327 * <li>Returns -1 if this < duration. (less-than) 328 * <li>Returns 0 if this == duration. (equal) 329 * <li>Returns 1 if this > duration. (greater-than) 330 * <li>Returns 2 if this <> duration. (incomparable) 331 * </ul> 332 * Two instances are incomparable if they have different amounts 333 * of information. 334 */ 335 public final int compareToGDuration(GDurationSpecification duration) 336 { 337 return GDurationBuilder.compareDurations(this, duration); 338 } 339 340 /** 341 * The natural string representation of the duration. 342 * <p> 343 * Any components that are zero are omitted. Note that if the duration 344 * is invalid, i.e., it has negative components, those negative 345 * components are serialized out here. To check for validity, use 346 * the isValid() method; and to normalize most durations to a valid 347 * form use the normalize() method. 348 */ 349 public String toString() 350 { 351 return GDurationBuilder.formatDuration(this); 352 } 353 354 /** 355 * Returns a new GDuration which is the sum of this one and the 356 * supplied duration. Does a fieldwise addition, with no normalization. 357 */ 358 public GDuration add(GDurationSpecification duration) 359 { 360 int sign = _sign * duration.getSign(); 361 return _add(duration, sign); 362 } 363 364 /** 365 * Returns a new GDuration which is the result of subtracting 366 * the supplied duration from this one. Does a fieldwise 367 * subtraction, with no normalization. 368 */ 369 public GDuration subtract(GDurationSpecification duration) 370 { 371 int sign = -_sign * duration.getSign(); 372 return _add(duration, sign); 373 } 374 375 private GDuration _add(GDurationSpecification duration, int sign) 376 { 377 GDuration result = new GDuration(this); 378 result._CY += sign * duration.getYear(); 379 result._M += sign * duration.getMonth(); 380 result._D += sign * duration.getDay(); 381 result._h += sign * duration.getHour(); 382 result._m += sign * duration.getMinute(); 383 result._s += sign * duration.getSecond(); 384 385 if (duration.getFraction().signum() == 0) 386 return result; 387 388 if (result._fs.signum() == 0 && sign == 1) 389 result._fs = duration.getFraction(); 390 else 391 result._fs = sign > 0 ? 392 result._fs.add(duration.getFraction()) : 393 result._fs.subtract(duration.getFraction()); 394 return result; 395 } 396 397 /** 398 * Two GDurations are equal if all their fields are equal. 399 * The equals function does not apply normalizatin. 400 */ 401 public boolean equals(Object obj) 402 { 403 if (obj == this) 404 return true; 405 if (!(obj instanceof GDuration)) 406 return false; 407 408 GDuration duration = (GDuration)obj; 409 return (_sign == duration.getSign() && 410 _CY == duration.getYear() && 411 _M == duration.getMonth() && 412 _D == duration.getDay() && 413 _h == duration.getHour() && 414 _m == duration.getMinute() && 415 _s == duration.getSecond() && 416 _fs.equals(duration.getFraction())); 417 } 418 419 public int hashCode() 420 { 421 return (_s + 422 _m * (60 + 7) + 423 _h * (60 * 60 + 7) + 424 _D * (60 * 60 * 24 + 7) + 425 _M * (60 * 60 * 24 * 31 + 7) + 426 _CY *(60 * 60 * 24 * 372 + 7) + 427 _sign * 11917049); 428 } 429 430 }