/***************************************************************************
* Copyright © 2005-2009 by Gabriele Svelto *
* gabriele.svelto@gmail.com *
* *
* This file is part of Jelatine. *
* *
* Jelatine is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* Jelatine is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with Jelatine. If not, see
SimpleTimeZone with the given time offset
* from GMT and without daylight savings.
* @param rawOffset the time offset from GMT in milliseconds.
* @param id The identifier of this time zone.
*/
public VMTimeZone(int rawOffset, String id)
{
this.rawOffset = rawOffset;
useDaylight = false;
startYear = 0;
}
/**
* Sets the first year, where daylight savings applies. The daylight
* savings rule never apply for years in the BC era. Note that this
* is gregorian calendar specific.
* @param year the start year.
*/
public void setStartYear(int year)
{
startYear = year;
useDaylight = true;
}
/**
* Checks if the month, day, dayOfWeek arguments are in range and
* returns the mode of the rule.
* @param month the month parameter as in the constructor
* @param day the day parameter as in the constructor
* @param dayOfWeek the day of week parameter as in the constructor
* @return the mode of this rule see startMode.
* @throws IllegalArgumentException if parameters are out of range.
*/
private int checkRule(int month, int day, int dayOfWeek)
{
if (month < 0 || month > 11)
throw new IllegalArgumentException();
int daysInMonth = getDaysInMonth(month, 1);
if (dayOfWeek == 0)
{
if (day <= 0 || day > daysInMonth)
throw new IllegalArgumentException();
return DOM_MODE;
}
else if (dayOfWeek > 0)
{
if (Math.abs(day) > (daysInMonth + 6) / 7)
throw new IllegalArgumentException();
if (dayOfWeek > Calendar.SATURDAY)
throw new IllegalArgumentException();
return DOW_IN_MONTH_MODE;
}
else
{
if (day == 0 || Math.abs(day) > daysInMonth)
throw new IllegalArgumentException();
if (dayOfWeek < -Calendar.SATURDAY)
throw new IllegalArgumentException();
if (day < 0)
return DOW_LE_DOM_MODE;
else
return DOW_GE_DOM_MODE;
}
}
/**
* Sets the daylight savings start rule. You must also set the
* end rule with setEndRule or the result of
* getOffset is undefined. For the parameters see the ten-argument
* constructor above.
*
* @param month The month where daylight savings start, zero
* based. You should use the constants in Calendar.
* @param day A day of month or day of week in month.
* @param dayOfWeek The day of week where daylight savings start.
* @param time The time in milliseconds standard time where daylight
* savings start.
* @throws IllegalArgumentException if parameters are out of range.
*/
public void setStartRule(int month, int day, int dayOfWeek, int time)
{
this.startMode = checkRule(month, day, dayOfWeek);
this.startMonth = month;
this.startDay = day;
this.startDayOfWeek = Math.abs(dayOfWeek);
if (this.startTimeMode == WALL_TIME
|| this.startTimeMode == STANDARD_TIME)
{
this.startTime = time;
}
else
{
// Convert from UTC to STANDARD
this.startTime = time + this.rawOffset;
}
useDaylight = true;
}
/**
* Sets the daylight savings start rule. You must also set the
* end rule with setEndRule or the result of
* getOffset is undefined. For the parameters see the ten-argument
* constructor above.
*
* Note that this API isn't incredibly well specified. It appears that the
* after flag must override the parameters, since normally, the day and
* dayofweek can select this. I.e., if day < 0 and dayOfWeek < 0, on or
* before mode is chosen. But if after == true, this implementation
* overrides the signs of the other arguments. And if dayOfWeek == 0, it
* falls back to the behavior in the other APIs. I guess this should be
* checked against Sun's implementation.
*
* @param month The month where daylight savings start, zero
* based. You should use the constants in Calendar.
* @param day A day of month or day of week in month.
* @param dayOfWeek The day of week where daylight savings start.
* @param time The time in milliseconds standard time where daylight
* savings start.
* @param after If true, day and dayOfWeek specify first day of week on or
* after day, else first day of week on or before.
*/
public void setStartRule(int month, int day, int dayOfWeek, int time,
boolean after)
{
// FIXME: XXX: Validate that checkRule and offset processing work with
// on or before mode.
this.startDay = after ? Math.abs(day) : -Math.abs(day);
this.startDayOfWeek =
after ? Math.abs(dayOfWeek) : -Math.abs(dayOfWeek);
this.startMode = (dayOfWeek != 0)
? (after ? DOW_GE_DOM_MODE : DOW_LE_DOM_MODE)
: checkRule(month, day, dayOfWeek);
this.startDay = Math.abs(this.startDay);
this.startDayOfWeek = Math.abs(this.startDayOfWeek);
this.startMonth = month;
if (this.startTimeMode == WALL_TIME
|| this.startTimeMode == STANDARD_TIME)
{
this.startTime = time;
}
else
{
// Convert from UTC to STANDARD
this.startTime = time + this.rawOffset;
}
useDaylight = true;
}
/**
* Sets the daylight savings start rule. You must also set the
* end rule with setEndRule or the result of
* getOffset is undefined. For the parameters see the ten-argument
* constructor above.
*
* @param month The month where daylight savings start, zero
* based. You should use the constants in Calendar.
* @param day A day of month or day of week in month.
* @param time The time in milliseconds standard time where daylight
* savings start.
*/
public void setStartRule(int month, int day, int time)
{
setStartRule(month, day, 0, time);
}
/**
* Sets the daylight savings end rule. You must also set the
* start rule with setStartRule or the result of
* getOffset is undefined. For the parameters see the ten-argument
* constructor above.
*
* @param month The end month of daylight savings.
* @param day A day in month, or a day of week in month.
* @param dayOfWeek A day of week, when daylight savings ends.
* @param time A time in millis in standard time.
*/
public void setEndRule(int month, int day, int dayOfWeek, int time)
{
this.endMode = checkRule(month, day, dayOfWeek);
this.endMonth = month;
this.endDay = day;
this.endDayOfWeek = Math.abs(dayOfWeek);
if (this.endTimeMode == WALL_TIME)
this.endTime = time;
else if (this.endTimeMode == STANDARD_TIME)
{
// Convert from STANDARD to DST
this.endTime = time + this.dstSavings;
}
else
{
// Convert from UTC to DST
this.endTime = time + this.rawOffset + this.dstSavings;
}
useDaylight = true;
}
/**
* Sets the daylight savings end rule. You must also set the
* start rule with setStartRule or the result of
* getOffset is undefined. For the parameters see the ten-argument
* constructor above.
*
* Note that this API isn't incredibly well specified. It appears that the
* after flag must override the parameters, since normally, the day and
* dayofweek can select this. I.e., if day < 0 and dayOfWeek < 0, on or
* before mode is chosen. But if after == true, this implementation
* overrides the signs of the other arguments. And if dayOfWeek == 0, it
* falls back to the behavior in the other APIs. I guess this should be
* checked against Sun's implementation.
*
* @param month The end month of daylight savings.
* @param day A day in month, or a day of week in month.
* @param dayOfWeek A day of week, when daylight savings ends.
* @param time A time in millis in standard time.
* @param after If true, day and dayOfWeek specify first day of week on or
* after day, else first day of week on or before.
*/
public void setEndRule(int month, int day, int dayOfWeek, int time,
boolean after)
{
// FIXME: XXX: Validate that checkRule and offset processing work with
// on or before mode.
this.endDay = after ? Math.abs(day) : -Math.abs(day);
this.endDayOfWeek = after ? Math.abs(dayOfWeek) : -Math.abs(dayOfWeek);
this.endMode = (dayOfWeek != 0)
? (after ? DOW_GE_DOM_MODE : DOW_LE_DOM_MODE)
: checkRule(month, day, dayOfWeek);
this.endDay = Math.abs(this.endDay);
this.endDayOfWeek = Math.abs(endDayOfWeek);
this.endMonth = month;
if (this.endTimeMode == WALL_TIME)
this.endTime = time;
else if (this.endTimeMode == STANDARD_TIME)
{
// Convert from STANDARD to DST
this.endTime = time + this.dstSavings;
}
else
{
// Convert from UTC to DST
this.endTime = time + this.rawOffset + this.dstSavings;
}
useDaylight = true;
}
/**
* Sets the daylight savings end rule. You must also set the
* start rule with setStartRule or the result of
* getOffset is undefined. For the parameters see the ten-argument
* constructor above.
*
* @param month The end month of daylight savings.
* @param day A day in month, or a day of week in month.
* @param time A time in millis in standard time.
*/
public void setEndRule(int month, int day, int time)
{
setEndRule(month, day, 0, time);
}
/**
* Gets the time zone offset, for current date, modified in case of
* daylight savings. This is the offset to add to UTC to get the local
* time.
*
* In the standard JDK the results given by this method may result in
* inaccurate results at the end of February or the beginning of March.
* To avoid this, you should use Calendar instead:
* offset = cal.get(Calendar.ZONE_OFFSET)
* + cal.get(Calendar.DST_OFFSET);
*
* This version doesn't suffer this inaccuracy.
*
* The arguments don't follow the approach for setting start and end rules.
* The day must be a positive number and dayOfWeek must be a positive value
* from Calendar. dayOfWeek is redundant, but must match the other values
* or an inaccurate result may be returned.
*
* @param era the era of the given date
* @param year the year of the given date
* @param month the month of the given date, 0 for January.
* @param day the day of month
* @param dayOfWeek the day of week; this must match the other fields.
* @param millis the millis in the day (in local standard time)
* @return the time zone offset in milliseconds.
* @throws IllegalArgumentException if arguments are incorrect.
*/
public int getOffset(int era, int year, int month, int day, int dayOfWeek,
int millis)
{
int daysInMonth = getDaysInMonth(month, year);
if (day < 1 || day > daysInMonth)
throw new IllegalArgumentException();
if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY)
throw new IllegalArgumentException();
if (month < Calendar.JANUARY || month > Calendar.DECEMBER)
throw new IllegalArgumentException();
// This method is called by Calendar, so we mustn't use that class.
int daylightSavings = 0;
if (useDaylight && era == 1 && year >= startYear)
{
// This does only work for Gregorian calendars :-(
// This is mainly because setStartYear doesn't take an era.
boolean afterStart = ! isBefore(year, month, day, dayOfWeek, millis,
startMode, startMonth, startDay, startDayOfWeek, startTime);
boolean beforeEnd = isBefore(year, month, day, dayOfWeek,
millis + dstSavings, endMode, endMonth, endDay, endDayOfWeek,
endTime);
if (startMonth < endMonth)
{
// use daylight savings, if the date is after the start of
// savings, and before the end of savings.
daylightSavings = afterStart && beforeEnd ? dstSavings : 0;
}
else
{
// use daylight savings, if the date is before the end of
// savings, or after the start of savings.
daylightSavings = beforeEnd || afterStart ? dstSavings : 0;
}
}
return rawOffset + daylightSavings;
}
/**
* Returns the time zone offset to GMT in milliseconds, ignoring
* day light savings.
* @return the time zone offset.
*/
public int getRawOffset()
{
return rawOffset;
}
/**
* Sets the standard time zone offset to GMT.
* @param rawOffset The time offset from GMT in milliseconds.
*/
public void setRawOffset(int rawOffset)
{
this.rawOffset = rawOffset;
}
/**
* Gets the daylight savings offset. This is a positive offset in
* milliseconds with respect to standard time. Typically this
* is one hour, but for some time zones this may be half an our.
* @return the daylight savings offset in milliseconds.
*/
public int getDSTSavings()
{
return dstSavings;
}
/**
* Sets the daylight savings offset. This is a positive offset in
* milliseconds with respect to standard time.
*
* @param dstSavings the daylight savings offset in milliseconds.
*/
public void setDSTSavings(int dstSavings)
{
if (dstSavings <= 0)
throw new IllegalArgumentException();
this.dstSavings = dstSavings;
}
/**
* Returns if this time zone uses daylight savings time.
* @return true, if we use daylight savings time, false otherwise.
*/
public boolean useDaylightTime()
{
return useDaylight;
}
/**
* Returns the number of days in the given month.
* Uses gregorian rules prior to 1582 (The default and earliest cutover)
* @param month The month, zero based; use one of the Calendar constants.
* @param year The year.
*/
private int getDaysInMonth(int month, int year)
{
if (month == Calendar.FEBRUARY)
{
if ((year & 3) != 0)
return 28;
// Assume default Gregorian cutover,
// all years prior to this must be Julian
if (year < 1582)
return 29;
// Gregorian rules
return ((year % 100) != 0 || (year % 400) == 0) ? 29 : 28;
}
else
return monthArr[month];
}
/**
* Checks if the date given in calXXXX, is before the change between
* dst and standard time.
* @param calYear the year of the date to check (for leap day checking).
* @param calMonth the month of the date to check.
* @param calDayOfMonth the day of month of the date to check.
* @param calDayOfWeek the day of week of the date to check.
* @param calMillis the millis of day of the date to check (standard time).
* @param mode the change mode; same semantic as startMode.
* @param month the change month; same semantic as startMonth.
* @param day the change day; same semantic as startDay.
* @param dayOfWeek the change day of week;
* @param millis the change time in millis since midnight standard time.
* same semantic as startDayOfWeek.
* @return true, if cal is before the change, false if cal is on
* or after the change.
*/
private boolean isBefore(int calYear, int calMonth, int calDayOfMonth,
int calDayOfWeek, int calMillis, int mode, int month, int day,
int dayOfWeek, int millis)
{
// This method is called by Calendar, so we mustn't use that class.
// We have to do all calculations by hand.
// check the months:
// XXX - this is not correct:
// for the DOW_GE_DOM and DOW_LE_DOM modes the change date may
// be in a different month.
if (calMonth != month)
return calMonth < month;
// check the day:
switch (mode)
{
case DOM_MODE:
if (calDayOfMonth != day)
return calDayOfMonth < day;
break;
case DOW_IN_MONTH_MODE:
{
// This computes the day of month of the day of type
// "dayOfWeek" that lies in the same (sunday based) week as cal.
calDayOfMonth += (dayOfWeek - calDayOfWeek);
// Now we convert it to 7 based number (to get a one based offset
// after dividing by 7). If we count from the end of the
// month, we get want a -7 based number counting the days from
// the end:
if (day < 0)
calDayOfMonth -= getDaysInMonth(calMonth, calYear) + 7;
else
calDayOfMonth += 6;
// day > 0 day < 0
// S M T W T F S S M T W T F S
// 7 8 9 10 11 12 -36-35-34-33-32-31
// 13 14 15 16 17 18 19 -30-29-28-27-26-25-24
// 20 21 22 23 24 25 26 -23-22-21-20-19-18-17
// 27 28 29 30 31 32 33 -16-15-14-13-12-11-10
// 34 35 36 -9 -8 -7
// Now we calculate the day of week in month:
int week = calDayOfMonth / 7;
// day > 0 day < 0
// S M T W T F S S M T W T F S
// 1 1 1 1 1 1 -5 -5 -4 -4 -4 -4
// 1 2 2 2 2 2 2 -4 -4 -4 -3 -3 -3 -3
// 2 3 3 3 3 3 3 -3 -3 -3 -2 -2 -2 -2
// 3 4 4 4 4 4 4 -2 -2 -2 -1 -1 -1 -1
// 4 5 5 -1 -1 -1
if (week != day)
return week < day;
if (calDayOfWeek != dayOfWeek)
return calDayOfWeek < dayOfWeek;
// daylight savings starts/ends on the given day.
break;
}
case DOW_LE_DOM_MODE:
// The greatest sunday before or equal December, 12
// is the same as smallest sunday after or equal December, 6.
day = Math.abs(day) - 6;
case DOW_GE_DOM_MODE:
// Calculate the day of month of the day of type
// "dayOfWeek" that lies before (or on) the given date.
calDayOfMonth -= (calDayOfWeek < dayOfWeek ? 7 : 0)
+ calDayOfWeek - dayOfWeek;
if (calDayOfMonth < day)
return true;
if (calDayOfWeek != dayOfWeek || calDayOfMonth >= day + 7)
return false;
// now we have the same day
break;
}
// the millis decides:
return (calMillis < millis);
}
/**
* Generates the hashCode for the SimpleDateFormat object. It is
* the rawOffset, possibly, if useDaylightSavings is true, xored
* with startYear, startMonth, startDayOfWeekInMonth, ..., endTime.
*/
public synchronized int hashCode()
{
return rawOffset
^ (useDaylight
? startMonth ^ startDay ^ startDayOfWeek ^ startTime ^ endMonth
^ endDay ^ endDayOfWeek ^ endTime : 0);
}
/**
* Returns a string representation of this SimpleTimeZone object.
* @return a string representation of this SimpleTimeZone object.
*/
public String toString()
{
// the test for useDaylight is an incompatibility to jdk1.2, but
// I think this shouldn't hurt.
return getClass().getName() + "[" + "id=" + getID() + ",offset="
+ rawOffset + ",dstSavings=" + dstSavings + ",useDaylight="
+ useDaylight
+ (useDaylight
? ",startYear=" + startYear + ",startMode=" + startMode
+ ",startMonth=" + startMonth + ",startDay=" + startDay
+ ",startDayOfWeek=" + startDayOfWeek + ",startTime="
+ startTime + ",startTimeMode=" + startTimeMode + ",endMode="
+ endMode + ",endMonth=" + endMonth + ",endDay=" + endDay
+ ",endDayOfWeek=" + endDayOfWeek + ",endTime=" + endTime
+ ",endTimeMode=" + endTimeMode : "") + "]";
}
}