blob: fd89daecf67df00731ccc92b345e6bab318ad32d [file] [log] [blame]
/* 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.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.SimpleTimeZone;
/**
* Represents an XML Schema-compatible Gregorian date.
* <p>
* There are many date types in XML Schema, and this type
* represents the natural union of all those types. A GDate
* can hold any subset of date fields (Year, Month, Day, Time,
* Timezone, or some combination). Wherever the specification
* provides guidance, the guidelines in the
* <a target="_blank" href="http://www.w3.org/TR/xmlschema-2/">XML Schema 1.0 specification</a>
* (plus <a target="_blank" href="http://www.w3.org/2001/05/xmlschema-errata">published errata</a>) are followed.
* <p>
* Instances may separately have values or no values for
* the year, month, day-of-month, and time-of-day. Not all
* operations are meaningful on all combinations.
*/
public final class GDate implements GDateSpecification, java.io.Serializable
{
private static final long serialVersionUID = 1L;
// for fast equality comparison, hashing, and serialization
private transient String _canonicalString;
private transient String _string;
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;
/* package */ static final BigDecimal _zero = BigDecimal.valueOf(0);
/* package */ static final BigDecimal _one = BigDecimal.valueOf(1);
/**
* Constructs a GDate based on a lexical representation.
*/
public GDate(String string)
{
// first trim XML whitespace
int len = string.length();
int start = 0;
while (len > 0 && isSpace(string.charAt(len - 1)))
len -= 1;
while (start < len && isSpace(string.charAt(start)))
start += 1;
// pick optional timezone off the end
if (len - start >= 1 && string.charAt(len - 1) == 'Z')
{
_bits |= HAS_TIMEZONE;
len -= 1;
}
else if (len - start >= 6)
timezone: {
int tzsign;
int tzhour;
int tzminute;
if (string.charAt(len - 3) != ':')
break timezone;
switch (string.charAt(len - 6))
{
case '-':
tzsign = -1; break;
case '+':
tzsign = 1; break;
default:
break timezone;
}
tzhour = twoDigit(string, len - 5);
tzminute = twoDigit(string, len - 2);
if (tzhour > 14)
throw new IllegalArgumentException("time zone hour must be two digits between -14 and +14");
if (tzminute > 59)
throw new IllegalArgumentException("time zone minute must be two digits between 00 and 59");
_bits |= HAS_TIMEZONE;
_tzsign = tzsign;
_tzh = tzhour;
_tzm = tzminute;
len -= 6;
}
// pick date fields off the beginning if it doesn't look like a time
if (start < len && (start + 2 >= len || string.charAt(start + 2) != ':'))
scandate:
{
// parse year sign
boolean negyear = false;
if (start < len && string.charAt(start) == '-')
{
negyear = true;
start += 1;
}
// scan year digits
int value = 0;
int digits = -start;
char ch;
for (;;)
{
ch = start < len ? string.charAt(start) : '\0';
if (!isDigit(ch))
break;
value = value * 10 + digitVal(ch);
start += 1;
}
digits += start;
if (digits > 6)
throw new IllegalArgumentException("year too long (up to 6 digits)");
else if (digits >= 4)
{
_bits |= HAS_YEAR;
_CY = negyear ? -value : value;
}
else if (digits > 0)
throw new IllegalArgumentException("year must be four digits (may pad with zeroes, e.g., 0560)");
// hyphen introduces a month
if (ch != '-')
{
if (negyear && !hasYear())
throw new IllegalArgumentException(); // a single minus
else
break scandate;
}
start += 1;
// two-digit month
if (len - start >= 2)
{
value = twoDigit(string, start);
if (value >= 1 && value <= 12)
{
_bits |= HAS_MONTH;
_M = value;
start += 2;
}
}
// hyphen introduces a day
ch = start < len ? string.charAt(start) : '\0';
if (ch != '-')
{
if (!hasMonth())
throw new IllegalArgumentException(); // minus after a year
else
break scandate;
}
start += 1;
// two-digit day
if (len - start >= 2)
{
value = twoDigit(string, start);
if (value >= 1 && value <= 31)
{
_bits |= HAS_DAY;
_D = value;
start += 2;
}
}
if (!hasDay())
{
// error in the original schema spec permits an extra '-' here
if (hasMonth() && !hasYear())
{
ch = start < len ? string.charAt(start) : '\0';
if (ch == '-')
{
start += 1;
break scandate;
}
}
throw new IllegalArgumentException(); // minus after a month
}
}
// time
if (start < len)
{
if (hasYear() || hasMonth() || hasDay())
{
if (string.charAt(start) != 'T')
throw new IllegalArgumentException("date and time must be separated by 'T'");
start += 1;
}
if (len < start + 8 || string.charAt(start + 2) != ':' || string.charAt(start + 5) != ':')
throw new IllegalArgumentException();
int h = twoDigit(string, start);
if (h >= 24)
throw new IllegalArgumentException("hour must be between 00 and 23");
int m = twoDigit(string, start + 3);
if (m >= 60)
throw new IllegalArgumentException("minute must be between 00 and 59");
int s = twoDigit(string, start + 6);
if (s >= 60)
throw new IllegalArgumentException("second must be between 00 and 59");
start += 8;
BigDecimal fs = _zero;
if (start < len)
{
if (string.charAt(start) != '.')
throw new IllegalArgumentException();
if (start + 1 < len)
{
for (int i = start + 1; i < len; i++)
{
if (!isDigit(string.charAt(i)))
throw new IllegalArgumentException();
}
try
{
fs = new BigDecimal(string.substring(start, len));
}
catch (Throwable e)
{
throw new IllegalArgumentException();
}
}
}
_bits |= HAS_TIME;
_h = h;
_m = m;
_s = s;
_fs = fs;
}
if (!isValid())
throw new IllegalArgumentException("invalid date");
}
/**
* Constructs a GDate 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 GDate
* becomes partially unordered with respect to times that
* do have a specified timezone.
*/
public GDate(
int year,
int month,
int day,
int hour,
int minute,
int second,
BigDecimal fraction)
{
_bits = HAS_YEAR | HAS_MONTH | HAS_DAY | HAS_TIME;
_CY = year;
_M = month;
_D = day;
_h = hour;
_m = minute;
_s = second;
_fs = fraction == null ? _zero : fraction;
if (!isValid())
throw new IllegalArgumentException();
}
/**
* Constructs an absolute GDate with the specified year,
* month, day, hours, minutes, seconds, and optional fractional
* seconds, and in the timezone specified.
* <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.
*/
public GDate(
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;
_CY = year;
_M = month;
_D = day;
_h = hour;
_m = minute;
_s = second;
_fs = fraction == null ? _zero : fraction;
_tzsign = tzSign;
_tzh = tzHour;
_tzm = tzMinute;
if (!isValid())
throw new IllegalArgumentException();
}
/**
* Constructs a GDate 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.
*/
public GDate(Date date)
{
// requires some date math, so ctor lives on GDateBuilder
this(new GDateBuilder(date));
}
/**
* Constructs a GDate based on a java.util.Calendar.
* <p>
* If the calendar does not have some fields set, the same absence
* of information is reflected in the GDate. Note that
* java.util.GregorianCalendar fills in all fields as soon as any
* are fetched, so constructing a GDate with the same calendar object
* twice may result in a different GDate because of a changed calendar.
* Note that org.apache.xmlbeans.XmlCalendar is stable if you re-get a set field,
* so it does not have the same problem.
*/
public GDate(Calendar calendar)
{
XmlCalendar xCal = null;
boolean isXC = false;
if (calendar instanceof XmlCalendar)
{
isXC = true;
xCal = (XmlCalendar) calendar;
}
// we must scrape the "isSet" information out before accessing anything
boolean isSetYear = calendar.isSet(Calendar.YEAR);
boolean isSetEra = calendar.isSet(Calendar.ERA);
boolean isSetMonth = calendar.isSet(Calendar.MONTH);
boolean isSetDay = calendar.isSet(Calendar.DAY_OF_MONTH);
boolean isSetHourOfDay = calendar.isSet(Calendar.HOUR_OF_DAY);
boolean isSetHour = calendar.isSet(Calendar.HOUR);
boolean isSetAmPm = calendar.isSet(Calendar.AM_PM);
boolean isSetMinute = calendar.isSet(Calendar.MINUTE);
boolean isSetSecond = calendar.isSet(Calendar.SECOND);
boolean isSetMillis = calendar.isSet(Calendar.MILLISECOND);
boolean isSetZone = calendar.isSet(Calendar.ZONE_OFFSET);
boolean isSetDst = calendar.isSet(Calendar.DST_OFFSET);
if (isSetYear)
{
int y = isXC ? xCal.peek(Calendar.YEAR) : calendar.get(Calendar.YEAR);
if (isSetEra && calendar instanceof GregorianCalendar)
if ((isXC ? xCal.peek(Calendar.ERA) : calendar.get(Calendar.ERA)) == GregorianCalendar.BC)
y = 1 - y;
_bits |= HAS_YEAR;
_CY = y;
}
if (isSetMonth)
{
_bits |= HAS_MONTH;
_M = (isXC ? xCal.peek(Calendar.MONTH) : calendar.get(Calendar.MONTH)) + 1; // !!note
}
if (isSetDay)
{
_bits |= HAS_DAY;
_D = isXC ? xCal.peek(Calendar.DAY_OF_MONTH) : calendar.get(Calendar.DAY_OF_MONTH);
}
boolean gotTime = false;
int h = 0;
int m = 0;
int s = 0;
BigDecimal fs = _zero;
if (isSetHourOfDay)
{
h = isXC ? xCal.peek(Calendar.HOUR_OF_DAY) : calendar.get(Calendar.HOUR_OF_DAY);
gotTime = true;
}
else if (isSetHour && isSetAmPm)
{
if (isXC)
h = xCal.peek(Calendar.HOUR) + xCal.peek(Calendar.AM_PM) * 12;
else
h = calendar.get(Calendar.HOUR) + calendar.get(Calendar.AM_PM) * 12;
gotTime = true;
}
if (isSetMinute)
{
m = isXC ? xCal.peek(Calendar.MINUTE) : calendar.get(Calendar.MINUTE);
gotTime = true;
}
if (isSetSecond)
{
s = isXC ? xCal.peek(Calendar.SECOND) : calendar.get(Calendar.SECOND);
gotTime = true;
}
if (isSetMillis)
{
fs = BigDecimal.valueOf(isXC ? xCal.peek(Calendar.MILLISECOND)
: calendar.get(Calendar.MILLISECOND), 3);
gotTime = true;
}
if (gotTime)
{
_bits |= HAS_TIME;
_h = h;
_m = m;
_s = s;
_fs = fs;
}
if (isSetZone)
{
int zoneOffsetInMilliseconds = isXC ? xCal.peek(Calendar.ZONE_OFFSET)
: calendar.get(Calendar.ZONE_OFFSET);
if (isSetDst)
zoneOffsetInMilliseconds += isXC ? xCal.peek(Calendar.DST_OFFSET)
: calendar.get(Calendar.DST_OFFSET);
_bits |= HAS_TIMEZONE;
if (zoneOffsetInMilliseconds == 0)
{
_tzsign = 0;
_tzh = 0;
_tzm = 0;
TimeZone zone = calendar.getTimeZone();
String id = zone.getID();
if (id != null && id.length() > 3) switch (id.charAt(3))
{
case '+': _tzsign = 1; break; // GMT+00:00
case '-': _tzsign = -1; break; // GMT-00:00
}
}
else
{
_tzsign = (zoneOffsetInMilliseconds < 0 ? -1 : +1);
zoneOffsetInMilliseconds = zoneOffsetInMilliseconds * _tzsign;
_tzh = zoneOffsetInMilliseconds / 3600000;
_tzm = (zoneOffsetInMilliseconds - _tzh * 3600000) / 60000;
}
}
}
/**
* Constructs a GDate based on another GDateSpecification.
*/
public GDate(GDateSpecification gdate)
{
if (gdate.hasTimeZone())
{
_bits |= HAS_TIMEZONE;
_tzsign = gdate.getTimeZoneSign();
_tzh = gdate.getTimeZoneHour();
_tzm = gdate.getTimeZoneMinute();
}
if (gdate.hasTime())
{
_bits |= HAS_TIME;
_h = gdate.getHour();
_m = gdate.getMinute();
_s = gdate.getSecond();
_fs = gdate.getFraction();
}
if (gdate.hasDay())
{
_bits |= HAS_DAY;
_D = gdate.getDay();
}
if (gdate.hasMonth())
{
_bits |= HAS_MONTH;
_M = gdate.getMonth();
}
if (gdate.hasYear())
{
_bits |= HAS_YEAR;
_CY = gdate.getYear();
}
}
/* package */ static final boolean isDigit(char ch)
{
return ((char)(ch - '0') <= '9' - '0'); // char is unsigned
}
/* package */ static final boolean isSpace(char ch)
{
switch (ch)
{
case ' ':
case '\t':
case '\r':
case '\n':
return true;
default:
return false;
}
}
/* package */ static final int digitVal(char ch)
{
return (ch - '0');
}
private static final int twoDigit(String str, int index)
{
char ch1 = str.charAt(index);
char ch2 = str.charAt(index + 1);
if (!isDigit(ch1) || !isDigit(ch2))
return 100; // not two digits
return digitVal(ch1) * 10 + digitVal(ch2);
}
/**
* Returns true: all GDate instances are immutable.
*/
public final boolean isImmutable()
{
return true;
}
/**
* 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; }
/**
* 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 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; }
/**
* Gets the rounded millisecond value. Range from 0 to 999
*/
public int getMillisecond()
{
if (_fs == null)
return 0;
return _fs.setScale(3, BigDecimal.ROUND_HALF_UP).unscaledValue().intValue();
}
/**
* 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()
{
ensureCanonicalString();
return _canonicalString;
}
/**
* True if this GDate corresponds to a valid gregorian date value
* in XML schema.
*/
public boolean isValid()
{
return GDateBuilder.isValidGDate(this);
}
/**
* Returns the Julian date corresponding to this Gregorian date.
* The Julian date (JD) is a continuous count of days from
* 1 January 4713 BC (= -4712 January 1).
*/
public int getJulianDate()
{
return GDateBuilder.julianDateForGDate(this);
}
/**
* 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 GDateBuilder.dateForGDate(this);
}
/**
* 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.
*/
public int compareToGDate(GDateSpecification datespec)
{
return GDateBuilder.compareGDate(this, datespec);
}
/**
* 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 int getBuiltinTypeCode()
{
return GDateBuilder.btcForFlags(_bits);
}
/**
* Adds a duration to this GDate, and returns a new GDate.
*/
public GDate add(GDurationSpecification duration)
{
GDateBuilder builder = new GDateBuilder(this);
builder.addGDuration(duration);
return builder.toGDate();
}
/**
* Adds a duration to this GDate, and returns a new GDate.
*/
public GDate subtract(GDurationSpecification duration)
{
GDateBuilder builder = new GDateBuilder(this);
builder.subtractGDuration(duration);
return builder.toGDate();
}
/**
* GDate is an immutable class, and equality is computed based
* on its canonical value.
*/
public boolean equals(Object obj)
{
if (obj == this)
return true;
if (!(obj instanceof GDate))
return false;
ensureCanonicalString();
return _canonicalString.equals(((GDate)obj).canonicalString());
}
/**
* Returns a hash code for this GDate.
*/
public int hashCode()
{
ensureCanonicalString();
return _canonicalString.hashCode();
}
/**
* 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.
*/
private void ensureCanonicalString()
{
if (_canonicalString != null)
return;
boolean needNormalize =
(hasTimeZone() && getTimeZoneSign() != 0 && hasTime() &&
((hasDay() == hasMonth() && hasDay() == hasYear())));
if (!needNormalize && getFraction() != null && getFraction().scale() > 0)
{
BigInteger bi = getFraction().unscaledValue();
needNormalize = (bi.mod(GDateBuilder.TEN).signum() == 0);
}
if (!needNormalize)
_canonicalString = toString();
else
{
GDateBuilder gdb = new GDateBuilder(this);
gdb.normalize();
_canonicalString = gdb.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 String toString()
{
if (_string == null)
_string = formatGDate(this);
return _string;
}
private final static char[] _tensDigit =
{
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
'4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
'5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
'6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
'7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
'8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
};
private final static char[] _onesDigit =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
};
private static final int _padTwoAppend(char[] b, int i, int n)
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(n >= 0 && n < 100);
b[i] = _tensDigit[n];
b[i + 1] = _onesDigit[n];
return i + 2;
}
private static final int _padFourAppend(char[] b, int i, int n)
{
if (n < 0)
{
b[i++] = '-';
n = -n;
}
if (n >= 10000)
{
String s = Integer.toString(n);
s.getChars(0, s.length(), b, i);
return i + s.length();
}
int q = n / 100;
int r = n - q * 100;
b[i] = _tensDigit[q];
b[i + 1] = _onesDigit[q];
b[i + 2] = _tensDigit[r];
b[i + 3] = _onesDigit[r];
return i + 4;
}
private static final TimeZone GMTZONE = TimeZone.getTimeZone("GMT");
private static final int ONE_MINUTE = 60 * 1000;
private static final int ONE_HOUR = 60 * ONE_MINUTE;
private static final TimeZone[] MINUSZONE =
{
new SimpleTimeZone(0, "GMT-00:00"),
new SimpleTimeZone(-1 * ONE_HOUR, "GMT-01:00"),
new SimpleTimeZone(-2 * ONE_HOUR, "GMT-02:00"),
new SimpleTimeZone(-3 * ONE_HOUR, "GMT-03:00"),
new SimpleTimeZone(-4 * ONE_HOUR, "GMT-04:00"),
new SimpleTimeZone(-5 * ONE_HOUR, "GMT-05:00"),
new SimpleTimeZone(-6 * ONE_HOUR, "GMT-06:00"),
new SimpleTimeZone(-7 * ONE_HOUR, "GMT-07:00"),
new SimpleTimeZone(-8 * ONE_HOUR, "GMT-08:00"),
new SimpleTimeZone(-9 * ONE_HOUR, "GMT-09:00"),
new SimpleTimeZone(-10 * ONE_HOUR, "GMT-10:00"),
new SimpleTimeZone(-11 * ONE_HOUR, "GMT-11:00"),
new SimpleTimeZone(-12 * ONE_HOUR, "GMT-12:00"),
new SimpleTimeZone(-13 * ONE_HOUR, "GMT-13:00"),
new SimpleTimeZone(-14 * ONE_HOUR, "GMT-14:00")
};
private static final TimeZone[] PLUSZONE =
{
new SimpleTimeZone(0, "GMT+00:00"),
new SimpleTimeZone(1 * ONE_HOUR, "GMT+01:00"),
new SimpleTimeZone(2 * ONE_HOUR, "GMT+02:00"),
new SimpleTimeZone(3 * ONE_HOUR, "GMT+03:00"),
new SimpleTimeZone(4 * ONE_HOUR, "GMT+04:00"),
new SimpleTimeZone(5 * ONE_HOUR, "GMT+05:00"),
new SimpleTimeZone(6 * ONE_HOUR, "GMT+06:00"),
new SimpleTimeZone(7 * ONE_HOUR, "GMT+07:00"),
new SimpleTimeZone(8 * ONE_HOUR, "GMT+08:00"),
new SimpleTimeZone(9 * ONE_HOUR, "GMT+09:00"),
new SimpleTimeZone(10 * ONE_HOUR, "GMT+10:00"),
new SimpleTimeZone(11 * ONE_HOUR, "GMT+11:00"),
new SimpleTimeZone(12 * ONE_HOUR, "GMT+12:00"),
new SimpleTimeZone(13 * ONE_HOUR, "GMT+13:00"),
new SimpleTimeZone(14 * ONE_HOUR, "GMT+14:00")
};
/* package */ static final TimeZone timeZoneForGDate(GDateSpecification date)
{
// use a cached timezone if integral; otherwise make a new one.
if (!date.hasTimeZone())
return TimeZone.getDefault();
if (date.getTimeZoneSign() == 0)
return GMTZONE;
if (date.getTimeZoneMinute() == 0 && date.getTimeZoneHour() <= 14 && date.getTimeZoneHour() >= 0)
return date.getTimeZoneSign() < 0 ? MINUSZONE[date.getTimeZoneHour()] : PLUSZONE[date.getTimeZoneHour()];
int h = date.getTimeZoneHour(),
m = date.getTimeZoneMinute(),
offset = h * ONE_HOUR + m * ONE_MINUTE;
char[] zb = new char[9];
zb[0] = 'G';
zb[1] = 'M';
zb[2] = 'T';
zb[3] = (date.getTimeZoneSign() < 0) ? '-' : '+';
GDate._padTwoAppend(zb, 4, h);
zb[6] = ':';
GDate._padTwoAppend(zb, 7, m);
return new SimpleTimeZone(zb[3] == '+' ? offset : -offset, new String(zb));
}
/* package */ static String formatGDate(GDateSpecification spec)
{
// We've used a char[] rather than a StringBuffer for a 4x speedup
// -YY(10)YY-MM-DDTHH:MM:SS.FFFFFF+ZH:ZM
// 1 + 10 + 3+ 3+ 3+ 3+ 3+1 + s + 3+ 3 = 33 + s
BigDecimal fs = spec.getFraction();
char[] message = new char[33 + (fs == null ? 0 : fs.scale())];
int i = 0;
if (spec.hasYear() || spec.hasMonth() || spec.hasDay())
{
dmy: {
if (spec.hasYear())
i = _padFourAppend(message, 0, spec.getYear());
else
message[i++] = '-';
if (!(spec.hasMonth() || spec.hasDay()))
break dmy;
message[i++] = '-';
if (spec.hasMonth())
i = _padTwoAppend(message, i, spec.getMonth());
if (!spec.hasDay())
break dmy;
message[i++] = '-';
i = _padTwoAppend(message, i, spec.getDay());
break dmy;
}
if (spec.hasTime())
message[i++] = 'T';
}
if (spec.hasTime())
{
i = _padTwoAppend(message, i, spec.getHour());
message[i++] = ':';
i = _padTwoAppend(message, i, spec.getMinute());
message[i++] = ':';
i = _padTwoAppend(message, i, spec.getSecond());
if (fs != _zero) // (optimization ~3%)
{
String frac = spec.getFraction().toString();
int point = frac.indexOf('.');
if (point >= 0)
{
frac.getChars(point, frac.length(), message, i);
i += frac.length() - point;
}
}
}
if (spec.hasTimeZone())
{
if (spec.getTimeZoneSign() == 0)
{
message[i++] = 'Z';
}
else
{
message[i++] = spec.getTimeZoneSign() > 0 ? '+' : '-';
i = _padTwoAppend(message, i, spec.getTimeZoneHour());
message[i++] = ':';
i = _padTwoAppend(message, i, spec.getTimeZoneMinute());
}
}
// it would be nice to use (0, i, message) ctor instead
return new String(message, 0, i);
}
}