/** * 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; } }
/** * 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; }