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