| /* Copyright 2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.xmlbeans; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.math.RoundingMode; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.TimeZone; |
| |
| /** |
| * Used to build {@link GDate GDates}. |
| * <p> |
| * Like GDate, a GDateBuilder represents an Gregorian Date, Time, |
| * and Timezone, or subset of information (Year, Month, Day, |
| * Time, Timezone, or some combination). Wherever it provides |
| * guidance, the XML Schema 1.0 specification (plus published |
| * errata) is followed. |
| * <p> |
| * Instances may separately set or clear the year, month, |
| * day-of-month, and time-of-day. Not all operations are |
| * meaningful on all combinations. In particular, timezone |
| * normalization is only possible if there is a time, or |
| * a time together with a full date. |
| */ |
| public final class GDateBuilder implements GDateSpecification, java.io.Serializable { |
| private static final long serialVersionUID = 1L; |
| |
| private int _bits; |
| private int _CY; |
| private int _M; |
| private int _D; |
| private int _h; |
| private int _m; |
| private int _s; |
| private BigDecimal _fs; |
| private int _tzsign; |
| private int _tzh; |
| private int _tzm; |
| |
| /** |
| * Constructs a GDateBuilder specifying no date or time |
| */ |
| public GDateBuilder() { |
| } |
| |
| /** |
| * Builds another GDateBuilder with the same value |
| * as this one. |
| */ |
| public Object clone() { |
| return new GDateBuilder(this); |
| } |
| |
| /** |
| * Builds a GDate from this GDateBuilder. |
| */ |
| public GDate toGDate() { |
| return new GDate(this); |
| } |
| |
| /** |
| * Construts a GDateBuilder by copying another GDateSpecificaiton. |
| */ |
| public GDateBuilder(GDateSpecification gdate) { |
| if (gdate.hasTimeZone()) { |
| setTimeZone(gdate.getTimeZoneSign(), gdate.getTimeZoneHour(), gdate.getTimeZoneMinute()); |
| } |
| |
| if (gdate.hasTime()) { |
| setTime(gdate.getHour(), gdate.getMinute(), gdate.getSecond(), gdate.getFraction()); |
| } |
| |
| if (gdate.hasDay()) { |
| setDay(gdate.getDay()); |
| } |
| |
| if (gdate.hasMonth()) { |
| setMonth(gdate.getMonth()); |
| } |
| |
| if (gdate.hasYear()) { |
| setYear(gdate.getYear()); |
| } |
| } |
| |
| // Forms: |
| |
| // Date part: |
| // Year: (-?\d{4,}) |
| // YearMonth: (-?\d{4,})-(\d{2}) |
| // Date: (-?\d{4,})-(\d{2})-(\d{2}) |
| // Month: --(\d{2})(--)? //errata R-48 |
| // MonthDay: --(\d{2})-(\d{2}) |
| // Day: ---(\d{2}) |
| |
| // Time part: |
| // Time: (\d{2}):(\d{2}):(\d{2})(.\d*)? |
| |
| // Timezone part: |
| // TZ: (Z)|([+-]\d{2}):(\d{2}) |
| |
| /** |
| * Constructs a GDateBuilder from a lexical |
| * representation. The lexical space contains the |
| * union of the lexical spaces of all the schema |
| * date/time types (except for duration). |
| */ |
| public GDateBuilder(CharSequence string) { |
| this(new GDate(string)); |
| } |
| |
| |
| public GDateBuilder(Calendar calendar) { |
| this(new GDate(calendar)); |
| } |
| |
| /** |
| * Constructs a GDateBuilder with the specified year, month, day, |
| * hours, minutes, seconds, and optional fractional seconds, in |
| * an unspecified timezone. |
| * <p> |
| * Note that by not specifying the timezone the GDateBuilder |
| * becomes partially unordered with respect to timesthat do have a |
| * specified timezone. |
| * |
| * @param year The year |
| * @param month The month, from 1-12 |
| * @param day The day of month, from 1-31 |
| * @param hour The hour of day, from 0-23 |
| * @param minute The minute of hour, from 0-59 |
| * @param second The second of minute, from 0-59 |
| * @param fraction The fraction of second, 0.0 to 0.999... (may be null) |
| */ |
| public GDateBuilder( |
| int year, |
| int month, |
| int day, |
| int hour, |
| int minute, |
| int second, |
| BigDecimal fraction) { |
| _bits = HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME; |
| |
| if (year == 0) { |
| throw new IllegalArgumentException(); |
| } |
| |
| _CY = (year > 0 ? year : year + 1); |
| _M = month; |
| _D = day; |
| _h = hour; |
| _m = minute; |
| _s = second; |
| _fs = fraction == null ? GDate._zero : fraction; |
| |
| if (!isValid()) { |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| /** |
| * Constructs an absolute GDateBuilder with the specified year, |
| * month, day, hours, minutes, seconds, and optional fractional |
| * seconds, and in the timezone specified. |
| * <p> |
| * Note that you can reexpress the GDateBuilder in any timezone using |
| * normalizeToTimeZone(). The normalize() method normalizes to UTC. |
| * <p> |
| * If you wish to have a time or date that isn't in a specified timezone, |
| * then use the constructor that does not include the timezone arguments. |
| * |
| * @param year the year |
| * @param month the month, from 1-12 |
| * @param day the day of month, from 1-31 |
| * @param hour the hour of day, from 0-23 |
| * @param minute the minute of hour, from 0-59 |
| * @param second the second of minute, from 0-59 |
| * @param fraction the fraction of second, 0.0 to 0.999... (may be null) |
| * @param tzSign the timezone offset sign, either +1, 0, or -1 |
| * @param tzHour the timezone offset hour |
| * @param tzMinute the timezone offset minute |
| */ |
| public GDateBuilder( |
| int year, |
| int month, |
| int day, |
| int hour, |
| int minute, |
| int second, |
| BigDecimal fraction, |
| int tzSign, |
| int tzHour, |
| int tzMinute) { |
| _bits = HAS_TIMEZONE | HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME; |
| |
| if (year == 0) { |
| throw new IllegalArgumentException(); |
| } |
| |
| _CY = (year > 0 ? year : year + 1); |
| _M = month; |
| _D = day; |
| _h = hour; |
| _m = minute; |
| _s = second; |
| _fs = fraction == null ? GDate._zero : fraction; |
| _tzsign = tzSign; |
| _tzh = tzHour; |
| _tzm = tzMinute; |
| |
| if (!isValid()) { |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| /** |
| * Constructs a GDateBuilder based on a java.util.Date. |
| * <p> |
| * The current offset of the default timezone is used as the timezone. |
| * <p> |
| * For example, if eastern daylight time is in effect at the given |
| * date, the timezone on the east coast of the united states |
| * translates to GMT-05:00 (EST) + 1:00 (DT offset) == GMT-04:00. |
| * |
| * @param date the date object to copy |
| */ |
| public GDateBuilder(Date date) { |
| setDate(date); |
| } |
| |
| /** |
| * True if the instance is immutable. |
| */ |
| public boolean isImmutable() { |
| return false; |
| } |
| |
| /** |
| * Returns a combination of flags indicating the information |
| * contained by this GDate. The five flags are |
| * HAS_TIMEZONE, HAS_YEAR, HAS_MONTH, HAS_DAY, and HAS_TIME. |
| */ |
| public int getFlags() { |
| return _bits; |
| } |
| |
| /** |
| * True if this date/time specification specifies a timezone. |
| */ |
| public final boolean hasTimeZone() { |
| return ((_bits & HAS_TIMEZONE) != 0); |
| } |
| |
| /** |
| * True if this date/time specification specifies a year. |
| */ |
| public final boolean hasYear() { |
| return ((_bits & HAS_YEAR) != 0); |
| } |
| |
| /** |
| * True if this date/time specification specifies a month-of-year. |
| */ |
| public final boolean hasMonth() { |
| return ((_bits & HAS_MONTH) != 0); |
| } |
| |
| /** |
| * True if this date/time specification specifies a day-of-month. |
| */ |
| public final boolean hasDay() { |
| return ((_bits & HAS_DAY) != 0); |
| } |
| |
| /** |
| * True if this date/time specification specifies a time-of-day. |
| */ |
| public final boolean hasTime() { |
| return ((_bits & HAS_TIME) != 0); |
| } |
| |
| /** |
| * True if this date/time specification specifies a full date (year, month, day) |
| */ |
| public final boolean hasDate() { |
| return ((_bits & (HAS_DAY | HAS_MONTH | HAS_YEAR)) == (HAS_DAY | HAS_MONTH | HAS_YEAR)); |
| } |
| |
| /** |
| * Gets the year. Should be a four-digit year specification. |
| */ |
| public final int getYear() { |
| return (_CY > 0 ? _CY : _CY - 1); |
| } |
| |
| /** |
| * Gets the month-of-year. January is 1. |
| */ |
| public final int getMonth() { |
| return _M; |
| } |
| |
| /** |
| * Gets the day-of-month. The first day of each month is 1. |
| */ |
| public final int getDay() { |
| return _D; |
| } |
| |
| /** |
| * Gets the hour-of-day. Midnight is 0, and 11PM is 23. |
| */ |
| public final int getHour() { |
| return _h; |
| } |
| |
| /** |
| * Gets the minute-of-hour. Range from 0 to 59. |
| */ |
| public final int getMinute() { |
| return _m; |
| } |
| |
| /** |
| * Gets the second-of-minute. Range from 0 to 59. |
| */ |
| public final int getSecond() { |
| return _s; |
| } |
| |
| |
| /** |
| * Gets the fraction-of-second. Range from 0 (inclusive) to 1 (exclusive). |
| */ |
| public final BigDecimal getFraction() { |
| return _fs; |
| } |
| |
| |
| /** |
| * Gets the rounded millisecond value. Range from 0 to 999 |
| */ |
| public final int getMillisecond() { |
| if (_fs == null || GDate._zero.equals(_fs)) { |
| return 0; |
| } |
| return _fs.setScale(3, RoundingMode.HALF_UP).unscaledValue().intValue(); |
| } |
| |
| /** |
| * Gets the time zone sign. For time zones east of GMT, |
| * this is positive; for time zones west, this is negative. |
| */ |
| public final int getTimeZoneSign() { |
| return _tzsign; |
| } |
| |
| /** |
| * Gets the time zone hour. |
| * This is always positive: for the sign, look at |
| * getTimeZoneSign(). |
| */ |
| public final int getTimeZoneHour() { |
| return _tzh; |
| } |
| |
| /** |
| * Gets the time zone minutes. |
| * This is always positive: for the sign, look at |
| * getTimeZoneSign(). |
| */ |
| public final int getTimeZoneMinute() { |
| return _tzm; |
| } |
| |
| |
| /** |
| * Sets the year. Should be a four-digit year specification. |
| * |
| * @param year the year |
| */ |
| public void setYear(int year) { |
| if (year < GDate.MIN_YEAR || year > GDate.MAX_YEAR) { |
| throw new IllegalArgumentException("year out of range"); |
| } |
| if (year == 0) { |
| throw new IllegalArgumentException("year cannot be 0"); |
| } |
| _bits |= HAS_YEAR; |
| _CY = (year > 0 ? year : year + 1); |
| } |
| |
| /** |
| * Sets the month-of-year. January is 1. |
| * |
| * @param month the month, from 1-12 |
| */ |
| public void setMonth(int month) { |
| if (month < 1 || month > 12) { |
| throw new IllegalArgumentException("month out of range"); |
| } |
| _bits |= HAS_MONTH; |
| _M = month; |
| } |
| |
| /** |
| * Sets the day-of-month. The first day of each month is 1. |
| * |
| * @param day the day of month, from 1-31 |
| */ |
| public void setDay(int day) { |
| if (day < 1 || day > 31) { |
| throw new IllegalArgumentException("day out of range"); |
| } |
| _bits |= HAS_DAY; |
| _D = day; |
| } |
| |
| /** |
| * Sets the time. Hours in the day range from 0 to 23; |
| * minutes and seconds range from 0 to 59; and fractional |
| * seconds range from 0 (inclusive) to 1 (exclusive). |
| * The fraction can be null and is assumed to be zero. |
| * |
| * @param hour the hour of day, from 0-23 or 24 only if min, sec and fraction are 0 |
| * @param minute the minute of hour, from 0-59 |
| * @param second the second of minute, from 0-59 |
| * @param fraction the fraction of second, 0.0 to 0.999... (may be null) |
| */ |
| public void setTime(int hour, int minute, int second, BigDecimal fraction) { |
| if (hour < 0 || hour > 24) { |
| throw new IllegalArgumentException("hour out of range"); |
| } |
| if (minute < 0 || minute > 59) { |
| throw new IllegalArgumentException("minute out of range"); |
| } |
| if (second < 0 || second > 59) { |
| throw new IllegalArgumentException("second out of range"); |
| } |
| if (fraction != null && (fraction.signum() < 0 || GDate._one.compareTo(fraction) <= 0)) { |
| throw new IllegalArgumentException("fraction out of range"); |
| } |
| if (hour == 24 && (minute != 0 || second != 0 || (fraction != null && (GDate._zero.compareTo(fraction) != 0)))) { |
| throw new IllegalArgumentException("when hour is 24, min sec and fracton must be 0"); |
| } |
| |
| _bits |= HAS_TIME; |
| _h = hour; |
| _m = minute; |
| _s = second; |
| _fs = fraction == null ? GDate._zero : fraction; |
| } |
| |
| /** |
| * Sets the time zone without changing the other time |
| * fields. If you with to adjust other time fields to express |
| * the same actual moment in time in a different time zone, |
| * use normalizeToTimeZone. |
| * <p> |
| * Timezones must be between -14:00 and +14:00. Sign |
| * must be -1 or 1 (or 0 for UTC only), and the offset hours |
| * and minute arguments must be nonnegative. |
| * |
| * @param tzSign the timezone offset sign, either +1, 0, or -1 |
| * @param tzHour the timezone offset hour |
| * @param tzMinute the timezone offset minute |
| */ |
| public void setTimeZone(int tzSign, int tzHour, int tzMinute) { |
| if (!((tzSign == 0 && tzHour == 0 && tzMinute == 0) || |
| ((tzSign == -1 || tzSign == 1) && |
| (tzHour >= 0 && tzMinute >= 0) && |
| (tzHour == 14 && tzMinute == 0 || tzHour < 14 && tzMinute < 60)))) { |
| throw new IllegalArgumentException("time zone out of range (-14:00 to +14:00). (" + |
| (tzSign < 0 ? "-" : "+") + tzHour + ":" + tzMinute + ")"); |
| } |
| |
| _bits |= HAS_TIMEZONE; |
| _tzsign = tzSign; |
| _tzh = tzHour; |
| _tzm = tzMinute; |
| } |
| |
| /** |
| * Sets the time zone based on a number of offset minutes rather |
| * than sign/hour/minute; for example, setTimeZone(-60) is the |
| * same as setTimeZone(-1, 1, 0). |
| */ |
| public void setTimeZone(int tzTotalMinutes) { |
| if (tzTotalMinutes < -14 * 60 || tzTotalMinutes > 14 * 60) { |
| throw new IllegalArgumentException("time zone out of range (-840 to 840 minutes). (" + tzTotalMinutes + ")"); |
| } |
| |
| int tzSign = Integer.compare(tzTotalMinutes, 0); |
| tzTotalMinutes *= tzSign; |
| int tzH = tzTotalMinutes / 60; |
| int tzM = tzTotalMinutes - tzH * 60; |
| |
| setTimeZone(tzSign, tzH, tzM); |
| } |
| |
| /** |
| * Clears the year. After clearing, hasYear returns false and the |
| * value of getYear is undefined. |
| */ |
| public void clearYear() { |
| _bits &= ~HAS_YEAR; |
| _CY = 0; |
| } |
| |
| /** |
| * Clears the month-of-year. After clearing. hasMonth returns false and |
| * the value of getMonth is undefined. |
| */ |
| public void clearMonth() { |
| _bits &= ~HAS_MONTH; |
| _M = 0; |
| } |
| |
| /** |
| * Clears the day-of-month. After clearing. hasDay returns false and |
| * the value of getDay is undefined. |
| */ |
| public void clearDay() { |
| _bits &= ~HAS_DAY; |
| _D = 0; |
| } |
| |
| /** |
| * Clears the time-of-day. |
| * After clearing. hasTime returns false and |
| * the value of getTime is undefined. |
| */ |
| public void clearTime() { |
| _bits &= ~HAS_TIME; |
| _h = 0; |
| _m = 0; |
| _s = 0; |
| _fs = null; |
| } |
| |
| /** |
| * Clears the timezone. After clearing. hasTimeZone returns false and |
| * the value of getTimeZoneHour and getTimeZoneMinute are undefined. |
| * Does not change the other time fields. |
| */ |
| public void clearTimeZone() { |
| _bits &= ~HAS_TIMEZONE; |
| _tzsign = 0; |
| _tzh = 0; |
| _tzm = 0; |
| } |
| |
| /** |
| * True if all date fields lie within their legal ranges. A GDateBuilder |
| * can be invalid, for example, if you change the month to February |
| * and the day-of-month is 31. |
| */ |
| public boolean isValid() { |
| return isValidGDate(this); |
| } |
| |
| /* package */ |
| static boolean isValidGDate(GDateSpecification date) { |
| if (date.hasYear() && date.getYear() == 0) { |
| return false; |
| } |
| |
| if (date.hasMonth() && (date.getMonth() < 1 || date.getMonth() > 12)) { |
| return false; |
| } |
| |
| if (date.hasDay() && |
| (date.getDay() < 1 || date.getDay() > 31 || |
| date.getDay() > 28 && |
| date.hasMonth() && |
| (date.hasYear() ? |
| date.getDay() > _maxDayInMonthFor((date.getYear() > 0 ? |
| date.getYear() : |
| date.getYear() + 1), |
| date.getMonth()) : |
| date.getDay() > _maxDayInMonth(date.getMonth())))) { |
| return false; |
| } |
| |
| if (date.hasTime() && ((date.getHour() < 0 || date.getHour() > 23 || |
| date.getMinute() < 0 || date.getMinute() > 59 || |
| date.getSecond() < 0 || date.getSecond() > 59 || |
| date.getFraction().signum() < 0 || date.getFraction().compareTo(GDate._one) >= 0)) && |
| // check for 24:00:00 valid format |
| !(date.getHour() == 24 && date.getMinute() == 0 && date.getSecond() == 0 && |
| date.getFraction().compareTo(GDate._zero) == 0)) { |
| return false; |
| } |
| |
| if (date.hasTimeZone() && |
| (!((date.getTimeZoneSign() == 0 && date.getTimeZoneHour() == 0 && date.getTimeZoneMinute() == 0) || |
| ((date.getTimeZoneSign() == -1 || date.getTimeZoneSign() == +1) && |
| // NB: allow +00:00 and -00:00 |
| // (date.getTimeZoneHour() == 0 && date.getTimeZoneMinute() > 0 || date.getTimeZoneHour() > 0 && date.getTimeZoneMinute() >= 0) && |
| (date.getTimeZoneHour() >= 0 && date.getTimeZoneMinute() >= 0) && |
| (date.getTimeZoneHour() == 14 && date.getTimeZoneMinute() == 0 || date.getTimeZoneHour() < 14 && date.getTimeZoneMinute() < 60))))) { |
| return false; |
| } |
| |
| // everyting looks kosher |
| return true; |
| } |
| |
| |
| /** |
| * Normalizes the instance, ensuring date and time fields are within |
| * their normal ranges. |
| * <p> |
| * If no timezone or no time is specified, or if a partial date is specified, this |
| * method does nothing, and leaves the timezone information as-is. |
| * <p> |
| * If a time or time and date is specified, this method normalizes the timezone |
| * to UTC. |
| */ |
| public void normalize() { |
| // DateTime or Time, with TimeZone: normalize to UTC. |
| // In the process all the fields will be normalized. |
| if (hasDay() == hasMonth() && hasDay() == hasYear() && |
| hasTimeZone() && hasTime()) { |
| normalizeToTimeZone(0, 0, 0); |
| } else { |
| // No timezone, or incomplete date. |
| _normalizeTimeAndDate(); |
| } |
| |
| // remove trailing zeros from fractional seconds |
| if (hasTime() && _fs != null && _fs.scale() > 0) { |
| if (_fs.signum() == 0) { |
| _fs = GDate._zero; |
| } else { |
| BigInteger bi = _fs.unscaledValue(); |
| String str = bi.toString(); |
| int lastzero; |
| for (lastzero = str.length(); lastzero > 0; lastzero -= 1) { |
| if (str.charAt(lastzero - 1) != '0') { |
| break; |
| } |
| } |
| if (lastzero < str.length()) { |
| _fs = _fs.setScale(_fs.scale() - str.length() + lastzero, RoundingMode.UNNECESSARY); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Normalizes the instance when hour is 24. If day is present, hour 24 is equivalent to hour 00 next day. |
| */ |
| void normalize24h() { |
| if (!hasTime() || getHour() != 24) { |
| return; |
| } |
| |
| _normalizeTimeAndDate(); |
| } |
| |
| |
| private void _normalizeTimeAndDate() { |
| long carry = 0; |
| |
| if (hasTime()) { |
| carry = _normalizeTime(); |
| } |
| |
| if (hasDay()) { |
| _D += carry; |
| } |
| |
| if (hasDate()) { |
| _normalizeDate(); |
| } else if (hasMonth()) { |
| // with incomplete dates, just months can be normalized: |
| // days stay denormalized. |
| if (_M < 1 || _M > 12) { |
| int temp = _M; |
| _M = _modulo(temp, 1, 13); |
| if (hasYear()) { |
| _CY = _CY + (int) _fQuotient(temp, 1, 13); |
| } |
| } |
| } |
| } |
| |
| /** |
| * If the time and timezone are known, this method changes the timezone to the |
| * specified UTC offset, altering minutes, hours, day, month, and year as |
| * necessary to ensure that the actual described moment in time is the same. |
| * <p> |
| * It is an error to operate on instances without a time or timezone, or |
| * with a partially specified date. |
| * |
| * @param tzSign the timezone offset sign, either +1, 0, or -1 |
| * @param tzHour the timezone offset hour |
| * @param tzMinute the timezone offset minute |
| */ |
| public void normalizeToTimeZone(int tzSign, int tzHour, int tzMinute) { |
| if (!((tzSign == 0 && tzHour == 0 && tzMinute == 0) || |
| ((tzSign == -1 || tzSign == 1) && |
| (tzHour >= 0 && tzMinute >= 0) && |
| (tzHour == 14 && tzMinute == 0 || tzHour < 14 && tzMinute < 60)))) { |
| throw new IllegalArgumentException("time zone must be between -14:00 and +14:00"); |
| } |
| |
| if (!hasTimeZone() || !hasTime()) { |
| throw new IllegalStateException("cannot normalize time zone without both time and timezone"); |
| } |
| |
| if (!(hasDay() == hasMonth() && hasDay() == hasYear())) { |
| throw new IllegalStateException("cannot do date math without a complete date"); |
| } |
| |
| int hshift = tzSign * tzHour - _tzsign * _tzh; |
| int mshift = tzSign * tzMinute - _tzsign * _tzm; |
| |
| _tzsign = tzSign; |
| _tzh = tzHour; |
| _tzm = tzMinute; |
| addDuration(1, 0, 0, 0, hshift, mshift, 0, null); |
| } |
| |
| /** |
| * Normalizes to a time zone specified by a number of offset minutes rather |
| * than sign/hour/minute; for example, normalizeToTimeZone(-60) is the |
| * same as normalizeToTimeZone(-1, 1, 0). |
| */ |
| public void normalizeToTimeZone(int tzTotalMinutes) { |
| if (tzTotalMinutes < -14 * 60 || tzTotalMinutes > 14 * 60) { |
| throw new IllegalArgumentException("time zone out of range (-840 to 840 minutes). (" + tzTotalMinutes + ")"); |
| } |
| |
| int tzSign = Integer.compare(tzTotalMinutes, 0); |
| tzTotalMinutes *= tzSign; |
| int tzH = tzTotalMinutes / 60; |
| int tzM = tzTotalMinutes - tzH * 60; |
| |
| normalizeToTimeZone(tzSign, tzH, tzM); |
| } |
| |
| |
| /** |
| * Adds a given duration to the date/time. |
| * |
| * @param duration the duration to add |
| */ |
| public void addGDuration(GDurationSpecification duration) { |
| addDuration(duration.getSign(), duration.getYear(), duration.getMonth(), duration.getDay(), |
| duration.getHour(), duration.getMinute(), duration.getSecond(), duration.getFraction()); |
| } |
| |
| /** |
| * Subtracts a given duration from the date/time. |
| * |
| * @param duration the duration to subtract |
| */ |
| public void subtractGDuration(GDurationSpecification duration) { |
| addDuration(-duration.getSign(), duration.getYear(), duration.getMonth(), duration.getDay(), |
| duration.getHour(), duration.getMinute(), duration.getSecond(), duration.getFraction()); |
| } |
| |
| |
| /** |
| * Normalizes the date by carrying over to the year any months outside 1..12 |
| * and carrying over to the month any days outside 1..(days-in-month). |
| */ |
| private void _normalizeDate() { |
| if (_M < 1 || _M > 12 || _D < 1 || _D > _maxDayInMonthFor(_CY, _M)) { |
| // fix months first |
| int temp = _M; |
| _M = _modulo(temp, 1, 13); |
| _CY = _CY + (int) _fQuotient(temp, 1, 13); |
| |
| // then pull days out |
| int extradays = _D - 1; |
| _D = 1; |
| |
| // then use the julian date function to fix |
| setJulianDate(getJulianDate() + extradays); |
| } |
| } |
| |
| /** |
| * Normalizes time so that fractions are 0..1(exc), seconds/minutes 0..59, |
| * and hours 0..24. Returns the number of days to carry over from normalizing |
| * away more than 24 hours. |
| */ |
| private long _normalizeTime() { |
| long carry = 0; |
| long temp; |
| |
| // fractions |
| if (_fs != null && (_fs.signum() < 0 || _fs.compareTo(GDate._one) >= 0)) { |
| BigDecimal bdcarry = _fs.setScale(0, RoundingMode.FLOOR); |
| _fs = _fs.subtract(bdcarry); |
| carry = bdcarry.longValue(); |
| } |
| |
| if (carry != 0 || _s < 0 || _s > 59 || _m < 0 || _m > 50 || _h < 0 || _h > 23) { |
| // seconds |
| temp = _s + carry; |
| carry = _fQuotient(temp, 60); |
| _s = _mod(temp, 60, carry); |
| |
| // minutes |
| temp = _m + carry; |
| carry = _fQuotient(temp, 60); |
| _m = _mod(temp, 60, carry); |
| |
| // hours |
| temp = _h + carry; |
| carry = _fQuotient(temp, 24); |
| _h = _mod(temp, 24, carry); |
| } |
| |
| return carry; |
| } |
| |
| /** |
| * Adds a given duration to the date/time. |
| * |
| * @param sign +1 to add, -1 to subtract |
| * @param year the number of years to add |
| * @param month the number of months to add |
| * @param day the number of days to add |
| * @param hour the number of hours to add |
| * @param minute the number of minutes to add |
| * @param second the number of seconds to add |
| * @param fraction the number of fractional seconds to add (may be null) |
| */ |
| public void addDuration(int sign, int year, int month, int day, |
| int hour, int minute, int second, BigDecimal fraction) { |
| boolean timemath = hour != 0 || minute != 0 || second != 0 || fraction != null && fraction.signum() != 0; |
| if (timemath && !hasTime()) { |
| throw new IllegalStateException("cannot do time math without a complete time"); |
| } |
| boolean datemath = hasDay() && (day != 0 || timemath); |
| if (datemath && !hasDate()) { |
| throw new IllegalStateException("cannot do date math without a complete date"); |
| } |
| |
| int temp; |
| |
| // months + years are easy |
| if (month != 0 || year != 0) { |
| // Prepare the _D to be pegged before changing month |
| if (hasDay()) { |
| _normalizeDate(); |
| } |
| |
| // Add months and years |
| temp = _M + sign * month; |
| _M = _modulo(temp, 1, 13); |
| _CY = _CY + sign * year + (int) _fQuotient(temp, 1, 13); |
| |
| // In new month, day may need to be pegged before proceeding |
| if (hasDay()) { |
| assert (_D >= 1); |
| temp = _maxDayInMonthFor(_CY, _M); |
| if (_D > temp) { |
| _D = temp; |
| } |
| } |
| } |
| |
| long carry = 0; |
| |
| if (timemath) { |
| // fractions |
| if (fraction != null && fraction.signum() != 0) { |
| if (_fs.signum() == 0 && sign == 1) { |
| _fs = fraction; |
| } else { |
| _fs = (sign == 1) ? _fs.add(fraction) : _fs.subtract(fraction); |
| } |
| } |
| |
| // seconds, minutes, hours |
| _s += sign * second; |
| _m += sign * minute; |
| _h += sign * hour; |
| |
| // normalize time |
| carry = _normalizeTime(); |
| } |
| |
| if (datemath) { |
| // days: may require renormalization |
| _D += sign * day + carry; |
| _normalizeDate(); |
| } |
| } |
| |
| /** |
| * Given {year,month} computes maximum |
| * number of days for given month |
| */ |
| private static int _maxDayInMonthFor(int year, int month) { |
| if (month == 4 || month == 6 || month == 9 || month == 11) { |
| return 30; |
| } |
| |
| if (month == 2) { |
| return (_isLeapYear(year) ? 29 : 28); |
| } |
| |
| return 31; |
| } |
| |
| /** |
| * Given {year,month} computes maximum |
| * number of days for given month |
| */ |
| private static int _maxDayInMonth(int month) { |
| if (month == 4 || month == 6 || month == 9 || month == 11) { |
| return 30; |
| } |
| |
| if (month == 2) { |
| return 29; |
| } |
| |
| return 31; |
| } |
| |
| /** |
| * Returns the Julian date corresponding to this Gregorian date. |
| * The Julian date (JD) is a continuous count of days from |
| * 1 January 4713 BC. |
| */ |
| public final int getJulianDate() { |
| return julianDateForGDate(this); |
| } |
| |
| |
| /** |
| * Sets the Gregorian date based on the given Julian date. |
| * The Julian date (JD) is a continuous count of days from |
| * 1 January 4713 BC. |
| * |
| * @param julianday the julian day number |
| */ |
| public void setJulianDate(int julianday) { |
| if (julianday < 0) { |
| throw new IllegalArgumentException("date before year -4713"); |
| } |
| |
| int temp; |
| int qepoc; |
| |
| // from http://aa.usno.navy.mil/faq/docs/JD_Formula.html |
| temp = julianday + 68569; |
| qepoc = 4 * temp / 146097; |
| temp = temp - (146097 * qepoc + 3) / 4; |
| _CY = 4000 * (temp + 1) / 1461001; |
| temp = temp - 1461 * _CY / 4 + 31; |
| _M = 80 * temp / 2447; |
| _D = temp - 2447 * _M / 80; |
| temp = _M / 11; |
| _M = _M + 2 - 12 * temp; |
| _CY = 100 * (qepoc - 49) + _CY + temp; |
| |
| _bits |= HAS_DAY | HAS_MONTH | HAS_YEAR; |
| } |
| |
| |
| /** |
| * Sets the current time and date based on a java.util.Date instance. |
| * <p> |
| * The timezone offset used is based on the default TimeZone. (The |
| * default TimeZone is consulted to incorporate daylight savings offsets |
| * if applicable for the current date as well as the base timezone offset.) |
| * <p> |
| * If you wish to normalize the timezone, e.g., to UTC, follow this with |
| * a call to normalizeToTimeZone. |
| * |
| * @param date the Date object to copy |
| */ |
| public void setDate(Date date) { |
| // Default timezone |
| TimeZone dtz = TimeZone.getDefault(); |
| int offset = dtz.getOffset(date.getTime()); |
| int offsetsign = 1; |
| if (offset < 0) { |
| offsetsign = -1; |
| offset = -offset; |
| } |
| int offsetmin = offset / (1000 * 60); |
| int offsethr = offsetmin / 60; |
| offsetmin = offsetmin - offsethr * 60; |
| |
| setTimeZone(offsetsign, offsethr, offsetmin); |
| |
| // paranoia: tz.getOffset can return fractions of minutes, but we must round |
| int roundedoffset = offsetsign * (offsethr * 60 + offsetmin) * 60 * 1000; |
| |
| // midnight |
| setTime(0, 0, 0, GDate._zero); |
| |
| // Set to January 1, 1970. |
| // setJulianDate(2440588); |
| _bits |= HAS_DAY | HAS_MONTH | HAS_YEAR; |
| _CY = 1970; |
| _M = 1; |
| _D = 1; |
| |
| // Add a duration representing the number of milliseconds |
| addGDuration(new GDuration(1, 0, 0, 0, 0, 0, 0, |
| BigDecimal.valueOf(date.getTime() + roundedoffset, 3))); |
| |
| // special case: ss.000 -> ss |
| if (_fs.signum() == 0) { |
| _fs = GDate._zero; |
| } |
| } |
| |
| /** |
| * Copies a GDateSpecification, completely replacing the current |
| * information in this GDateBuilder. |
| * |
| * @param gdate the GDateSpecification to copy |
| */ |
| public void setGDate(GDateSpecification gdate) { |
| _bits = gdate.getFlags() & (HAS_TIMEZONE | HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME); |
| int year = gdate.getYear(); |
| _CY = (year > 0 ? year : year + 1); |
| _M = gdate.getMonth(); |
| _D = gdate.getDay(); |
| _h = gdate.getHour(); |
| _m = gdate.getMinute(); |
| _s = gdate.getSecond(); |
| _fs = gdate.getFraction(); |
| _tzsign = gdate.getTimeZoneSign(); |
| _tzh = gdate.getTimeZoneHour(); |
| _tzm = gdate.getTimeZoneMinute(); |
| } |
| |
| |
| /** |
| * Retrieves the value of the current time as an {@link XmlCalendar}. |
| * <p> |
| * {@link XmlCalendar} is a subclass of {@link java.util.GregorianCalendar} |
| * which is slightly customized to match XML schema date rules. |
| * <p> |
| * The returned {@link XmlCalendar} has only those time and date fields |
| * set that are reflected in the GDate object. Because of the way the |
| * {@link java.util.Calendar} contract works, any information in the isSet() vanishes |
| * as soon as you view any unset field using get() methods. |
| * This means that if it is important to understand which date fields |
| * are set, you must call isSet() first before get(). |
| */ |
| public XmlCalendar getCalendar() { |
| return new XmlCalendar(this); |
| } |
| |
| /** |
| * Retrieves the value of the current time as a java.util.Date |
| * instance. |
| */ |
| public Date getDate() { |
| return dateForGDate(this); |
| } |
| |
| /* package */ |
| static int julianDateForGDate(GDateSpecification date) { |
| if (!date.hasDate()) { |
| throw new IllegalStateException("cannot do date math without a complete date"); |
| } |
| |
| // from http://aa.usno.navy.mil/faq/docs/JD_Formula.html |
| int day = date.getDay(); |
| int month = date.getMonth(); |
| int year = date.getYear(); |
| year = (year > 0 ? year : year + 1); |
| int result = day - 32075 + 1461 * (year + 4800 + (month - 14) / 12) / 4 + |
| 367 * (month - 2 - (month - 14) / 12 * 12) / 12 - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4; |
| |
| if (result < 0) { |
| throw new IllegalStateException("date too far in the past (year allowed to -4713)"); |
| } |
| |
| return result; |
| } |
| |
| /* package */ |
| static Date dateForGDate(GDateSpecification date) { |
| long jDate = julianDateForGDate(date); |
| long to1970Date = jDate - 2440588; |
| long to1970Ms = 1000 * 60 * 60 * 24 * to1970Date; |
| |
| to1970Ms += date.getMillisecond(); |
| to1970Ms += date.getSecond() * 1000; |
| to1970Ms += date.getMinute() * 60 * 1000; |
| to1970Ms += date.getHour() * 60 * 60 * 1000; |
| if (date.hasTimeZone()) { |
| to1970Ms -= (date.getTimeZoneMinute() * date.getTimeZoneSign()) * 60 * 1000; |
| to1970Ms -= (date.getTimeZoneHour() * date.getTimeZoneSign()) * 60 * 60 * 1000; |
| } else { |
| TimeZone def = TimeZone.getDefault(); |
| int offset = def.getOffset(to1970Ms); |
| to1970Ms -= offset; |
| } |
| |
| return new Date(to1970Ms); |
| } |
| |
| /** |
| * True for leap years. |
| */ |
| private static boolean _isLeapYear(int year) { |
| // BUGBUG: Julian calendar? |
| return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))); |
| } |
| |
| /** |
| * fQuotient(a, b) = the greatest integer less than or equal to a/b |
| */ |
| private static long _fQuotient(long a, int b) { |
| if ((a < 0) == (b < 0)) { |
| return a / b; |
| } |
| |
| return -((b - a - 1) / b); |
| } |
| |
| /** |
| * modulo(a, b) = a - fQuotient(a,b)*b |
| */ |
| private static int _mod(long a, int b, long quotient) { |
| return (int) (a - quotient * b); |
| } |
| |
| /** |
| * modulo(a - low, high - low) + low |
| */ |
| private static int _modulo(long temp, int low, int high) { |
| long a = temp - low; |
| int b = high - low; |
| return (_mod(a, b, _fQuotient(a, b)) + low); |
| } |
| |
| /** |
| * Quotient(a - low, high - low) |
| */ |
| private static long _fQuotient(long temp, int low, int high) { |
| return _fQuotient(temp - low, high - low); |
| } |
| |
| /** |
| * Sets to the first possible moment that matches the given |
| * specification. |
| */ |
| private void _setToFirstMoment() { |
| // 1584 was the first leap year during which the Gregorian |
| // calendar was in use: seems like the most reasonable "first" |
| // year to use in absence of a year. |
| |
| if (!hasYear()) { |
| setYear(1584); |
| } |
| |
| if (!hasMonth()) { |
| setMonth(1); |
| } |
| |
| if (!hasDay()) { |
| setDay(1); |
| } |
| |
| if (!hasTime()) { |
| setTime(0, 0, 0, GDate._zero); |
| } |
| } |
| |
| /** |
| * Comparison to another GDate. |
| * <ul> |
| * <li>Returns -1 if this < date. (less-than) |
| * <li>Returns 0 if this == date. (equal) |
| * <li>Returns 1 if this > date. (greater-than) |
| * <li>Returns 2 if this <> date. (incomparable) |
| * </ul> |
| * Two instances are incomparable if they have different amounts |
| * of information. |
| * |
| * @param datespec the date to compare against |
| */ |
| public final int compareToGDate(GDateSpecification datespec) { |
| return compareGDate(this, datespec); |
| } |
| |
| |
| /* package */ |
| static int compareGDate(GDateSpecification tdate, GDateSpecification datespec) { |
| // same amount of information: looks good |
| int bitdiff = tdate.getFlags() ^ datespec.getFlags(); |
| |
| if ((bitdiff & (HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME | HAS_TIMEZONE)) == 0) { |
| // If the other date needs to be normalized to |
| // our timezone, make a clone and do so if possible |
| if (tdate.hasTimeZone() && |
| (datespec.getTimeZoneHour() != tdate.getTimeZoneHour() || |
| datespec.getTimeZoneMinute() != tdate.getTimeZoneMinute() || |
| datespec.getTimeZoneSign() != tdate.getTimeZoneSign())) { |
| datespec = new GDateBuilder(datespec); |
| |
| int flags = tdate.getFlags() & (HAS_YEAR | HAS_MONTH | HAS_DAY); |
| if (flags != 0 && flags != (HAS_YEAR | HAS_MONTH | HAS_DAY) || !tdate.hasTime()) { |
| // in these cases we'll need to fill in fields |
| ((GDateBuilder) datespec)._setToFirstMoment(); |
| tdate = new GDateBuilder(tdate); |
| ((GDateBuilder) tdate)._setToFirstMoment(); |
| } |
| |
| ((GDateBuilder) datespec).normalizeToTimeZone(tdate.getTimeZoneSign(), tdate.getTimeZoneHour(), tdate.getTimeZoneMinute()); |
| } |
| |
| // compare by field |
| return fieldwiseCompare(tdate, datespec); |
| } |
| |
| // different amounts of information (except timezone): not comparable |
| if ((bitdiff & (HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME)) != 0) { |
| return 2; |
| } |
| |
| // The schema spec says we should try to compare with-timezone and |
| // without-timezone specifications... Well, OK, sure, if they say so. |
| |
| // We don't have a timezone but the other does: reverse the call |
| if (!tdate.hasTimeZone()) { |
| int result = compareGDate(datespec, tdate); |
| return result == 2 ? 2 : -result; |
| } |
| |
| // Now tdate is guaranteed to have a timezone and datespec not. |
| |
| // To muck with the times, make clones |
| GDateBuilder pdate = new GDateBuilder(tdate); |
| |
| // To cover the one uncovered case: if one date is 02/28 and the |
| // other date is 03/01, shift days closer by one to simulate being |
| // the last day of the month within a leap year |
| if ((tdate.getFlags() & (HAS_YEAR | HAS_MONTH | HAS_DAY)) == (HAS_MONTH | HAS_DAY)) { |
| if (tdate.getDay() == 28 && tdate.getMonth() == 2) { |
| if (datespec.getDay() == 1 && datespec.getMonth() == 3) { |
| pdate.setDay(29); |
| } |
| } else if (datespec.getDay() == 28 && datespec.getMonth() == 2) { |
| if (tdate.getDay() == 1 && tdate.getMonth() == 3) { |
| pdate.setMonth(2); |
| pdate.setDay(29); |
| } |
| } |
| } |
| |
| // For timespans, compare by first instant of time |
| // possible. Therefore, fill in Midnight, January 1, 1584 (a leap year) |
| // in absence of other information. |
| pdate._setToFirstMoment(); |
| |
| // P < Q if P < (Q with time zone +14:00) |
| GDateBuilder qplusdate = new GDateBuilder(datespec); |
| qplusdate._setToFirstMoment(); |
| qplusdate.setTimeZone(1, 14, 0); |
| qplusdate.normalizeToTimeZone(tdate.getTimeZoneSign(), tdate.getTimeZoneHour(), tdate.getTimeZoneMinute()); |
| if (fieldwiseCompare(pdate, qplusdate) == -1) { |
| return -1; |
| } |
| |
| // P > Q if P > (Q with time zone -14:00) |
| GDateBuilder qminusdate = qplusdate; |
| qminusdate.setGDate(datespec); |
| qminusdate._setToFirstMoment(); |
| qminusdate.setTimeZone(-1, 14, 0); |
| qminusdate.normalizeToTimeZone(tdate.getTimeZoneSign(), tdate.getTimeZoneHour(), tdate.getTimeZoneMinute()); |
| if (fieldwiseCompare(pdate, qminusdate) == 1) { |
| return 1; |
| } |
| |
| // P <> Q otherwise |
| return 2; |
| } |
| |
| /** |
| * Does a simple most-significant-digit-first comparison, |
| * ignoring any timezone or has/doesn't have issues. |
| * The data must have been digested first. |
| */ |
| private static int fieldwiseCompare(GDateSpecification tdate, GDateSpecification date) { |
| if (tdate.hasYear()) { |
| int CY = date.getYear(); |
| int TCY = tdate.getYear(); |
| if (TCY < CY) { |
| return -1; |
| } |
| if (TCY > CY) { |
| return 1; |
| } |
| } |
| if (tdate.hasMonth()) { |
| int M = date.getMonth(); |
| int TM = tdate.getMonth(); |
| if (TM < M) { |
| return -1; |
| } |
| if (TM > M) { |
| return 1; |
| } |
| } |
| if (tdate.hasDay()) { |
| int D = date.getDay(); |
| int TD = tdate.getDay(); |
| if (TD < D) { |
| return -1; |
| } |
| if (TD > D) { |
| return 1; |
| } |
| } |
| if (tdate.hasTime()) { |
| int h = date.getHour(); |
| int th = tdate.getHour(); |
| if (th < h) { |
| return -1; |
| } |
| if (th > h) { |
| return 1; |
| } |
| int m = date.getMinute(); |
| int tm = tdate.getMinute(); |
| if (tm < m) { |
| return -1; |
| } |
| if (tm > m) { |
| return 1; |
| } |
| int s = date.getSecond(); |
| int ts = tdate.getSecond(); |
| if (ts < s) { |
| return -1; |
| } |
| if (ts > s) { |
| return 1; |
| } |
| BigDecimal fs = date.getFraction(); |
| BigDecimal tfs = tdate.getFraction(); |
| if (tfs == null && fs == null) { |
| return 0; |
| } |
| return (tfs == null ? GDate._zero : tfs).compareTo(fs == null ? GDate._zero : fs); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Returns the builtin type code for the shape of the information |
| * contained in this instance, or 0 if the |
| * instance doesn't contain information corresponding to a |
| * Schema type. |
| * <p> |
| * Value will be equal to |
| * {@link SchemaType#BTC_NOT_BUILTIN}, |
| * {@link SchemaType#BTC_G_YEAR}, |
| * {@link SchemaType#BTC_G_YEAR_MONTH}, |
| * {@link SchemaType#BTC_G_MONTH}, |
| * {@link SchemaType#BTC_G_MONTH_DAY}, |
| * {@link SchemaType#BTC_G_DAY}, |
| * {@link SchemaType#BTC_DATE}, |
| * {@link SchemaType#BTC_DATE_TIME}, or |
| * {@link SchemaType#BTC_TIME}. |
| */ |
| public final int getBuiltinTypeCode() { |
| return btcForFlags(_bits); |
| } |
| |
| /* package */ |
| static int btcForFlags(int flags) { |
| switch (flags & (HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME)) { |
| case HAS_YEAR: |
| return SchemaType.BTC_G_YEAR; |
| case HAS_YEAR | HAS_MONTH: |
| return SchemaType.BTC_G_YEAR_MONTH; |
| case HAS_MONTH: |
| return SchemaType.BTC_G_MONTH; |
| case HAS_MONTH | HAS_DAY: |
| return SchemaType.BTC_G_MONTH_DAY; |
| case HAS_DAY: |
| return SchemaType.BTC_G_DAY; |
| case HAS_YEAR | HAS_MONTH | HAS_DAY: |
| return SchemaType.BTC_DATE; |
| case HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME: |
| return SchemaType.BTC_DATE_TIME; |
| case HAS_TIME: |
| return SchemaType.BTC_TIME; |
| default: |
| return SchemaType.BTC_NOT_BUILTIN; |
| } |
| } |
| |
| /** |
| * Clears the fields in this GDateBuilder that are not applicable |
| * for the given SchemaType date code. The code should be |
| * {@link SchemaType#BTC_G_YEAR}, |
| * {@link SchemaType#BTC_G_YEAR_MONTH}, |
| * {@link SchemaType#BTC_G_MONTH}, |
| * {@link SchemaType#BTC_G_MONTH_DAY}, |
| * {@link SchemaType#BTC_G_DAY}, |
| * {@link SchemaType#BTC_DATE}, |
| * {@link SchemaType#BTC_DATE_TIME}, or |
| * {@link SchemaType#BTC_TIME}. |
| * |
| * @param typeCode the type code to apply |
| */ |
| public void setBuiltinTypeCode(int typeCode) { |
| switch (typeCode) { |
| case SchemaType.BTC_G_YEAR: |
| //HAS_YEAR |
| clearMonth(); |
| clearDay(); |
| clearTime(); |
| return; |
| case SchemaType.BTC_G_YEAR_MONTH: |
| //HAS_YEAR | HAS_MONTH |
| clearDay(); |
| clearTime(); |
| return; |
| case SchemaType.BTC_G_MONTH: |
| //HAS_MONTH |
| clearYear(); |
| clearDay(); |
| clearTime(); |
| return; |
| case SchemaType.BTC_G_MONTH_DAY: |
| //HAS_MONTH | HAS_DAY |
| clearYear(); |
| clearTime(); |
| return; |
| case SchemaType.BTC_G_DAY: |
| //HAS_DAY |
| clearYear(); |
| clearMonth(); |
| clearTime(); |
| return; |
| case SchemaType.BTC_DATE: |
| //HAS_YEAR | HAS_MONTH | HAS_DAY |
| clearTime(); |
| return; |
| case SchemaType.BTC_DATE_TIME: |
| //HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME |
| return; |
| case SchemaType.BTC_TIME: |
| //HAS_TIME |
| clearYear(); |
| clearMonth(); |
| clearDay(); |
| return; |
| default: |
| throw new IllegalArgumentException("codeType must be one of SchemaType BTC_ DATE TIME related types."); |
| } |
| } |
| |
| |
| /* package */ static final BigInteger TEN = BigInteger.valueOf(10); |
| |
| /** |
| * The canonical string representation. Specific moments or |
| * times-of-day in a specified timezone are normalized to |
| * UTC time to produce a canonical string form for them. |
| * Other recurring time specifications keep their timezone |
| * information. |
| */ |
| public String canonicalString() { |
| boolean needNormalize = |
| (hasTimeZone() && getTimeZoneSign() != 0 && hasTime() && |
| ((hasDay() == hasMonth() && hasDay() == hasYear()))); |
| |
| if (!needNormalize && getFraction() != null && getFraction().scale() > 0) { |
| BigInteger bi = getFraction().unscaledValue(); |
| needNormalize = (bi.mod(TEN).signum() == 0); |
| } |
| |
| if (!needNormalize) { |
| return toString(); |
| } |
| |
| GDateBuilder cdate = new GDateBuilder(this); |
| cdate.normalize(); |
| return cdate.toString(); |
| } |
| |
| /** |
| * The natural string representation. This represents the information |
| * that is available, including timezone. For types that correspond |
| * to defined schema types (schemaBuiltinTypeCode() > 0), |
| * this provides the natural lexical representation. |
| * <p> |
| * When both time and timezone are specified, this string is not |
| * the canonical representation unless the timezone is UTC (Z) |
| * (since the same moment in time can be expressed in different |
| * timezones). To get a canonical string, use the canonicalString() |
| * method. |
| */ |
| public final String toString() { |
| return GDate.formatGDate(this); |
| } |
| |
| } |