blob: de0c687ed5250d24aff908794405ef92e9928959 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache
* XMLBeans", nor may "Apache" appear in their name, without prior
* written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
* ITS 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 2000-2003 BEA Systems
* Inc., <http://www.bea.com/>. For more information on the Apache Software
* Foundation, please see <http://www.apache.org/>.
*/
package org.apache.xmlbeans;
import java.math.BigDecimal;
/**
* Used to build {@link GDuration GDurations}.
*/
public class GDurationBuilder implements GDurationSpecification, java.io.Serializable
{
private static final long serialVersionUID = 1L;
private int _sign;
private int _CY;
private int _M;
private int _D;
private int _h;
private int _m;
private int _s;
private BigDecimal _fs;
/**
* Constructs an empty GDurationBuilder representing zero seconds.
*/
public GDurationBuilder()
{
_sign = +1;
_fs = GDate._zero;
}
/**
* Constructs a GDuration from a lexical
* representation.
*/
public GDurationBuilder(String s)
{
this(new GDuration(s));
}
/**
* Constructs a GDurationBuilder with the specified sign,
* year, month, day, hours, minutes, seconds, and optional
* fractional seconds.
*/
public GDurationBuilder(
int sign,
int year,
int month,
int day,
int hour,
int minute,
int second,
BigDecimal fraction)
{
if (sign != 1 && sign != -1)
throw new IllegalArgumentException();
_sign = sign;
_CY = year;
_M = month;
_D = day;
_h = hour;
_m = minute;
_s = second;
_fs = fraction == null ? GDate._zero : fraction;
}
/**
* Constructs a GDurationBuilder from another GDurationBuilderSpecification.
*/
public GDurationBuilder(GDurationSpecification gDuration)
{
_sign = gDuration.getSign();
_CY = gDuration.getYear();
_M = gDuration.getMonth();
_D = gDuration.getDay();
_h = gDuration.getHour();
_m = gDuration.getMinute();
_s = gDuration.getSecond();
_fs = gDuration.getFraction();
}
/**
* Builds another GDurationBuilder with the same value
* as this one.
*/
public Object clone()
{
return new GDurationBuilder(this);
}
/**
* Builds a GDuration from this GDurationBuilder.
*/
public GDuration toGDuration()
{
return new GDuration(this);
}
/**
* Adds to this duration. Does a fieldwise add, with no
* normalization.
*/
public void addGDuration(GDurationSpecification duration)
{
int sign = _sign * duration.getSign();
_add(duration, sign);
}
/**
* Subtracts from this duration. Does a fieldwise subtraction,
* with no normalization.
*/
public void subtractGDuration(GDurationSpecification duration)
{
int sign = -_sign * duration.getSign();
_add(duration, sign);
}
private void _add(GDurationSpecification duration, int sign)
{
_CY += sign * duration.getYear();
_M += sign * duration.getMonth();
_D += sign * duration.getDay();
_h += sign * duration.getHour();
_m += sign * duration.getMinute();
_s += sign * duration.getSecond();
if (duration.getFraction().signum() == 0)
return;
if (_fs.signum() == 0 && sign == 1)
_fs = duration.getFraction();
else
_fs = sign > 0 ?
_fs.add(duration.getFraction()) :
_fs.subtract(duration.getFraction());
}
/**
* Sets the sign.
*/
public final void setSign(int sign)
{
if (sign != 1 && sign != -1)
throw new IllegalArgumentException();
_sign = sign;
}
/**
* Sets the year component.
*/
public void setYear(int year)
{ _CY = year; }
/**
* Sets the month component.
*/
public void setMonth(int month)
{ _M = month; }
/**
* Sets the day component.
*/
public void setDay(int day)
{ _D = day; }
/**
* Sets the hour component.
*/
public void setHour(int hour)
{ _h = hour; }
/**
* Sets the minute component.
*/
public void setMinute(int minute)
{ _m = minute; }
/**
* Sets the second component.
*/
public void setSecond(int second)
{ _s = second; }
/**
* Sets the fraction-of-second component.
*/
public void setFraction(BigDecimal fraction)
{ _fs = fraction == null ? GDate._zero : fraction; }
/**
* All GDuration instances return true.
*/
public final boolean isImmutable()
{
return true;
}
/**
* Returns the sign of the duration: +1 is forwards
* and -1 is backwards in time.
* This value does not necessarily reflect the
* true direction of the duration if the duration
* is not normalized or not normalizable.
*/
public final int getSign()
{ return _sign; }
/**
* Gets the year component.
*/
public final int getYear()
{ return _CY; }
/**
* Gets the month-of-year component.
*/
public final int getMonth()
{ return _M; }
/**
* Gets the day-of-month component.
*/
public final int getDay()
{ return _D; }
/**
* Gets the hour-of-day component.
*/
public final int getHour()
{ return _h; }
/**
* Gets the minute-of-hour component.
*/
public final int getMinute()
{ return _m; }
/**
* Gets the second-of-minute component.
*/
public final int getSecond()
{ return _s; }
/**
* Gets the fraction-of-second. Range from 0 (inclusive) to 1 (exclusive).
*/
public BigDecimal getFraction()
{ return _fs; }
/**
* Returns true if all of the individual components
* of the duration are nonnegative.
*/
public boolean isValid()
{
return GDurationBuilder.isValidDuration(this);
}
/**
* Normalize a duration value. This ensures that months,
* hours, minutes, seconds, and fractions are positive and
* within the ranges 0..11, 0..23, 0..59, etc. Negative
* durations are indicated by a negative sign rather
* than negative components.
* <p>
* Most duration specifications can be normalized to
* valid durations with all positive components, but
* not all of them can.
* <p>
* The only situations which cannot be normalized are
* where the year/month and the day/hour/minute/second
* offsets are of opposite sign. Days cannot be carried
* into months since the length of a Gregorian month is
* variable depending on when the duration is applied.
* In these cases, this method normalizes the components
* so that "day" is the only negative component.
*/
public void normalize()
{
_normalizeImpl(true);
}
/**
* fQuotient(a, b) = the greatest integer less than or equal to a/b
*/
private static final long _fQuotient(long a, int b)
{
if ((a < 0) == (b < 0))
return a / b;
return -((b - a - 1) / b);
}
/**
* modulo(a, b) = a - fQuotient(a,b)*b
*/
private static final int _mod(long a, int b, long quotient)
{
return (int)(a - quotient*b) ;
}
/**
* Private implemenation of normalize. The flag is
* to facilitate this method calling itself without
* danger of infinite recursion.
*/
private void _normalizeImpl(boolean adjustSign)
{
long temp;
// months to years
if (_M < 0 || _M > 11)
{
temp = _M;
long ycarry = _fQuotient(temp, 12);
_M = _mod(temp, 12, ycarry);
_CY += ycarry;
}
long carry = 0;
// fractions to seconds
if (_fs != null && (_fs.signum() < 0 || _fs.compareTo(GDate._one) >= 0))
{
BigDecimal bdcarry = _fs.setScale(0, BigDecimal.ROUND_FLOOR);
_fs = _fs.subtract(bdcarry);
carry = bdcarry.intValue();
}
if (carry != 0 || _s < 0 || _s > 59 || _m < 0 || _m > 50 || _h < 0 || _h > 23)
{
// seconds
temp = _s + carry;
carry = _fQuotient(temp, 60);
_s = _mod(temp, 60, carry);
// minutes
temp = _m + carry;
carry = _fQuotient(temp, 60);
_m = _mod(temp, 60, carry);
// hours
temp = _h + carry;
carry = _fQuotient(temp, 24);
_h = _mod(temp, 24, carry);
_D += carry;
}
if (_CY == 0 && _M == 0 && _D == 0 && _h == 0 && _m == 0 && _s == 0 && (_fs == null || _fs.signum() == 0))
_sign = 1;
if (adjustSign && (_D < 0 || _CY < 0))
{
int sign = (_D <= 0 && (_CY < 0 || _CY == 0 && _M == 0)) ? -_sign : _getTotalSignSlowly();
if (sign == 2)
sign = (_CY < 0) ? -_sign : _sign;
if (sign == 0)
sign = 1;
if (sign != _sign)
{
_sign = sign;
_CY = -_CY;
_M = -_M;
_D = -_D;
_h = -_h;
_m = -_m;
_s = -_s;
if (_fs != null)
_fs = _fs.negate();
}
_normalizeImpl(false);
}
}
/* package */ static boolean isValidDuration(GDurationSpecification spec)
{
if (!(spec.getSign() == 1 || spec.getSign() == -1))
return false;
return (spec.getYear() >= 0 && spec.getMonth() >= 0 && spec.getDay() >= 0 &&
spec.getHour() >= 0 && spec.getMinute() >= 0 && spec.getSecond() >= 0 &&
spec.getFraction().signum() >= 0);
}
/**
* Comparison to another GDuration.
* <ul>
* <li>Returns -1 if this < date. (less-than)
* <li>Returns 0 if this == date. (equal)
* <li>Returns 1 if this > date. (greater-than)
* <li>Returns 2 if this <> date. (incomparable)
* </ul>
* Two instances are incomparable if they have different amounts
* of information.
*/
public final int compareToGDuration(GDurationSpecification duration)
{
return GDurationBuilder.compareDurations(this, duration);
}
/**
* The natural string representation of the duration.
* <p>
* Any components that are zero are omitted. Note that if the duration
* is invalid, i.e., it has negative components, those negative
* components are serialized out here. To check for validity, use
* the isValid() method; and to normalize most durations to a valid
* form use the normalize() method.
*/
public String toString()
{
return GDurationBuilder.formatDuration(this);
}
/* package */ static int compareDurations(GDurationSpecification d1, GDurationSpecification d2)
{
// first do an all-fields check
if (d1.getFraction().signum() == 0 && d2.getFraction().signum() == 0)
{
int s1 = d1.getSign();
int s2 = d2.getSign();
long month1 = s1 * ((long)d1.getYear() * 12 + d1.getMonth());
long month2 = s2 * ((long)d2.getYear() * 12 + d2.getMonth());
long sec1 = s1 * ((((long)d1.getDay() * 24 + d1.getHour()) * 60 + d1.getMinute()) * 60 + d1.getSecond());
long sec2 = s2 * ((((long)d2.getDay() * 24 + d2.getHour()) * 60 + d2.getMinute()) * 60 + d2.getSecond());
if (month1 == month2)
{
if (sec1 == sec2)
return 0;
if (sec1 < sec2)
return -1;
if (sec1 > sec2)
return 1;
}
if (month1 < month2 && sec1 - sec2 < 28 * 24 * 60 * 60)
return -1;
if (month1 > month2 && sec2 - sec1 < 28 * 24 * 60 * 60)
return 1;
}
// the answer isn't obvious, so then do a total-sign check
GDurationBuilder diff = new GDurationBuilder(d1);
diff.subtractGDuration(d2);
return diff._getTotalSignSlowly();
}
/**
* Per schema spec, comparison of durations is simply done
* by calculating adding the duration to these four dates and
* comparing the results. If the results are ambiguous, the
* answer is "incomparable".
*/
private static final GDate[] _compDate = new GDate[]
{
new GDate(1696, 9, 1, 0, 0, 0, null, 0, 0, 0),
new GDate(1697, 2, 1, 0, 0, 0, null, 0, 0, 0),
new GDate(1903, 3, 1, 0, 0, 0, null, 0, 0, 0),
new GDate(1903, 7, 1, 0, 0, 0, null, 0, 0, 0)
};
/**
* This returns the total sign of the duration, +1
* if the duration moves forward in time, -1 if the
* duration moves backwards in time, 0 if the duration
* is zero-length, and 2 if the duration may be positive
* or negative depending on the date.
*
* (For example, one month minus 30 days is indeterminate).
*/
private int _getTotalSignSlowly()
{
int pos = 0;
int neg = 0;
int zer = 0;
GDateBuilder enddate = new GDateBuilder();
for (int i = 0; i < _compDate.length; i++)
{
enddate.setGDate(_compDate[i]);
enddate.addGDuration(this);
switch (enddate.compareToGDate(_compDate[i]))
{
case -1:
neg++; break;
case 0:
zer++; break;
case 1:
pos++; break;
}
}
if (pos == _compDate.length)
return +1;
if (neg == _compDate.length)
return -1;
if (zer == _compDate.length)
return 0;
return 2;
}
/* package */ static String formatDuration(GDurationSpecification duration)
{
// Sign+P: (-)?P
// Year: (?:(\d+)Y)?
// Month: (?:(\d+)M)?
// Day: (?:(\d+)D)?
// Time: (?:(T)
// Hours: (?:(\d+)H)?
// Minutes: (?:(\d+)M)?
// Seconds: (?:(\d+(?:\.\d*)?|(?:.\d+)S)?
StringBuffer message = new StringBuffer(30);
if (duration.getSign() < 0)
message.append('-');
message.append('P');
if (duration.getYear() != 0)
{
message.append(duration.getYear());
message.append('Y');
}
if (duration.getMonth() != 0)
{
message.append(duration.getMonth());
message.append('M');
}
if (duration.getDay() != 0)
{
message.append(duration.getDay());
message.append('D');
}
if (duration.getHour() != 0 || duration.getMinute() != 0 || duration.getSecond() != 0 ||
(duration.getFraction().signum() != 0))
{
message.append('T');
}
if (duration.getHour() != 0)
{
message.append(duration.getHour());
message.append('H');
}
if (duration.getMinute() != 0)
{
message.append(duration.getMinute());
message.append('M');
}
if (duration.getFraction().signum() != 0)
{
BigDecimal s = duration.getFraction();
if (duration.getSecond() != 0)
s = s.add(BigDecimal.valueOf(duration.getSecond()));
message.append(s);
message.append('S');
}
else if (duration.getSecond() != 0)
{
message.append(duration.getSecond());
message.append('S');
}
else if (message.length() <= 2)
// Specify zero seconds if everything was 0
message.append("T0S");
return message.toString();
}
}