/** * 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 * @throws XPathException if the timezones are both present and inconsistent */ public static DateTimeValue makeDateTimeValue(DateValue date, TimeValue time) throws XPathException { if (date == null || time == null) { return null; } DayTimeDurationValue tz1 = (DayTimeDurationValue) date.getComponent(Component.TIMEZONE); DayTimeDurationValue tz2 = (DayTimeDurationValue) time.getComponent(Component.TIMEZONE); boolean zoneSpecified = (tz1 != null || tz2 != null); if (tz1 != null && tz2 != null && !tz1.equals(tz2)) { XPathException err = new XPathException("Supplied date and time are in different timezones"); err.setErrorCode("FORG0008"); throw err; } DateTimeValue v = new DateTimeValue(); v.year = (int) ((Int64Value) date.getComponent(Component.YEAR_ALLOWING_ZERO)).longValue(); v.month = (byte) ((Int64Value) date.getComponent(Component.MONTH)).longValue(); v.day = (byte) ((Int64Value) date.getComponent(Component.DAY)).longValue(); v.hour = (byte) ((Int64Value) time.getComponent(Component.HOURS)).longValue(); v.minute = (byte) ((Int64Value) time.getComponent(Component.MINUTES)).longValue(); final BigDecimal secs = ((DecimalValue) time.getComponent(Component.SECONDS)).getDecimalValue(); v.second = (byte) secs.intValue(); v.microsecond = secs.multiply(BigDecimal.valueOf(1000000)).intValue() % 1000000; if (zoneSpecified) { if (tz1 == null) { tz1 = tz2; } v.setTimezoneInMinutes((int) (tz1.getLengthInMicroseconds() / 60000000)); } v.typeLabel = BuiltInAtomicType.DATE_TIME; 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 */ public CalendarValue adjustTimezone(int timezone) { if (!hasTimezone()) { CalendarValue in = (CalendarValue) 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 += Math.floor(mi / 60.0); mi = (mi + 60 * 24) % 60; } if (h >= 0 && h < 24) { return new DateTimeValue( year, month, day, (byte) h, (byte) mi, second, microsecond, timezone); } // 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, microsecond, timezone); } if (h > 23) { h -= 24; DateValue t = DateValue.tomorrow(year, month, day); return new DateTimeValue( t.getYear(), t.getMonth(), t.getDay(), (byte) h, (byte) mi, second, microsecond, timezone); } 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 */ public CalendarValue add(DurationValue duration) throws XPathException { if (duration instanceof DayTimeDurationValue) { long microseconds = ((DayTimeDurationValue) duration).getLengthInMicroseconds(); BigDecimal seconds = BigDecimal.valueOf(microseconds) .divide(DecimalValue.BIG_DECIMAL_ONE_MILLION, 6, BigDecimal.ROUND_HALF_EVEN); BigDecimal julian = toJulianInstant(); julian = julian.add(seconds); DateTimeValue dt = fromJulianInstant(julian); dt.setTimezoneInMinutes(getTimezoneInMinutes()); 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; } return new DateTimeValue( y, (byte) m, (byte) d, hour, minute, second, microsecond, getTimezoneInMinutes()); } else { XPathException err = new XPathException( "DateTime arithmetic is not supported on xs:duration, only on its subtypes"); err.setIsTypeError(true); throw err; } }
/** * 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 (microsecond == 0) { return j; } else { return j.add( BigDecimal.valueOf(microsecond) .divide(DecimalValue.BIG_DECIMAL_ONE_MILLION, 6, BigDecimal.ROUND_HALF_EVEN)); } }
static int hashCode( int year, byte month, byte day, byte hour, byte minute, byte second, int microsecond, int tzMinutes) { int tz = -tzMinutes; int h = hour; int mi = minute; mi += tz; if (mi < 0 || mi > 59) { h += 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 ^ microsecond; }
/** * 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. */ public static DateTimeValue fromJulianInstant(BigDecimal instant) { BigInteger julianSecond = instant.toBigInteger(); BigDecimal microseconds = instant .subtract(new BigDecimal(julianSecond)) .multiply(DecimalValue.BIG_DECIMAL_ONE_MILLION); 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, microseconds.intValue(), 0); }
/** * Factory method: create a dateTime value from a supplied string, in ISO 8601 format * * @param s a string in the lexical space of xs:dateTime * @return either a DateTimeValue representing the xs:dateTime supplied, or a ValidationFailure if * the lexical value was invalid */ public static ConversionResult makeDateTimeValue(CharSequence s) { // input must have format [-]yyyy-mm-ddThh:mm:ss[.fff*][([+|-]hh:mm | Z)] DateTimeValue dt = new DateTimeValue(); StringTokenizer tok = new StringTokenizer(Whitespace.trimWhitespace(s).toString(), "-:.+TZ", true); if (!tok.hasMoreElements()) { return badDate("too short", s); } String part = (String) tok.nextElement(); int era = +1; if ("+".equals(part)) { return badDate("Date must not start with '+' sign", s); } else if ("-".equals(part)) { era = -1; if (!tok.hasMoreElements()) { return badDate("No year after '-'", s); } part = (String) tok.nextElement(); } int value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric year component", s); } 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) { return badDate("Year zero is not allowed", s); } if (era < 0) { dt.year++; // internal representation allows a year zero. } if (!tok.hasMoreElements()) { return badDate("Too short", s); } if (!"-".equals(tok.nextElement())) { return badDate("Wrong delimiter after year", s); } if (!tok.hasMoreElements()) { return badDate("Too short", s); } part = (String) tok.nextElement(); 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.hasMoreElements()) { return badDate("Too short", s); } if (!"-".equals(tok.nextElement())) { return badDate("Wrong delimiter after month", s); } if (!tok.hasMoreElements()) { return badDate("Too short", s); } part = (String) tok.nextElement(); 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.hasMoreElements()) { return badDate("Too short", s); } if (!"T".equals(tok.nextElement())) { return badDate("Wrong delimiter after day", s); } if (!tok.hasMoreElements()) { return badDate("Too short", s); } part = (String) tok.nextElement(); 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.hasMoreElements()) { return badDate("Too short", s); } if (!":".equals(tok.nextElement())) { return badDate("Wrong delimiter after hour", s); } if (!tok.hasMoreElements()) { return badDate("Too short", s); } part = (String) tok.nextElement(); 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.hasMoreElements()) { return badDate("Too short", s); } if (!":".equals(tok.nextElement())) { return badDate("Wrong delimiter after minute", s); } if (!tok.hasMoreElements()) { return badDate("Too short", s); } part = (String) tok.nextElement(); 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; int state = 0; while (tok.hasMoreElements()) { if (state == 9) { return badDate("Characters after the end", s); } String delim = (String) tok.nextElement(); if (".".equals(delim)) { if (state != 0) { return badDate("Decimal separator occurs twice", s); } if (!tok.hasMoreElements()) { return badDate("Decimal point must be followed by digits", s); } part = (String) tok.nextElement(); value = DurationValue.simpleInteger(part); if (value < 0) { return badDate("Non-numeric fractional seconds component", s); } double fractionalSeconds = Double.parseDouble('.' + part); dt.microsecond = (int) (Math.round(fractionalSeconds * 1000000)); if (dt.hour == 24 && dt.microsecond != 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.hasMoreElements()) { return badDate("Missing timezone", s); } part = (String) tok.nextElement(); 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)) { tz = -tz; } } else if (":".equals(delim)) { if (state != 2) { return badDate("Misplaced ':'", s); } state = 9; part = (String) tok.nextElement(); 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 (tz < 0) { tzminute = -tzminute; } if (Math.abs(tz) == 14 * 60 && tzminute != 0) { return badDate("Timezone is out of range (-14:00 to +14:00)", s); } tz += tzminute; 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; }
@Override public void visit(DateValue dv) { ValueExpression ve = new ValueSpecification(_dateConv, dv.getValue()); _exprStack.push(ve); }