All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.value.DateTimeValue Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 Saxonica Limited
// 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.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.value;

import net.sf.saxon.Controller;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.sort.XPathComparable;
import net.sf.saxon.functions.AccessorFn;
import net.sf.saxon.lib.ConversionRules;
import net.sf.saxon.lib.StringCollator;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.om.SequenceTool;
import net.sf.saxon.str.UnicodeBuilder;
import net.sf.saxon.str.UnicodeString;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.NoDynamicContextException;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.transpile.CSharpInjectMembers;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.type.AtomicType;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.ConversionResult;
import net.sf.saxon.type.ValidationFailure;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.time.*;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.*;

/**
 * A value of type {@code xs:dateTime}. This contains integer fields year, month, day, hour, minute,
 * second, and nanosecond. All these fields except year must be non-negative. In the internal
 * representation, the sequence of years runs -2, -1, 0, +1, +2: that is, the year before year 1
 * is year 0.
 * 

The value also contains a boolean flag hasNoYearZero. When this flag is set, accessor * methods that expose the year value subtract one if it is non-positive: that is, year 0 is displayed * as -1, year -1 as -2, and so on. Constructor methods, unless otherwise specified, do not set this * flag.

*

From Saxon 9.9, this class implements the Java 8 interface {@link TemporalAccessor} which enables * it to interoperate with Java 8 temporal classes such as {@link Instant} and {@link ZonedDateTime}.

*/ @CSharpInjectMembers(code={"" + "public static Saxon.Hej.value.DateTimeValue fromDateTime(System.DateTime dt) {" + " Saxon.Hej.value.DateTimeValue dtv = new (dt.Year, (byte)dt.Month, (byte)dt.Day, (byte)dt.Hour, (byte)dt.Minute, (byte)dt.Second, dt.Millisecond * 1000000, 0);" + " return (Saxon.Hej.value.DateTimeValue)dtv.copyAsSubType(Saxon.Hej.type.BuiltInAtomicType.DATE_TIME_STAMP);" + "}" + " public static Saxon.Hej.value.DateTimeValue fromDateTimeOffset(System.DateTimeOffset dt) {" + " Saxon.Hej.value.DateTimeValue dtv = new (dt.Year, (byte)dt.Month, (byte)dt.Day, (byte)dt.Hour, (byte)dt.Minute, (byte)dt.Second, dt.Millisecond * 1000000, (int)dt.Offset.TotalMinutes);" + " return (Saxon.Hej.value.DateTimeValue)dtv.copyAsSubType(Saxon.Hej.type.BuiltInAtomicType.DATE_TIME_STAMP);" + "}" }) public final class DateTimeValue extends CalendarValue implements XPathComparable , TemporalAccessor { private int year; // the year as written, +1 for BC years private byte month; // the month as written, range 1-12 private byte day; // the day as written, range 1-31 private byte hour; // the hour as written (except for midnight), range 0-23 private byte minute; // the minutes as written, range 0-59 private byte second; // the seconds as written, range 0-59 (no leap seconds) private int nanosecond; // the number of nanoseconds within the current second private boolean hasNoYearZero; // true if XSD 1.0 rules apply for negative years /** * Private default constructor */ private DateTimeValue() { } /** * Get the dateTime value representing the nominal * date/time of this transformation run. Two calls within the same * query or transformation will always return the same answer. * * @param context the XPath dynamic context. May be null, in which case * the current date and time are taken directly from the system clock * @return the current xs:dateTime */ /*@Nullable*/ public static DateTimeValue getCurrentDateTime(/*@Nullable*/ XPathContext context) { Controller c; if (context == null || (c = context.getController()) == null) { // non-XSLT/XQuery environment // We also take this path when evaluating compile-time expressions that require an implicit timezone. return now(); } else { return c.getCurrentDateTime(); } } /** * Get the dateTime value representing the moment of invocation of this method, * in the default timezone set for the platform on which the application is running. * @return the current dateTime, in the local timezone for the platform. */ @CSharpReplaceBody(code="return fromDateTimeOffset(System.DateTimeOffset.Now);") public static DateTimeValue now() { return DateTimeValue.fromZonedDateTime(ZonedDateTime.now()); } /** * Constructor: create a dateTime value given a Java calendar object. * The {@code #hasNoYearZero} flag is set to {@code true}. * * @param calendar holds the date and time * @param tzSpecified indicates whether the timezone is specified */ public DateTimeValue(/*@NotNull*/ Calendar calendar, boolean tzSpecified) { int era = calendar.get(GregorianCalendar.ERA); year = calendar.get(Calendar.YEAR); if (era == GregorianCalendar.BC) { year = 1 - year; } month = (byte) (calendar.get(Calendar.MONTH) + 1); day = (byte) calendar.get(Calendar.DATE); hour = (byte) calendar.get(Calendar.HOUR_OF_DAY); minute = (byte) calendar.get(Calendar.MINUTE); second = (byte) calendar.get(Calendar.SECOND); nanosecond = calendar.get(Calendar.MILLISECOND) * 1_000_000; if (tzSpecified) { int tz = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / 60_000; setTimezoneInMinutes(tz); } typeLabel = BuiltInAtomicType.DATE_TIME; hasNoYearZero = true; } /** * Factory method: create a dateTime value given a Java Date object. The returned dateTime * value will always have a timezone, which will always be UTC. * * @param suppliedDate holds the date and time * @return the corresponding xs:dateTime value * @throws XPathException if a dynamic error occurs */ /*@NotNull*/ public static DateTimeValue fromJavaDate(/*@NotNull*/ Date suppliedDate) throws XPathException { long millis = suppliedDate.getTime(); return EPOCH.add(DayTimeDurationValue.fromMilliseconds(millis)); } /** * Factory method: create a dateTime value given a Java time, expressed in milliseconds since 1970. The returned dateTime * value will always have a timezone, which will always be UTC. * * @param time the time in milliseconds since the epoch * @return the corresponding xs:dateTime value * @throws XPathException if a dynamic error occurs */ /*@NotNull*/ public static DateTimeValue fromJavaTime(long time) throws XPathException { return EPOCH.add(DayTimeDurationValue.fromMilliseconds(time)); } /** * Factory method: create a dateTime value given the components of a Java Instant. The java.time.Instant class * is new in JDK 8, and this method was introduced under older JDKs, so this method takes two arguments, * the seconds and nano-seconds values, which can be obtained from a Java Instant * using the methods getEpochSecond() and getNano() respectively * * @param seconds the time in seconds since the epoch * @param nano the additional nanoseconds * @return the corresponding xs:dateTime value, which will have timezone Z (UTC) * @throws XPathException if a dynamic error occurs (typically due to overflow) */ /*@NotNull*/ public static DateTimeValue fromJavaInstant(long seconds, int nano) throws XPathException { return EPOCH .add(DayTimeDurationValue.fromSeconds(BigDecimal.valueOf(seconds)) .add(DayTimeDurationValue.fromNanoseconds(nano))); } /** * Factory method: create a dateTime value given a Java {@link Instant}. The {@code java.time.Instant} class * is new in JDK 8. * * @param instant the point in time * @return the corresponding xs:dateTime value, which will have timezone Z (UTC) * @since 9.9 */ /*@NotNull*/ public static DateTimeValue fromJavaInstant(Instant instant) { try { return fromJavaInstant(instant.getEpochSecond(), instant.getNano()); } catch (XPathException e) { throw new AssertionError(); } } /** * Factory method: create a dateTime value given a Java {@link ZonedDateTime}. The {@code java.time.ZonedDateTime} class * is new in JDK 8. * * @param zonedDateTime the supplied zonedDateTime value * @return the corresponding xs:dateTime value, which will have the same timezone offset as the supplied ZonedDateTime; * the actual (civil) timezone information is lost. The returned value will be an instance of the built-in * subtype {@code xs:dateTimeStamp} * @since 9.9. Changed in 10.0 to retain nanosecond precision. */ /*@NotNull*/ public static DateTimeValue fromZonedDateTime(ZonedDateTime zonedDateTime) { return fromOffsetDateTime(zonedDateTime.toOffsetDateTime()); } /** * Factory method: create a dateTime value given a Java {@link OffsetDateTime}. The {@code java.time.OffsetDateTime} class * is new in JDK 8. * * @param offsetDateTime the supplied zonedDateTime value * @return the corresponding xs:dateTime value, which will have the same timezone offset as the supplied OffsetDateTime. * The returned value will be an instance of the built-in subtype {@code xs:dateTimeStamp} * @since 10.0. */ /*@NotNull*/ public static DateTimeValue fromOffsetDateTime(OffsetDateTime offsetDateTime) { LocalDateTime ldt = offsetDateTime.toLocalDateTime(); ZoneOffset zo = offsetDateTime.getOffset(); int tz = zo.getTotalSeconds() / 60; DateTimeValue dtv = new DateTimeValue(ldt.getYear(), (byte) ldt.getMonthValue(), (byte) ldt.getDayOfMonth(), (byte) ldt.getHour(), (byte) ldt.getMinute(), (byte) ldt.getSecond(), ldt.getNano(), tz); dtv.typeLabel = BuiltInAtomicType.DATE_TIME_STAMP; dtv.hasNoYearZero = false; return dtv; } /** * Factory method: create a dateTime value given a Java {@link LocalDateTime}. The {@code java.time.LocalDateTime} class * is new in JDK 8. * * @param localDateTime the supplied localDateTime value * @return the corresponding xs:dateTime value, which will have no timezone. * @since 9.9. Changed in 10.0 to retain nanosecond precision. */ /*@NotNull*/ public static DateTimeValue fromLocalDateTime(LocalDateTime localDateTime) { DateTimeValue dtv = new DateTimeValue(localDateTime.getYear(), (byte) localDateTime.getMonthValue(), (byte) localDateTime.getDayOfMonth(), (byte) localDateTime.getHour(), (byte) localDateTime.getMinute(), (byte) localDateTime.getSecond(), localDateTime.getNano(), NO_TIMEZONE); dtv.hasNoYearZero = false; return dtv; } /** * Fixed date/time used by Java (and Unix) as the origin of the universe: 1970-01-01T00:00:00Z */ /*@NotNull*/ public static final DateTimeValue EPOCH = new DateTimeValue(1970, (byte) 1, (byte) 1, (byte) 0, (byte) 0, (byte) 0, 0, 0, true); /** * Factory method: create a dateTime value given a date and a time. * * @param date the date * @param time the time * @return the dateTime with the given components. If either component is null, returns null. The returned * {@code DateTimeValue} will have the {@code #hasNoYearZero} property if and only if the supplied * date has this property. * @throws XPathException if the timezones are both present and inconsistent */ /*@Nullable*/ public static DateTimeValue makeDateTimeValue(/*@Nullable*/ DateValue date, /*@Nullable*/ TimeValue time) throws XPathException { if (date == null || time == null) { return null; } int tz1 = date.getTimezoneInMinutes(); int tz2 = time.getTimezoneInMinutes(); if (tz1 != NO_TIMEZONE && tz2 != NO_TIMEZONE && tz1 != tz2) { XPathException err = new XPathException("Supplied date and time are in different timezones"); err.setErrorCode("FORG0008"); throw err; } DateTimeValue v = date.toDateTime(); v.hour = time.getHour(); v.minute = time.getMinute(); v.second = time.getSecond(); v.nanosecond = time.getNanosecond(); v.setTimezoneInMinutes(Math.max(tz1, tz2)); v.typeLabel = BuiltInAtomicType.DATE_TIME; v.hasNoYearZero = date.hasNoYearZero; return v; } /** * Factory method: create a dateTime value from a supplied string, in * ISO 8601 format. *

If the supplied {@link ConversionRules} object has {@link ConversionRules#isAllowYearZero()} returning * true, then (a) a year value of zero is allowed in the supplied string, and (b) the {@code hasNoYearZero} * property in the result is set to false. If {@link ConversionRules#isAllowYearZero()} returns false, * the (a) the year value in the supplied string must not be zero, (b) a year value of -1 in the supplied * string is interpreted as representing the year before year 1, and (c) the {@code hasNoYearZero} property * in the result is set to true.

* * @param s a string in the lexical space of xs:dateTime * @param rules the conversion rules to be used (determining whether year zero is allowed) * @return either a DateTimeValue representing the xs:dateTime supplied, or a ValidationFailure if * the lexical value was invalid */ /*@NotNull*/ public static ConversionResult makeDateTimeValue(UnicodeString s, /*@NotNull*/ ConversionRules rules) { // input must have format [-]yyyy-mm-ddThh:mm:ss[.fff*][([+|-]hh:mm | Z)] DateTimeValue dt = new DateTimeValue(); dt.hasNoYearZero = !rules.isAllowYearZero(); StringTokenizer tok = new StringTokenizer(Whitespace.trim(s).toString(), "-:.+TZ", true); if (!tok.hasMoreTokens()) { return badDate("too short", s); } String part = tok.nextToken(); int era = +1; if ("+".equals(part)) { return badDate("Date must not start with '+' sign", s); } else if ("-".equals(part)) { era = -1; if (!tok.hasMoreTokens()) { return badDate("No year after '-'", s); } part = tok.nextToken(); } int value = DurationValue.simpleInteger(part); if (value < 0) { if (value == -1) { return badDate("Non-numeric year component", s); } else { return badDate("Year is outside the range that Saxon can handle", s, "FODT0001"); } } dt.year = value * era; if (part.length() < 4) { return badDate("Year is less than four digits", s); } if (part.length() > 4 && part.charAt(0) == '0') { return badDate("When year exceeds 4 digits, leading zeroes are not allowed", s); } if (dt.year == 0 && !rules.isAllowYearZero()) { return badDate("Year zero is not allowed", s); } if (era < 0 && !rules.isAllowYearZero()) { dt.year++; // if year zero not allowed, -0001 is the year before +0001, represented as 0 internally. } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } if (!"-".equals(tok.nextToken())) { return badDate("Wrong delimiter after year", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } part = tok.nextToken(); if (part.length() != 2) { return badDate("Month must be two digits", s); } value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric month component", s); } dt.month = (byte) value; if (dt.month < 1 || dt.month > 12) { return badDate("Month is out of range", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } if (!"-".equals(tok.nextToken())) { return badDate("Wrong delimiter after month", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } part = (String) tok.nextToken(); if (part.length() != 2) { return badDate("Day must be two digits", s); } value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric day component", s); } dt.day = (byte) value; if (dt.day < 1 || dt.day > 31) { return badDate("Day is out of range", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } if (!"T".equals(tok.nextToken())) { return badDate("Wrong delimiter after day", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } part = tok.nextToken(); if (part.length() != 2) { return badDate("Hour must be two digits", s); } value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric hour component", s); } dt.hour = (byte) value; if (dt.hour > 24) { return badDate("Hour is out of range", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } if (!":".equals(tok.nextToken())) { return badDate("Wrong delimiter after hour", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } part = tok.nextToken(); if (part.length() != 2) { return badDate("Minute must be two digits", s); } value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric minute component", s); } dt.minute = (byte) value; if (dt.minute > 59) { return badDate("Minute is out of range", s); } if (dt.hour == 24 && dt.minute != 0) { return badDate("If hour is 24, minute must be 00", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } if (!":".equals(tok.nextToken())) { return badDate("Wrong delimiter after minute", s); } if (!tok.hasMoreTokens()) { return badDate("Too short", s); } part = tok.nextToken(); if (part.length() != 2) { return badDate("Second must be two digits", s); } value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric second component", s); } dt.second = (byte) value; if (dt.second > 59) { return badDate("Second is out of range", s); } if (dt.hour == 24 && dt.second != 0) { return badDate("If hour is 24, second must be 00", s); } int tz = 0; boolean negativeTz = false; int state = 0; while (tok.hasMoreTokens()) { if (state == 9) { return badDate("Characters after the end", s); } String delim = (String) tok.nextToken(); if (".".equals(delim)) { if (state != 0) { return badDate("Decimal separator occurs twice", s); } if (!tok.hasMoreTokens()) { return badDate("Decimal point must be followed by digits", s); } part = (String) tok.nextToken(); if (part.length() > 9 && part.matches("^[0-9]+$")) { part = part.substring(0, 9); } value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric fractional seconds component", s); } double fractionalSeconds = Double.parseDouble('.' + part); int nanoSeconds = (int) Math.round(fractionalSeconds * 1_000_000_000); if (nanoSeconds == 1_000_000_000) { nanoSeconds--; // truncate fractional seconds to .999_999_999 if nanoseconds rounds to 1_000_000_000 } dt.nanosecond = nanoSeconds; if (dt.hour == 24 && dt.nanosecond != 0) { return badDate("If hour is 24, fractional seconds must be 0", s); } state = 1; } else if ("Z".equals(delim)) { if (state > 1) { return badDate("Z cannot occur here", s); } tz = 0; state = 9; // we've finished dt.setTimezoneInMinutes(0); } else if ("+".equals(delim) || "-".equals(delim)) { if (state > 1) { return badDate(delim + " cannot occur here", s); } state = 2; if (!tok.hasMoreTokens()) { return badDate("Missing timezone", s); } part = (String) tok.nextToken(); if (part.length() != 2) { return badDate("Timezone hour must be two digits", s); } value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric timezone hour component", s); } tz = value; if (tz > 14) { return badDate("Timezone is out of range (-14:00 to +14:00)", s); } tz *= 60; if ("-".equals(delim)) { negativeTz = true; } } else if (":".equals(delim)) { if (state != 2) { return badDate("Misplaced ':'", s); } state = 9; part = (String) tok.nextToken(); value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric timezone minute component", s); } int tzminute = value; if (part.length() != 2) { return badDate("Timezone minute must be two digits", s); } if (tzminute > 59) { return badDate("Timezone minute is out of range", s); } if (Math.abs(tz) == 14 * 60 && tzminute != 0) { return badDate("Timezone is out of range (-14:00 to +14:00)", s); } tz += tzminute; if (negativeTz) { tz = -tz; } dt.setTimezoneInMinutes(tz); } else { return badDate("Timezone format is incorrect", s); } } if (state == 2 || state == 3) { return badDate("Timezone incomplete", s); } boolean midnight = false; if (dt.hour == 24) { dt.hour = 0; midnight = true; } // Check that this is a valid calendar date if (!DateValue.isValidDate(dt.year, dt.month, dt.day)) { return badDate("Non-existent date", s); } // Adjust midnight to 00:00:00 on the next day if (midnight) { DateValue t = DateValue.tomorrow(dt.year, dt.month, dt.day); dt.year = t.getYear(); dt.month = t.getMonth(); dt.day = t.getDay(); } dt.typeLabel = BuiltInAtomicType.DATE_TIME; return dt; } /** * Factory method: create a dateTime value from a supplied string, in ISO 8601 format, allowing * a year value of 0 to represent the year before year 1 (that is, following the XSD 1.1 rules). *

The {@code hasNoYearZero} property in the result is set to false.

* * @param s a string in the lexical space of xs:dateTime * @return a DateTimeValue representing the xs:dateTime supplied, including a timezone offset if * present in the lexical representation * @throws DateTimeParseException if the format of the supplied string is invalid. * @since 9.9 */ public static DateTimeValue parse(UnicodeString s) throws DateTimeParseException { ConversionResult result = makeDateTimeValue(s, ConversionRules.DEFAULT); if (result instanceof ValidationFailure) { throw new DateTimeParseException(((ValidationFailure) result).getMessage(), s.toString(), 0); } else { return (DateTimeValue)result; } } private static ValidationFailure badDate(String msg, UnicodeString value) { ValidationFailure err = new ValidationFailure( "Invalid dateTime value " + Err.wrap(value, Err.VALUE) + " (" + msg + ")"); err.setErrorCode("FORG0001"); return err; } private static ValidationFailure badDate(String msg, UnicodeString value, String errorCode) { ValidationFailure err = new ValidationFailure( "Invalid dateTime value " + Err.wrap(value, Err.VALUE) + " (" + msg + ")"); err.setErrorCode(errorCode); return err; } /** * Constructor: construct a DateTimeValue from its components. * This constructor performs no validation. *

Note: this constructor accepts the fractional seconds value * to nanosecond precision. It creates a DateTimeValue that follows XSD 1.1 conventions: * that is, there is a year zero. This can be changed by setting the {@code hasNoYearZero} property.

* * @param year The year (the year before year 1 is year 0) * @param month The month, 1-12 * @param day The day 1-31 * @param hour the hour value, 0-23 * @param minute the minutes value, 0-59 * @param second the seconds value, 0-59 * @param nanosecond the number of nanoseconds, 0-999_999_999 * @param tz the timezone displacement in minutes from UTC. Supply the value * {@link CalendarValue#NO_TIMEZONE} if there is no timezone component. */ public DateTimeValue(int year, byte month, byte day, byte hour, byte minute, byte second, int nanosecond, int tz) { this.hasNoYearZero = false; this.year = year; this.month = month; this.day = day; this.hour = hour; this.minute = minute; this.second = second; this.nanosecond = nanosecond; setTimezoneInMinutes(tz); typeLabel = BuiltInAtomicType.DATE_TIME; } /** * Constructor: construct a DateTimeValue from its components. * This constructor performs no validation. *

Note: for historic reasons, this constructor accepts the fractional seconds value * only to microsecond precision. To get nanosecond precision, use the 8-argument constructor * and set the XSD 1.0 option separately

* * @param year The year as held internally (so the year before year 1 is year 0) * @param month The month, 1-12 * @param day The day 1-31 * @param hour the hour value, 0-23 * @param minute the minutes value, 0-59 * @param second the seconds value, 0-59 * @param microsecond the number of microseconds, 0-999999 * @param tz the timezone displacement in minutes from UTC. Supply the value * {@link CalendarValue#NO_TIMEZONE} if there is no timezone component. * @param hasNoYearZero true if the dateTime value should behave under XSD 1.0 rules, that is, * negative dates assume there is no year zero. Note that regardless of this * setting, the year argument is set on the basis that the year before +1 is * supplied as zero; but if the hasNoYearZero flag is set, this value will be displayed * with a year of -1, and {@link #getYear() will return -1} */ public DateTimeValue(int year, byte month, byte day, byte hour, byte minute, byte second, int microsecond, int tz, boolean hasNoYearZero) { this.hasNoYearZero = hasNoYearZero; this.year = year; this.month = month; this.day = day; this.hour = hour; this.minute = minute; this.second = second; this.nanosecond = microsecond*1000; setTimezoneInMinutes(tz); typeLabel = BuiltInAtomicType.DATE_TIME; } /** * Create a DateTime value equivalent to this value, except for a possible change to * the {@code hasNoYearZero} property. The internal representation is unchanged (year 0 is the * year before year 1), but if the property {@code hasNoYearZero} is set, then a value * with */ /** * Determine the primitive type of the value. This delivers the same answer as * getItemType().getPrimitiveItemType(). The primitive types are * the 19 primitive types of XML Schema, plus xs:integer, xs:dayTimeDuration and xs:yearMonthDuration, * and xs:untypedAtomic. For external objects, the result is AnyAtomicType. */ /*@NotNull*/ @Override public BuiltInAtomicType getPrimitiveType() { return BuiltInAtomicType.DATE_TIME; } /** * Get the year component, in its internal form (which allows a year zero) * * @return the year component */ public int getYear() { return year; } /** * Get the month component, 1-12 * * @return the month component */ public byte getMonth() { return month; } /** * Get the day component, 1-31 * * @return the day component */ public byte getDay() { return day; } /** * Get the hour component, 0-23 * * @return the hour component (never 24, even if the input was specified as 24:00:00) */ public byte getHour() { return hour; } /** * Get the minute component, 0-59 * * @return the minute component */ public byte getMinute() { return minute; } /** * Get the second component, 0-59 * * @return the second component */ public byte getSecond() { return second; } /** * Get the microsecond component, 0-999999 * * @return the nanosecond component divided by 1000, rounded towards zero */ public int getMicrosecond() { return nanosecond / 1000; } /** * Get the nanosecond component, 0-999999 * * @return the nanosecond component */ public int getNanosecond() { return nanosecond; } /** * Convert the value to an xs:dateTime, retaining all the components that are actually present, and * substituting conventional values for components that are missing. (This method does nothing in * the case of xs:dateTime, but is there to implement a method in the {@link CalendarValue} interface). * * @return the value as an xs:dateTime */ /*@NotNull*/ @Override public DateTimeValue toDateTime() { return this; } /** * Ask whether this value uses the XSD 1.0 rules (which don't allow year zero) or the XSD 1.1 rules (which do). * * @return true if the value uses the XSD 1.0 rules */ public boolean isXsd10Rules() { return hasNoYearZero; } /** * Check that the value can be handled in Saxon-JS * * @throws XPathException if it can't be handled in Saxon-JS */ @Override public void checkValidInJavascript() throws XPathException { if (year <= 0 || year > 9999) { throw new XPathException("Year out of range for Saxon-JS", "FODT0001"); } } /** * Normalize the date and time to be in timezone Z. * * @param implicitTimezone used to supply the implicit timezone, used when the value has * no explicit timezone * @return in general, a new DateTimeValue in timezone Z, representing the same instant in time. * Returns the original DateTimeValue if this is already in timezone Z. * @throws NoDynamicContextException if the implicit timezone is needed and is CalendarValue.MISSING_TIMEZONE * or CalendarValue.NO_TIMEZONE */ /*@NotNull*/ public DateTimeValue adjustToUTC(int implicitTimezone) throws NoDynamicContextException { if (hasTimezone()) { return adjustTimezone(0); } else { if (implicitTimezone == CalendarValue.MISSING_TIMEZONE || implicitTimezone == CalendarValue.NO_TIMEZONE) { throw new NoDynamicContextException("DateTime operation needs access to implicit timezone"); } DateTimeValue dt = copyAsSubType(null); dt.setTimezoneInMinutes(implicitTimezone); return dt.adjustTimezone(0); } } /** * Get the Julian instant: a decimal value whose integer part is the Julian day number * multiplied by the number of seconds per day, * and whose fractional part is the fraction of the second. * This method operates on the local time, ignoring the timezone. The caller should call normalize() * before calling this method to get a normalized time. * * @return the Julian instant corresponding to this xs:dateTime value */ public BigDecimal toJulianInstant() { int julianDay = DateValue.getJulianDayNumber(year, month, day); long julianSecond = julianDay * 24L * 60L * 60L; julianSecond += ((hour * 60L + minute) * 60L) + second; BigDecimal j = BigDecimal.valueOf(julianSecond); if (nanosecond == 0) { return j; } else { return j.add(BigDecimal.valueOf(nanosecond).divide(BigDecimalValue.BIG_DECIMAL_ONE_BILLION, 9, RoundingMode.HALF_EVEN)); } } /** * Get the DateTimeValue corresponding to a given Julian instant * * @param instant the Julian instant: a decimal value whose integer part is the Julian day number * multiplied by the number of seconds per day, and whose fractional part is the fraction of the second. * @return the xs:dateTime value corresponding to the Julian instant. This will always be in timezone Z. */ /*@NotNull*/ public static DateTimeValue fromJulianInstant(/*@NotNull*/ BigDecimal instant) { BigInteger julianSecond = instant.toBigInteger(); BigDecimal nanoseconds = instant.subtract(new BigDecimal(julianSecond)).multiply(BigDecimalValue.BIG_DECIMAL_ONE_BILLION); long js = julianSecond.longValue(); long jd = js / (24L * 60L * 60L); DateValue date = DateValue.dateFromJulianDayNumber((int) jd); js = js % (24L * 60L * 60L); byte hour = (byte) (js / (60L * 60L)); js = js % (60L * 60L); byte minute = (byte) (js / 60L); js = js % 60L; return new DateTimeValue(date.getYear(), date.getMonth(), date.getDay(), hour, minute, (byte) js, nanoseconds.intValue(), 0); } /** * Generate a value from the current date and time that can be used to seed a random number generator * @return a long derived arbitrarily from the current date and time */ @CSharpReplaceBody(code="return ((((long)minute)*360 + ((long)second))*60) * 1000000000L + (long)nanosecond;") public long randomSeed() { return getCalendar().getTimeInMillis(); } /** * Get a Java Calendar object representing the value of this DateTime. This will respect the timezone * if there is one (provided the timezone is within the range supported by the {@code GregorianCalendar} * class, which in practice means that it is not -14:00). If there is no timezone or if * the timezone is out of range, the result will be in GMT. * * @return a Java GregorianCalendar object representing the value of this xs:dateTime value. */ /*@NotNull*/ @Override public GregorianCalendar getCalendar() { int tz = hasTimezone() ? getTimezoneInMinutes() * 60000 : 0; TimeZone zone = new SimpleTimeZone(tz, "LLL"); GregorianCalendar calendar = new GregorianCalendar(zone); if (tz < calendar.getMinimum(Calendar.ZONE_OFFSET) || tz > calendar.getMaximum(Calendar.ZONE_OFFSET)) { return adjustTimezone(0).getCalendar(); } calendar.setGregorianChange(new Date(Long.MIN_VALUE)); calendar.setLenient(false); int yr = year; if (year <= 0) { yr = hasNoYearZero ? 1 - year : 0 - year; calendar.set(Calendar.ERA, GregorianCalendar.BC); } //noinspection MagicConstant calendar.set(yr, month - 1, day, hour, minute, second); calendar.set(Calendar.MILLISECOND, nanosecond / 1_000_000); // loses precision unavoidably calendar.set(Calendar.ZONE_OFFSET, tz); calendar.set(Calendar.DST_OFFSET, 0); return calendar; } /** * Get a Java 8 {@link Instant} corresponding to this date and time. The value will respect the time zone * offset if present, or will assume UTC otherwise. * @return an {@code Instant} representing this date and time */ public Instant toJavaInstant() { return Instant.from(this); } /** * Get a Java 8 {@link ZonedDateTime} corresponding to this date and time. The value will respect the time zone * offset if present, or will assume UTC otherwise. * @return a {@code ZonedDateTime} representing this date and time, including its timezone if present, or * interpreted as a UTC date/time otherwise. * @since 9.9 */ public ZonedDateTime toZonedDateTime() { if (hasTimezone()) { return ZonedDateTime.from(this); } else { try { return ZonedDateTime.from(adjustToUTC(0)); } catch (NoDynamicContextException e) { throw new AssertionError(e); } } } /** * Get a Java 8 {@link OffsetDateTime} corresponding to this date and time. The value will respect the time zone * offset if present, or will assume UTC otherwise. * * @return a {@code OffsetDateTime} representing this date and time, including its timezone if present, or * interpreted as a UTC date/time otherwise. * @since 9.9 */ public OffsetDateTime toOffsetDateTime() { if (hasTimezone()) { return OffsetDateTime.from(this); } else { try { return OffsetDateTime.from(adjustToUTC(0)); } catch (NoDynamicContextException e) { throw new AssertionError(e); } } } /** * Get a Java 8 {@link LocalDateTime} corresponding to this date and time. The value will ignore any timezone * offset present in this value. * @return a {@code LocalDateTime} equivalent to this date and time, discarding any time zone offset if present. * @since 9.9 */ public LocalDateTime toLocalDateTime() { return LocalDateTime.from(this); } /** * Convert to string * * @return ISO 8601 representation. The value returned is the localized representation: * that is it uses the timezone contained within the value itself. In the case * of a year earlier than year 1, the value output is the internally-held year, * unless the {@code hasNoYearZero} flag is set, in which case it is the * internal year minus one. */ /*@NotNull*/ @Override public UnicodeString getPrimitiveStringValue() { UnicodeBuilder sb = new UnicodeBuilder(32); int yr = year; if (year <= 0) { yr = -yr + (hasNoYearZero ? 1 : 0); // no year zero in lexical space for XSD 1.0 if (yr != 0) { sb.append('-'); } } appendString(sb, yr, yr > 9999 ? (yr + "").length() : 4); sb.append('-'); appendTwoDigits(sb, month); sb.append('-'); appendTwoDigits(sb, day); sb.append('T'); appendTwoDigits(sb, hour); sb.append(':'); appendTwoDigits(sb, minute); sb.append(':'); appendTwoDigits(sb, second); if (nanosecond != 0) { sb.append('.'); int ms = nanosecond; int div = 100_000_000; while (ms > 0) { int d = ms / div; sb.append((char) (d + '0')); ms = ms % div; div /= 10; } } if (hasTimezone()) { appendTimezone(sb); } return sb.toUnicodeString(); } /** * Extract the Date part * * @return a DateValue representing the date part of the dateTime, retaining the timezone or its absence */ /*@NotNull*/ public DateValue toDateValue() { return new DateValue(year, month, day, getTimezoneInMinutes(), hasNoYearZero); } /** * Extract the Time part * * @return a TimeValue representing the date part of the dateTime, retaining the timezone or its absence */ /*@NotNull*/ public TimeValue toTimeValue() { return new TimeValue(hour, minute, second, nanosecond, getTimezoneInMinutes(), ""); } /** * Get the canonical lexical representation as defined in XML Schema. This is not always the same * as the result of casting to a string according to the XPath rules. For an xs:dateTime it is the * date/time adjusted to UTC. * * @return the canonical lexical representation as defined in XML Schema */ @Override public UnicodeString getCanonicalLexicalRepresentation() { if (hasTimezone() && getTimezoneInMinutes() != 0) { return adjustTimezone(0).getUnicodeStringValue(); } else { return this.getUnicodeStringValue(); } } /** * Make a copy of this date, time, or dateTime value, but with a new type label * * @param typeLabel the type label to be attached to the new copy. It is the caller's responsibility * to ensure that the value actually conforms to the rules for this type. */ /*@NotNull*/ @Override public DateTimeValue copyAsSubType(AtomicType typeLabel) { DateTimeValue v = new DateTimeValue(year, month, day, hour, minute, second, nanosecond, getTimezoneInMinutes()); v.hasNoYearZero = hasNoYearZero; v.typeLabel = typeLabel; return v; } /** * Return a new dateTime with the same normalized value, but * in a different timezone. * * @param timezone the new timezone offset, in minutes * @return the date/time in the new timezone. This will be a new DateTimeValue unless no change * was required to the original value */ /*@NotNull*/ @Override public DateTimeValue adjustTimezone(int timezone) { if (!hasTimezone()) { DateTimeValue in = copyAsSubType(typeLabel); in.setTimezoneInMinutes(timezone); return in; } int oldtz = getTimezoneInMinutes(); if (oldtz == timezone) { return this; } int tz = timezone - oldtz; int h = hour; int mi = minute; mi += tz; if (mi < 0 || mi > 59) { h += (int)Math.floor(mi / 60.0); mi = (mi + 60 * 24) % 60; } if (h >= 0 && h < 24) { DateTimeValue d2 = new DateTimeValue(year, month, day, (byte) h, (byte) mi, second, nanosecond, timezone); d2.hasNoYearZero = hasNoYearZero; return d2; } // Following code is designed to handle the corner case of adjusting from -14:00 to +14:00 or // vice versa, which can cause a change of two days in the date DateTimeValue dt = this; while (h < 0) { h += 24; DateValue t = DateValue.yesterday(dt.getYear(), dt.getMonth(), dt.getDay()); dt = new DateTimeValue(t.getYear(), t.getMonth(), t.getDay(), (byte) h, (byte) mi, second, nanosecond, timezone); dt.hasNoYearZero = hasNoYearZero; } if (h > 23) { h -= 24; DateValue t = DateValue.tomorrow(year, month, day); dt = new DateTimeValue(t.getYear(), t.getMonth(), t.getDay(), (byte) h, (byte) mi, second, nanosecond, timezone); dt.hasNoYearZero = hasNoYearZero; } return dt; } /** * Add a duration to a dateTime * * @param duration the duration to be added (may be negative) * @return the new date * @throws net.sf.saxon.trans.XPathException * if the duration is an xs:duration, as distinct from * a subclass thereof */ /*@NotNull*/ @Override public DateTimeValue add(/*@NotNull*/ DurationValue duration) throws XPathException { if (duration instanceof DayTimeDurationValue) { BigDecimal seconds = duration.getTotalSeconds(); BigDecimal julian = toJulianInstant(); julian = julian.add(seconds); DateTimeValue dt = fromJulianInstant(julian); dt.setTimezoneInMinutes(getTimezoneInMinutes()); dt.hasNoYearZero = this.hasNoYearZero; return dt; } else if (duration instanceof YearMonthDurationValue) { int months = ((YearMonthDurationValue) duration).getLengthInMonths(); int m = (month - 1) + months; int y = year + m / 12; m = m % 12; if (m < 0) { m += 12; y -= 1; } m++; int d = day; while (!DateValue.isValidDate(y, m, d)) { d -= 1; } DateTimeValue dtv = new DateTimeValue(y, (byte) m, (byte) d, hour, minute, second, nanosecond, getTimezoneInMinutes()); dtv.hasNoYearZero = hasNoYearZero; return dtv; } else { XPathException err = new XPathException("DateTime arithmetic is not supported on xs:duration, only on its subtypes"); err.setErrorCode("XPTY0004"); err.setIsTypeError(true); throw err; } } /** * Determine the difference between two points in time, as a duration * * @param other the other point in time * @param context the XPath dynamic context * @return the duration as an xs:dayTimeDuration * @throws net.sf.saxon.trans.XPathException * for example if one value is a date and the other is a time */ @Override public DayTimeDurationValue subtract(/*@NotNull*/ CalendarValue other, XPathContext context) throws XPathException { if (!(other instanceof DateTimeValue)) { XPathException err = new XPathException("First operand of '-' is a dateTime, but the second is not"); err.setErrorCode("XPTY0004"); err.setIsTypeError(true); throw err; } return super.subtract(other, context); } public BigDecimal secondsSinceEpoch() { try { DateTimeValue dtv = adjustToUTC(0); BigDecimal d1 = dtv.toJulianInstant(); BigDecimal d2 = EPOCH.toJulianInstant(); return d1.subtract(d2); } catch (NoDynamicContextException e) { throw new AssertionError(e); } } /** * Get a component of the value. Returns null if the timezone component is * requested and is not present. * @param component identifies the required component */ /*@Nullable*/ @Override public AtomicValue getComponent(AccessorFn.Component component) throws XPathException { switch (component) { case YEAR_ALLOWING_ZERO: return Int64Value.makeIntegerValue(year); case YEAR: return Int64Value.makeIntegerValue(year > 0 || !hasNoYearZero ? year : year - 1); case MONTH: return Int64Value.makeIntegerValue(month); case DAY: return Int64Value.makeIntegerValue(day); case HOURS: return Int64Value.makeIntegerValue(hour); case MINUTES: return Int64Value.makeIntegerValue(minute); case SECONDS: BigDecimal d = BigDecimal.valueOf(nanosecond); d = d.divide(BigDecimalValue.BIG_DECIMAL_ONE_BILLION, 6, RoundingMode.HALF_UP); d = d.add(BigDecimal.valueOf(second)); return new BigDecimalValue(d); case WHOLE_SECONDS: //(internal use only) return Int64Value.makeIntegerValue(second); case MICROSECONDS: // internal use only return new Int64Value(nanosecond / 1000); case NANOSECONDS: // internal use only return new Int64Value(nanosecond); case TIMEZONE: if (hasTimezone()) { return DayTimeDurationValue.fromMilliseconds(60000L * getTimezoneInMinutes()); } else { return null; } default: throw new IllegalArgumentException("Unknown component for dateTime: " + component); } } private SequenceIterator oneInt(long value) { return SingletonIterator.makeIterator(Int64Value.makeIntegerValue(value)); } @Override public boolean isSupported(TemporalField field) { if (field.equals(ChronoField.OFFSET_SECONDS)) { return getTimezoneInMinutes() != NO_TIMEZONE; } else if (field instanceof ChronoField) { return true; } else { return field.isSupportedBy(this); } } /** * Gets the value of the specified field as a {@code long}. *

This queries the date-time for the value of the specified field. * The returned value may be outside the valid range of values for the field. * If the date-time cannot return the value, because the field is unsupported or for * some other reason, an exception will be thrown.

* *

The specification requires some fields (for example {@link ChronoField#EPOCH_DAY} to * reflect the local time. The Saxon implementation does not have access to the local time, * so it adjusts to UTC instead.

* * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs *

Note: Implementations must check and handle all fields defined in {@link ChronoField}. * If the field is supported, then the value of the field must be returned. * If unsupported, then an {@code UnsupportedTemporalTypeException} must be thrown.

*

If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} * passing {@code this} as the argument.

*

Implementations must ensure that no observable state is altered when this * read-only method is invoked.

*/ @Override public long getLong(TemporalField field) { if (field instanceof ChronoField) { switch ((ChronoField) field) { case NANO_OF_SECOND: return nanosecond; case NANO_OF_DAY: return (hour * 3600 + minute * 60 + second) * 1_000_000_000L + nanosecond; case MICRO_OF_SECOND: return nanosecond / 1000; case MICRO_OF_DAY: return (hour * 3600 + minute * 60 + second) * 1_000_000L + (nanosecond / 1000); case MILLI_OF_SECOND: return nanosecond / 1_000_000; case MILLI_OF_DAY: return (hour * 3600 + minute * 60 + second)*1000L + nanosecond / 1_000_000; case SECOND_OF_MINUTE: return second; case SECOND_OF_DAY: return hour*3600 + minute*60 + second; case MINUTE_OF_HOUR: return minute; case MINUTE_OF_DAY: return hour*60 + minute; case HOUR_OF_AMPM: return hour%12; case CLOCK_HOUR_OF_AMPM: return (hour+11)%12 + 1; case HOUR_OF_DAY: return hour; case CLOCK_HOUR_OF_DAY: return (hour+23)%24 + 1; case AMPM_OF_DAY: return hour/12; // specification is unclear about noon and midnight case DAY_OF_WEEK: return DateValue.getDayOfWeek(year, month, day); case ALIGNED_DAY_OF_WEEK_IN_MONTH: return (day - 1) % 7 + 1; case ALIGNED_DAY_OF_WEEK_IN_YEAR: return (DateValue.getDayWithinYear(year, month, day) - 1) % 7 + 1; case DAY_OF_MONTH: return day; case DAY_OF_YEAR: return DateValue.getDayWithinYear(year, month, day); case EPOCH_DAY: BigDecimal secs = secondsSinceEpoch(); long days = secondsSinceEpoch().longValue() / (24*60*60); return secs.signum() < 0 ? days-1 : days; case ALIGNED_WEEK_OF_MONTH: return (day - 1) / 7 + 1; case ALIGNED_WEEK_OF_YEAR: return (DateValue.getDayWithinYear(year, month, day) - 1) / 7 + 1; case MONTH_OF_YEAR: return month; case PROLEPTIC_MONTH: return year*12 + month - 1; case YEAR_OF_ERA: return Math.abs(year) + (year<0 ? 1 : 0); case YEAR: return year; case ERA: return year<0 ? 0 : 1; case INSTANT_SECONDS: return secondsSinceEpoch().setScale(0, BigDecimal.ROUND_FLOOR).longValue(); case OFFSET_SECONDS: int tz = getTimezoneInMinutes(); if (tz == NO_TIMEZONE) { throw new UnsupportedTemporalTypeException("xs:dateTime value has no timezone"); } else { return tz * 60; } default: throw new UnsupportedTemporalTypeException(field.toString()); } } else { return field.getFrom(this); } } /** * Compare the value to another dateTime value, following the XPath comparison semantics * * @param other The other dateTime value * @param implicitTimezone The implicit timezone to be used for a value with no timezone * @return negative value if this one is the earler, 0 if they are chronologically equal, * positive value if this one is the later. For this purpose, dateTime values with an unknown * timezone are considered to be values in the implicit timezone (the Comparable interface requires * a total ordering). * @throws ClassCastException if the other value is not a DateTimeValue (the parameter * is declared as CalendarValue to satisfy the interface) * @throws NoDynamicContextException if the implicit timezone is needed and is not available */ @Override public int compareTo(/*@NotNull*/ CalendarValue other, int implicitTimezone) throws NoDynamicContextException { if (!(other instanceof DateTimeValue)) { throw new ClassCastException("DateTime values are not comparable to " + other.getClass()); } DateTimeValue v2 = (DateTimeValue) other; if (getTimezoneInMinutes() == v2.getTimezoneInMinutes()) { // both values are in the same timezone (explicitly or implicitly) if (year != v2.year) { return IntegerValue.signum(year - v2.year); } if (month != v2.month) { return IntegerValue.signum(month - v2.month); } if (day != v2.day) { return IntegerValue.signum(day - v2.day); } if (hour != v2.hour) { return IntegerValue.signum(hour - v2.hour); } if (minute != v2.minute) { return IntegerValue.signum(minute - v2.minute); } if (second != v2.second) { return IntegerValue.signum(second - v2.second); } if (nanosecond != v2.nanosecond) { return IntegerValue.signum(nanosecond - v2.nanosecond); } return 0; } return adjustToUTC(implicitTimezone).compareTo(v2.adjustToUTC(implicitTimezone), implicitTimezone); } @Override public XPathComparable getXPathComparable(StringCollator collator, int implicitTimezone) throws NoDynamicContextException { if (hasTimezone()) { return this; } else if (implicitTimezone == MISSING_TIMEZONE) { throw new NoDynamicContextException("Unknown implicit timezone"); } else { return adjustTimezone(implicitTimezone); } } /** * Context-free comparison of two DateTimeValue values. For this to work, * the two values must either both have a timezone or both have none. * * @param v2 the other value * @return the result of the comparison: -1 if the first is earlier, 0 if they * are equal, +1 if the first is later * @throws ClassCastException if the values are not comparable (which might be because * no timezone is available) */ @Override public int compareTo(XPathComparable v2) { if (v2 instanceof DateTimeValue) { try { return compareTo((DateTimeValue)v2, MISSING_TIMEZONE); } catch (Exception err) { throw new ClassCastException("DateTime comparison requires access to implicit timezone"); } } else { throw new ClassCastException("Cannot compare xs:dateTime with " + v2.toString()); } } /*@NotNull*/ public DateTimeComparable getSchemaComparable() { return new DateTimeComparable(this); } /** * DateTimeComparable is an object that implements the XML Schema rules for comparing date/time values */ public static class DateTimeComparable implements Comparable { private final DateTimeValue value; public DateTimeComparable(DateTimeValue value) { this.value = value; } // Rules from XML Schema Part 2 @Override public int compareTo(DateTimeComparable o) { DateTimeValue dt0 = value; DateTimeValue dt1 = o.value; if (dt0.hasTimezone()) { if (dt1.hasTimezone()) { dt0 = dt0.adjustTimezone(0); dt1 = dt1.adjustTimezone(0); return dt0.compareTo(dt1); } else { DateTimeValue dt1max = dt1.adjustTimezone(14 * 60); if (dt0.compareTo(dt1max) < 0) { return -1; } DateTimeValue dt1min = dt1.adjustTimezone(-14 * 60); if (dt0.compareTo(dt1min) > 0) { return +1; } return SequenceTool.INDETERMINATE_ORDERING; } } else { if (dt1.hasTimezone()) { DateTimeValue dt0min = dt0.adjustTimezone(-14 * 60); if (dt0min.compareTo(dt1) < 0) { return -1; } DateTimeValue dt0max = dt0.adjustTimezone(14 * 60); if (dt0max.compareTo(dt1) > 0) { return +1; } return SequenceTool.INDETERMINATE_ORDERING; } else { dt0 = dt0.adjustTimezone(0); dt1 = dt1.adjustTimezone(0); return dt0.compareTo(dt1); } } } public boolean equals(/*@NotNull*/ Object o) { return o instanceof DateTimeComparable && value.hasTimezone() == ((DateTimeComparable) o).value.hasTimezone() && compareTo((DateTimeComparable) o) == 0; } public int hashCode() { DateTimeValue dt0 = value.adjustTimezone(0); return (dt0.year << 20) ^ (dt0.month << 16) ^ (dt0.day << 11) ^ (dt0.hour << 7) ^ (dt0.minute << 2) ^ (dt0.second * 1_000_000_000 + dt0.nanosecond); } } /** * Context-free comparison of two dateTime values * * @param o the other date time value * @return true if the two values represent the same instant in time. Return false if one value has * a timezone and the other does not (this is the result needed when using keys in a map, and also the * result needed for XSD comparisons for example in enumeration facets and identity constraints) */ public boolean equals(Object o) { return o instanceof DateTimeValue && compareTo((DateTimeValue)o) == 0; } /** * Hash code for context-free comparison of date time values. Note that equality testing * and therefore hashCode() works only for values with a timezone * * @return a hash code */ public int hashCode() { return computeHashCode(year, month, day, hour, minute, second, nanosecond, getTimezoneInMinutes()); } public static int computeHashCode(int year, byte month, byte day, byte hour, byte minute, byte second, int nanosecond, int tzMinutes) { int tz = tzMinutes == CalendarValue.NO_TIMEZONE ? 0 : -tzMinutes; int h = hour; int mi = minute; mi += tz; if (mi < 0 || mi > 59) { h += (int)Math.floor(mi / 60.0); mi = (mi + 60 * 24) % 60; } while (h < 0) { h += 24; DateValue t = DateValue.yesterday(year, month, day); year = t.getYear(); month = t.getMonth(); day = t.getDay(); } while (h > 23) { h -= 24; DateValue t = DateValue.tomorrow(year, month, day); year = t.getYear(); month = t.getMonth(); day = t.getDay(); } return (year << 4) ^ (month << 28) ^ (day << 23) ^ (h << 18) ^ (mi << 13) ^ second ^ nanosecond; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy