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 import java.math.BigInteger; 20 21 /** 22 * Used to build {@link GDuration GDurations}. 23 */ 24 public class GDurationBuilder implements GDurationSpecification, java.io.Serializable 25 { 26 private static final long serialVersionUID = 1L; 27 28 private int _sign; 29 private int _CY; 30 private int _M; 31 private int _D; 32 private int _h; 33 private int _m; 34 private int _s; 35 private BigDecimal _fs; 36 37 /** 38 * Constructs an empty GDurationBuilder representing zero seconds. 39 */ 40 public GDurationBuilder() 41 { 42 _sign = +1; 43 _fs = GDate._zero; 44 } 45 46 /** 47 * Constructs a GDuration from a lexical 48 * representation. 49 */ 50 public GDurationBuilder(String s) 51 { 52 this(new GDuration(s)); 53 } 54 55 /** 56 * Constructs a GDurationBuilder with the specified sign, 57 * year, month, day, hours, minutes, seconds, and optional 58 * fractional seconds. 59 * @param sign +1 for a positive duration, -1 for a negative duration 60 * @throws java.lang.IllegalArgumentException if the sign is not 1 or -1 61 */ 62 public GDurationBuilder( 63 int sign, 64 int year, 65 int month, 66 int day, 67 int hour, 68 int minute, 69 int second, 70 BigDecimal fraction) 71 { 72 if (sign != 1 && sign != -1) 73 throw new IllegalArgumentException(); 74 _sign = sign; 75 _CY = year; 76 _M = month; 77 _D = day; 78 _h = hour; 79 _m = minute; 80 _s = second; 81 _fs = fraction == null ? GDate._zero : fraction; 82 } 83 84 /** 85 * Constructs a GDurationBuilder from another GDurationBuilderSpecification. 86 */ 87 public GDurationBuilder(GDurationSpecification gDuration) 88 { 89 _sign = gDuration.getSign(); 90 _CY = gDuration.getYear(); 91 _M = gDuration.getMonth(); 92 _D = gDuration.getDay(); 93 _h = gDuration.getHour(); 94 _m = gDuration.getMinute(); 95 _s = gDuration.getSecond(); 96 _fs = gDuration.getFraction(); 97 } 98 99 /** 100 * Builds another GDurationBuilder with the same value 101 * as this one. 102 */ 103 public Object clone() 104 { 105 return new GDurationBuilder(this); 106 } 107 108 /** 109 * Builds a GDuration from this GDurationBuilder. 110 */ 111 public GDuration toGDuration() 112 { 113 return new GDuration(this); 114 } 115 116 /** 117 * Adds to this duration. Does a fieldwise add, with no 118 * normalization. 119 */ 120 public void addGDuration(GDurationSpecification duration) 121 { 122 int sign = _sign * duration.getSign(); 123 _add(duration, sign); 124 } 125 126 /** 127 * Subtracts from this duration. Does a fieldwise subtraction, 128 * with no normalization. 129 */ 130 public void subtractGDuration(GDurationSpecification duration) 131 { 132 int sign = -_sign * duration.getSign(); 133 _add(duration, sign); 134 } 135 136 private void _add(GDurationSpecification duration, int sign) 137 { 138 _CY += sign * duration.getYear(); 139 _M += sign * duration.getMonth(); 140 _D += sign * duration.getDay(); 141 _h += sign * duration.getHour(); 142 _m += sign * duration.getMinute(); 143 _s += sign * duration.getSecond(); 144 145 if (duration.getFraction().signum() == 0) 146 return; 147 148 if (_fs.signum() == 0 && sign == 1) 149 _fs = duration.getFraction(); 150 else 151 _fs = sign > 0 ? 152 _fs.add(duration.getFraction()) : 153 _fs.subtract(duration.getFraction()); 154 } 155 156 /** 157 * Sets the sign. 158 */ 159 public final void setSign(int sign) 160 { 161 if (sign != 1 && sign != -1) 162 throw new IllegalArgumentException(); 163 _sign = sign; 164 } 165 166 /** 167 * Sets the year component. 168 */ 169 public void setYear(int year) 170 { _CY = year; } 171 172 /** 173 * Sets the month component. 174 */ 175 public void setMonth(int month) 176 { _M = month; } 177 178 /** 179 * Sets the day component. 180 */ 181 public void setDay(int day) 182 { _D = day; } 183 184 /** 185 * Sets the hour component. 186 */ 187 public void setHour(int hour) 188 { _h = hour; } 189 190 /** 191 * Sets the minute component. 192 */ 193 public void setMinute(int minute) 194 { _m = minute; } 195 196 /** 197 * Sets the second component. 198 */ 199 public void setSecond(int second) 200 { _s = second; } 201 202 /** 203 * Sets the fraction-of-second component. 204 */ 205 public void setFraction(BigDecimal fraction) 206 { _fs = fraction == null ? GDate._zero : fraction; } 207 208 /** 209 * All GDuration instances return true. 210 */ 211 public final boolean isImmutable() 212 { 213 return true; 214 } 215 216 /** 217 * Returns the sign of the duration: +1 is forwards 218 * and -1 is backwards in time. 219 * This value does not necessarily reflect the 220 * true direction of the duration if the duration 221 * is not normalized or not normalizable. 222 */ 223 public final int getSign() 224 { return _sign; } 225 226 /** 227 * Gets the year component. 228 */ 229 public final int getYear() 230 { return _CY; } 231 232 /** 233 * Gets the month-of-year component. 234 */ 235 public final int getMonth() 236 { return _M; } 237 238 /** 239 * Gets the day-of-month component. 240 */ 241 public final int getDay() 242 { return _D; } 243 244 /** 245 * Gets the hour-of-day component. 246 */ 247 public final int getHour() 248 { return _h; } 249 250 /** 251 * Gets the minute-of-hour component. 252 */ 253 public final int getMinute() 254 { return _m; } 255 256 /** 257 * Gets the second-of-minute component. 258 */ 259 public final int getSecond() 260 { return _s; } 261 262 263 /** 264 * Gets the fraction-of-second. Range from 0 (inclusive) to 1 (exclusive). 265 */ 266 public BigDecimal getFraction() 267 { return _fs; } 268 269 /** 270 * Returns true if all of the individual components 271 * of the duration are nonnegative. 272 */ 273 public boolean isValid() 274 { 275 return GDurationBuilder.isValidDuration(this); 276 } 277 278 279 /** 280 * Normalize a duration value. This ensures that months, 281 * hours, minutes, seconds, and fractions are positive and 282 * within the ranges 0..11, 0..23, 0..59, etc. Negative 283 * durations are indicated by a negative sign rather 284 * than negative components. 285 * <p> 286 * Most duration specifications can be normalized to 287 * valid durations with all positive components, but 288 * not all of them can. 289 * <p> 290 * The only situations which cannot be normalized are 291 * where the year/month and the day/hour/minute/second 292 * offsets are of opposite sign. Days cannot be carried 293 * into months since the length of a Gregorian month is 294 * variable depending on when the duration is applied. 295 * In these cases, this method normalizes the components 296 * so that "day" is the only negative component. 297 */ 298 public void normalize() 299 { 300 _normalizeImpl(true); 301 } 302 303 /** 304 * fQuotient(a, b) = the greatest integer less than or equal to a/b 305 */ 306 private static final long _fQuotient(long a, int b) 307 { 308 if ((a < 0) == (b < 0)) 309 return a / b; 310 311 return -((b - a - 1) / b); 312 } 313 314 /** 315 * modulo(a, b) = a - fQuotient(a,b)*b 316 */ 317 private static final int _mod(long a, int b, long quotient) 318 { 319 return (int)(a - quotient*b) ; 320 } 321 322 323 /** 324 * Private implemenation of normalize. The flag is 325 * to facilitate this method calling itself without 326 * danger of infinite recursion. 327 */ 328 private void _normalizeImpl(boolean adjustSign) 329 { 330 long temp; 331 332 // months to years 333 if (_M < 0 || _M > 11) 334 { 335 temp = _M; 336 long ycarry = _fQuotient(temp, 12); 337 _M = _mod(temp, 12, ycarry); 338 _CY += ycarry; 339 } 340 341 long carry = 0; 342 343 // fractions to seconds 344 if (_fs != null && (_fs.signum() < 0 || _fs.compareTo(GDate._one) >= 0)) 345 { 346 BigDecimal bdcarry = _fs.setScale(0, BigDecimal.ROUND_FLOOR); 347 _fs = _fs.subtract(bdcarry); 348 carry = bdcarry.intValue(); 349 } 350 351 if (carry != 0 || _s < 0 || _s > 59 || _m < 0 || _m > 50 || _h < 0 || _h > 23) 352 { 353 // seconds 354 temp = _s + carry; 355 carry = _fQuotient(temp, 60); 356 _s = _mod(temp, 60, carry); 357 358 // minutes 359 temp = _m + carry; 360 carry = _fQuotient(temp, 60); 361 _m = _mod(temp, 60, carry); 362 363 // hours 364 temp = _h + carry; 365 carry = _fQuotient(temp, 24); 366 _h = _mod(temp, 24, carry); 367 _D += carry; 368 } 369 370 if (_CY == 0 && _M == 0 && _D == 0 && _h == 0 && _m == 0 && _s == 0 && (_fs == null || _fs.signum() == 0)) 371 _sign = 1; 372 373 if (adjustSign && (_D < 0 || _CY < 0)) 374 { 375 int sign = (_D <= 0 && (_CY < 0 || _CY == 0 && _M == 0)) ? -_sign : _getTotalSignSlowly(); 376 if (sign == 2) 377 sign = (_CY < 0) ? -_sign : _sign; 378 if (sign == 0) 379 sign = 1; 380 if (sign != _sign) 381 { 382 _sign = sign; 383 _CY = -_CY; 384 _M = -_M; 385 _D = -_D; 386 _h = -_h; 387 _m = -_m; 388 _s = -_s; 389 if (_fs != null) 390 _fs = _fs.negate(); 391 } 392 _normalizeImpl(false); 393 } 394 } 395 396 397 /* package */ static boolean isValidDuration(GDurationSpecification spec) 398 { 399 if (!(spec.getSign() == 1 || spec.getSign() == -1)) 400 return false; 401 402 return (spec.getYear() >= 0 && spec.getMonth() >= 0 && spec.getDay() >= 0 && 403 spec.getHour() >= 0 && spec.getMinute() >= 0 && spec.getSecond() >= 0 && 404 spec.getFraction().signum() >= 0); 405 } 406 407 /** 408 * Comparison to another GDuration. 409 * <ul> 410 * <li>Returns -1 if this < duration. (less-than) 411 * <li>Returns 0 if this == duration. (equal) 412 * <li>Returns 1 if this > duration. (greater-than) 413 * <li>Returns 2 if this <> duration. (incomparable) 414 * </ul> 415 * Two instances are incomparable if they have different amounts 416 * of information. 417 */ 418 public final int compareToGDuration(GDurationSpecification duration) 419 { 420 return GDurationBuilder.compareDurations(this, duration); 421 } 422 423 /** 424 * The natural string representation of the duration. 425 * <p> 426 * Any components that are zero are omitted. Note that if the duration 427 * is invalid, i.e., it has negative components, those negative 428 * components are serialized out here. To check for validity, use 429 * the isValid() method; and to normalize most durations to a valid 430 * form use the normalize() method. 431 */ 432 public String toString() 433 { 434 return GDurationBuilder.formatDuration(this); 435 } 436 437 /* package */ static int compareDurations(GDurationSpecification d1, GDurationSpecification d2) 438 { 439 // first do an all-fields check 440 if (d1.getFraction().signum() == 0 && d2.getFraction().signum() == 0) 441 { 442 int s1 = d1.getSign(); 443 int s2 = d2.getSign(); 444 long month1 = s1 * ((long)d1.getYear() * 12 + d1.getMonth()); 445 long month2 = s2 * ((long)d2.getYear() * 12 + d2.getMonth()); 446 long sec1 = s1 * ((((long)d1.getDay() * 24 + d1.getHour()) * 60 + d1.getMinute()) * 60 + d1.getSecond()); 447 long sec2 = s2 * ((((long)d2.getDay() * 24 + d2.getHour()) * 60 + d2.getMinute()) * 60 + d2.getSecond()); 448 if (month1 == month2) 449 { 450 if (sec1 == sec2) 451 return 0; 452 if (sec1 < sec2) 453 return -1; 454 if (sec1 > sec2) 455 return 1; 456 } 457 if (month1 < month2 && sec1 - sec2 < 28 * 24 * 60 * 60) 458 return -1; 459 if (month1 > month2 && sec2 - sec1 < 28 * 24 * 60 * 60) 460 return 1; 461 } 462 463 // the answer isn't obvious, so then do a total-sign check 464 GDurationBuilder diff = new GDurationBuilder(d1); 465 diff.subtractGDuration(d2); 466 return diff._getTotalSignSlowly(); 467 } 468 469 /** 470 * Per schema spec, comparison of durations is simply done 471 * by calculating adding the duration to these four dates and 472 * comparing the results. If the results are ambiguous, the 473 * answer is "incomparable". 474 */ 475 private static final GDate[] _compDate = new GDate[] 476 { 477 new GDate(1696, 9, 1, 0, 0, 0, null, 0, 0, 0), 478 new GDate(1697, 2, 1, 0, 0, 0, null, 0, 0, 0), 479 new GDate(1903, 3, 1, 0, 0, 0, null, 0, 0, 0), 480 new GDate(1903, 7, 1, 0, 0, 0, null, 0, 0, 0) 481 }; 482 483 484 /** 485 * This returns the total sign of the duration, +1 486 * if the duration moves forward in time, -1 if the 487 * duration moves backwards in time, 0 if the duration 488 * is zero-length, and 2 if the duration may be positive 489 * or negative depending on the date. 490 * 491 * (For example, one month minus 30 days is indeterminate). 492 */ 493 private int _getTotalSignSlowly() 494 { 495 int pos = 0; 496 int neg = 0; 497 int zer = 0; 498 499 GDateBuilder enddate = new GDateBuilder(); 500 for (int i = 0; i < _compDate.length; i++) 501 { 502 enddate.setGDate(_compDate[i]); 503 enddate.addGDuration(this); 504 switch (enddate.compareToGDate(_compDate[i])) 505 { 506 case -1: 507 neg++; break; 508 case 0: 509 zer++; break; 510 case 1: 511 pos++; break; 512 } 513 } 514 515 if (pos == _compDate.length) 516 return +1; 517 if (neg == _compDate.length) 518 return -1; 519 if (zer == _compDate.length) 520 return 0; 521 return 2; 522 } 523 524 /* package */ static String formatDuration(GDurationSpecification duration) 525 { 526 // Sign+P: (-)?P 527 // Year: (?:(\d+)Y)? 528 // Month: (?:(\d+)M)? 529 // Day: (?:(\d+)D)? 530 // Time: (?:(T) 531 // Hours: (?:(\d+)H)? 532 // Minutes: (?:(\d+)M)? 533 // Seconds: (?:(\d+(?:\.\d*)?|(?:.\d+)S)? 534 535 StringBuffer message = new StringBuffer(30); 536 537 if (duration.getSign() < 0) 538 message.append('-'); 539 540 message.append('P'); 541 542 if (duration.getYear() != 0) 543 { 544 message.append(duration.getYear()); 545 message.append('Y'); 546 } 547 548 if (duration.getMonth() != 0) 549 { 550 message.append(duration.getMonth()); 551 message.append('M'); 552 } 553 554 if (duration.getDay() != 0) 555 { 556 message.append(duration.getDay()); 557 message.append('D'); 558 } 559 560 if (duration.getHour() != 0 || duration.getMinute() != 0 || duration.getSecond() != 0 || 561 (duration.getFraction().signum() != 0)) 562 { 563 message.append('T'); 564 } 565 566 if (duration.getHour() != 0) 567 { 568 message.append(duration.getHour()); 569 message.append('H'); 570 } 571 572 if (duration.getMinute() != 0) 573 { 574 message.append(duration.getMinute()); 575 message.append('M'); 576 } 577 578 if (duration.getFraction().signum() != 0) 579 { 580 BigDecimal s = duration.getFraction(); 581 if (duration.getSecond() != 0) 582 s = s.add(BigDecimal.valueOf(duration.getSecond())); 583 // todo when upgrade to 1.5 message.append(s.stripTrailingZeros().toPlainString()); 584 message.append(stripTrailingZeros(toPlainString(s))); 585 message.append('S'); 586 } 587 else if (duration.getSecond() != 0) 588 { 589 message.append(duration.getSecond()); 590 message.append('S'); 591 } 592 else if (message.length() <= 2) 593 // Specify zero seconds if everything was 0 594 message.append("T0S"); 595 596 return message.toString(); 597 } 598 599 public static String toPlainString(BigDecimal bd) 600 { 601 BigInteger intVal = bd.unscaledValue(); 602 int scale = bd.scale(); 603 String intValStr = intVal.toString(); 604 if (scale == 0) 605 return intValStr; 606 607 boolean isNegative = (intValStr.charAt(0) == '-'); 608 609 int point = intValStr.length() - scale - (isNegative ? 1 : 0); 610 611 StringBuffer sb = new StringBuffer(intValStr.length() + 2 + (point <= 0 ? (-point + 1) : 0)); 612 if (point <= 0) 613 { 614 // prepend zeros and a decimal point. 615 if (isNegative) sb.append('-'); 616 sb.append('0').append('.'); 617 while (point < 0) 618 { 619 sb.append('0'); 620 point++; 621 } 622 sb.append(intValStr.substring(isNegative ? 1 : 0)); 623 } 624 else if (point < intValStr.length()) 625 { 626 // No zeros needed 627 sb.append(intValStr); 628 sb.insert(point + (isNegative ? 1 : 0), '.'); 629 } 630 else 631 { 632 // append zeros if not 0 633 sb.append(intValStr); 634 if (!intVal.equals(BigInteger.ZERO)) 635 for (int i = intValStr.length(); i < point; i++) 636 sb.append('0'); 637 } 638 return sb.toString(); 639 } 640 641 public static String stripTrailingZeros(String s) 642 { 643 boolean seenDot = false; 644 int i = s.length() - 1; 645 int zeroIndex = i; 646 647 while(i>=0) 648 { 649 if (s.charAt(i)!='0') 650 break; 651 i--; 652 zeroIndex--; 653 } 654 while(i>=0) 655 { 656 if (s.charAt(i)=='E') 657 return s; 658 if (s.charAt(i)=='.') 659 { 660 seenDot = true; 661 break; 662 } 663 i--; 664 } 665 666 return seenDot? s.substring(0, zeroIndex+1) : s; 667 } 668 }