/* * 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 ; } }