- case UCAL_WEEK_OF_MONTH:
- {
- // This is tricky, because during the roll we may have to shift
- // to a different day of the week. For example:
-
- // s m t w r f s
- // 1 2 3 4 5
- // 6 7 8 9 10 11 12
-
- // When rolling from the 6th or 7th back one week, we go to the
- // 1st (assuming that the first partial week counts). The same
- // thing happens at the end of the month.
-
- // The other tricky thing is that we have to figure out whether
- // the first partial week actually counts or not, based on the
- // minimal first days in the week. And we have to use the
- // correct first day of the week to delineate the week
- // boundaries.
-
- // Here's our algorithm. First, we find the real boundaries of
- // the month. Then we discard the first partial week if it
- // doesn't count in this locale. Then we fill in the ends with
- // phantom days, so that the first partial week and the last
- // partial week are full weeks. We then have a nice square
- // block of weeks. We do the usual rolling within this block,
- // as is done elsewhere in this method. If we wind up on one of
- // the phantom days that we added, we recognize this and pin to
- // the first or the last day of the month. Easy, eh?
-
- // Normalize the DAY_OF_WEEK so that 0 is the first day of the week
- // in this locale. We have dow in 0..6.
- int32_t dow = internalGet(UCAL_DAY_OF_WEEK) - getFirstDayOfWeek();
- if (dow < 0) dow += 7;
-
- // Find the day of the week (normalized for locale) for the first
- // of the month.
- int32_t fdm = (dow - internalGet(UCAL_DAY_OF_MONTH) + 1) % 7;
- if (fdm < 0) fdm += 7;
-
- // Get the first day of the first full week of the month,
- // including phantom days, if any. Figure out if the first week
- // counts or not; if it counts, then fill in phantom days. If
- // not, advance to the first real full week (skip the partial week).
- int32_t start;
- if ((7 - fdm) < getMinimalDaysInFirstWeek())
- start = 8 - fdm; // Skip the first partial week
- else
- start = 1 - fdm; // This may be zero or negative
-
- // Get the day of the week (normalized for locale) for the last
- // day of the month.
- int32_t monthLen = getActualMaximum(UCAL_DAY_OF_MONTH, status);
- int32_t ldm = (monthLen - internalGet(UCAL_DAY_OF_MONTH) + dow) % 7;
- // We know monthLen >= DAY_OF_MONTH so we skip the += 7 step here.
-
- // Get the limit day for the blocked-off rectangular month; that
- // is, the day which is one past the last day of the month,
- // after the month has already been filled in with phantom days
- // to fill out the last week. This day has a normalized DOW of 0.
- int32_t limit = monthLen + 7 - ldm;
-
- // Now roll between start and (limit - 1).
- int32_t gap = limit - start;
- int32_t day_of_month = (internalGet(UCAL_DAY_OF_MONTH) + amount*7 -
- start) % gap;
- if (day_of_month < 0) day_of_month += gap;
- day_of_month += start;
-
- // Finally, pin to the real start and end of the month.
- if (day_of_month < 1) day_of_month = 1;
- if (day_of_month > monthLen) day_of_month = monthLen;
-
- // Set the DAY_OF_MONTH. We rely on the fact that this field
- // takes precedence over everything else (since all other fields
- // are also set at this point). If this fact changes (if the
- // disambiguation algorithm changes) then we will have to unset
- // the appropriate fields here so that DAY_OF_MONTH is attended
- // to.
- set(UCAL_DAY_OF_MONTH, day_of_month);
- return;
- }
- case UCAL_WEEK_OF_YEAR:
- {
- // This follows the outline of WEEK_OF_MONTH, except it applies
- // to the whole year. Please see the comment for WEEK_OF_MONTH
- // for general notes.
-
- // Normalize the DAY_OF_WEEK so that 0 is the first day of the week
- // in this locale. We have dow in 0..6.
- int32_t dow = internalGet(UCAL_DAY_OF_WEEK) - getFirstDayOfWeek();
- if (dow < 0) dow += 7;
-
- // Find the day of the week (normalized for locale) for the first
- // of the year.
- int32_t fdy = (dow - internalGet(UCAL_DAY_OF_YEAR) + 1) % 7;
- if (fdy < 0) fdy += 7;
-
- // Get the first day of the first full week of the year,
- // including phantom days, if any. Figure out if the first week
- // counts or not; if it counts, then fill in phantom days. If
- // not, advance to the first real full week (skip the partial week).
- int32_t start;
- if ((7 - fdy) < getMinimalDaysInFirstWeek())
- start = 8 - fdy; // Skip the first partial week
- else
- start = 1 - fdy; // This may be zero or negative
-
- // Get the day of the week (normalized for locale) for the last
- // day of the year.
- int32_t yearLen = getActualMaximum(UCAL_DAY_OF_YEAR,status);
- int32_t ldy = (yearLen - internalGet(UCAL_DAY_OF_YEAR) + dow) % 7;
- // We know yearLen >= DAY_OF_YEAR so we skip the += 7 step here.
-
- // Get the limit day for the blocked-off rectangular year; that
- // is, the day which is one past the last day of the year,
- // after the year has already been filled in with phantom days
- // to fill out the last week. This day has a normalized DOW of 0.
- int32_t limit = yearLen + 7 - ldy;
-
- // Now roll between start and (limit - 1).
- int32_t gap = limit - start;
- int32_t day_of_year = (internalGet(UCAL_DAY_OF_YEAR) + amount*7 -
- start) % gap;
- if (day_of_year < 0) day_of_year += gap;
- day_of_year += start;
-
- // Finally, pin to the real start and end of the month.
- if (day_of_year < 1) day_of_year = 1;
- if (day_of_year > yearLen) day_of_year = yearLen;
-
- // Make sure that the year and day of year are attended to by
- // clearing other fields which would normally take precedence.
- // If the disambiguation algorithm is changed, this section will
- // have to be updated as well.
- set(UCAL_DAY_OF_YEAR, day_of_year);
- clear(UCAL_MONTH);
- return;
- }
- case UCAL_DAY_OF_YEAR:
- {
- // Roll the day of year using millis. Compute the millis for
- // the start of the year, and get the length of the year.
- double delta = amount * kOneDay; // Scale up from days to millis
- double min2 = internalGet(UCAL_DAY_OF_YEAR)-1;
- min2 *= kOneDay;
- min2 = internalGetTime() - min2;
-
- // double min2 = internalGetTime() - (internalGet(UCAL_DAY_OF_YEAR) - 1.0) * kOneDay;
- double newtime;
-
- double yearLength = getActualMaximum(UCAL_DAY_OF_YEAR,status);
- double oneYear = yearLength;
- oneYear *= kOneDay;
- newtime = uprv_fmod((internalGetTime() + delta - min2), oneYear);
- if (newtime < 0) newtime += oneYear;
- setTimeInMillis(newtime + min2, status);
- return;
- }
- case UCAL_DAY_OF_WEEK:
- case UCAL_DOW_LOCAL:
- {
- // Roll the day of week using millis. Compute the millis for
- // the start of the week, using the first day of week setting.
- // Restrict the millis to [start, start+7days).
- double delta = amount * kOneDay; // Scale up from days to millis
- // Compute the number of days before the current day in this
- // week. This will be a value 0..6.
- int32_t leadDays = internalGet(field);
- leadDays -= (field == UCAL_DAY_OF_WEEK) ? getFirstDayOfWeek() : 1;
- if (leadDays < 0) leadDays += 7;
- double min2 = internalGetTime() - leadDays * kOneDay;
- double newtime = uprv_fmod((internalGetTime() + delta - min2), kOneWeek);
- if (newtime < 0) newtime += kOneWeek;
- setTimeInMillis(newtime + min2, status);
- return;
- }
- case UCAL_DAY_OF_WEEK_IN_MONTH:
- {
- // Roll the day of week in the month using millis. Determine
- // the first day of the week in the month, and then the last,
- // and then roll within that range.
- double delta = amount * kOneWeek; // Scale up from weeks to millis
- // Find the number of same days of the week before this one
- // in this month.
- int32_t preWeeks = (internalGet(UCAL_DAY_OF_MONTH) - 1) / 7;
- // Find the number of same days of the week after this one
- // in this month.
- int32_t postWeeks = (getActualMaximum(UCAL_DAY_OF_MONTH,status) -
- internalGet(UCAL_DAY_OF_MONTH)) / 7;
- // From these compute the min and gap millis for rolling.
- double min2 = internalGetTime() - preWeeks * kOneWeek;
- double gap2 = kOneWeek * (preWeeks + postWeeks + 1); // Must add 1!
- // Roll within this range
- double newtime = uprv_fmod((internalGetTime() + delta - min2), gap2);
- if (newtime < 0) newtime += gap2;
- setTimeInMillis(newtime + min2, status);
- return;
- }
- case UCAL_JULIAN_DAY:
- set(field, internalGet(field) + amount);
- return;
- default:
- // Other fields cannot be rolled by this method
+ // Keep the day of month in range. We don't want to spill over
+ // into the next month; e.g., we don't want jan31 + 1 mo -> feb31 ->
+ // mar3.
+ pinField(UCAL_DAY_OF_MONTH,status);
+ return;
+ }
+
+ case UCAL_YEAR:
+ case UCAL_YEAR_WOY:
+ {
+ // * If era==0 and years go backwards in time, change sign of amount.
+ // * Until we have new API per #9393, we temporarily hardcode knowledge of
+ // which calendars have era 0 years that go backwards.
+ UBool era0WithYearsThatGoBackwards = FALSE;
+ int32_t era = get(UCAL_ERA, status);
+ if (era == 0) {
+ const char * calType = getType();
+ if ( uprv_strcmp(calType,"gregorian")==0 || uprv_strcmp(calType,"roc")==0 || uprv_strcmp(calType,"coptic")==0 ) {
+ amount = -amount;
+ era0WithYearsThatGoBackwards = TRUE;
+ }
+ }
+ int32_t newYear = internalGet(field) + amount;
+ if (era > 0 || newYear >= 1) {
+ int32_t maxYear = getActualMaximum(field, status);
+ if (maxYear < 32768) {
+ // this era has real bounds, roll should wrap years
+ if (newYear < 1) {
+ newYear = maxYear - ((-newYear) % maxYear);
+ } else if (newYear > maxYear) {
+ newYear = ((newYear - 1) % maxYear) + 1;
+ }
+ // else era is unbounded, just pin low year instead of wrapping
+ } else if (newYear < 1) {
+ newYear = 1;
+ }
+ // else we are in era 0 with newYear < 1;
+ // calendars with years that go backwards must pin the year value at 0,
+ // other calendars can have years < 0 in era 0
+ } else if (era0WithYearsThatGoBackwards) {
+ newYear = 1;
+ }
+ set(field, newYear);
+ pinField(UCAL_MONTH,status);
+ pinField(UCAL_DAY_OF_MONTH,status);
+ return;
+ }
+
+ case UCAL_EXTENDED_YEAR:
+ // Rolling the year can involve pinning the DAY_OF_MONTH.
+ set(field, internalGet(field) + amount);
+ pinField(UCAL_MONTH,status);
+ pinField(UCAL_DAY_OF_MONTH,status);
+ return;
+
+ case UCAL_WEEK_OF_MONTH:
+ {
+ // This is tricky, because during the roll we may have to shift
+ // to a different day of the week. For example:
+
+ // s m t w r f s
+ // 1 2 3 4 5
+ // 6 7 8 9 10 11 12
+
+ // When rolling from the 6th or 7th back one week, we go to the
+ // 1st (assuming that the first partial week counts). The same
+ // thing happens at the end of the month.
+
+ // The other tricky thing is that we have to figure out whether
+ // the first partial week actually counts or not, based on the
+ // minimal first days in the week. And we have to use the
+ // correct first day of the week to delineate the week
+ // boundaries.
+
+ // Here's our algorithm. First, we find the real boundaries of
+ // the month. Then we discard the first partial week if it
+ // doesn't count in this locale. Then we fill in the ends with
+ // phantom days, so that the first partial week and the last
+ // partial week are full weeks. We then have a nice square
+ // block of weeks. We do the usual rolling within this block,
+ // as is done elsewhere in this method. If we wind up on one of
+ // the phantom days that we added, we recognize this and pin to
+ // the first or the last day of the month. Easy, eh?
+
+ // Normalize the DAY_OF_WEEK so that 0 is the first day of the week
+ // in this locale. We have dow in 0..6.
+ int32_t dow = internalGet(UCAL_DAY_OF_WEEK) - getFirstDayOfWeek();
+ if (dow < 0) dow += 7;
+
+ // Find the day of the week (normalized for locale) for the first
+ // of the month.
+ int32_t fdm = (dow - internalGet(UCAL_DAY_OF_MONTH) + 1) % 7;
+ if (fdm < 0) fdm += 7;
+
+ // Get the first day of the first full week of the month,
+ // including phantom days, if any. Figure out if the first week
+ // counts or not; if it counts, then fill in phantom days. If
+ // not, advance to the first real full week (skip the partial week).
+ int32_t start;
+ if ((7 - fdm) < getMinimalDaysInFirstWeek())
+ start = 8 - fdm; // Skip the first partial week
+ else
+ start = 1 - fdm; // This may be zero or negative
+
+ // Get the day of the week (normalized for locale) for the last
+ // day of the month.
+ int32_t monthLen = getActualMaximum(UCAL_DAY_OF_MONTH, status);
+ int32_t ldm = (monthLen - internalGet(UCAL_DAY_OF_MONTH) + dow) % 7;
+ // We know monthLen >= DAY_OF_MONTH so we skip the += 7 step here.
+
+ // Get the limit day for the blocked-off rectangular month; that
+ // is, the day which is one past the last day of the month,
+ // after the month has already been filled in with phantom days
+ // to fill out the last week. This day has a normalized DOW of 0.
+ int32_t limit = monthLen + 7 - ldm;
+
+ // Now roll between start and (limit - 1).
+ int32_t gap = limit - start;
+ int32_t day_of_month = (internalGet(UCAL_DAY_OF_MONTH) + amount*7 -
+ start) % gap;
+ if (day_of_month < 0) day_of_month += gap;
+ day_of_month += start;
+
+ // Finally, pin to the real start and end of the month.
+ if (day_of_month < 1) day_of_month = 1;
+ if (day_of_month > monthLen) day_of_month = monthLen;
+
+ // Set the DAY_OF_MONTH. We rely on the fact that this field
+ // takes precedence over everything else (since all other fields
+ // are also set at this point). If this fact changes (if the
+ // disambiguation algorithm changes) then we will have to unset
+ // the appropriate fields here so that DAY_OF_MONTH is attended
+ // to.
+ set(UCAL_DAY_OF_MONTH, day_of_month);
+ return;
+ }
+ case UCAL_WEEK_OF_YEAR:
+ {
+ // This follows the outline of WEEK_OF_MONTH, except it applies
+ // to the whole year. Please see the comment for WEEK_OF_MONTH
+ // for general notes.
+
+ // Normalize the DAY_OF_WEEK so that 0 is the first day of the week
+ // in this locale. We have dow in 0..6.
+ int32_t dow = internalGet(UCAL_DAY_OF_WEEK) - getFirstDayOfWeek();
+ if (dow < 0) dow += 7;
+
+ // Find the day of the week (normalized for locale) for the first
+ // of the year.
+ int32_t fdy = (dow - internalGet(UCAL_DAY_OF_YEAR) + 1) % 7;
+ if (fdy < 0) fdy += 7;
+
+ // Get the first day of the first full week of the year,
+ // including phantom days, if any. Figure out if the first week
+ // counts or not; if it counts, then fill in phantom days. If
+ // not, advance to the first real full week (skip the partial week).
+ int32_t start;
+ if ((7 - fdy) < getMinimalDaysInFirstWeek())
+ start = 8 - fdy; // Skip the first partial week
+ else
+ start = 1 - fdy; // This may be zero or negative
+
+ // Get the day of the week (normalized for locale) for the last
+ // day of the year.
+ int32_t yearLen = getActualMaximum(UCAL_DAY_OF_YEAR,status);
+ int32_t ldy = (yearLen - internalGet(UCAL_DAY_OF_YEAR) + dow) % 7;
+ // We know yearLen >= DAY_OF_YEAR so we skip the += 7 step here.
+
+ // Get the limit day for the blocked-off rectangular year; that
+ // is, the day which is one past the last day of the year,
+ // after the year has already been filled in with phantom days
+ // to fill out the last week. This day has a normalized DOW of 0.
+ int32_t limit = yearLen + 7 - ldy;
+
+ // Now roll between start and (limit - 1).
+ int32_t gap = limit - start;
+ int32_t day_of_year = (internalGet(UCAL_DAY_OF_YEAR) + amount*7 -
+ start) % gap;
+ if (day_of_year < 0) day_of_year += gap;
+ day_of_year += start;
+
+ // Finally, pin to the real start and end of the month.
+ if (day_of_year < 1) day_of_year = 1;
+ if (day_of_year > yearLen) day_of_year = yearLen;
+
+ // Make sure that the year and day of year are attended to by
+ // clearing other fields which would normally take precedence.
+ // If the disambiguation algorithm is changed, this section will
+ // have to be updated as well.
+ set(UCAL_DAY_OF_YEAR, day_of_year);
+ clear(UCAL_MONTH);
+ return;
+ }
+ case UCAL_DAY_OF_YEAR:
+ {
+ // Roll the day of year using millis. Compute the millis for
+ // the start of the year, and get the length of the year.
+ double delta = amount * kOneDay; // Scale up from days to millis
+ double min2 = internalGet(UCAL_DAY_OF_YEAR)-1;
+ min2 *= kOneDay;
+ min2 = internalGetTime() - min2;
+
+ // double min2 = internalGetTime() - (internalGet(UCAL_DAY_OF_YEAR) - 1.0) * kOneDay;
+ double newtime;
+
+ double yearLength = getActualMaximum(UCAL_DAY_OF_YEAR,status);
+ double oneYear = yearLength;
+ oneYear *= kOneDay;
+ newtime = uprv_fmod((internalGetTime() + delta - min2), oneYear);
+ if (newtime < 0) newtime += oneYear;
+ setTimeInMillis(newtime + min2, status);
+ return;
+ }
+ case UCAL_DAY_OF_WEEK:
+ case UCAL_DOW_LOCAL:
+ {
+ // Roll the day of week using millis. Compute the millis for
+ // the start of the week, using the first day of week setting.
+ // Restrict the millis to [start, start+7days).
+ double delta = amount * kOneDay; // Scale up from days to millis
+ // Compute the number of days before the current day in this
+ // week. This will be a value 0..6.
+ int32_t leadDays = internalGet(field);
+ leadDays -= (field == UCAL_DAY_OF_WEEK) ? getFirstDayOfWeek() : 1;
+ if (leadDays < 0) leadDays += 7;
+ double min2 = internalGetTime() - leadDays * kOneDay;
+ double newtime = uprv_fmod((internalGetTime() + delta - min2), kOneWeek);
+ if (newtime < 0) newtime += kOneWeek;
+ setTimeInMillis(newtime + min2, status);
+ return;
+ }
+ case UCAL_DAY_OF_WEEK_IN_MONTH:
+ {
+ // Roll the day of week in the month using millis. Determine
+ // the first day of the week in the month, and then the last,
+ // and then roll within that range.
+ double delta = amount * kOneWeek; // Scale up from weeks to millis
+ // Find the number of same days of the week before this one
+ // in this month.
+ int32_t preWeeks = (internalGet(UCAL_DAY_OF_MONTH) - 1) / 7;
+ // Find the number of same days of the week after this one
+ // in this month.
+ int32_t postWeeks = (getActualMaximum(UCAL_DAY_OF_MONTH,status) -
+ internalGet(UCAL_DAY_OF_MONTH)) / 7;
+ // From these compute the min and gap millis for rolling.
+ double min2 = internalGetTime() - preWeeks * kOneWeek;
+ double gap2 = kOneWeek * (preWeeks + postWeeks + 1); // Must add 1!
+ // Roll within this range
+ double newtime = uprv_fmod((internalGetTime() + delta - min2), gap2);
+ if (newtime < 0) newtime += gap2;
+ setTimeInMillis(newtime + min2, status);
+ return;
+ }
+ case UCAL_JULIAN_DAY:
+ set(field, internalGet(field) + amount);
+ return;
+ default:
+ // Other fields cannot be rolled by this method