// The day might have changed, which could happen if
// the daylight saving time transition brings it to
// the next day, although it's very unlikely. But we
// have to make sure not to change the larger fields.
CalendarDate d = calsys.getCalendarDate(time, getZone());
if (internalGet(DAY_OF_MONTH) != d.getDayOfMonth()) {
d.setDate(internalGet(YEAR),
internalGet(MONTH) + 1,
internalGet(DAY_OF_MONTH));
if (field == HOUR) {
assert (internalGet(AM_PM) == PM);
d.addHours(+12); // restore PM
}
time = calsys.getTime(d);
}
int hourOfDay = d.getHours();
internalSet(field, hourOfDay % unit);
if (field == HOUR) {
internalSet(HOUR_OF_DAY, hourOfDay);
} else {
internalSet(AM_PM, hourOfDay / 12);
internalSet(HOUR, hourOfDay % 12);
}
// Time zone offset and/or daylight saving might have changed.
int zoneOffset = d.getZoneOffset();
int saving = d.getDaylightSaving();
internalSet(ZONE_OFFSET, zoneOffset - saving);
internalSet(DST_OFFSET, saving);
return;
}
case MONTH:
// Rolling the month involves both pinning the final value to [0, 11]
// and adjusting the DAY_OF_MONTH if necessary. We only adjust the
// DAY_OF_MONTH if, after updating the MONTH field, it is illegal.
// E.g., <jan31>.roll(MONTH, 1) -> <feb28> or <feb29>.
{
if (!isCutoverYear(cdate.getNormalizedYear())) {
int mon = (internalGet(MONTH) + amount) % 12;
if (mon < 0) {
mon += 12;
}
set(MONTH, mon);
// Keep the day of month in the range. We don't want to spill over
// into the next month; e.g., we don't want jan31 + 1 mo -> feb31 ->
// mar3.
int monthLen = monthLength(mon);
if (internalGet(DAY_OF_MONTH) > monthLen) {
set(DAY_OF_MONTH, monthLen);
}
} else {
// We need to take care of different lengths in
// year and month due to the cutover.
int yearLength = getActualMaximum(MONTH) + 1;
int mon = (internalGet(MONTH) + amount) % yearLength;
if (mon < 0) {
mon += yearLength;
}
set(MONTH, mon);
int monthLen = getActualMaximum(DAY_OF_MONTH);
if (internalGet(DAY_OF_MONTH) > monthLen) {
set(DAY_OF_MONTH, monthLen);
}
}
return;
}
case WEEK_OF_YEAR:
{
int y = cdate.getNormalizedYear();
max = getActualMaximum(WEEK_OF_YEAR);
set(DAY_OF_WEEK, internalGet(DAY_OF_WEEK));
int woy = internalGet(WEEK_OF_YEAR);
int value = woy + amount;
if (!isCutoverYear(y)) {
// If the new value is in between min and max
// (exclusive), then we can use the value.
if (value > min && value < max) {
set(WEEK_OF_YEAR, value);
return;
}
long fd = getCurrentFixedDate();
// Make sure that the min week has the current DAY_OF_WEEK
long day1 = fd - (7 * (woy - min));
if (calsys.getYearFromFixedDate(day1) != y) {
min++;
}
// Make sure the same thing for the max week
fd += 7 * (max - internalGet(WEEK_OF_YEAR));
if (calsys.getYearFromFixedDate(fd) != y) {
max--;
}
break;
}
// Handle cutover here.
long fd = getCurrentFixedDate();
BaseCalendar cal;
if (gregorianCutoverYear == gregorianCutoverYearJulian) {
cal = getCutoverCalendarSystem();
} else if (y == gregorianCutoverYear) {
cal = gcal;
} else {
cal = getJulianCalendarSystem();
}
long day1 = fd - (7 * (woy - min));
// Make sure that the min week has the current DAY_OF_WEEK
if (cal.getYearFromFixedDate(day1) != y) {
min++;
}
// Make sure the same thing for the max week
fd += 7 * (max - woy);
cal = (fd >= gregorianCutoverDate) ? gcal : getJulianCalendarSystem();
if (cal.getYearFromFixedDate(fd) != y) {
max--;
}
// value: the new WEEK_OF_YEAR which must be converted
// to month and day of month.
value = getRolledValue(woy, amount, min, max) - 1;
BaseCalendar.Date d = getCalendarDate(day1 + value * 7);
set(MONTH, d.getMonth() - 1);
set(DAY_OF_MONTH, d.getDayOfMonth());
return;
}
case WEEK_OF_MONTH:
{
boolean isCutoverYear = isCutoverYear(cdate.getNormalizedYear());
// dow: relative day of week from first day of week
int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek();
if (dow < 0) {
dow += 7;
}
long fd = getCurrentFixedDate();
long month1; // fixed date of the first day (usually 1) of the month
int monthLength; // actual month length
if (isCutoverYear) {
month1 = getFixedDateMonth1(cdate, fd);
monthLength = actualMonthLength();
} else {
month1 = fd - internalGet(DAY_OF_MONTH) + 1;
monthLength = calsys.getMonthLength(cdate);
}
// the first day of week of the month.
long monthDay1st = calsys.getDayOfWeekDateOnOrBefore(month1 + 6,
getFirstDayOfWeek());
// if the week has enough days to form a week, the
// week starts from the previous month.
if ((int)(monthDay1st - month1) >= getMinimalDaysInFirstWeek()) {
monthDay1st -= 7;
}
max = getActualMaximum(field);
// value: the new WEEK_OF_MONTH value
int value = getRolledValue(internalGet(field), amount, 1, max) - 1;
// nfd: fixed date of the rolled date
long nfd = monthDay1st + value * 7 + dow;
// Unlike WEEK_OF_YEAR, we need to change day of week if the
// nfd is out of the month.
if (nfd < month1) {
nfd = month1;
} else if (nfd >= (month1 + monthLength)) {
nfd = month1 + monthLength - 1;
}
int dayOfMonth;
if (isCutoverYear) {
// If we are in the cutover year, convert nfd to
// its calendar date and use dayOfMonth.
BaseCalendar.Date d = getCalendarDate(nfd);
dayOfMonth = d.getDayOfMonth();
} else {
dayOfMonth = (int)(nfd - month1) + 1;
}
set(DAY_OF_MONTH, dayOfMonth);
return;
}
case DAY_OF_MONTH:
{
if (!isCutoverYear(cdate.getNormalizedYear())) {
max = calsys.getMonthLength(cdate);
break;
}
// Cutover year handling
long fd = getCurrentFixedDate();
long month1 = getFixedDateMonth1(cdate, fd);
// It may not be a regular month. Convert the date and range to
// the relative values, perform the roll, and
// convert the result back to the rolled date.
int value = getRolledValue((int)(fd - month1), amount, 0, actualMonthLength() - 1);
BaseCalendar.Date d = getCalendarDate(month1 + value);
assert d.getMonth()-1 == internalGet(MONTH);
set(DAY_OF_MONTH, d.getDayOfMonth());
return;
}
case DAY_OF_YEAR:
{
max = getActualMaximum(field);
if (!isCutoverYear(cdate.getNormalizedYear())) {
break;
}
// Handle cutover here.
long fd = getCurrentFixedDate();
long jan1 = fd - internalGet(DAY_OF_YEAR) + 1;
int value = getRolledValue((int)(fd - jan1) + 1, amount, min, max);
BaseCalendar.Date d = getCalendarDate(jan1 + value - 1);
set(MONTH, d.getMonth() - 1);
set(DAY_OF_MONTH, d.getDayOfMonth());
return;
}
case DAY_OF_WEEK:
{
if (!isCutoverYear(cdate.getNormalizedYear())) {
// If the week of year is in the same year, we can
// just change DAY_OF_WEEK.
int weekOfYear = internalGet(WEEK_OF_YEAR);
if (weekOfYear > 1 && weekOfYear < 52) {
set(WEEK_OF_YEAR, weekOfYear); // update stamp[WEEK_OF_YEAR]
max = SATURDAY;
break;
}
}
// We need to handle it in a different way around year
// boundaries and in the cutover year. Note that
// changing era and year values violates the roll
// rule: not changing larger calendar fields...
amount %= 7;
if (amount == 0) {
return;
}
long fd = getCurrentFixedDate();
long dowFirst = calsys.getDayOfWeekDateOnOrBefore(fd, getFirstDayOfWeek());
fd += amount;
if (fd < dowFirst) {
fd += 7;
} else if (fd >= dowFirst + 7) {
fd -= 7;
}
BaseCalendar.Date d = getCalendarDate(fd);
set(ERA, (d.getNormalizedYear() <= 0 ? BCE : CE));
set(d.getYear(), d.getMonth() - 1, d.getDayOfMonth());
return;
}
case DAY_OF_WEEK_IN_MONTH:
{
min = 1; // after normalized, min should be 1.
if (!isCutoverYear(cdate.getNormalizedYear())) {
int dom = internalGet(DAY_OF_MONTH);
int monthLength = calsys.getMonthLength(cdate);
int lastDays = monthLength % 7;
max = monthLength / 7;
int x = (dom - 1) % 7;
if (x < lastDays) {
max++;
}
set(DAY_OF_WEEK, internalGet(DAY_OF_WEEK));
break;
}
// Cutover year handling
long fd = getCurrentFixedDate();
long month1 = getFixedDateMonth1(cdate, fd);
int monthLength = actualMonthLength();
int lastDays = monthLength % 7;
max = monthLength / 7;
int x = (int)(fd - month1) % 7;
if (x < lastDays) {
max++;
}
int value = getRolledValue(internalGet(field), amount, min, max) - 1;
fd = month1 + value * 7 + x;
BaseCalendar cal = (fd >= gregorianCutoverDate) ? gcal : getJulianCalendarSystem();
BaseCalendar.Date d = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.NO_TIMEZONE);
cal.getCalendarDateFromFixedDate(d, fd);
set(DAY_OF_MONTH, d.getDayOfMonth());
return;
}
}
set(field, getRolledValue(internalGet(field), amount, min, max));