/*
* 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.apache.jena.datatypes.xsd;
import java.math.BigDecimal;
import java.util.Arrays ;
import org.apache.jena.datatypes.xsd.impl.XSDAbstractDateTimeType ;
/**
* Represent an XSD duration value. We use a seven dimensional space
* with years, months, days, hours, minutes, seconds and fractional seconds.
* This deviates from the spec which allows arbitrary position
* decimals for seconds.
*/
public class XSDDuration extends AbstractDateTime {
/**
* Constructor - should only be used by the internals but public scope because
* the internals spread across multiple packages.
*
* @param value the date/time value returned by the parsing
*/
public XSDDuration(Object value) {
super(value);
}
/**
* Return the number of years in the duration
*/
public int getYears() {
return data[CY];
}
/**
* Return the number of months in the duration
*/
public int getMonths() {
return data[M];
}
/**
* Return the number of years in the duration
*/
public int getDays() {
return data[D];
}
/**
* Return the number of hours in the duration
*/
public int getHours() {
return data[h];
}
/**
* Return the number of minutes in the duration
*/
public int getMinutes() {
return data[m];
}
/**
* Return the number of full seconds in the duration
*/
public int getFullSeconds() {
return data[s];
}
/**
* Return the number of seconds in the duration, including fractional part
*/
public double getSeconds() {
return data[s] + fractionalSeconds;
}
/**
* Return the number of seconds in the duration, including the fractional part,
* in a lossless but expensive notation - i.e. a BigDecimal.
*/
public BigDecimal getBigSeconds() {
return BigDecimal.valueOf( data[ms], data[msscale])
.add( BigDecimal.valueOf(data[s]));
}
/**
* Return the time component of the duration - i.e. just the hours/mins/seconds,
* and returns the values in seconds.
*/
public double getTimePart() {
return ((data[h]) * 60l + data[m]) * 60l + getSeconds();
}
/**
* Serializer
*/
@Override
public String toString() {
// All zeros -> return canonical zero duration.
if ( data[CY]==0 && data[M]==0 && data[D]==0 && data[h]==0 && data[m]==0 && data[s]==0 && data[ms]==0 )
return "PT0S" ;
StringBuffer message = new StringBuffer(30);
int negate = 1;
if ( data[CY]<0 || data[M]<0 || data[D]<0 || data[h]<0 || data[m]<0 || data[s]<0 || data[ms]<0 ) {
message.append('-');
negate=-1;
}
// All zeros -> return canonical zero duration.
if ( data[CY]==0 && data[M]==0 && data[D]==0 && data[h]==0 && data[m]==0 && data[s]==0 && data[ms]==0 )
return "PT0S" ;
message.append('P');
if (data[CY] != 0) {
message.append(negate * data[CY]);
message.append('Y');
}
if (data[M] != 0) {
message.append(negate * data[M]);
message.append('M');
}
if (data[D] != 0) {
message.append(negate * data[D]);
message.append('D');
}
if (data[h] != 0 || data[m] != 0 || data[s] != 0 || data[ms] != 0) {
message.append('T');
if (data[h] != 0) {
message.append(negate * data[h]);
message.append('H');
}
if (data[m] != 0) {
message.append(negate * data[m]);
message.append('M');
}
if (data[s] != 0 || data[ms] != 0) {
message.append(negate * data[s]);
if ( data[ms] != 0 )
{
message.append('.');
XSDAbstractDateTimeType.appendFractionalTime(message, negate * data[ms], data[msscale]);
}
message.append('S');
}
}
return message.toString();
}
// The following duration comparison code is based on Xerces DurationDV, Apache Software Foundation
// order-relation on duration is a partial order. The dates below are used to
// for comparison of 2 durations, based on the fact that
// duration x and y is x<=y iff s+x<=s+y
// see 3.2.6 duration W3C schema datatype specs
//
// the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
private final static int[][] DATETIMES= {
{1696, 9, 1, 0, 0, 0, 0, 'Z'},
{1697, 2, 1, 0, 0, 0, 0, 'Z'},
{1903, 3, 1, 0, 0, 0, 0, 'Z'},
{1903, 7, 1, 0, 0, 0, 0, 'Z'}};
private int[][] fDuration = null;
/**
* Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration")
*
* @param date1 Unnormalized duration
* @param date2 Unnormalized duration
* @param strict (min/max)Exclusive strict == true ( LESS_THAN ) or ( GREATER_THAN )
* (min/max)Inclusive strict == false (LESS_EQUAL) or (GREATER_EQUAL)
* @return INDETERMINATE if the order relationship between date1 and date2 is indeterminate.
* EQUAL if the order relation between date1 and date2 is EQUAL.
* If the strict parameter is true, return LESS_THAN if date1 is less than date2 and
* return GREATER_THAN if date1 is greater than date2.
* If the strict parameter is false, return LESS_THAN if date1 is less than OR equal to date2 and
* return GREATER_THAN if date1 is greater than OR equal to date2
*/
@Override
protected short compareValues(int[] date1, int[] date2, boolean strict) {
date1 = canonical(date1) ;
date2 = canonical(date2) ;
//REVISIT: this is unoptimazed vs of comparing 2 durations
// Algorithm is described in 3.2.6.2 W3C Schema Datatype specs
//
//add constA to both durations
short resultA, resultB= INDETERMINATE;
//try and see if the objects are equal
resultA = compareOrder (date1, date2);
short baseResult = resultA; // Full comparison including time fractions
if ( resultA == 0 ) {
return 0;
}
if ( fDuration == null ) {
fDuration = new int[2][TOTAL_SIZE];
}
//long comparison algorithm is required
int[] tempA = addDuration (date1, 0, fDuration[0]);
int[] tempB = addDuration (date2, 0, fDuration[1]);
resultA = compareOrder(tempA, tempB);
if ( resultA == INDETERMINATE ) {
return INDETERMINATE;
}
tempA = addDuration(date1, 1, fDuration[0]);
tempB = addDuration(date2, 1, fDuration[1]);
resultB = compareOrder(tempA, tempB);
resultA = compareResults(resultA, resultB, strict);
if (resultA == INDETERMINATE) {
return INDETERMINATE;
}
tempA = addDuration(date1, 2, fDuration[0]);
tempB = addDuration(date2, 2, fDuration[1]);
resultB = compareOrder(tempA, tempB);
resultA = compareResults(resultA, resultB, strict);
if (resultA == INDETERMINATE) {
return INDETERMINATE;
}
tempA = addDuration(date1, 3, fDuration[0]);
tempB = addDuration(date2, 3, fDuration[1]);
resultB = compareOrder(tempA, tempB);
resultA = compareResults(resultA, resultB, strict);
if (resultA == 0) {
// determinate equality for data portion, so base comparison
// (which includes fractional time) is the correct result
return baseResult;
}
return resultA;
}
private short compareResults(short resultA, short resultB, boolean strict){
if ( resultB == INDETERMINATE ) {
return INDETERMINATE;
}
else if ( resultA!=resultB && strict ) {
return INDETERMINATE;
}
else if ( resultA!=resultB && !strict ) {
if ( resultA!=0 && resultB!=0 ) {
return INDETERMINATE;
}
else {
return (resultA!=0)?resultA:resultB;
}
}
return resultA;
}
/**
* Equality function (value based).
*/
@Override
public boolean equals(Object obj) {
if ( this == obj )
return true ;
if ( obj == null )
return false ;
if (obj instanceof XSDDuration) {
XSDDuration dur = (XSDDuration) obj;
int[] data1 = canonical(this.data) ;
int[] data2 = canonical(dur.data) ;
for (int i = 0; i < data1.length; i++) {
if (data1[i] != data2[i])
return false;
}
return true;
}
return super.equals(obj) ;
}
@Override
public int hashCode() {
int[] data1 = canonical(this.data) ;
int hash = 1816 ;
for ( int aData : data1 )
hash = ( hash << 1 ) ^ aData;
return hash;
}
private int[] addDuration(int[] date, int index, int[] duration) {
//REVISIT: some code could be shared between normalize() and this method,
// however is it worth moving it? The structures are different...
//
resetDateObj(duration);
//add months (may be modified additionaly below)
int temp = DATETIMES[index][M] + date[M];
duration[M] = modulo (temp, 1, 13);
int carry = fQuotient (temp, 1, 13);
//add years (may be modified additionaly below)
duration[CY]=DATETIMES[index][CY] + date[CY] + carry;
//add seconds
temp = DATETIMES[index][s] + date[s];
carry = fQuotient (temp, 60);
duration[s] = mod(temp, 60, carry);
//add minutes
temp = DATETIMES[index][m] +date[m] + carry;
carry = fQuotient (temp, 60);
duration[m]= mod(temp, 60, carry);
//add hours
temp = DATETIMES[index][h] + date[h] + carry;
carry = fQuotient(temp, 24);
duration[h] = mod(temp, 24, carry);
duration[D]=DATETIMES[index][D] + date[D] + carry;
while ( true ) {
temp=maxDayInMonthFor(duration[CY], duration[M]);
if ( duration[D] < 1 ) { //original duration was negative
duration[D] = duration[D] + maxDayInMonthFor(duration[CY], duration[M]-1);
carry=-1;
}
else if ( duration[D] > temp ) {
duration[D] = duration[D] - temp;
carry=1;
}
else {
break;
}
temp = duration[M]+carry;
duration[M] = modulo(temp, 1, 13);
duration[CY] = duration[CY]+fQuotient(temp, 1, 13);
}
duration[utc]='Z';
return duration;
}
// XXX Signedness?
// Day-time, year-month canonicalization.
private static int[] canonical(int[] val) {
val = Arrays.copyOf(val, val.length) ;
while ( val[ms] >= 1000 ) {
val[s] += 1 ;
val[ms] -= 1000 ;
}
while ( val[ms] <= -1000 ) {
val[s] -= 1 ;
val[ms] += 1000 ;
}
while ( val[s] >= 60 ) {
val[m] += 1 ;
val[s] -= 60 ;
}
while ( val[s] <= -60 ) {
val[m] -= 1 ;
val[s] += 60 ;
}
while ( val[m] >= 60 ) {
val[h] += 1 ;
val[m] -= 60 ;
}
while ( val[m] <= -60 ) {
val[h] -= 1 ;
val[m] += 60 ;
}
while ( val[h] >= 24 ) {
val[D] += 1 ;
val[h] -= 24 ;
}
while ( val[h] <= -24 ) {
val[D] -= 1 ;
val[h] += 24 ;
}
while ( val[M] >= 12 ) {
val[CY] += 1 ;
val[M] -= 12 ;
}
while ( val[M] <= -12 ) {
val[CY] -= 1 ;
val[M] += 12 ;
}
return val ;
}
}