Tue, 10 Feb 2015 18:12:00 +0100
Import initial revisions of existing project AndroidCaldavSyncAdapater,
forked from upstream repository at 27e8a0f8495c92e0780d450bdf0c7cec77a03a55.
1 /**
2 * Copyright (c) 2012, Ben Fortuna
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * o Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * o Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * o Neither the name of Ben Fortuna nor the names of any other contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 package net.fortuna.ical4j.model;
34 import java.io.IOException;
35 import java.io.Serializable;
36 import java.util.Calendar;
37 import java.util.Date;
38 import java.util.StringTokenizer;
39 import net.fortuna.ical4j.util.Dates;
41 import org.apache.commons.lang.builder.HashCodeBuilder;
43 /**
44 * $Id$
45 *
46 * Created on 20/06/2005
47 *
48 * Represents a duration of time in iCalendar. Note that according to RFC2445 durations represented in weeks are
49 * mutually exclusive of other duration fields.
50 *
51 * <pre>
52 * 4.3.6 Duration
53 *
54 * Value Name: DURATION
55 *
56 * Purpose: This value type is used to identify properties that contain
57 * a duration of time.
58 *
59 * Formal Definition: The value type is defined by the following
60 * notation:
61 *
62 * dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
63 *
64 * dur-date = dur-day [dur-time]
65 * dur-time = "T" (dur-hour / dur-minute / dur-second)
66 * dur-week = 1*DIGIT "W"
67 * dur-hour = 1*DIGIT "H" [dur-minute]
68 * dur-minute = 1*DIGIT "M" [dur-second]
69 * dur-second = 1*DIGIT "S"
70 * dur-day = 1*DIGIT "D"
71 * </pre>
72 *
73 * @author Ben Fortuna
74 */
75 public class Dur implements Comparable, Serializable {
77 private static final long serialVersionUID = 5013232281547134583L;
79 private static final int DAYS_PER_WEEK = 7;
81 private static final int SECONDS_PER_MINUTE = 60;
83 private static final int MINUTES_PER_HOUR = 60;
85 private static final int HOURS_PER_DAY = 24;
87 private static final int DAYS_PER_YEAR = 365;
89 private boolean negative;
91 private int weeks;
93 private int days;
95 private int hours;
97 private int minutes;
99 private int seconds;
101 /**
102 * Constructs a new duration instance from a string representation.
103 * @param value a string representation of a duration
104 */
105 public Dur(final String value) {
106 negative = false;
107 weeks = 0;
108 days = 0;
109 hours = 0;
110 minutes = 0;
111 seconds = 0;
113 String token = null;
114 String prevToken = null;
116 final StringTokenizer t = new StringTokenizer(value, "+-PWDTHMS", true);
117 while (t.hasMoreTokens()) {
118 prevToken = token;
119 token = t.nextToken();
121 if ("+".equals(token)) {
122 negative = false;
123 }
124 else if ("-".equals(token)) {
125 negative = true;
126 }
127 else if ("P".equals(token)) {
128 // does nothing..
129 }
130 else if ("W".equals(token)) {
131 weeks = Integer.parseInt(prevToken);
132 }
133 else if ("D".equals(token)) {
134 days = Integer.parseInt(prevToken);
135 }
136 else if ("T".equals(token)) {
137 // does nothing..
138 }
139 else if ("H".equals(token)) {
140 hours = Integer.parseInt(prevToken);
141 }
142 else if ("M".equals(token)) {
143 minutes = Integer.parseInt(prevToken);
144 }
145 else if ("S".equals(token)) {
146 seconds = Integer.parseInt(prevToken);
147 }
148 }
149 }
151 /**
152 * Constructs a new duration from the specified weeks.
153 * @param weeks a duration in weeks.
154 */
155 public Dur(final int weeks) {
156 this.weeks = Math.abs(weeks);
157 this.days = 0;
158 this.hours = 0;
159 this.minutes = 0;
160 this.seconds = 0;
161 this.negative = weeks < 0;
162 }
164 /**
165 * Constructs a new duration from the specified arguments.
166 * @param days duration in days
167 * @param hours duration in hours
168 * @param minutes duration in minutes
169 * @param seconds duration in seconds
170 */
171 public Dur(final int days, final int hours, final int minutes,
172 final int seconds) {
174 if (!(days >= 0 && hours >= 0 && minutes >= 0 && seconds >= 0)
175 && !(days <= 0 && hours <= 0 && minutes <= 0 && seconds <= 0)) {
177 throw new IllegalArgumentException("Invalid duration representation");
178 }
180 this.weeks = 0;
181 this.days = Math.abs(days);
182 this.hours = Math.abs(hours);
183 this.minutes = Math.abs(minutes);
184 this.seconds = Math.abs(seconds);
186 this.negative = days < 0 || hours < 0 || minutes < 0 || seconds < 0;
187 }
189 /**
190 * Constructs a new duration representing the time between the two specified dates. The end date may precede the
191 * start date in order to represent a negative duration.
192 * @param date1 the first date of the duration
193 * @param date2 the second date of the duration
194 */
195 public Dur(final Date date1, final Date date2) {
197 Date start = null;
198 Date end = null;
200 // Negative range? (start occurs after end)
201 negative = date1.compareTo(date2) > 0;
202 if (negative) {
203 // Swap the dates (which eliminates the need to bother with
204 // negative after this!)
205 start = date2;
206 end = date1;
207 }
208 else {
209 start = date1;
210 end = date2;
211 }
213 final Calendar startCal;
214 if (start instanceof net.fortuna.ical4j.model.Date) {
215 startCal = Dates.getCalendarInstance((net.fortuna.ical4j.model.Date)start);
216 } else {
217 startCal = Calendar.getInstance();
218 }
219 startCal.setTime(start);
220 final Calendar endCal = Calendar.getInstance(startCal.getTimeZone());
221 endCal.setTime(end);
223 // Init our duration interval (which is in units that evolve as we
224 // compute, below)
225 int dur = 0;
227 // Count days to get to the right year (loop in the very rare chance
228 // that a leap year causes us to come up short)
229 int nYears = endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR);
230 while (nYears > 0) {
231 startCal.add(Calendar.DATE, DAYS_PER_YEAR * nYears);
232 dur += DAYS_PER_YEAR * nYears;
233 nYears = endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR);
234 }
236 // Count days to get to the right day
237 dur += endCal.get(Calendar.DAY_OF_YEAR)
238 - startCal.get(Calendar.DAY_OF_YEAR);
240 // Count hours to get to right hour
241 dur *= HOURS_PER_DAY; // days -> hours
242 dur += endCal.get(Calendar.HOUR_OF_DAY)
243 - startCal.get(Calendar.HOUR_OF_DAY);
245 // ... to the right minute
246 dur *= MINUTES_PER_HOUR; // hours -> minutes
247 dur += endCal.get(Calendar.MINUTE) - startCal.get(Calendar.MINUTE);
249 // ... and second
250 dur *= SECONDS_PER_MINUTE; // minutes -> seconds
251 dur += endCal.get(Calendar.SECOND) - startCal.get(Calendar.SECOND);
253 // Now unwind our units
254 seconds = dur % SECONDS_PER_MINUTE;
255 dur = dur / SECONDS_PER_MINUTE; // seconds -> minutes (drop remainder seconds)
256 minutes = dur % MINUTES_PER_HOUR;
257 dur /= MINUTES_PER_HOUR; // minutes -> hours (drop remainder minutes)
258 hours = dur % HOURS_PER_DAY;
259 dur /= HOURS_PER_DAY; // hours -> days (drop remainder hours)
260 days = dur;
261 weeks = 0;
263 // Special case for week-only representation
264 if (seconds == 0 && minutes == 0 && hours == 0
265 && (days % DAYS_PER_WEEK) == 0) {
266 weeks = days / DAYS_PER_WEEK;
267 days = 0;
268 }
269 }
271 /**
272 * Returns a date representing the end of this duration from the specified start date.
273 * @param start the date to start the duration
274 * @return the end of the duration as a date
275 */
276 public final Date getTime(final Date start) {
277 final Calendar cal;
278 if (start instanceof net.fortuna.ical4j.model.Date) {
279 cal = Dates.getCalendarInstance((net.fortuna.ical4j.model.Date)start);
280 } else {
281 cal = Calendar.getInstance();
282 }
284 cal.setTime(start);
285 if (isNegative()) {
286 cal.add(Calendar.WEEK_OF_YEAR, -weeks);
287 cal.add(Calendar.DAY_OF_WEEK, -days);
288 cal.add(Calendar.HOUR_OF_DAY, -hours);
289 cal.add(Calendar.MINUTE, -minutes);
290 cal.add(Calendar.SECOND, -seconds);
291 }
292 else {
293 cal.add(Calendar.WEEK_OF_YEAR, weeks);
294 cal.add(Calendar.DAY_OF_WEEK, days);
295 cal.add(Calendar.HOUR_OF_DAY, hours);
296 cal.add(Calendar.MINUTE, minutes);
297 cal.add(Calendar.SECOND, seconds);
298 }
299 return cal.getTime();
300 }
302 /**
303 * Provides a negation of this instance.
304 * @return a Dur instance that represents a negation of this instance
305 */
306 public final Dur negate() {
307 final Dur negated = new Dur(days, hours, minutes, seconds);
308 negated.weeks = weeks;
309 negated.negative = !negative;
310 return negated;
311 }
313 /**
314 * Add two durations. Durations may only be added if they are both positive
315 * or both negative durations.
316 * @param duration the duration to add to this duration
317 * @return a new instance representing the sum of the two durations.
318 */
319 public final Dur add(final Dur duration) {
320 if ((!isNegative() && duration.isNegative())
321 || (isNegative() && !duration.isNegative())) {
323 throw new IllegalArgumentException(
324 "Cannot add a negative and a positive duration");
325 }
327 Dur sum = null;
328 if (weeks > 0 && duration.weeks > 0) {
329 sum = new Dur(weeks + duration.weeks);
330 }
331 else {
332 int daySum = (weeks > 0) ? weeks * DAYS_PER_WEEK + days : days;
333 int hourSum = hours;
334 int minuteSum = minutes;
335 int secondSum = seconds;
337 if ((secondSum + duration.seconds) / SECONDS_PER_MINUTE > 0) {
338 minuteSum += (secondSum + duration.seconds) / SECONDS_PER_MINUTE;
339 secondSum = (secondSum + duration.seconds) % SECONDS_PER_MINUTE;
340 }
341 else {
342 secondSum += duration.seconds;
343 }
345 if ((minuteSum + duration.minutes) / MINUTES_PER_HOUR > 0) {
346 hourSum += (minuteSum + duration.minutes) / MINUTES_PER_HOUR;
347 minuteSum = (minuteSum + duration.minutes) % MINUTES_PER_HOUR;
348 }
349 else {
350 minuteSum += duration.minutes;
351 }
353 if ((hourSum + duration.hours) / HOURS_PER_DAY > 0) {
354 daySum += (hourSum + duration.hours) / HOURS_PER_DAY;
355 hourSum = (hourSum + duration.hours) % HOURS_PER_DAY;
356 }
357 else {
358 hourSum += duration.hours;
359 }
361 daySum += (duration.weeks > 0) ? duration.weeks * DAYS_PER_WEEK
362 + duration.days : duration.days;
364 sum = new Dur(daySum, hourSum, minuteSum, secondSum);
365 }
366 sum.negative = negative;
367 return sum;
368 }
370 /**
371 * {@inheritDoc}
372 */
373 public final String toString() {
374 final StringBuffer b = new StringBuffer();
375 if (negative) {
376 b.append('-');
377 }
378 b.append('P');
379 if (weeks > 0) {
380 b.append(weeks);
381 b.append('W');
382 }
383 else {
384 if (days > 0) {
385 b.append(days);
386 b.append('D');
387 }
388 if (hours > 0 || minutes > 0 || seconds > 0) {
389 b.append('T');
390 if (hours > 0) {
391 b.append(hours);
392 b.append('H');
393 }
394 if (minutes > 0) {
395 b.append(minutes);
396 b.append('M');
397 }
398 if (seconds > 0) {
399 b.append(seconds);
400 b.append('S');
401 }
402 }
403 // handle case of zero length duration
404 if ((hours + minutes + seconds + days + weeks) == 0) {
405 b.append("T0S");
406 }
407 }
408 return b.toString();
409 }
411 /**
412 * {@inheritDoc}
413 */
414 public final int compareTo(final Object arg0) {
415 return compareTo((Dur) arg0);
416 }
418 /**
419 * Compares this duration with another, acording to their length.
420 * @param arg0 another duration instance
421 * @return a postive value if this duration is longer, zero if the duration
422 * lengths are equal, otherwise a negative value
423 */
424 public final int compareTo(final Dur arg0) {
425 int result;
426 if (isNegative() != arg0.isNegative()) {
427 // return Boolean.valueOf(isNegative()).compareTo(Boolean.valueOf(arg0.isNegative()));
428 // for pre-java 1.5 compatibility..
429 if (isNegative()) {
430 return Integer.MIN_VALUE;
431 }
432 else {
433 return Integer.MAX_VALUE;
434 }
435 }
436 else if (getWeeks() != arg0.getWeeks()) {
437 result = getWeeks() - arg0.getWeeks();
438 }
439 else if (getDays() != arg0.getDays()) {
440 result = getDays() - arg0.getDays();
441 }
442 else if (getHours() != arg0.getHours()) {
443 result = getHours() - arg0.getHours();
444 }
445 else if (getMinutes() != arg0.getMinutes()) {
446 result = getMinutes() - arg0.getMinutes();
447 }
448 else {
449 result = getSeconds() - arg0.getSeconds();
450 }
451 // invert sense of all tests if both durations are negative
452 if (isNegative()) {
453 return -result;
454 }
455 else {
456 return result;
457 }
458 }
460 /**
461 * {@inheritDoc}
462 */
463 public boolean equals(final Object obj) {
464 if (obj instanceof Dur) {
465 return ((Dur) obj).compareTo(this) == 0;
466 }
467 return super.equals(obj);
468 }
470 /**
471 * {@inheritDoc}
472 */
473 public int hashCode() {
474 return new HashCodeBuilder().append(weeks).append(days).append(
475 hours).append(minutes).append(seconds).append(negative).toHashCode();
476 }
478 /**
479 * @return Returns the days.
480 */
481 public final int getDays() {
482 return days;
483 }
485 /**
486 * @return Returns the hours.
487 */
488 public final int getHours() {
489 return hours;
490 }
492 /**
493 * @return Returns the minutes.
494 */
495 public final int getMinutes() {
496 return minutes;
497 }
499 /**
500 * @return Returns the negative.
501 */
502 public final boolean isNegative() {
503 return negative;
504 }
506 /**
507 * @return Returns the seconds.
508 */
509 public final int getSeconds() {
510 return seconds;
511 }
513 /**
514 * @return Returns the weeks.
515 */
516 public final int getWeeks() {
517 return weeks;
518 }
520 /**
521 * @param stream
522 * @throws IOException
523 * @throws ClassNotFoundException
524 */
525 private void readObject(final java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
526 stream.defaultReadObject();
527 }
528 }