package client.net.sf.saxon.ce.value; import client.net.sf.saxon.ce.expr.XPathContext; import client.net.sf.saxon.ce.expr.sort.ComparisonKey; import client.net.sf.saxon.ce.lib.StringCollator; import client.net.sf.saxon.ce.trans.NoDynamicContextException; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.tree.util.FastStringBuffer; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import java.math.BigDecimal; /** * Abstract superclass for Date, Time, and DateTime. */ public abstract class CalendarValue extends client.net.sf.saxon.ce.value.AtomicValue { // This is a reimplementation that makes no use of the Java Calendar/Date types except for computations. private int tzMinutes = NO_TIMEZONE; // timezone offset in minutes: or the special value NO_TIMEZONE public static final int NO_TIMEZONE = Integer.MIN_VALUE; public static final int BAD_TIMEZONE = Integer.MAX_VALUE; /** * Determine whether this value includes a timezone * @return true if there is a timezone in the value, false if not */ public final boolean hasTimezone() { return tzMinutes != NO_TIMEZONE; } /** * Modify the timezone value held in this object. This must be done only while the value is being * constructed. * @param minutes The timezone offset from GMT in minutes, positive or negative; or the special * value NO_TIMEZONE indicating that the value is not in a timezone (this is the default if this * method is not called) */ public final void setTimezoneInMinutes(int minutes) { tzMinutes = minutes; } /** * Convert the value to a DateTime, retaining all the components that are actually present, and * substituting conventional values for components that are missing * @return the equivalent DateTimeValue */ public abstract DateTimeValue toDateTime(); /** * Get the timezone value held in this object. * @return The timezone offset from GMT in minutes, positive or negative; or the special * value NO_TIMEZONE indicating that the value is not in a timezone */ public final int getTimezoneInMinutes() { return tzMinutes; } private static RegExp timezonePattern = RegExp.compile("[-+]([0-9][0-9]):([0-9][0-9])"); public static int parseTimezone(String zone) { if (zone==null || zone.isEmpty()) { return NO_TIMEZONE; } else if (zone.equals("Z")) { return 0; } else { MatchResult match = timezonePattern.exec(zone); if (match == null) { return BAD_TIMEZONE; } int h = DurationValue.simpleInteger(match.getGroup(1)); int m = DurationValue.simpleInteger(match.getGroup(2)); if (h > 14 || (h == 14 && m > 0)) { return BAD_TIMEZONE; } int tz = h*60 + m; if (zone.charAt(0) == '-') { tz = -tz; } return tz; } } /** * Convert the value to a string */ // public final String getStringValue() { // return getStringValueCS().toString(); // } /** * Add a duration to this date/time value * @param duration the duration to be added (which might be negative) * @return a new date/time value representing the result of adding the duration. The original * object is not modified. * @throws XPathException */ public abstract CalendarValue add(DurationValue duration) throws XPathException; /** * Determine the difference between two points in time, as a duration * @param other the other point in time * @param context the dynamic context, used to obtain timezone information. May be set to null * only if both values contain an explicit timezone, or if neither does so. * @return the duration as an xs:dayTimeDuration * @throws client.net.sf.saxon.ce.trans.XPathException for example if one value is a date and the other is a time */ public DayTimeDurationValue subtract(CalendarValue other, XPathContext context) throws XPathException { DateTimeValue dt1 = toDateTime(); DateTimeValue dt2 = other.toDateTime(); if (dt1.getTimezoneInMinutes() != dt2.getTimezoneInMinutes()) { dt1 = dt1.normalize(context); dt2 = dt2.normalize(context); } BigDecimal d1 = dt1.toJulianInstant(); BigDecimal d2 = dt2.toJulianInstant(); BigDecimal difference = d1.subtract(d2); return DayTimeDurationValue.fromSeconds(difference); } /** * Create a copy of this atomic value * * @return the copied value */ protected abstract AtomicValue copy(); /** * Return a date, time, or dateTime with the same localized value, but * without the timezone component * @return the result of removing the timezone */ public final CalendarValue removeTimezone() { CalendarValue c = (CalendarValue) copy(); c.tzMinutes = NO_TIMEZONE; return c; } /** * Return a new date, time, or dateTime with the same normalized value, but * in a different timezone * @param tz the new timezone offset from UTC, in minutes * @return the date/time in the new timezone */ public abstract CalendarValue adjustTimezone(int tz); /** * Get an object value that implements the XPath equality and ordering comparison semantics for this value. * If the ordered parameter is set to true, the result will be a Comparable and will support a compareTo() * method with the semantics of the XPath lt/gt operator, provided that the other operand is also obtained * using the getXPathComparable() method. In all cases the result will support equals() and hashCode() methods * that support the semantics of the XPath eq operator, again provided that the other operand is also obtained * using the getXPathComparable() method. A context argument is supplied for use in cases where the comparison * semantics are context-sensitive, for example where they depend on the implicit timezone or the default * collation. * * @param ordered true if an ordered comparison is required. In this case the result is null if the * type is unordered; in other cases the returned value will be a Comparable. * @param collator collation used for strings * @param context the XPath dynamic evaluation context, used in cases where the comparison is context * sensitive @return an Object whose equals() and hashCode() methods implement the XPath comparison semantics */ public Object getXPathComparable(boolean ordered, StringCollator collator, XPathContext context) throws NoDynamicContextException { if (ordered && !(this instanceof Comparable)) { return null; } return (hasTimezone() ? this : adjustTimezone(context.getImplicitTimezone())); } /** * Compare this value to another value of the same type, using the supplied Configuration * to get the implicit timezone if required. * @param other the other value to be compared * @param context the XPath dynamic evaluation context * @return the comparison result * @throws NoDynamicContextException if the supplied context is an early evaluation context and the * result depends on the implicit timezone, which is not available at compile time */ public abstract int compareTo(CalendarValue other, XPathContext context) throws NoDynamicContextException; /** * Get a comparison key for this value. Two values are equal if and only if they their comparison * keys are equal * @param context XPath dynamic evaluation context, used to obtain implicit timezone * @return a comparison key * @throws NoDynamicContextException if the implicit timezone is needed and is not available */ public abstract ComparisonKey getComparisonKey(XPathContext context) throws NoDynamicContextException; /** * Add a string representation of the timezone, typically * formatted as "Z" or "+03:00" or "-10:00", to a supplied * string buffer * @param sb The StringBuffer that will be updated with the resulting string * representation */ public final void appendTimezone(FastStringBuffer sb) { if (hasTimezone()) { appendTimezone(getTimezoneInMinutes(), sb); } } /** * Format a timezone and append it to a buffer * @param tz the timezone * @param sb the buffer */ public static void appendTimezone(int tz, FastStringBuffer sb) { if (tz == 0) { sb.append("Z"); } else { sb.append(tz > 0 ? "+" : "-"); tz = Math.abs(tz); appendTwoDigits(sb, tz/60); sb.append(':'); appendTwoDigits(sb, tz%60); } } /** * Append an integer, formatted with leading zeros to a fixed size, to a string buffer * @param sb the string buffer * @param value the integer to be formatted * @param size the number of digits required (max 9) */ static void appendString(FastStringBuffer sb, int value, int size) { String s = "000000000"+value; sb.append( s.substring(s.length()-size) ); } /** * Append an integer, formatted as two digits, to a string buffer * @param sb the string buffer * @param value the integer to be formatted (must be in the range 0..99 */ static void appendTwoDigits(FastStringBuffer sb, int value) { sb.append((char)(value/10 + '0')); sb.append((char)(value%10 + '0')); } } // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.