blob: 146678a329e084fab81868af9d3ea71554b73375 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2010 Andrea Bittau, University College London, and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Andrea Bittau - initial API and implementation from the PsychoPath XPath 2.0
* Mukul Gandhi - bug 273760 - wrong namespace for functions and data types
* Mukul Gandhi - bug 274792 - improvements to xs:date constructor function.
* David Carver - bug 282223 - implementation of xs:duration.
* fixed casting issue.
* David Carver - bug 280547 - fix dates for comparison
* Jesper Steen Moller - bug 262765 - fix type tests
* Mukul Gandhi - bug 280798 - PsychoPath support for JDK 1.4
*******************************************************************************/
package org.eclipse.wst.xml.xpath2.processor.internal.types;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import org.eclipse.wst.xml.xpath2.api.DynamicContext;
import org.eclipse.wst.xml.xpath2.api.Item;
import org.eclipse.wst.xml.xpath2.api.ResultBuffer;
import org.eclipse.wst.xml.xpath2.api.ResultSequence;
import org.eclipse.wst.xml.xpath2.api.typesystem.TypeDefinition;
import org.eclipse.wst.xml.xpath2.processor.DynamicError;
import org.eclipse.wst.xml.xpath2.processor.ResultSequenceFactory;
import org.eclipse.wst.xml.xpath2.processor.internal.function.CmpEq;
import org.eclipse.wst.xml.xpath2.processor.internal.function.CmpGt;
import org.eclipse.wst.xml.xpath2.processor.internal.function.CmpLt;
import org.eclipse.wst.xml.xpath2.processor.internal.function.MathMinus;
import org.eclipse.wst.xml.xpath2.processor.internal.function.MathPlus;
import org.eclipse.wst.xml.xpath2.processor.internal.types.builtin.BuiltinTypeLibrary;
/**
* Representation of a date of the form year-month-day and optional timezone
*/
public class XSDate extends CalendarType implements CmpEq, CmpLt, CmpGt,
MathMinus, MathPlus,
Cloneable {
private static final String XS_DATE = "xs:date";
private Calendar _calendar;
private boolean _timezoned;
private XSDuration _tz;
/**
* Initializes a new representation of a supplied date
*
* @param cal
* The Calendar representation of the date to be stored
* @param tz
* The time zone of the date to be stored.
*/
public XSDate(Calendar cal, XSDuration tz) {
_calendar = cal;
_tz = tz;
if (tz == null)
_timezoned = false;
else
_timezoned = true;
}
/**
* Initializes a new representation of the current date
*/
public XSDate() {
this(new GregorianCalendar(TimeZone.getTimeZone("GMT")), null);
}
/**
* Retrieves the datatype name
*
* @return "date" which is the dataype name
*/
public String type_name() {
return "date";
}
/**
* Creates a copy of this date representation
*
* @return A copy of this date representation
*/
public Object clone() throws CloneNotSupportedException {
Calendar c = (Calendar) calendar().clone();
XSDuration t = tz();
if (t != null)
t = (XSDuration) t.clone();
return new XSDate(c, t);
}
/**
* Parses a String representation of a date (of the form year-month-day or
* year-month-day+timezone) and constructs a new XSDate representation of
* it.
*
* @param str
* The String representation of the date (and optional timezone)
* @return The XSDate representation of the supplied date
*/
public static XSDate parse_date(String str) {
String date = "";
String time = "T00:00:00.0";
int index = str.indexOf('+', 1);
if (index == -1) {
index = str.indexOf('-', 1);
if (index == -1)
return null;
index = str.indexOf('-', index + 1);
if (index == -1)
return null;
index = str.indexOf('-', index + 1);
}
if (index == -1)
index = str.indexOf('Z', 1);
if (index != -1) {
date = str.substring(0, index);
// here we go
date += time;
date += str.substring(index, str.length());
} else {
date = str + time;
}
// sorry again =D
XSDateTime dt = XSDateTime.parseDateTime(date);
if (dt == null)
return null;
return new XSDate(dt.calendar(), dt.tz());
}
/**
* Creates a new result sequence consisting of the retrievable date value in
* the supplied result sequence
*
* @param arg
* The result sequence from which to extract the date value.
* @throws DynamicError
* @return A new result sequence consisting of the date value supplied.
*/
public ResultSequence constructor(ResultSequence arg) throws DynamicError {
if (arg.empty())
return ResultBuffer.EMPTY;
Item aat = arg.first();
if (!isCastable(aat)) {
throw DynamicError.invalidType();
}
XSDate dt = castDate(aat);
if (dt == null)
throw DynamicError.cant_cast(null);
return dt;
}
private boolean isCastable(Item aat) {
// We might be able to cast these.
if (aat instanceof XSString || aat instanceof XSUntypedAtomic
|| aat instanceof NodeType) {
return true;
}
if (aat instanceof XSTime) {
return false;
}
if (aat instanceof XSDateTime) {
return true;
}
if (aat instanceof XSDate) {
return true;
}
return false;
}
private XSDate castDate(Item aat) {
if (aat instanceof XSDate) {
XSDate date = (XSDate) aat;
return new XSDate(date.calendar(), date.tz());
}
if (aat instanceof XSDateTime) {
XSDateTime dateTime = (XSDateTime) aat;
return new XSDate(dateTime.calendar(), dateTime.tz());
}
return parse_date(aat.getStringValue());
}
/**
* Retrieve the year from the date stored
*
* @return the year value of the date stored
*/
public int year() {
int y = _calendar.get(Calendar.YEAR);
if (_calendar.get(Calendar.ERA) == GregorianCalendar.BC)
y *= -1;
return y;
}
/**
* Retrieve the month from the date stored
*
* @return the month value of the date stored
*/
public int month() {
return _calendar.get(Calendar.MONTH) + 1;
}
/**
* Retrieve the day from the date stored
*
* @return the day value of the date stored
*/
public int day() {
return _calendar.get(Calendar.DAY_OF_MONTH);
}
/**
* Retrieves whether this date has an optional timezone associated with it
*
* @return True if there is a timezone associated with this date. False
* otherwise.
*/
public boolean timezoned() {
return _timezoned;
}
/**
* Retrieves a String representation of the date stored
*
* @return String representation of the date stored
*/
public String getStringValue() {
String ret = "";
Calendar adjustFortimezone = calendar();
if (adjustFortimezone.get(Calendar.ERA) == GregorianCalendar.BC) {
ret += "-";
}
ret += XSDateTime.pad_int(adjustFortimezone.get(Calendar.YEAR), 4);
ret += "-";
ret += XSDateTime.pad_int(month(), 2);
ret += "-";
ret += XSDateTime.pad_int(adjustFortimezone.get(Calendar.DAY_OF_MONTH),
2);
if (timezoned()) {
int hrs = _tz.hours();
int min = _tz.minutes();
double secs = _tz.seconds();
if (hrs == 0 && min == 0 && secs == 0) {
ret += "Z";
} else {
String tZoneStr = "";
if (_tz.negative()) {
tZoneStr += "-";
} else {
tZoneStr += "+";
}
tZoneStr += XSDateTime.pad_int(hrs, 2);
tZoneStr += ":";
tZoneStr += XSDateTime.pad_int(min, 2);
ret += tZoneStr;
}
}
return ret;
}
/**
* Retrive the datatype full pathname
*
* @return "xs:date" which is the datatype full pathname
*/
public String string_type() {
return XS_DATE;
}
/**
* Retrieves the Calendar representation of the date stored
*
* @return Calendar representation of the date stored
*/
public Calendar calendar() {
return _calendar;
}
/**
* Retrieves the timezone associated with the date stored
*
* @return the timezone associated with the date stored
*/
public XSDuration tz() {
return _tz;
}
// comparisons
/**
* Equality comparison on this and the supplied dates (taking timezones into
* account)
*
* @param arg
* XSDate representation of the date to compare to
* @throws DynamicError
* @return True if the two dates are represent the same exact point in time.
* False otherwise.
*/
public boolean eq(AnyType arg, DynamicContext dynamicContext) throws DynamicError {
XSDate val = (XSDate) NumericType.get_single_type((Item)arg, XSDate.class);
Calendar thiscal = normalizeCalendar(calendar(), tz());
Calendar thatcal = normalizeCalendar(val.calendar(), val.tz());
return thiscal.equals(thatcal);
}
/**
* Comparison on this and the supplied dates (taking timezones into account)
*
* @param arg
* XSDate representation of the date to compare to
* @throws DynamicError
* @return True if in time, this date lies before the date supplied. False
* otherwise.
*/
public boolean lt(AnyType arg, DynamicContext context) throws DynamicError {
XSDate val = (XSDate) NumericType.get_single_type((Item)arg, XSDate.class);
Calendar thiscal = normalizeCalendar(calendar(), tz());
Calendar thatcal = normalizeCalendar(val.calendar(), val.tz());
return thiscal.before(thatcal);
}
/**
* Comparison on this and the supplied dates (taking timezones into account)
*
* @param arg
* XSDate representation of the date to compare to
* @throws DynamicError
* @return True if in time, this date lies after the date supplied. False
* otherwise.
*/
public boolean gt(AnyType arg, DynamicContext context) throws DynamicError {
XSDate val = (XSDate) NumericType.get_single_type((Item)arg, XSDate.class);
Calendar thiscal = normalizeCalendar(calendar(), tz());
Calendar thatcal = normalizeCalendar(val.calendar(), val.tz());
return thiscal.after(thatcal);
}
// XXX this is incorrect [epoch]
/**
* Currently unsupported method. Retrieves the date in milliseconds since
* the begining of epoch
*
* @return Number of milliseconds since the begining of the epoch
*/
public double value() {
return calendar().getTimeInMillis() / 1000.0;
}
// math
/**
* Mathematical minus operator between this XSDate and a supplied result
* sequence (XSDate, XSYearMonthDuration and XSDayTimeDuration are only
* valid ones).
*
* @param arg
* The supplied ResultSequence that is on the right of the minus
* operator. If this is an XSDate, the result will be a
* XSDayTimeDuration of the duration of time between these two
* dates. If arg is an XSYearMonthDuration or an
* XSDayTimeDuration the result will be a XSDate of the result of
* the current date minus the duration of time supplied.
* @return New ResultSequence consisting of the result of the mathematical
* minus operation.
*/
public ResultSequence minus(ResultSequence arg) throws DynamicError {
if (arg.size() != 1)
throw DynamicError.throw_type_error();
Item at = arg.first();
if (!(at instanceof XSDate) && !(at instanceof XSYearMonthDuration)
&& !(at instanceof XSDayTimeDuration)) {
throw DynamicError.throw_type_error();
}
if (at instanceof XSDate) {
return minusXSDate(arg);
}
if (at instanceof XSYearMonthDuration) {
return minusXSYearMonthDuration((XSYearMonthDuration)at);
}
if (at instanceof XSDayTimeDuration) {
return minusXSDayTimeDuration((XSDayTimeDuration)at);
}
return null;
}
private ResultSequence minusXSDayTimeDuration(AnyType at) {
XSDuration val = (XSDuration) at;
try {
XSDate res = (XSDate) clone();
XMLGregorianCalendar xmlCal = _datatypeFactory
.newXMLGregorianCalendar(
(GregorianCalendar) calendar());
Duration dtduration = _datatypeFactory
.newDuration(val.getStringValue());
xmlCal.add(dtduration.negate());
res = new XSDate(xmlCal.toGregorianCalendar(), res.tz());
return ResultSequenceFactory.create_new(res);
} catch (CloneNotSupportedException ex) {
}
return null;
}
private ResultSequence minusXSYearMonthDuration(AnyType at) {
XSYearMonthDuration val = (XSYearMonthDuration) at;
try {
XSDate res = (XSDate) clone();
res.calendar().add(Calendar.MONTH, val.monthValue() * -1);
return ResultSequenceFactory.create_new(res);
} catch (CloneNotSupportedException ex) {
}
return null;
}
private ResultSequence minusXSDate(ResultSequence arg) throws DynamicError {
XSDate val = (XSDate) NumericType.get_single_type(arg, XSDate.class);
Duration dtduration = null;
Calendar thisCal = normalizeCalendar(calendar(), tz());
Calendar thatCal = normalizeCalendar(val.calendar(), val.tz());
long duration = thisCal.getTimeInMillis()
- thatCal.getTimeInMillis();
dtduration = _datatypeFactory.newDuration(duration);
return ResultSequenceFactory.create_new(XSDayTimeDuration
.parseDTDuration(dtduration.toString()));
}
/**
* Mathematical addition operator between this XSDate and a supplied result
* sequence (XDTYearMonthDuration and XDTDayTimeDuration are only valid
* ones).
*
* @param arg
* The supplied ResultSequence that is on the right of the minus
* operator. If arg is an XDTYearMonthDuration or an
* XDTDayTimeDuration the result will be a XSDate of the result
* of the current date minus the duration of time supplied.
* @return New ResultSequence consisting of the result of the mathematical
* minus operation.
*/
public ResultSequence plus(ResultSequence arg) throws DynamicError {
if (arg.size() != 1)
DynamicError.throw_type_error();
Item at = arg.first();
try {
if (at instanceof XSYearMonthDuration) {
XSYearMonthDuration val = (XSYearMonthDuration) at;
XSDate res = (XSDate) clone();
res.calendar().add(Calendar.MONTH, val.monthValue());
return ResultSequenceFactory.create_new(res);
} else if (at instanceof XSDayTimeDuration) {
XSDayTimeDuration val = (XSDayTimeDuration) at;
XSDate res = (XSDate) clone();
// We only need to add the Number of days dropping the rest.
int days = val.days();
if (val.negative()) {
days *= -1;
}
res.calendar().add(Calendar.DAY_OF_MONTH, days);
res.calendar().add(Calendar.MILLISECOND,
(int) (val.time_value() * 1000.0));
return ResultSequenceFactory.create_new(res);
} else {
DynamicError.throw_type_error();
return null; // unreach
}
} catch (CloneNotSupportedException err) {
assert false;
return null;
}
}
public TypeDefinition getTypeDefinition() {
return BuiltinTypeLibrary.XS_DATE;
}
}