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

org.apache.xmlbeans.GDateBuilder Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*   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}.
 * 

* 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. *

* 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); } /** * Constructs a GDateBuilder by copying another GDateSpecification. */ 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. *

* 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. *

* Note that you can reexpress the GDateBuilder in any timezone using * normalizeToTimeZone(). The normalize() method normalizes to UTC. *

* 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. *

* The current offset of the default timezone is used as the timezone. *

* 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. *

* 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. *

* 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. *

* 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 = Math.addExact(_D, Math.toIntExact(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. *

* 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 = Math.addExact(_D, Math.toIntExact(Math.addExact(Math.multiplyExact(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; } return month == 2 ? (_isLeapYear(year) ? 29 : 28) : 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; } return month == 2 ? 29 : 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. *

* 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.) *

* 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}. *

* {@link XmlCalendar} is a subclass of {@link java.util.GregorianCalendar} * which is slightly customized to match XML schema date rules. *

* 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() * 1000L; to1970Ms += date.getMinute() * 60 * 1000L; to1970Ms += date.getHour() * 60 * 60 * 1000L; if (date.hasTimeZone()) { to1970Ms -= date.getTimeZoneMinute() * date.getTimeZoneSign() * 60 * 1000L; to1970Ms -= date.getTimeZoneHour() * date.getTimeZoneSign() * 60 * 60 * 1000L; } 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) { return ((a < 0) == (b < 0)) ? (a / b) : -((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. *

    *
  • Returns -1 if this < date. (less-than) *
  • Returns 0 if this == date. (equal) *
  • Returns 1 if this > date. (greater-than) *
  • Returns 2 if this <> date. (incomparable) *
* 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. *

* 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. *

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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy