/*
* $Id: Dur.java,v 1.9 2006/05/27 13:21:25 fortuna Exp $
*
* Created on 20/06/2005
*
* Copyright (c) 2005, Ben Fortuna
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* o Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* o Neither the name of Ben Fortuna nor the names of any other contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.fortuna.ical4j.model;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Represents a duration of time in iCalendar. Note that according to RFC2445
* durations represented in weeks are mutually exclusive of other duration
* fields.
* <pre>
* 4.3.6 Duration
*
* Value Name: DURATION
*
* Purpose: This value type is used to identify properties that contain
* a duration of time.
*
* Formal Definition: The value type is defined by the following
* notation:
*
* dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
*
* dur-date = dur-day [dur-time]
* dur-time = "T" (dur-hour / dur-minute / dur-second)
* dur-week = 1*DIGIT "W"
* dur-hour = 1*DIGIT "H" [dur-minute]
* dur-minute = 1*DIGIT "M" [dur-second]
* dur-second = 1*DIGIT "S"
* dur-day = 1*DIGIT "D"
* </pre>
*
* @author Ben Fortuna
*/
public class Dur implements Comparable, Serializable {
private static final long serialVersionUID = 5013232281547134583L;
private static final int DAYS_PER_WEEK = 7;
private static final int WEEKS_PER_YEAR = 52;
private static final int SECONDS_PER_MINUTE = 60;
private static final int MINUTES_PER_HOUR = 60;
private static final int HOURS_PER_DAY = 24;
private static final int DAYS_PER_YEAR = 365;
private Log log = LogFactory.getLog(Dur.class);
private boolean negative;
private int weeks;
private int days;
private int hours;
private int minutes;
private int seconds;
/**
* Constructs a new duration instance from a string representation.
* @param value a string representation of a duration
*/
public Dur(final String value) {
negative = false;
weeks = 0;
days = 0;
hours = 0;
minutes = 0;
seconds = 0;
String token = null;
String prevToken = null;
for (StringTokenizer t = new StringTokenizer(value, "+-PWDTHMS", true); t.hasMoreTokens();) {
prevToken = token;
token = t.nextToken();
if ("+".equals(token)) {
negative = false;
}
else if ("-".equals(token)) {
negative = true;
}
else if ("P".equals(token)) {
// does nothing..
if (log.isDebugEnabled()) {
log.debug("Redundant [P] token ignored.");
}
}
else if ("W".equals(token)) {
weeks = Integer.parseInt(prevToken);
}
else if ("D".equals(token)) {
days = Integer.parseInt(prevToken);
}
else if ("T".equals(token)) {
// does nothing..
if (log.isDebugEnabled()) {
log.debug("Redundant [T] token ignored.");
}
}
else if ("H".equals(token)) {
hours = Integer.parseInt(prevToken);
}
else if ("M".equals(token)) {
minutes = Integer.parseInt(prevToken);
}
else if ("S".equals(token)) {
seconds = Integer.parseInt(prevToken);
}
}
}
/**
* Constructs a new duration from the specified weeks.
* @param weeks a duration in weeks.
*/
public Dur(final int weeks) {
this.weeks = weeks;
this.days = 0;
this.hours = 0;
this.minutes = 0;
this.seconds = 0;
}
/**
* Constructs a new duration from the specified arguments.
* @param days duration in days
* @param hours duration in hours
* @param minutes duration in minutes
* @param seconds duration in seconds
*/
public Dur(final int days, final int hours, final int minutes, final int seconds) {
this.weeks = 0;
this.days = days;
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
}
/**
* Constructs a new duration representing the time between the two
* specified dates. The end date may precede the start date in order
* to represent a negative duration.
* @param start the start date of the duration
* @param end the end date of the duration
*/
public Dur(final Date start, final Date end) {
Calendar startCal = Calendar.getInstance();
startCal.setTime(start);
Calendar endCal = Calendar.getInstance();
endCal.setTime(end);
int yearDelta = endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR);
int weekDelta = endCal.get(Calendar.WEEK_OF_YEAR) - startCal.get(Calendar.WEEK_OF_YEAR);
int dayDelta = endCal.get(Calendar.DAY_OF_YEAR) - startCal.get(Calendar.DAY_OF_YEAR);
int hourDelta = endCal.get(Calendar.HOUR_OF_DAY) - startCal.get(Calendar.HOUR_OF_DAY);
int minuteDelta = endCal.get(Calendar.MINUTE) - startCal.get(Calendar.MINUTE);
int secondDelta = endCal.get(Calendar.SECOND) - startCal.get(Calendar.SECOND);
// test for negativity..
if (yearDelta < 0
|| (yearDelta == 0 && dayDelta < 0)
|| (yearDelta == 0 && dayDelta == 0 && hourDelta < 0)
|| (yearDelta == 0 && dayDelta == 0 && hourDelta == 0 && minuteDelta < 0)
|| (yearDelta == 0 && dayDelta == 0 && hourDelta == 0 && minuteDelta == 0 && secondDelta < 0)) {
negative = true;
}
if ((dayDelta % DAYS_PER_WEEK) + hourDelta + minuteDelta + secondDelta == 0) {
// weeks..
while (startCal.get(Calendar.YEAR) != endCal.get(Calendar.YEAR)) {
weekDelta = WEEKS_PER_YEAR * (endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR));
// if (weekDelta < 0) {
// negative = true;
// }
weeks += weekDelta;
startCal.add(Calendar.WEEK_OF_YEAR, weekDelta);
}
if (startCal.get(Calendar.WEEK_OF_YEAR) != endCal.get(Calendar.WEEK_OF_YEAR)) {
weekDelta = endCal.get(Calendar.WEEK_OF_YEAR) - startCal.get(Calendar.WEEK_OF_YEAR);
// if (weeks == 0 && weekDelta < 0) {
// negative = true;
// }
weeks += weekDelta;
}
weeks = Math.abs(weeks);
days = 0;
hours = 0;
minutes = 0;
seconds = 0;
}
else {
// seconds..
if (secondDelta > 0 && negative) {
startCal.add(Calendar.MINUTE, -1);
seconds = SECONDS_PER_MINUTE - secondDelta;
}
else if (secondDelta < 0 && !negative) {
startCal.add(Calendar.MINUTE, 1);
seconds = SECONDS_PER_MINUTE + secondDelta;
}
else {
seconds = Math.abs(secondDelta);
}
// minutes..
minuteDelta = endCal.get(Calendar.MINUTE) - startCal.get(Calendar.MINUTE);
if (minuteDelta > 0 && negative) {
startCal.add(Calendar.HOUR, -1);
minutes = MINUTES_PER_HOUR - minuteDelta;
}
else if (minuteDelta < 0 && !negative) {
startCal.add(Calendar.HOUR, 1);
minutes = MINUTES_PER_HOUR + minuteDelta;
}
else {
minutes = Math.abs(minuteDelta);
}
// hours..
hourDelta = endCal.get(Calendar.HOUR_OF_DAY) - startCal.get(Calendar.HOUR_OF_DAY);
if (hourDelta > 0 && negative) {
startCal.add(Calendar.DAY_OF_YEAR, -1);
hours = HOURS_PER_DAY - hourDelta;
}
else if (hourDelta < 0 && !negative) {
startCal.add(Calendar.DAY_OF_YEAR, 1);
hours = HOURS_PER_DAY + hourDelta;
}
else {
hours = Math.abs(hourDelta);
}
// days..
while (startCal.get(Calendar.YEAR) != endCal.get(Calendar.YEAR)) {
dayDelta = DAYS_PER_YEAR * (endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR));
days += dayDelta;
startCal.add(Calendar.DAY_OF_YEAR, dayDelta);
}
if (startCal.get(Calendar.DAY_OF_YEAR) != endCal.get(Calendar.DAY_OF_YEAR)) {
days += endCal.get(Calendar.DAY_OF_YEAR) - startCal.get(Calendar.DAY_OF_YEAR);
}
days = Math.abs(days);
weeks = 0;
}
}
/**
* Returns a date representing the end of this duration from
* the specified start date.
* @param start the date to start the duration
* @return the end of the duration as a date
*/
public final Date getTime(final Date start) {
Calendar cal = Calendar.getInstance();
cal.setTime(start);
if (isNegative()) {
cal.add(Calendar.WEEK_OF_YEAR, -weeks);
cal.add(Calendar.DAY_OF_WEEK, -days);
cal.add(Calendar.HOUR_OF_DAY, -hours);
cal.add(Calendar.MINUTE, -minutes);
cal.add(Calendar.SECOND, -seconds);
}
else {
cal.add(Calendar.WEEK_OF_YEAR, weeks);
cal.add(Calendar.DAY_OF_WEEK, days);
cal.add(Calendar.HOUR_OF_DAY, hours);
cal.add(Calendar.MINUTE, minutes);
cal.add(Calendar.SECOND, seconds);
}
return cal.getTime();
}
/**
* Provides a negation of this instance.
* @return a Dur instance that represents a negation of this instance
*/
public final Dur negate() {
Dur negated = new Dur(days, hours, minutes, seconds);
negated.weeks = weeks;
negated.negative = !negative;
return negated;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public final String toString() {
StringBuffer b = new StringBuffer();
if (negative) {
b.append('-');
}
b.append('P');
if (weeks > 0) {
b.append(weeks);
b.append('W');
}
else {
if (days > 0) {
b.append(days);
b.append('D');
}
if (hours > 0 || minutes > 0 || seconds > 0) {
b.append('T');
if (hours > 0) {
b.append(hours);
b.append('H');
}
if (minutes > 0) {
b.append(minutes);
b.append('M');
}
if (seconds > 0) {
b.append(seconds);
b.append('S');
}
}
}
return b.toString();
}
/**
* @param arg0
* @return
*/
public final int compareTo(final Object arg0) {
return compareTo((Dur) arg0);
}
/**
* Compares this duration with another.
* @param arg0
* @return
*/
public final int compareTo(final Dur arg0) {
if (isNegative() != arg0.isNegative()) {
// return Boolean.valueOf(isNegative()).compareTo(Boolean.valueOf(arg0.isNegative()));
// for pre-java 1.5 compatibility..
if (isNegative()) {
return Integer.MAX_VALUE;
}
else {
return Integer.MIN_VALUE;
}
}
else if (getWeeks() != arg0.getWeeks()) {
return getWeeks() - arg0.getWeeks();
}
else if (getDays() != arg0.getDays()) {
return getDays() - arg0.getDays();
}
else if (getHours() != arg0.getHours()) {
return getHours() - arg0.getHours();
}
else if (getMinutes() != arg0.getMinutes()) {
return getMinutes() - arg0.getMinutes();
}
return getSeconds() - arg0.getSeconds();
}
/**
* @return Returns the days.
*/
public final int getDays() {
return days;
}
/**
* @return Returns the hours.
*/
public final int getHours() {
return hours;
}
/**
* @return Returns the minutes.
*/
public final int getMinutes() {
return minutes;
}
/**
* @return Returns the negative.
*/
public final boolean isNegative() {
return negative;
}
/**
* @return Returns the seconds.
*/
public final int getSeconds() {
return seconds;
}
/**
* @return Returns the weeks.
*/
public final int getWeeks() {
return weeks;
}
}