/*
* @(#)$Id: BigDateTimeValueType.java,v 1.14 2002/09/08 20:18:55 kk122374 Exp $
*
* Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the proprietary information of Sun Microsystems, Inc.
* Use is subject to license terms.
*
*/
package com.sun.msv.datatype.xsd.datetime;
import java.math.BigInteger;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.SimpleTimeZone;
import com.sun.msv.datatype.xsd.Comparator;
/**
* DateTimeValueType object that can hold all lexically valid dateTime value.
*
* This class provides:
* <ol>
* <li> Unlimited digits for year (e.g., "year 9999999999999999999999")
* <li> Unlimited digits for fraction of second (e.g. 0.00000000000001 sec)
* </ol>
*
* To provide methods that can change date/time values, normalize method
* should be modified too.
*
* @author Kohsuke KAWAGUCHI
*/
public class BigDateTimeValueType implements IDateTimeValueType {
/** year value.
* this variable is null if no year is specified.
*
* Since there is no year 0, value 0 indicates year -1. -1 indicates -2, and so forth.
*/
private BigInteger year;
public BigInteger getYear() { return year; }
/** month (always between 0 and 11)
* this variable is null if no year is specified
*/
private Integer month;
public Integer getMonth() { return month; }
/** day (always normalized)
* this variable is null if no year is specified
*/
private Integer day;
public Integer getDay() { return day; }
/** hour (always between 0 and 23)
* this variable is null if no year is specified
*/
private Integer hour;
public Integer getHour() { return hour; }
/** minute (always between 0 and 59)
* this variable is null if no year is specified
*/
private Integer minute;
public Integer getMinute() { return minute; }
/** second (always in [0,60) )
* this variable is null if no year is specified
*/
private BigDecimal second;
public BigDecimal getSecond() { return second; }
/** time zone specifier */
private TimeZone zone;
public TimeZone getTimeZone() { return zone; }
/** creates an instance with the specified BigDateTimeValueType,
* with modified time zone.
*
* created object shares its date/time value component with the original one,
* so special care is necessary not to mutate those values.
*/
public BigDateTimeValueType( BigDateTimeValueType base, TimeZone newTimeZone ) {
this( base.year, base.month, base.day, base.hour, base.minute, base.second, newTimeZone );
}
public BigDateTimeValueType( BigInteger year, int month, int day, int hour, int minute, BigDecimal second, TimeZone timeZone ) {
this( year, new Integer(month), new Integer(day), new Integer(hour), new Integer(minute), second, timeZone );
}
public BigDateTimeValueType( BigInteger year, Integer month, Integer day, Integer hour, Integer minute, BigDecimal second, TimeZone timeZone ) {
this.year = year;
this.month = month;
this.day = day;
this.hour = hour;
this.minute = minute;
this.second = second;
this.zone = timeZone;
}
public BigDateTimeValueType() {}
public BigDateTimeValueType getBigValue() {
return this;
}
public boolean equals( Object o ) {
return equals( (IDateTimeValueType)o );
}
public boolean equals( IDateTimeValueType rhs ) {
if(!(rhs instanceof BigDateTimeValueType))
rhs = rhs.getBigValue();
return equals( this, (BigDateTimeValueType)rhs );
}
public boolean equals( BigDateTimeValueType lhs, BigDateTimeValueType rhs ) {
return compare(lhs,rhs)==Comparator.EQUAL;
}
/** gets a human-readable representation of this object.
*
* return value is not intended to be compliant with the canonical representation
* of "dateTime" type.
*/
public String toString() {
StringBuffer r = new StringBuffer();
if( year!=null ) r.append(year);
r.append('-');
if( month!=null ) r.append(month.intValue()+1);
r.append('-');
if( day!=null ) r.append(day.intValue()+1);
r.append('T');
if( hour!=null ) r.append(hour);
r.append(':');
if( minute!=null ) r.append(minute);
r.append(':');
if( second!=null ) r.append(second);
if( zone!=null ) {
if( zone.minutes==0 ) r.append('Z');
else {
if( zone.minutes<0 ) r.append('-');
else r.append('+');
r.append(Math.abs(zone.minutes/60));
r.append(Math.abs(zone.minutes%60));
}
}
return new String(r);
}
public int hashCode() {
// to be consistent with equals method, we have to normalize
// value before computation.
BigDateTimeValueType n = (BigDateTimeValueType)this.normalize();
return Util.objHashCode(n.year) + Util.objHashCode(n.month) + Util.objHashCode(n.day)
+ Util.objHashCode(n.hour) + Util.objHashCode(n.minute) + Util.objHashCode(n.second)
+ Util.objHashCode(n.zone);
}
public int compare( IDateTimeValueType o ) {
if(!(o instanceof BigDateTimeValueType) )
o = o.getBigValue();
return compare( this, (BigDateTimeValueType)o );
}
/**
* compares two BigDateTimeValueType and returns one of the constant defined in
* {@link Comparator}.
*
* Order-relation between two dateTime is defined in
* http://www.w3.org/TR/xmlschema-2/#dateTime
*/
protected static int compare( BigDateTimeValueType lhs, BigDateTimeValueType rhs ) {
lhs = (BigDateTimeValueType)lhs.normalize();
rhs = (BigDateTimeValueType)rhs.normalize();
if( (lhs.zone!=null && rhs.zone!=null) || (lhs.zone==null && rhs.zone==null) ) {
if(!Util.objEqual(lhs.year,rhs.year)) return Util.objCompare(lhs.year,rhs.year);
if(!Util.objEqual(lhs.month,rhs.month)) return Util.objCompare(lhs.month,rhs.month);
if(!Util.objEqual(lhs.day,rhs.day)) return Util.objCompare(lhs.day,rhs.day);
if(!Util.objEqual(lhs.hour,rhs.hour)) return Util.objCompare(lhs.hour,rhs.hour);
if(!Util.objEqual(lhs.minute,rhs.minute)) return Util.objCompare(lhs.minute,rhs.minute);
if(!Util.objEqual(lhs.second,rhs.second)) return Util.objCompare(lhs.second,rhs.second);
return Comparator.EQUAL;
}
if( lhs.zone==null ) {
int r;
r = compare( (BigDateTimeValueType)new BigDateTimeValueType(lhs,Util.timeZoneNeg14).normalize(), rhs );
if( r==Comparator.EQUAL || r==Comparator.LESS )
return Comparator.LESS; // lhs < rhs
r = compare( (BigDateTimeValueType)new BigDateTimeValueType(lhs,Util.timeZonePos14).normalize(), rhs );
if( r==Comparator.EQUAL || r==Comparator.GREATER )
return Comparator.GREATER; // lhs > rhs
return Comparator.UNDECIDABLE; // lhs <> rhs
} else {
int r;
r = compare( lhs, (BigDateTimeValueType)new BigDateTimeValueType(rhs,Util.timeZonePos14) );
if( r==Comparator.EQUAL || r==Comparator.LESS )
return Comparator.LESS; // lhs < rhs
r = compare( lhs, (BigDateTimeValueType)new BigDateTimeValueType(rhs,Util.timeZoneNeg14) );
if( r==Comparator.EQUAL || r==Comparator.GREATER )
return Comparator.GREATER; // lhs > rhs
return Comparator.UNDECIDABLE; // lhs <> rhs
}
}
/** normalized DateTimeValue of this object.
*
* once when the normalized value is computed,
* the value is kept in this varible so that
* successive calls to normalize method need not
* have to compute it again.
*
* This approach assumes that modification to the date/time component
* will never be made.
*/
private IDateTimeValueType normalizedValue = null;
public IDateTimeValueType normalize() {
// see if this object is already normalized
if( zone==null || zone.minutes==0 ) return this;
// see if there is cached normalized value
if( normalizedValue!=null ) return normalizedValue;
// for normalization to work correctly,
// we have to extend the precision.
// otherwise, addition will remove unspecified fields,
// and the result becomes incorrect. For example,
// --03-- + (+08:00) --> --02--
// which is apparently not what we wanted.
// update: it seems to me that this unintuitive behavior is
// not going to be corrected in XML Schema 1.0
// faster performance can be achieved by writing optimized inline addition code.
normalizedValue =
this.add( BigTimeDurationValueType.fromMinutes(-zone.minutes) );
((BigDateTimeValueType)normalizedValue).zone=TimeZone.GMT;
return normalizedValue;
}
private static BigInteger nullAs0( BigInteger o ) {
if(o!=null) return o;
else return BigInteger.ZERO;
}
private static BigDecimal nullAs0( BigDecimal o ) {
if(o!=null) return o;
else return Util.decimal0;
}
private static BigInteger[] divideAndRemainder( BigInteger x1, BigInteger x2 ) {
BigInteger[] r = x1.divideAndRemainder(x2);
if(r[1].signum()<0) {
// in BigInteger, -2/10 = -2, which is not preferable.
// we want -2/10 to be 8, with quodrant of -1.
r[1] = r[1].add(x2);
r[0] = r[0].subtract(BigInteger.ONE);
}
return r;
}
public IDateTimeValueType add( ITimeDurationValueType _rhs ) {
if( _rhs instanceof BigTimeDurationValueType ) {
// big + big
BigTimeDurationValueType rhs = (BigTimeDurationValueType)_rhs;
BigInteger[] quoAndMod = divideAndRemainder(
Util.int2bi(this.month).add(rhs.month),
Util.the12);
BigInteger oyear; int omonth;
int ohour, ominute; BigDecimal osecond;
omonth = quoAndMod[1].intValue();
oyear = quoAndMod[0].add(nullAs0(this.year)).add(nullAs0(rhs.year));
BigDecimal sec = nullAs0(this.second).add(nullAs0(rhs.second));
// quo = floor((this.second+rhs.second)/60)
// = floor( (this.second+rhs.second)*10^scale / (60*10^scale) )
// = (this.second+rhs.second).unscaled / 60*10^scale
quoAndMod = divideAndRemainder(
sec.unscaledValue(),
Util.the60.multiply(Util.the10.pow(sec.scale())) );
osecond = new BigDecimal(quoAndMod[1], sec.scale());
quoAndMod = divideAndRemainder(
quoAndMod[0].add(Util.int2bi(this.minute)).add(rhs.minute), Util.the60 );
ominute = quoAndMod[1].intValue();
quoAndMod = divideAndRemainder(
quoAndMod[0].add(Util.int2bi(this.hour)).add(rhs.hour), Util.the24 );
ohour = quoAndMod[1].intValue();
int tempDays;
int md = Util.maximumDayInMonthFor(oyear,omonth);
{
int dayValue = (this.day!=null)?this.day.intValue():0;
if( dayValue<0 ) tempDays=0;
else if( dayValue>=md ) tempDays=md-1;
else tempDays=dayValue;
}
BigInteger oday = rhs.day.add(quoAndMod[0]).add(Util.int2bi(tempDays));
while(true) {
int carry;
if( oday.signum()==-1 ) { // day<0
oday = oday.add(Util.int2bi(Util.maximumDayInMonthFor(oyear,(omonth+11)%12)));
carry = -1;
} else {
BigInteger bmd = Util.int2bi(Util.maximumDayInMonthFor(oyear,omonth));
if( oday.compareTo(bmd)>=0 ) {
oday = oday.subtract(bmd);
carry = +1;
} else
break;
}
omonth += carry;
if( omonth<0 ) {
omonth += 12;
oyear = oyear.subtract(BigInteger.ONE);
}
oyear = oyear.add( Util.int2bi(omonth/12) );
omonth %= 12;
}
// set those fields blank which are not originally specified.
return new BigDateTimeValueType(
this.year!=null ? oyear:null,
this.month!=null ? new Integer(omonth):null,
this.day!=null ? new Integer(oday.intValue()):null,
this.hour!=null ? new Integer(ohour):null,
this.minute!=null? new Integer(ominute):null,
this.second!=null? osecond:null,
this.zone );
} else {
// big + small
// TODO : implement this to achive better performance
// just for now, convert it to BigTimeDurationValue and then compute the result.
return add( _rhs.getBigValue() );
}
}
public Calendar toCalendar() {
// set fields of Calendar.
// In BigDateTimeValueType, the first day of the month is 0,
// where it is 1 in java.util.Calendar.
Calendar cal = new java.util.GregorianCalendar(createJavaTimeZone());
cal.clear(); // reset all fields. This method does not reset the time zone.
if( getYear()!=null ) cal.set( cal.YEAR, getYear().intValue() );
if( getMonth()!=null ) cal.set( cal.MONTH, getMonth().intValue() );
if( getDay()!=null ) cal.set( cal.DAY_OF_MONTH, getDay().intValue()+1/*offset*/ );
if( getHour()!=null ) cal.set( cal.HOUR_OF_DAY, getHour().intValue() );
if( getMinute()!=null ) cal.set( cal.MINUTE, getMinute().intValue() );
if( getSecond()!=null ) {
cal.set( cal.SECOND, getSecond().intValue() );
cal.set( cal.MILLISECOND, getSecond().movePointRight(3).intValue()%1000 );
}
return cal;
}
/** creates the equivalent Java TimeZone object. */
protected java.util.TimeZone createJavaTimeZone() {
if(getTimeZone()!=null)
return new SimpleTimeZone( getTimeZone().minutes*60*1000, "custom" );
else
// if the time zone is not present, assume the system default.
return SimpleTimeZone.getDefault();
}
/*
public static void main( String[] args )
{
Object o1 = new BigDateTimeValueType( new BigInteger("2001"), new Integer(5), new Integer(1), null, null, null, null );
Object o2 = new BigDateTimeValueType( new BigInteger("2001"), new Integer(5), new Integer(1), null, null, null, null );
System.out.println(o1.hashCode());
System.out.println(o2.hashCode());
System.out.println(o1.equals(o2));
System.out.println(o2.equals(o1));
java.util.Set s = new java.util.HashSet();
s.add(o1);
System.out.println( s.contains(o2) );
}
public static void main( String[] args )
{
Object o1 = new BigDateTimeValueType( new BigInteger("1512"), new Integer(1), new Integer(4), null, null, null, TimeZone.create(-12*60) );
Object o2 = new BigDateTimeValueType( new BigInteger("1512"), new Integer(1), new Integer(5), null, null, null, TimeZone.create(+12*60) );
System.out.println(o1.hashCode());
System.out.println(o2.hashCode());
System.out.println(o1.equals(o2));
System.out.println(o2.equals(o1));
}
*/
}