View Javadoc
1 /* 2 * ==================================================================== The 3 * Apache Software License, Version 1.1 4 * 5 * Copyright (c) 2003 Digital Clash LLC. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions are met: 1. 9 * Redistributions of source code must retain the above copyright notice, this 10 * list of conditions and the following disclaimer. 2. Redistributions in 11 * binary form must reproduce the above copyright notice, this list of 12 * conditions and the following disclaimer in the documentation and/or other 13 * materials provided with the distribution. 3. The end-user documentation 14 * included with the redistribution, if any, must include the following 15 * acknowledgment: "This product includes software developed by the ChronicJ 16 * team (http://www.chronicj.org/)." Alternately, this acknowledgment may 17 * appear in the software itself, if and wherever such third-party 18 * acknowledgments normally appear. 4. The names "ChronicJ" and "Digital Clash" 19 * not be used to endorse or promote products derived from this software 20 * without prior written permission. For written permission, please contact 21 * info@digitalclash.com. 5. Products derived from this software may not be 22 * called "ChronicJ", "Digital Clash", nor may "ChronicJ" or "Digital Clash" 23 * appear in their name, without prior written permission of Digital Clash LLC. 24 * 25 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 27 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 32 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 * ==================================================================== This 36 * product includes software developed by the by the Apache Software Foundation 37 * (http://www.apache.org/). 38 * ==================================================================== 39 */ 40 package org.chronicj; 41 42 import java.text.SimpleDateFormat; 43 44 import java.util.Calendar; 45 import java.util.Date; 46 import java.util.GregorianCalendar; 47 48 /*** 49 * <p> 50 * Based directly on the <a href="http://martinfowler.com/ap2/timePoint.html"> 51 * Time Point</a> pattern described by Martin Fowler. This class wraps the 52 * standard {@link java.util.GregorianCalendar GregorianCalendar}in order to 53 * to simplify date precision. Also, some convenience constructors and methods 54 * are provided. 55 * </p> 56 * 57 * <p> 58 * Since things will break if a non- <code>GregorianCalendar</code> is 59 * supplied, declarations explicitly deal with the concrete class instead of 60 * working with the abstract supertype {@link java.util.Calendar Calendar}. 61 * </p> 62 * 63 * @author <a href="mlipper@US-ABP.com">Matthew Lipper</a> 64 */ 65 public class TimePoint implements Comparable 66 { 67 /*** Default precision is {@link DatePrecision#MINUTE}. */ 68 private static final DatePrecision DEFAULT_PRECISION = DatePrecision.MINUTE; 69 private static final SimpleDateFormat sdf = 70 new SimpleDateFormat("EEE, d MMM yyyy h:mm:ss.S a Z"); 71 private DatePrecision datePrecision; 72 private GregorianCalendar base; 73 74 /*** 75 * Convenience constructor allowing the more natural 1-based-index for 76 * specifying month. Timezone is ignored. 77 * 78 * @param year 79 * the year 80 * @param month 81 * the month using 1 to 12, where 1 == January and 12 == 82 * DECEMBER. NOTE: java.util.Calendar is zero based, this is <b> 83 * not</b>. 84 * @param day 85 * the day of the month using 1 to 31 86 */ 87 public TimePoint(int year, int month, int day) 88 { 89 validate(year, DatePrecision.YEAR); 90 validate(month, DatePrecision.MONTH); 91 validate(day, DatePrecision.DATE); 92 93 initializeToPrecision( 94 new GregorianCalendar(year, month - 1, day), 95 DatePrecision.DATE); 96 } 97 98 /*** 99 * Convenience constructor allowing the more natural 1-based-index for 100 * specifying month. Timezone is ignored. 101 * 102 * @param year 103 * the year 104 * @param month 105 * the month using 1 to 12, where 1 == January and 12 == 106 * DECEMBER. NOTE: java.util.Calendar is zero based, this is <b> 107 * not</b>. 108 */ 109 public TimePoint(int year, int month) 110 { 111 validate(year, DatePrecision.YEAR); 112 validate(month, DatePrecision.MONTH); 113 114 //The date will be reset :-) 115 initializeToPrecision( 116 new GregorianCalendar(year, month - 1, 1), 117 DatePrecision.MONTH); 118 } 119 120 /*** 121 * Convenience constructor allowing the more natural 1-based-index for 122 * specifying month. Timezone is ignored. 123 * 124 * @param year 125 * the year 126 * @param month 127 * the month using 1 to 12, where 1 == January and 12 == 128 * DECEMBER. NOTE: java.util.Calendar is zero based, this is 129 * not. 130 * @param day 131 * the day of the month using 1 to 31 132 * @param hour 133 * the hour of the day using 0 to 24 134 */ 135 public TimePoint(int year, int month, int day, int hour) 136 { 137 validate(year, DatePrecision.YEAR); 138 validate(month, DatePrecision.MONTH); 139 validate(day, DatePrecision.DATE); 140 validate(hour, DatePrecision.HOUR_OF_DAY); 141 142 initializeToPrecision( 143 new GregorianCalendar(year, month - 1, day, hour, 0), 144 DatePrecision.HOUR_OF_DAY); 145 } 146 147 /*** 148 * Convenience constructor allowing the more natural 1-based-index for 149 * specifying month. Timezone is ignored. 150 * 151 * @param year 152 * the year 153 * @param month 154 * the month using 1 to 12, where 1 == January and 12 == 155 * DECEMBER. NOTE: java.util.Calendar is zero based, this is 156 * not. 157 * @param day 158 * the day of the month using 1 to 31 159 * @param hour 160 * the hour of the day using 0 to 24 161 * @param minute 162 * the minute of the hour using 0 to 60 163 */ 164 public TimePoint(int year, int month, int day, int hour, int minute) 165 { 166 validate(year, DatePrecision.YEAR); 167 validate(month, DatePrecision.MONTH); 168 validate(day, DatePrecision.DATE); 169 validate(hour, DatePrecision.HOUR_OF_DAY); 170 validate(minute, DatePrecision.MINUTE); 171 172 initializeToPrecision( 173 new GregorianCalendar(year, month - 1, day, hour, minute), 174 DatePrecision.MINUTE); 175 } 176 177 /*** 178 * Delegates call to {@link TimePoint#TimePoint(GregorianCalendar, 179 * DatePrecision)} using constant {@link TimePoint#DEFAULT_PRECISION}. 180 * 181 * @param arg 182 * the GregorianCalendar which will be set using 183 * TimePoint#DEFAULT_PRECISION. 184 */ 185 public TimePoint(GregorianCalendar arg) 186 { 187 //delegate constructor 188 this(arg, DEFAULT_PRECISION); 189 } 190 191 /*** 192 * Creates a TimePoint using default DatePrecision. 193 * 194 * @param aDate 195 * the Date which will be set using {@link 196 * TimePoint#DEFAULT_PRECISION}. 197 */ 198 public TimePoint(Date aDate) 199 { 200 this(aDate, DEFAULT_PRECISION); 201 } 202 203 /*** 204 * Creates a TimePoint using the specified DatePrecision. 205 * 206 * @param aDate 207 * the Date to be set 208 * @param precision 209 * the DatePrecision to use 210 * 211 * @throws NullPointerException 212 * if supplied Date is null 213 */ 214 public TimePoint(Date aDate, DatePrecision precision) 215 { 216 if (aDate == null) 217 { 218 throw new NullPointerException("Date argument cannot be null."); 219 } 220 221 GregorianCalendar aCalendar = new GregorianCalendar(); 222 aCalendar.setTime(aDate); 223 initializeToPrecision(aCalendar, precision); 224 } 225 226 /*** 227 * Creates a TimePoint using specified DatePrecision. Timezone information 228 * is assumed set or not set by the caller. <br/>NOTE: ATM, 229 * GregorianCalendar argument obeys pass by value semantics. 230 * 231 * @param arg 232 * the GregorianCalendar with the requested date value 233 * @param precision 234 * the DatePrecision to use 235 */ 236 public TimePoint(GregorianCalendar arg, DatePrecision precision) 237 { 238 this(arg.getTime(), precision); 239 } 240 241 /*** 242 * Getter method for the wrapped GregorianCalendar. 243 * 244 * @return the wrapped GregorianCalendar 245 */ 246 public GregorianCalendar getCalendar() 247 { 248 return base; 249 } 250 251 /*** 252 * Getter method for DatePrecision field. 253 * 254 * @return DatePrecision of this TimePoint 255 */ 256 public DatePrecision getDatePrecision() 257 { 258 return datePrecision; 259 } 260 261 /*** 262 * Getter for day of the month. 263 * 264 * @return int day of the month 265 */ 266 public int getDayOfMonth() 267 { 268 return base.get(Calendar.DAY_OF_MONTH); 269 } 270 271 /*** 272 * Getter for day of the week. 273 * 274 * @return int day of the week. 275 */ 276 public int getDayOfWeek() 277 { 278 return base.get(Calendar.DAY_OF_WEEK); 279 } 280 281 /*** 282 * Getter for hour of the day (24hr). 283 * 284 * @return int hour of the day 285 */ 286 public int getHourOfDay() 287 { 288 return base.get(Calendar.HOUR_OF_DAY); 289 } 290 291 /*** 292 * Getter for milliseconds. 293 * 294 * @return int milliseconds 295 */ 296 public int getMilliSeconds() 297 { 298 return base.get(Calendar.MILLISECOND); 299 } 300 301 /*** 302 * Getter for minutes. 303 * 304 * @return int minutes 305 */ 306 public int getMinute() 307 { 308 return base.get(Calendar.MINUTE); 309 } 310 311 /*** 312 * Getter for month (<b>not</b> zero-indexed). Values will be between 1 313 * and 12 for {@link GregorianCalendar}s. 314 * 315 * @return int month 316 */ 317 public int getMonth() 318 { 319 return base.get(Calendar.MONTH) + 1; 320 } 321 322 /*** 323 * Getter for seconds. 324 * 325 * @return int seconds 326 */ 327 public int getSeconds() 328 { 329 return base.get(Calendar.SECOND); 330 } 331 332 /*** 333 * Convenience getter method for {@link #getCalendar} 334 * 335 * @return the wrapped GregorianCalendar as a Date 336 */ 337 public Date getTime() 338 { 339 return base.getTime(); 340 } 341 342 /*** 343 * Getter for year. 344 * 345 * @return int year 346 */ 347 public int getYear() 348 { 349 return base.get(Calendar.YEAR); 350 } 351 352 /*** 353 * Getter returning the wrapped {@link java.util.Calendar}month which is 354 * zero-indexed. Values will be between 0 to 11 (inclusive) for {@link 355 * GregorianCalendar}s. 356 * 357 * @return int zero-indexed month 358 */ 359 public int getZeroIndexedMonth() 360 { 361 return base.get(Calendar.MONTH); 362 } 363 364 /*** 365 * Create a new TimePoint using the current instance plus n days. NOTE: the 366 * existing instance will <b>not</b> change! 367 * 368 * @param arg 369 * number of days to add 370 * 371 * @return a brand new TimePoint 372 */ 373 public TimePoint addDays(int arg) 374 { 375 return new TimePoint( 376 new GregorianCalendar( 377 getYear(), 378 getZeroIndexedMonth(), 379 getDayOfMonth() + arg, 380 getHourOfDay(), 381 getMinute()) 382 .getTime(), 383 getDatePrecision()); 384 } 385 386 /*** 387 * Create a new TimePoint using the current instance plus n hours. NOTE: 388 * the existing instance will <b>not</b> change! 389 * 390 * @param arg 391 * number of hours to add 392 * 393 * @return a brand new TimePoint 394 */ 395 public TimePoint addHours(int arg) 396 { 397 return new TimePoint( 398 (new GregorianCalendar(getYear(), 399 getZeroIndexedMonth(), 400 getDayOfMonth(), 401 getHourOfDay() + arg, 402 getMinute())) 403 .getTime(), 404 getDatePrecision()); 405 } 406 407 /*** 408 * Create a new TimePoint using the current instance plus n milliseconds. 409 * NOTE: the existing instance will <b>not</b> change! 410 * 411 * @param arg 412 * number of milliseconds to add 413 * 414 * @return a brand new TimePoint 415 */ 416 public TimePoint addMilliSeconds(int arg) 417 { 418 //Add milliseconds (automatic promotion to long!) 419 long currentTimePlusDelta = base.getTimeInMillis() + arg; 420 421 Calendar aCalendar = Calendar.getInstance(); 422 423 aCalendar.setTimeInMillis(currentTimePlusDelta); 424 425 return new TimePoint(aCalendar.getTime(), getDatePrecision()); 426 } 427 428 /*** 429 * Create a new TimePoint using the current instance plus n minutes. NOTE: 430 * the existing instance will <b>not</b> change! 431 * 432 * @param arg 433 * number of minutes to add 434 * 435 * @return a brand new TimePoint 436 */ 437 public TimePoint addMinutes(int arg) 438 { 439 return new TimePoint( 440 new GregorianCalendar( 441 getYear(), 442 getZeroIndexedMonth(), 443 getDayOfMonth(), 444 getHourOfDay(), 445 getMinute() + arg) 446 .getTime(), 447 getDatePrecision()); 448 } 449 450 /*** 451 * Create a new TimePoint using the current instance plus n months. NOTE: 452 * the existing instance will <b>not</b> change! 453 * 454 * @param arg 455 * number of months to add 456 * 457 * @return a brand new TimePoint 458 */ 459 public TimePoint addMonths(int arg) 460 { 461 return new TimePoint( 462 new GregorianCalendar( 463 getYear(), 464 getZeroIndexedMonth() + arg, 465 getDayOfMonth(), 466 getHourOfDay(), 467 getMinute()) 468 .getTime(), 469 getDatePrecision()); 470 } 471 472 /*** 473 * Create a new TimePoint using the current instance plus n seconds. NOTE: 474 * the existing instance will <b>not</b> change! 475 * 476 * @param arg 477 * number of seconds to add 478 * 479 * @return a brand new TimePoint 480 */ 481 public TimePoint addSeconds(int arg) 482 { 483 return new TimePoint( 484 new GregorianCalendar( 485 getYear(), 486 getZeroIndexedMonth(), 487 getDayOfMonth(), 488 getHourOfDay(), 489 getMinute(), 490 getSeconds() + arg) 491 .getTime(), 492 getDatePrecision()); 493 } 494 495 /*** 496 * Create a new TimePoint using the current instance plus n years. NOTE: 497 * the existing instance will <b>not</b> change! 498 * 499 * @param arg 500 * number of years to add 501 * 502 * @return a brand new TimePoint 503 */ 504 public TimePoint addYears(int arg) 505 { 506 return new TimePoint( 507 new GregorianCalendar( 508 getYear() + arg, 509 getZeroIndexedMonth(), 510 getDayOfMonth(), 511 getHourOfDay(), 512 getMinute()) 513 .getTime(), 514 getDatePrecision()); 515 } 516 517 /*** 518 * Method to see whether this TimePoint instance occurs after another 519 * TimePoint instance. 520 * 521 * @param arg 522 * the TimePoint to check 523 * 524 * @return true if this TimePoint occurs after the one provided false if 525 * this TimePoint occurs before the one provided 526 * 527 * @throws NullPointerException 528 * if argument is null 529 */ 530 public boolean after(TimePoint arg) 531 { 532 if (arg == null) 533 { 534 throw new NullPointerException("TimePoint argument cannot be null."); 535 } 536 537 return getTime().after(arg.getTime()); 538 } 539 540 /*** 541 * Method to see whether this TimePoint instance occurs before another 542 * TimePoint instance. 543 * 544 * @param arg 545 * the TimePoint to check 546 * 547 * @return true if this TimePoint occurs before the one provided false if 548 * this TimePoint occurs after the one provided 549 * 550 * @throws NullPointerException 551 * if argument is null 552 */ 553 public boolean before(TimePoint arg) 554 { 555 if (arg == null) 556 { 557 throw new NullPointerException("TimePoint argument cannot be null."); 558 } 559 560 return getTime().before(arg.getTime()); 561 } 562 563 /*** 564 * Compare wrapped <code>GregorianCalendar</code>s. 565 * 566 * @param arg 567 * Object to compare 568 * 569 * @return <ul> 570 * <li>-1 if this TimePoint occurs after the one provided</li> 571 * <li>0 if this TimePoint occurs at the same time as the one 572 * provided</li> 573 * <li>1 if this TimePoint occurs before the one provided</li> 574 * </ul> 575 * 576 * @see {@link Comparable#compareTo(java.lang.Object)} 577 */ 578 public int compareTo(Object arg) 579 { 580 TimePoint other = (TimePoint) arg; 581 582 return getTime().compareTo(other.getTime()); 583 } 584 585 /*** 586 * Compare wrapped <code>GregorianCalendar</code> s for equality 587 * 588 * @param arg 589 * Object to check 590 * 591 * @return true if underlying GregorianCalendar instances are equal 592 * 593 * @see {@link Object#equals(java.lang.Object)} 594 */ 595 public boolean equals(Object arg) 596 { 597 if (this == arg) 598 { 599 return true; 600 } 601 602 if (!(arg instanceof TimePoint)) 603 { 604 return false; 605 } 606 607 TimePoint other = (TimePoint) arg; 608 609 return (base.equals(other.getCalendar())); 610 } 611 612 /* 613 * @see java.lang.Object#hashCode() 614 */ 615 public int hashCode() 616 { 617 return base.hashCode(); 618 } 619 620 /*** 621 * Increment this <code>TimePoint</code> using the current internal 622 * {@link DatePrecision}by the number of units specified. This is 623 * semantically, equivalent to calling {@link Calendar#add(int, int)}with 624 * the first argument being the current TimePoint's DatePrecsion and the 625 * second argument being the supplied positive or negative integer amount. 626 * 627 * @param amount 628 * int number of units to increment by. 629 * 630 * @return TimePoint a brand new TimePoint 631 */ 632 public TimePoint increment(int amount) 633 { 634 return getDatePrecision().increment(this, amount); 635 } 636 637 /*** 638 * Create a <i>new</i> TimePoint using this <code>TimePoint</code> 639 * current value set to the supplied {@link DatePrecision}. 640 * 641 * @param precision 642 * DatePrecision to use when creating the new TimePoint 643 * 644 * @return TimePoint a brand new TimePoint 645 */ 646 public TimePoint toPrecison(DatePrecision precision) 647 { 648 return new TimePoint(base, precision); 649 } 650 651 /*** 652 * Create a new TimePoint using the current instance minus n days. NOTE: 653 * the existing instance will <b>not</b> change! 654 * 655 * @param arg 656 * number of days to subtract 657 * 658 * @return a brand new TimePoint 659 */ 660 public TimePoint minusDays(int arg) 661 { 662 return addDays(-arg); 663 } 664 665 /* 666 * @see java.lang.Object#toString() 667 */ 668 public String toString() 669 { 670 return sdf.format(base.getTime()); 671 } 672 673 private void clearAndRecompute(Calendar aCalendar, int field) 674 { 675 //Clear first! 676 aCalendar.clear(field); 677 678 //Side effect - force internal recompute :( 679 aCalendar.get(field); 680 681 //Normalize 682 aCalendar.set(field, aCalendar.getActualMinimum(field)); 683 } 684 685 private void initializeToPrecision( 686 GregorianCalendar arg, 687 DatePrecision precision) 688 { 689 if (arg == null) 690 { 691 throw new NullPointerException("Calendar argument cannot be null."); 692 } 693 694 if (precision == null) 695 { 696 throw new NullPointerException("DatePrecision argument cannot be null."); 697 } 698 699 datePrecision = precision; 700 701 //Lenient? 702 arg.setLenient(false); 703 704 //It's either this or a switch...I prefer this :) 705 if (DatePrecision.MILLISECOND.equals(precision)) 706 { 707 //No trim necessary 708 base = arg; 709 } else if (DatePrecision.SECOND.equals(precision)) 710 { 711 base = trimToSeconds(arg); 712 } else if (DatePrecision.MINUTE.equals(precision)) 713 { 714 base = trimToMinutes(arg); 715 } else if (DatePrecision.HOUR_OF_DAY.equals(precision)) 716 { 717 base = trimToHours(arg); 718 } else if (DatePrecision.DATE.equals(precision)) 719 { 720 base = trimToDays(arg); 721 } else if (DatePrecision.MONTH.equals(precision)) 722 { 723 base = trimToMonths(arg); 724 } else if (DatePrecision.YEAR.equals(precision)) 725 { 726 base = trimToYear(arg); 727 } else 728 { 729 throw new UnsupportedOperationException("Unknown precision."); 730 } 731 } 732 733 private GregorianCalendar trimToDays(GregorianCalendar arg) 734 { 735 GregorianCalendar result = arg; 736 737 trimToHours(result); 738 739 clearAndRecompute(result, Calendar.HOUR_OF_DAY); 740 741 return result; 742 } 743 744 private GregorianCalendar trimToHours(GregorianCalendar arg) 745 { 746 GregorianCalendar result = arg; 747 trimToMinutes(result); 748 clearAndRecompute(result, Calendar.MINUTE); 749 750 return result; 751 } 752 753 private GregorianCalendar trimToMinutes(GregorianCalendar arg) 754 { 755 GregorianCalendar result = arg; 756 clearAndRecompute(result, Calendar.SECOND); 757 clearAndRecompute(result, Calendar.MILLISECOND); 758 759 return result; 760 } 761 762 private GregorianCalendar trimToMonths(GregorianCalendar arg) 763 { 764 GregorianCalendar result = arg; 765 trimToDays(result); 766 clearAndRecompute(result, Calendar.DAY_OF_MONTH); 767 768 return result; 769 } 770 771 private GregorianCalendar trimToSeconds(GregorianCalendar arg) 772 { 773 GregorianCalendar result = arg; 774 clearAndRecompute(result, Calendar.MILLISECOND); 775 776 return result; 777 } 778 779 private GregorianCalendar trimToYear(GregorianCalendar arg) 780 { 781 GregorianCalendar result = arg; 782 trimToMonths(result); 783 clearAndRecompute(result, Calendar.MONTH); 784 785 return result; 786 } 787 788 private void validate(int value, DatePrecision precision) 789 { 790 if (DatePrecision.MILLISECOND.equals(precision) 791 && ((value < 0) || (value > 1000))) 792 { 793 throw new IllegalArgumentException("Invalid second: " + value); 794 } else if ( 795 DatePrecision.SECOND.equals(precision) 796 && ((value < 0) || (value > 60))) 797 { 798 throw new IllegalArgumentException("Invalid second: " + value); 799 } else if ( 800 DatePrecision.MINUTE.equals(precision) 801 && ((value < 0) || (value > 60))) 802 { 803 throw new IllegalArgumentException("Invalid minute: " + value); 804 } else if ( 805 DatePrecision.HOUR_OF_DAY.equals(precision) 806 && ((value < 0) || (value > 24))) 807 { 808 throw new IllegalArgumentException("Invalid hour: " + value); 809 } else if ( 810 DatePrecision.DATE.equals(precision) 811 && ((value < 1) || (value > 31))) 812 { 813 throw new IllegalArgumentException("Invalid date: " + value); 814 } else if ( 815 DatePrecision.MONTH.equals(precision) 816 && ((value < 1) || (value > 12))) 817 { 818 throw new IllegalArgumentException("Invalid month: " + value); 819 } else if (DatePrecision.YEAR.equals(precision)) 820 { 821 //Do nothing until I remember where to find min & max for 822 // Calendars 823 //throw new IllegalArgumentException("Invalid year: " + value); 824 } 825 } 826 }

This page was automatically generated by Maven