/*
* 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.*;
import org.apache.jena.datatypes.xsd.impl.XSDAbstractDateTimeType ;
/**
* Represent an XSD date/time value. Rather than have a separate type for each
* legal date/time value combination this is a combination type than does runtime
* checks whether a given field is legal in the current circumstances.
*/
public class XSDDateTime extends AbstractDateTime {
/** Mask to indicate whether year is present */
public static final short YEAR_MASK = 0x1;
/** Mask to indicate whether month is present */
public static final short MONTH_MASK = 0x2;
/** Mask to indicate whether day is present */
public static final short DAY_MASK = 0x4;
/** Mask to indicate whether time is present */
public static final short TIME_MASK = 0x8;
/** Mask to indicate all date/time are present */
public static final short FULL_MASK = 0xf;
// Unused /** table mapping xs type name to mask of legal values */
//public static final HashMap maskMap = new HashMap();
/** Set of legal fields for the particular date/time instance */
protected short mask;
/**
* 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
* @param mask bitmask defining which components are valid in this instance
* (e.g. dates don't have valid time fields).
*/
public XSDDateTime(Object value, int mask) {
super(value);
this.mask = (short)mask;
}
/**
* Constructor - create a full DateTime object from a java calendar instance.
*
* @param date java calendar instance
*/
public XSDDateTime(Calendar date) {
super(convertCalendar(date));
this.mask = FULL_MASK;
}
/**
* Return the most specific xsd type which can represent
* this date/time
*/
public XSDDatatype getNarrowedDatatype() {
switch (mask) {
case TIME_MASK:
return XSDDatatype.XSDtime;
case MONTH_MASK:
return XSDDatatype.XSDgMonth;
case DAY_MASK:
return XSDDatatype.XSDgDay;
case YEAR_MASK:
return XSDDatatype.XSDgYear;
case MONTH_MASK | DAY_MASK:
return XSDDatatype.XSDgMonthDay;
case MONTH_MASK | YEAR_MASK:
return XSDDatatype.XSDgYearMonth;
case MONTH_MASK | YEAR_MASK | DAY_MASK:
return XSDDatatype.XSDdate;
default:
return XSDDatatype.XSDdateTime;
}
}
/**
* Set the mask for this date/time to be that appropriate
* for the given XSD subtype. If the type is a subtype of XSDdateTime the
* mask will be narrowed appropriately, other types will be silently ignored.
*/
public void narrowType(XSDDatatype dt) {
if (dt.equals(XSDDatatype.XSDtime)) {
mask = TIME_MASK;
} else if (dt.equals(XSDDatatype.XSDgMonth)) {
mask = MONTH_MASK;
} else if (dt.equals(XSDDatatype.XSDgDay)) {
mask = DAY_MASK;
} else if (dt.equals(XSDDatatype.XSDgYear)) {
mask = YEAR_MASK;
} else if (dt.equals(XSDDatatype.XSDgMonthDay)) {
mask = MONTH_MASK | DAY_MASK;
} else if (dt.equals(XSDDatatype.XSDgYearMonth)) {
mask = YEAR_MASK | MONTH_MASK;
} else if (dt.equals(XSDDatatype.XSDdate)) {
mask = MONTH_MASK | YEAR_MASK | DAY_MASK;
}
}
/**
* Convert a java calendar object to a new int[] in the format used by XSDAbstractDateTime
*/
private static int[] convertCalendar(Calendar date) {
int[] data = new int[TOTAL_SIZE];
Calendar cal = (Calendar)date.clone();
// By observation (Sun Java 6), this is necesary (to force internal calculations presumably) ...
cal.get(Calendar.ZONE_OFFSET) ;
// ... then we can rebase the calendar
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
data[AbstractDateTime.CY] = cal.get(Calendar.YEAR);
data[AbstractDateTime.M] = cal.get(Calendar.MONTH) + 1;
data[AbstractDateTime.D] = cal.get(Calendar.DAY_OF_MONTH);
data[AbstractDateTime.h] = cal.get(Calendar.HOUR_OF_DAY);
data[AbstractDateTime.m] = cal.get(Calendar.MINUTE);
data[AbstractDateTime.s] = cal.get(Calendar.SECOND);
data[AbstractDateTime.utc] = 'Z';
int ms = cal.get(Calendar.MILLISECOND);
// Store value in a canonical form - no scale for trailing zeros.
// This must align with the string parsing code.
if ( ms == 0 ) {
data[AbstractDateTime.ms] = 0 ;
data[AbstractDateTime.msscale] = 0 ;
} else if ( ms % 100 == 0 ) {
data[AbstractDateTime.ms] = ms/100;
data[AbstractDateTime.msscale] = 1 ;
} else if ( ms % 10 == 0 ) {
data[AbstractDateTime.ms] = ms/10;
data[AbstractDateTime.msscale] = 2 ;
} else {
data[AbstractDateTime.ms] = ms;
data[AbstractDateTime.msscale] = 3;
}
return data;
}
/**
* Return the date time as a java Calendar object.
* If the timezone has been specified then the object is normalized to GMT.
* If the zone has not been specified then we use the default timezone.
*
* @throws IllegalDateTimeFieldException if this is not a full date + time
*/
public Calendar asCalendar () throws IllegalDateTimeFieldException {
TimeZone tz = data[utc] == 'Z' ? TimeZone.getTimeZone("GMT") : TimeZone.getDefault();
Calendar calendar = new GregorianCalendar(tz);
calendar.set(data[CY], data[M] - 1, data[D], data[h], data[m], data[s]);
calendar.set(Calendar.MILLISECOND, (int)Math.round(1000.0 * fractionalSeconds));
// was this to work around problems with some Linux JDKs
// calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}
/**
* Return the number of years in the dateTime.
* @throws IllegalDateTimeFieldException if there is no legal year component
*/
public int getYears() throws IllegalDateTimeFieldException {
if ((mask & YEAR_MASK) == 0) throw new IllegalDateTimeFieldException("Year not available");
return data[CY];
}
/**
* Return the month in the dateTime, this is in ISO8601 format so january = 1
* @throws IllegalDateTimeFieldException if there is no legal month component
*/
public int getMonths() throws IllegalDateTimeFieldException {
if ((mask & MONTH_MASK) == 0) throw new IllegalDateTimeFieldException("Month not available");
return data[M];
}
/**
* Return the number of years in the dateTime
* @throws IllegalDateTimeFieldException if there is no legal day component
*/
public int getDays() throws IllegalDateTimeFieldException {
if ((mask & DAY_MASK) == 0) throw new IllegalDateTimeFieldException("Day not available");
return data[D];
}
/**
* Return the number of hours in the dateTime
* @throws IllegalDateTimeFieldException if there is no legal time component
*/
public int getHours() throws IllegalDateTimeFieldException {
if ((mask & TIME_MASK) == 0) throw new IllegalDateTimeFieldException("Time not available");
return data[h];
}
/**
* Return the number of minutes in the dateTime
* @throws IllegalDateTimeFieldException if there is no legal time component
*/
public int getMinutes() throws IllegalDateTimeFieldException {
if ((mask & TIME_MASK) == 0) throw new IllegalDateTimeFieldException("Time not available");
return data[m];
}
/**
* Return the number of full seconds in the dateTime
* @throws IllegalDateTimeFieldException if there is no legal time component
*/
public int getFullSeconds() throws IllegalDateTimeFieldException {
if ((mask & TIME_MASK) == 0) throw new IllegalDateTimeFieldException("Time not available");
return data[s];
}
/**
* Return the number of seconds in the dateTime, including fractional part
* @throws IllegalDateTimeFieldException if there is no legal time component
*/
public double getSeconds() throws IllegalDateTimeFieldException {
if ((mask & TIME_MASK) == 0) throw new IllegalDateTimeFieldException("Time not available");
return data[s] + fractionalSeconds;
}
/**
* Return the time component of the dateTime - i.e. just the hours/mins/seconds,
* and returns the values in seconds.
* @throws IllegalDateTimeFieldException if there is no legal time component
*/
public double getTimePart() throws IllegalDateTimeFieldException {
if ((mask & TIME_MASK) == 0) throw new IllegalDateTimeFieldException("Time not available");
return ((data[h]) * 60l + data[m]) * 60l + getSeconds();
}
/**
* Return legal serialized form.
*/
@Override
public String toString() {
StringBuilder buff = new StringBuilder();
if ((mask & YEAR_MASK) != 0) {
int cy = data[CY] ;
int absCY = Math.abs(cy) ;
// XSD dateTime requires at least 4 digits for the year field
if ( absCY < 10 ) buff.append('0') ;
if ( absCY < 100 ) buff.append('0') ;
if ( absCY < 1000 ) buff.append('0') ;
buff.append(data[CY]);
} else {
buff.append("-");
}
if ((mask & (MONTH_MASK | DAY_MASK)) != 0) {
buff.append("-");
if ((mask & MONTH_MASK) != 0) {
if (data[M] <= 9) buff.append('0');
buff.append(data[M]);
} else {
buff.append("-");
}
if ((mask & DAY_MASK) != 0) {
if (mask != DAY_MASK) buff.append('-');
if (data[D] <= 9) buff.append('0');
buff.append(data[D]);
}
}
if ((mask & TIME_MASK) != 0 ) {
buff.append('T');
buff.append(timeLexicalForm());
}
if ( data[utc] != 0 )
buff.append('Z');
return buff.toString();
}
/**
* Return the lexical form of the time component.
*/
public String timeLexicalForm() {
StringBuffer buff = new StringBuffer();
if(data[h]<10) buff.append("0");
buff.append(data[h]);
buff.append(":");
if(data[m]<10) buff.append("0");
buff.append(data[m]);
buff.append(":");
if(data[s]<10) buff.append("0");
buff.append(data[s]);
if (data[ms] != 0) {
buff.append(".");
XSDAbstractDateTimeType.appendFractionalTime(buff, data[ms], data[msscale]);
}
return buff.toString();
}
}