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