/*
* 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.util.Arrays ;
/**
* Base class for representation of XSD duration, time, date/time
* and related datatype instances. We are using the Xerces internal
* packages for the all heavy lifting which represent date/times
* using an int array. These wrapper classes just provide more
* convenient access to the date values.
* <p>
* This class includes code derived from Xerces 2.6.0
* Copyright (c) 1999-2002 The Apache Software Foundation.
* All rights reserved.
* </p>
*/
public class AbstractDateTime implements Comparable<AbstractDateTime> {
/** The array of year/month etc values as ints */
protected int[] data;
/** The fractional seconds */
protected double fractionalSeconds;
//define constants
protected final static int CY = 0, M = 1, D = 2, h = 3,
m = 4, s = 5, ms = 6, utc=7, msscale=8 ;
// Timezone constants
protected final static int hh=0, mm=1;
//size for all objects must have the same fields:
//CCYY, MM, DD, h, m, s, ms + timeZone
protected final static int TOTAL_SIZE = 9;
// The number of comparable values
protected final static int COMPARABLE_SUBSET = 6;
/** constant to indicate a less than relationship from compare() */
public static final short LESS_THAN = -1;
/** constant to indicate an equals relationship from compare() */
public static final short EQUAL = 0;
/** constant to indicate a greater than relationship from compare() */
public static final short GREATER_THAN = 1;
/** constant to indicate an indeterminate relationship from compare() */
public static final short INDETERMINATE = 2;
/**
* Constructor
* @param value the date/time value returned by the parsing
*/
public AbstractDateTime(Object value) {
data = (int[]) value;
//if (data[utc] == 0) data[utc]='Z';
extractFractionalSeconds();
}
/**
* Comparison function. Not quite the same as normal java compare
* because XSD date/times are not always comparable.
*
* @param other the time/date to compare to
* @return an order flag - one of LESS_THAN, EQUAL, GREATER_THEN, INDETERMINATE
*/
public int compare(AbstractDateTime other) {
return compareValues(data, other.data, true);
}
/**
* Normal java comparison function. Treats INDETERMINATE as the same
* as equals. This is not strictly correct but seems like an appropriate
* way to handle partially ordered objects.
*/
@Override
public int compareTo(AbstractDateTime o) {
switch (compare(o)) {
case EQUAL:
case INDETERMINATE:
return 0;
case LESS_THAN:
return -1;
case GREATER_THAN:
return 1;
}
return 0;
}
/**
* Convert fractional second representation to a simple double..
*/
protected void extractFractionalSeconds() {
fractionalSeconds = 0.0 ;
if (data[ms] != 0) {
int fs = data[ms];
fractionalSeconds = (fs) / Math.pow(10.0, data[msscale]);
}
}
/**
* Equality function
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof AbstractDateTime) {
AbstractDateTime adt = (AbstractDateTime) obj;
for (int i = 0; i < data.length; i++) {
if (data[i] != adt.data[i]) return false;
}
// fractionalSeconds is generated from data[ms]
return true;
}
return false;
}
@Override
public int hashCode() {
// See JENA-1140.
// Eclipse generated - without fractionalSeconds which is generated from data[ms]
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(data);
return result;
}
// --------------------------------------------------------------------
// This code is adapated from Xerces 2.6.0 AbstractDateTimeDV.
// Copyright (c) 1999-2003 The Apache Software Foundation. All rights
// reserved.
// --------------------------------------------------------------------
/**
* Compare algorithm described in dateDime (3.2.7).
*
* @param date1 normalized date representation of the first value
* @param date2 normalized date representation of the second value
* @param strict
* @return less, greater, less_equal, greater_equal, equal
*/
protected short compareValues(int[] date1, int[] date2, boolean strict) {
if ( date1[utc]==date2[utc] ) {
return compareOrder(date1, date2);
}
short c1, c2;
int[] tempDate = new int[TOTAL_SIZE];
int[] timeZone = new int[2];
if ( date1[utc]=='Z' ) {
//compare date1<=date1<=(date2 with time zone -14)
//
cloneDate(date2, tempDate); //clones date1 value to global temporary storage: fTempDate
timeZone[hh]=14;
timeZone[mm]=0;
tempDate[utc]='+';
normalize(tempDate, timeZone);
c1 = compareOrder(date1, tempDate);
if (c1 == LESS_THAN)
return c1;
//compare date1>=(date2 with time zone +14)
//
cloneDate(date2, tempDate); //clones date1 value to global temporary storage: tempDate
timeZone[hh]=14;
timeZone[mm]=0;
tempDate[utc]='-';
normalize(tempDate, timeZone);
c2 = compareOrder(date1, tempDate);
if (c2 == GREATER_THAN)
return c2;
return INDETERMINATE;
}
else if ( date2[utc]=='Z' ) {
//compare (date1 with time zone -14)<=date2
//
cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
timeZone[hh]=14;
timeZone[mm]=0;
tempDate[utc]='-';
normalize(tempDate, timeZone);
c1 = compareOrder(tempDate, date2);
if (c1 == LESS_THAN)
return c1;
//compare (date1 with time zone +14)<=date2
//
cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
timeZone[hh]=14;
timeZone[mm]=0;
tempDate[utc]='+';
normalize(tempDate, timeZone);
c2 = compareOrder(tempDate, date2);
if (c2 == GREATER_THAN)
return c2;
return INDETERMINATE;
}
return INDETERMINATE;
}
/**
* Given normalized values, determines order-relation
* between give date/time objects.
*
* @param date1 date/time object
* @param date2 date/time object
* @return 0 if date1 and date2 are equal, a value less than 0 if date1 is less than date2, a value greater than 0 if date1 is greater than date2
*/
protected short compareOrder (int[] date1, int[] date2) {
for ( int i=0; i < COMPARABLE_SUBSET;i++ ) {
if (date1[i] < date2[i]) {
return -1;
} else if (date1[i] > date2[i]) {
return 1;
}
}
// Compare subsecond components
int maxScale = Math.max(date1[msscale], date2[msscale]);
int ss1 = scale(date1[ms], date1[msscale], maxScale);
int ss2 = scale(date2[ms], date2[msscale], maxScale);
if (ss1 < ss2) {
return -1;
} else if (ss1 > ss2) {
return 1;
}
return 0;
}
/**
* Scale up a subsecond part to make comparable to another of the given scale
*/
private int scale(int val, int scale, int targetScale) {
for (int i = scale; i < targetScale; i++) val *= 10;
return val;
}
/**
* If timezone present - normalize dateTime [E Adding durations to dateTimes]
* Public to allow reuse with type objects.
*
* @param date CCYY-MM-DDThh:mm:ss+03
*/
public static void normalize (int[] date, int[] timeZone) {
// REVISIT: we have common code in addDuration() for durations
// should consider reorganizing it.
//
//add minutes (from time zone)
int negate = 1;
if (date[utc]=='+') {
negate = -1;
}
int temp = date[m] + negate*timeZone[mm];
int carry = fQuotient (temp, 60);
date[m]= mod(temp, 60, carry);
//add hours
temp = date[h] + negate*timeZone[hh] + carry;
carry = fQuotient(temp, 24);
date[h]=mod(temp, 24, carry);
date[D]=date[D]+carry;
while ( true ) {
temp=maxDayInMonthFor(date[CY], date[M]);
if (date[D]<1) {
date[D] = date[D] + maxDayInMonthFor(date[CY], date[M]-1);
carry=-1;
}
else if ( date[D]>temp ) {
date[D]=date[D]-temp;
carry=1;
}
else {
break;
}
temp=date[M]+carry;
date[M]=modulo(temp, 1, 13);
date[CY]=date[CY]+fQuotient(temp, 1, 13);
}
date[utc]='Z';
}
/**
* Resets object representation of date/time
*
* @param data date/time object
*/
protected void resetDateObj (int[] data) {
for ( int i=0;i<TOTAL_SIZE;i++ ) {
data[i]=0;
}
}
/**
* Given {year,month} computes maximum
* number of days for given month
*
* @param year
* @param month
* @return integer containg the number of days in a given month
*/
protected static int maxDayInMonthFor(int year, int month) {
//validate days
if ( month==4 || month==6 || month==9 || month==11 ) {
return 30;
}
else if ( month==2 ) {
if ( isLeapYear(year) ) {
return 29;
}
else {
return 28;
}
}
else {
return 31;
}
}
private static boolean isLeapYear(int year) {
//REVISIT: should we take care about Julian calendar?
return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
}
//
// help function described in W3C PR Schema [E Adding durations to dateTimes]
//
protected static int mod (int a, int b, int quotient) {
//modulo(a, b) = a - fQuotient(a,b)*b
return (a - quotient*b) ;
}
//
// help function described in W3C PR Schema [E Adding durations to dateTimes]
//
protected static int fQuotient (int a, int b) {
//fQuotient(a, b) = the greatest integer less than or equal to a/b
return (int)Math.floor((float)a/b);
}
//
// help function described in W3C PR Schema [E Adding durations to dateTimes]
//
protected static int modulo (int temp, int low, int high) {
//modulo(a - low, high - low) + low
int a = temp - low;
int b = high - low;
return (mod (a, b, fQuotient(a, b)) + low) ;
}
//
// help function described in W3C PR Schema [E Adding durations to dateTimes]
//
protected static int fQuotient (int temp, int low, int high) {
//fQuotient(a - low, high - low)
return fQuotient(temp - low, high - low);
}
//
//Private help functions
//
private void cloneDate (int[] finalValue, int[] tempDate) {
System.arraycopy(finalValue, 0, tempDate, 0, TOTAL_SIZE);
}
// --------------------------------------------------------------------
// End of code is adapated from Xerces 2.6.0 AbstractDateTimeDV.
// --------------------------------------------------------------------
}