1    /*
2     * Copyright 2009 :torweg free software group
3     *
4     * This program is free software: you can redistribute it and/or modify
5     * it under the terms of the GNU General Public License as published by
6     * the Free Software Foundation, either version 3 of the License, or
7     * (at your option) any later version.
8     * 
9     * This program is distributed in the hope that it will be useful,
10    * but WITHOUT ANY WARRANTY; without even the implied warranty of
11    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    * GNU General Public License for more details.
13    * 
14    * You should have received a copy of the GNU General Public License
15    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16    *
17    */
18   package org.torweg.pulse.util.time;
19   
20   import java.io.Serializable;
21   import java.text.SimpleDateFormat;
22   import java.util.Calendar;
23   import java.util.Date;
24   import java.util.Locale;
25   import java.util.TimeZone;
26   
27   import javax.persistence.Basic;
28   import javax.persistence.Embeddable;
29   import javax.xml.bind.annotation.XmlAccessType;
30   import javax.xml.bind.annotation.XmlAccessorType;
31   import javax.xml.bind.annotation.XmlAttribute;
32   import javax.xml.bind.annotation.XmlElement;
33   import javax.xml.bind.annotation.XmlRootElement;
34   import javax.xml.bind.annotation.XmlTransient;
35   
36   /**
37    * represents a duration in time.
38    * 
39    * @author Thomas Weber
40    * @version $Revision: 1625 $
41    */
42   @XmlRootElement(name = "duration")
43   @XmlAccessorType(XmlAccessType.PROPERTY)
44   @Embeddable
45   public final class Duration implements Comparable<TimeSpan>, IHasDuration,
46           Serializable {
47   
48       /**
49        * serialVersionUID.
50        */
51       private static final long serialVersionUID = -7767710131035673016L;
52   
53       /**
54        * the start date of the duration.
55        */
56       @Basic
57       private Long startDate;
58   
59       /**
60        * the end date of the duration.
61        */
62       @Basic
63       private Long endDate;
64   
65       /**
66        * creates a {@code Duration} starting now and lasting <tt>zero</tt>
67        * milliseconds.
68        */
69       public Duration() {
70           super();
71           this.endDate = System.currentTimeMillis();
72           this.startDate = this.endDate;
73       }
74   
75       /**
76        * creates a new {@code Duration} lasting for the given time span string and
77        * <tt>now</tt> as the reference date.
78        * 
79        * @param ts
80        *            the time span as a string
81        */
82       public Duration(final String ts) {
83           super();
84           this.startDate = System.currentTimeMillis();
85           this.endDate = this.startDate + new TimeSpan(ts).getMilliseconds();
86           normalize();
87       }
88   
89       /**
90        * creates a new {@code Duration} lasting for the given time span.
91        * <p>
92        * If the time span is negative, the duration's start date is
93        * <tt>now - time span</tt>. Otherwise the start date is <tt>now</tt>.
94        * </p>
95        * 
96        * @param ts
97        *            the time span
98        */
99       public Duration(final TimeSpan ts) {
100          super();
101          this.startDate = System.currentTimeMillis();
102          this.endDate = this.startDate + ts.getMilliseconds();
103          normalize();
104      }
105  
106      /**
107       * creates a new {@code Duration} lasting for the given time span and the
108       * given reference date.
109       * 
110       * @param d
111       *            the reference date
112       * @param ts
113       *            the time span
114       */
115      public Duration(final Date d, final String ts) {
116          super();
117          this.startDate = d.getTime();
118          this.endDate = this.startDate + new TimeSpan(ts).getMilliseconds();
119          normalize();
120      }
121  
122      /**
123       * creates a new {@code Duration} lasting for the given time span and the
124       * given reference date.
125       * 
126       * @param d
127       *            the reference date
128       * @param ts
129       *            the time span
130       */
131      public Duration(final Date d, final TimeSpan ts) {
132          super();
133          this.startDate = d.getTime();
134          this.endDate = this.startDate + ts.getMilliseconds();
135          normalize();
136      }
137  
138      /**
139       * creates a new {@code Duration} with a positive {@code TimeSpan} , making
140       * the less recent {@code Date} the reference date.
141       * 
142       * @param a
143       *            boundary date a
144       * @param b
145       *            boundary date b
146       */
147      public Duration(final Date a, final Date b) {
148          super();
149          this.startDate = a.getTime();
150          this.endDate = b.getTime();
151          normalize();
152      }
153  
154      /**
155       * creates a new {@code Duration} with a positive {@code TimeSpan} , making
156       * the less recent {@code Calendar} the reference date.
157       * 
158       * @param a
159       *            boundary {@code Calendar} a
160       * @param b
161       *            boundary {@code Calendar} b
162       */
163      public Duration(final Calendar a, final Calendar b) {
164          super();
165          this.startDate = a.getTimeInMillis();
166          this.endDate = b.getTimeInMillis();
167          normalize();
168      }
169  
170      /**
171       * creates a new {@code Duration} with a positive {@code TimeSpan} , making
172       * the less recent time-stamp the reference date.
173       * 
174       * @param a
175       *            boundary time-stamp a
176       * @param b
177       *            boundary time-stamp b
178       */
179      public Duration(final long a, final long b) {
180          super();
181          this.startDate = a;
182          this.endDate = b;
183          normalize();
184      }
185  
186      /**
187       * Creates a new {@code Duration} from the given {@code Duration}.
188       * 
189       * @param duration
190       *            the {@code Duration}
191       */
192      public Duration(final Duration duration) {
193          super();
194          this.startDate = duration.getStartMillis();
195          this.endDate = duration.getEndMillis();
196      }
197  
198      /**
199       * used to normalise the {@code Duration}.
200       */
201      private void normalize() {
202          if (this.startDate > this.endDate) {
203              long end = this.endDate;
204              this.endDate = this.startDate;
205              this.startDate = end;
206          }
207      }
208  
209      /**
210       * returns the start date.
211       * 
212       * @return the start date
213       */
214      @XmlAttribute(name = "start-date", required = true)
215      public Date getStartDate() {
216          if (this.endDate >= 0) {
217              return new Date(this.startDate);
218          }
219          return new Date(this.startDate + this.endDate);
220      }
221  
222      /**
223       * returns the UTC start date.
224       * 
225       * @return the UTC start date
226       */
227      @XmlAttribute(name = "start-date-utc-string")
228      public String getStartDateUTCString() {
229          SimpleDateFormat format = new SimpleDateFormat(
230                  "yyyy-MM-dd'T'HH:mm:ss.SSS+00:00", Locale.getDefault());
231          format.setTimeZone(TimeZone.getTimeZone("UTC"));
232          return format.format(getStartDate());
233      }
234  
235      /**
236       * Returns the milliseconds-value of the start of the {@code Duration}.
237       * 
238       * @return <tt>startDate</tt>
239       */
240      @XmlElement(name = "start-millis")
241      public long getStartMillis() {
242          return this.startDate;
243      }
244  
245      /**
246       * returns the end date.
247       * 
248       * @return the end date
249       */
250      @XmlAttribute(name = "end-date")
251      public Date getEndDate() {
252          return new Date(this.endDate);
253      }
254  
255      /**
256       * returns the UTC end date.
257       * 
258       * @return the UTC end date
259       */
260      @XmlAttribute(name = "end-date-utc-string")
261      public String getEndDateUTCString() {
262          SimpleDateFormat format = new SimpleDateFormat(
263                  "yyyy-MM-dd'T'HH:mm:ss.SSS+00:00", Locale.getDefault());
264          format.setTimeZone(TimeZone.getTimeZone("UTC"));
265          return format.format(getEndDate());
266      }
267  
268      /**
269       * Returns the milliseconds-value of the end of the {@code Duration}.
270       * 
271       * @return <tt>endDate</tt>
272       */
273      @XmlElement(name = "end-millis")
274      public long getEndMillis() {
275          return this.endDate;
276      }
277  
278      /**
279       * Sets the given value as <tt>endDate</tt>.
280       * 
281       * @param millis
282       *            the value to set.
283       * 
284       * @throws IllegalArgumentException
285       *             if the record is {@code null}
286       */
287      public void setEndDateMillis(final long millis) {
288          if (millis <= this.startDate) {
289              throw new IllegalArgumentException(
290                      "The value for the end date must not be smaller than or equal to "
291                              + "the start date of the duration.");
292          }
293          this.endDate = millis;
294      }
295  
296      /**
297       * returns the reference date.
298       * 
299       * @return the reference date
300       */
301      @XmlTransient
302      public Date getReferenceDate() {
303          return new Date(this.startDate);
304      }
305  
306      /**
307       * returns the milliseconds.
308       * 
309       * @return the milliseconds
310       */
311      @XmlTransient
312      public long getMilliseconds() {
313          return this.endDate - this.startDate;
314      }
315  
316      /**
317       * returns the {@code TimeSpan} of the {@code Duration}.
318       * 
319       * @return the time span
320       */
321      @XmlTransient
322      public TimeSpan getTimeSpan() {
323          return new TimeSpan(this.endDate - this.startDate);
324      }
325  
326      /**
327       * sets the milliseconds for the time span.
328       * 
329       * @param ms
330       *            the time span
331       */
332      public void setMilliseconds(final long ms) {
333          this.endDate = this.startDate + ms;
334          normalize();
335      }
336  
337      /**
338       * returns the string value.
339       * 
340       * @return the string value
341       */
342      @XmlAttribute(name = "span")
343      public String getSpan() {
344          return new TimeSpan(this.endDate - this.startDate).toString();
345      }
346  
347      /**
348       * sets the time span.
349       * 
350       * @param s
351       *            the time span string
352       */
353      public void setSpan(final String s) {
354          this.endDate = this.startDate + new TimeSpan(s).getMilliseconds();
355          normalize();
356      }
357  
358      /**
359       * adds a given time span to this time span.
360       * 
361       * @param s
362       *            the time span to add
363       * @return this time span plus the given time span
364       */
365      public TimeSpan add(final TimeSpan s) {
366          setMilliseconds(this.endDate + s.getMilliseconds());
367          return new TimeSpan(this.endDate - this.startDate);
368      }
369  
370      /**
371       * subtracts a given time span from this time span.
372       * 
373       * @param s
374       *            the time span to add
375       * @return this time span minus the given time span
376       */
377      public TimeSpan subtract(final TimeSpan s) {
378          setMilliseconds(this.endDate - s.getMilliseconds());
379          return new TimeSpan(this.endDate - this.startDate);
380      }
381  
382      /**
383       * returns a new {@code Date} with the time span added to the {@code Date}.
384       * 
385       * @param d
386       *            the date
387       * @return the computed date
388       */
389      public Date addTo(final Date d) {
390          return new Date(d.getTime() + getMilliseconds());
391      }
392  
393      /**
394       * returns a new {@code Date} with the time span subtracted from the {@code
395       * Date}.
396       * 
397       * @param d
398       *            the date
399       * @return the computed date
400       */
401      public Date subtractFrom(final Date d) {
402          return new Date(d.getTime() - getMilliseconds());
403      }
404  
405      /**
406       * @return the hash code
407       */
408      @Override
409      public int hashCode() {
410          int hc = this.endDate.hashCode();
411          hc = hc * 31 + this.startDate.hashCode();
412          return hc;
413      }
414  
415      /**
416       * checks whether the given object is equal to the duration.
417       * 
418       * @param obj
419       *            the object to check against
420       * @return {@code true}, if and only if both start and end dates are equal
421       */
422      @Override
423      public boolean equals(final Object obj) {
424          if ((obj != null) && obj.getClass().equals(this.getClass())) {
425              Duration d = (Duration) obj;
426              return (this.getStartDate().equals(d.getStartDate()) && this
427                      .getEndDate().equals(d.getEndDate()));
428          }
429          return false;
430      }
431  
432      /**
433       * compare the time spans of the duration with the given time span.
434       * 
435       * @param o
436       *            the time span to compare with
437       * @return a negative integer, zero, or a positive integer as this time span
438       *         is less than, equal to, or greater than the specified time span.
439       * @see java.lang.Comparable#compareTo(java.lang.Object)
440       */
441      public int compareTo(final TimeSpan o) {
442          return new TimeSpan(this.endDate - this.startDate).compareTo(o);
443      }
444  
445      /**
446       * returns whether a given date is contained in the duration.
447       * 
448       * @param date
449       *            the date
450       * @return {@code true}, if and only if the given date is within the
451       *         duration. Otherwise {@code false}.
452       */
453      public boolean contains(final Date date) {
454          if (date == null) {
455              return false;
456          }
457          return contains(date.getTime());
458      }
459  
460      /**
461       * returns whether a given time stamp is contained in the duration.
462       * 
463       * @param time
464       *            the time stamp
465       * @return {@code true}, if and only if the given time stamp is within the
466       *         duration. Otherwise {@code false}.
467       */
468      public boolean contains(final long time) {
469          return (time >= getStartDate().getTime())
470                  && (time <= getEndDate().getTime());
471      }
472  
473      /**
474       * Checks if the given duration is within or equal the duration.
475       * 
476       * @param duration
477       *            the {@code Duration}
478       * 
479       * @return {@code true}, if and only if the given {@code Duration} is within
480       *         or equal the duration, {@code false} otherwise
481       */
482      public boolean contains(final Duration duration) {
483          if (duration == null) {
484              return false;
485          }
486          return (duration.getStartMillis() >= getStartMillis() && duration
487                  .getEndMillis() <= getEndMillis());
488      }
489  
490      /**
491       * Returns a new {@code Duration} with the values of the {@code Duration}.
492       * 
493       * @return a new {@code Duration} with the values of the {@code Duration}
494       * 
495       * @see org.torweg.pulse.util.time.IHasDuration#getDuration()
496       */
497      public Duration getDuration() {
498          return new Duration(this);
499      }
500  
501      /**
502       * Applies the given offset to the start/end time stamp of {@code Duration}
503       * if the offset is not {@code null}.
504       * 
505       * @param offset
506       *            the offset to scroll by
507       * 
508       * @return the modified {@code Duration}
509       */
510      public Duration applyOffset(final Long offset) {
511          if (offset == null) {
512              return this;
513          }
514          this.startDate += offset;
515          this.endDate += offset;
516          return this;
517      }
518  
519      /**
520       * Modifies the start/end time stamp of the {@code Duration} to match the
521       * start/end of the given {@code Period}.
522       * 
523       * @param period
524       *            {@code Period}
525       * 
526       * @return the modified {@code Duration}
527       */
528      public Duration levelBoundaries(final Period period) {
529          if (!period.equals(Period.UNDEFINED)) {
530              this.startDate = Period.Level.level(this.startDate, period,
531                      Period.Level.START).getTimeInMillis();
532              this.endDate = Period.Level.level(this.endDate, period,
533                      Period.Level.END).getTimeInMillis();
534          }
535          return this;
536      }
537  
538      /**
539       * @return a string-representation of the {@code Duration}
540       */
541      @Override
542      @SuppressWarnings("deprecation")
543      public String toString() {
544          return "{" + super.toString() + "[" + getStartDate().toGMTString()
545                  + " - " + getEndDate().toGMTString() + ", " + getSpan() + "]}";
546      }
547  
548  }
549