blob: 990174eab47f9b39380b220fb2d0450ce1b26417 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.ofbiz.base.util;
import java.io.Serializable;
import org.ofbiz.base.lang.SourceMonitored;
import org.ofbiz.base.lang.ThreadSafe;
import com.ibm.icu.util.Calendar;
/** An immutable representation of a period of time. */
@SourceMonitored
@ThreadSafe
@SuppressWarnings("serial")
public class TimeDuration implements Serializable, Comparable<TimeDuration> {
/** A <code>TimeDuration</code> instance that represents a zero time duration. */
public static final TimeDuration ZeroTimeDuration = new NullDuration();
protected final int milliseconds;
protected final int seconds;
protected final int minutes;
protected final int hours;
protected final int days;
protected final int months;
protected final int years;
/**
* @param years The number of years in this duration
* @param months The number of months in this duration
* @param days The number of days in this duration
* @param hours The number of hours in this duration
* @param minutes The number of minutes in this duration
* @param seconds The number of years in this duration
* @param milliseconds The number of milliseconds in this duration
*/
public TimeDuration(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds) {
this.milliseconds = milliseconds;
this.seconds = seconds;
this.minutes = minutes;
this.hours = hours;
this.days = days;
this.months = months;
this.years = years;
}
/** Elapsed time constructor. The time duration will be computed from the
* two <code>Calendar</code> instances.
* @param cal1
* @param cal2
*/
public TimeDuration(Calendar cal1, Calendar cal2) {
// set up Calendar objects
Calendar calStart;
Calendar calEnd;
int factor;
if (cal1.before(cal2)) {
factor = 1;
calStart = (Calendar) cal1.clone();
calEnd = (Calendar) cal2.clone();
} else {
factor = -1;
calStart = (Calendar) cal2.clone();
calEnd = (Calendar) cal1.clone();
}
/* Strategy: Using millisecond arithmetic alone will produce inaccurate results.
* Using a Calendar alone will take too long. So, we use millisecond arithmetic
* to get near the correct result, then zero in on the correct result using a
* Calendar.
*/
long targetMillis = calEnd.getTimeInMillis();
long deltaMillis = computeDeltaMillis(calStart.getTimeInMillis(), targetMillis);
// shortcut for equal dates
if (deltaMillis == 0) {
this.years = this.months = this.days = this.hours = this.minutes = this.seconds = this.milliseconds = 0;
return;
}
// compute elapsed years
long yearMillis = 86400000 * calStart.getLeastMaximum(Calendar.DAY_OF_YEAR);
float units = deltaMillis / yearMillis;
this.years = factor * advanceCalendar(calStart, calEnd, (int) units, Calendar.YEAR);
deltaMillis = computeDeltaMillis(calStart.getTimeInMillis(), targetMillis);
// compute elapsed months
long monthMillis = 86400000 * (calStart.getMaximum(Calendar.DAY_OF_MONTH) / 2);
units = deltaMillis / monthMillis;
this.months = factor * advanceCalendar(calStart, calEnd, (int) units, Calendar.MONTH);
deltaMillis = computeDeltaMillis(calStart.getTimeInMillis(), targetMillis);
// compute elapsed days
units = deltaMillis / 86400000;
this.days = factor * advanceCalendar(calStart, calEnd, (int) units, Calendar.DAY_OF_MONTH);
deltaMillis = computeDeltaMillis(calStart.getTimeInMillis(), targetMillis);
// compute elapsed hours
units = deltaMillis / 3600000;
this.hours = factor * advanceCalendar(calStart, calEnd, (int) units, Calendar.HOUR);
deltaMillis = computeDeltaMillis(calStart.getTimeInMillis(), targetMillis);
// compute elapsed minutes
units = deltaMillis / 60000;
this.minutes = factor * advanceCalendar(calStart, calEnd, (int) units, Calendar.MINUTE);
deltaMillis = computeDeltaMillis(calStart.getTimeInMillis(), targetMillis);
// compute elapsed seconds
units = deltaMillis / 1000;
this.seconds = factor * advanceCalendar(calStart, calEnd, (int) units, Calendar.SECOND);
deltaMillis = computeDeltaMillis(calStart.getTimeInMillis(), targetMillis);
this.milliseconds = factor * (int) deltaMillis;
}
private static long computeDeltaMillis(long start, long end) {
if (start < 0) {
return end + (-start);
}
return end - start;
}
private static int advanceCalendar(Calendar start, Calendar end, int units, int type) {
if (units >= 1) {
// Bother, the below needs explanation.
//
// If start has a day value of 31, and you add to the month,
// and the target month is not allowed to have 31 as the day
// value, then the day will be changed to a value that is in
// range. But, when the code needs to then subtract 1 from
// the month, because it has advanced to far, the day is *not*
// set back to the original value of 31.
//
// This bug can be triggered by having a duration of -1 day,
// then adding this duration to a calendar that represents 0
// milliseconds, then creating a new duration by using the 2
// Calendar constructor, with cal1 being 0, and cal2 being the
// new calendar that you added the duration to.
//
// To solve this problem, we make a temporary copy of the
// start calendar, and only modify it if we actually have to.
Calendar tmp = (Calendar) start.clone();
int tmpUnits = units;
tmp.add(type, tmpUnits);
while (tmp.after(end)) {
tmp.add(type, -1);
units--;
}
if (units != 0) {
start.add(type, units);
}
}
return units;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
try {
TimeDuration that = (TimeDuration) obj;
return this.years == that.years && this.months == that.months && this.days == that.days
&& this.hours == that.hours && this.minutes == that.minutes && this.seconds == that.seconds
&& this.milliseconds == that.milliseconds;
} catch (Exception e) {}
return false;
}
/** Returns a <code>String</code> formatted as
* years:months:days:hours:minutes:seconds:millseconds.
*/
@Override
public String toString() {
return this.years + ":" + this.months + ":" + this.days + ":" + this.hours + ":" + this.minutes + ":" + this.seconds + ":" + this.milliseconds;
}
@Override
public int compareTo(TimeDuration arg0) {
if (this == arg0) {
return 0;
}
int r = this.years - arg0.years;
if (r != 0) {
return r;
}
r = this.months - arg0.months;
if (r != 0) {
return r;
}
r = this.days - arg0.days;
if (r != 0) {
return r;
}
r = this.hours - arg0.hours;
if (r != 0) {
return r;
}
r = this.minutes - arg0.minutes;
if (r != 0) {
return r;
}
r = this.seconds - arg0.seconds;
if (r != 0) {
return r;
}
return this.milliseconds - arg0.milliseconds;
}
/** Returns <code>true</code> if this duration is negative.
*
* @return <code>true</code> if this duration is negative
*/
public boolean isNegative() {
return years < 0 || months < 0 || days < 0 || hours < 0 || minutes < 0 || seconds < 0 || milliseconds < 0;
}
/** Returns <code>true</code> if this duration is zero.
*
* @return <code>true</code> if this duration is zero
*/
public boolean isZero() {
return this.milliseconds == 0 && this.seconds == 0 &&
this.minutes == 0 && this.hours == 0 && this.days == 0 &&
this.months == 0 && this.years == 0;
}
/** Returns the milliseconds in this time duration. */
public int milliseconds() {
return this.milliseconds;
}
/** Returns the seconds in this time duration. */
public int seconds() {
return this.seconds;
}
/** Returns the minutes in this time duration. */
public int minutes() {
return this.minutes;
}
/** Returns the hours in this time duration. */
public int hours() {
return this.hours;
}
/** Returns the days in this time duration. */
public int days() {
return this.days;
}
/** Returns the months in this time duration. */
public int months() {
return this.months;
}
/** Returns the years in this time duration. */
public int years() {
return this.years;
}
/** Add this time duration to a Calendar instance. Returns the original
* Calendar instance.
* @param cal
* @return <code>cal</code>
*/
public Calendar addToCalendar(Calendar cal) {
cal.add(Calendar.MILLISECOND, this.milliseconds);
cal.add(Calendar.SECOND, this.seconds);
cal.add(Calendar.MINUTE, this.minutes);
cal.add(Calendar.HOUR, this.hours);
cal.add(Calendar.DAY_OF_MONTH, this.days);
cal.add(Calendar.MONTH, this.months);
cal.add(Calendar.YEAR, this.years);
return cal;
}
/** Returns a <code>TimeDuration</code> instance derived from an encoded
* <code>long</code> value. This method is intended to be used in tandem with the
* <code>toLong</code> method. <b>Note:</b> this
* method should not be used to calculate elapsed time - use the elapsed
* time constructor instead.
*
* @param duration An encoded duration
* @return A <code>TimeDuration</code> instance
*/
public static TimeDuration fromLong(long duration) {
if (duration == 0) {
return ZeroTimeDuration;
}
long units = duration / 0x757B12C00L;
int years = (int) units;
duration -= 0x757B12C00L * years;
units = duration / 0x9CA41900L;
int months = (int) units;
duration -= 0x9CA41900L * months;
units = duration / 86400000;
int days = (int) units;
duration -= 86400000 * (long) days;
units = duration / 3600000;
int hours = (int) units;
duration -= 3600000 * (long) hours;
units = duration / 60000;
int minutes = (int) units;
duration -= 60000 * (long) minutes;
units = duration / 1000;
int seconds = (int) units;
duration -= 1000 * (long) seconds;
return new TimeDuration(years, months, days, hours, minutes, seconds, (int) duration);
}
/** Returns a <code>TimeDuration</code> instance derived from a <code>Number</code>
* instance. If <code>number</code> is <code>null</code>,
* returns a zero <code>TimeDuration</code>.<p>This is a convenience method
* intended to be used with entity engine fields. Some duration fields are
* stored as a <code>Long</code>, while others are stored as a
* <code>Double</code>. This method will decode both types.</p>
*
* @param number A <code>Number</code> instance, can be <code>null</code>
* @return A <code>TimeDuration</code> instance
*/
public static TimeDuration fromNumber(Number number) {
return number == null ? ZeroTimeDuration : fromLong(number.longValue());
}
public static TimeDuration parseDuration(String duration) {
if (UtilValidate.isEmpty(duration)) {
return ZeroTimeDuration;
}
boolean isZero = true;
int[] intArray = {0, 0, 0, 0, 0, 0, 0};
int i = intArray.length - 1;
String[] strArray = duration.split(":", -1);
for (int s = strArray.length - 1; s >= 0; s--) {
if (UtilValidate.isNotEmpty(strArray[s])) {
intArray[i] = Integer.parseInt(strArray[s].trim());
if (intArray[i] != 0) {
isZero = false;
}
}
i--;
}
if (isZero) {
return ZeroTimeDuration;
}
return new TimeDuration(intArray[0], intArray[1], intArray[2],
intArray[3], intArray[4], intArray[5], intArray[6]);
}
/** Returns a <code>long</code> value derived from a <code>TimeDuration</code>
* instance. This method is intended to be used in tandem with the
* <code>fromLong</code> method.
*
* @param duration
* @return the duration encoded as a <code>long</code> value
*/
public static long toLong(TimeDuration duration) {
return
(0x757B12C00L * duration.years) +
(0x9CA41900L * duration.months) +
(86400000 * (long) duration.days) +
(3600000 * (long) duration.hours) +
(60000 * (long) duration.minutes) +
(1000 * (long) duration.seconds) +
duration.milliseconds;
}
protected static class NullDuration extends TimeDuration {
protected NullDuration() {
super(0, 0, 0, 0, 0, 0, 0);
}
@Override
public Calendar addToCalendar(Calendar cal) {
return cal;
}
@Override
public boolean isZero() {
return true;
}
}
}