Skip to content

Commit

Permalink
ICU-22991 Clean up gregorian calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
FrankYFTang committed Jan 8, 2025
1 parent 4d6f08d commit e700e4a
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 30 deletions.
51 changes: 21 additions & 30 deletions icu4c/source/i18n/gregoimp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,49 +119,35 @@ int64_t Grego::fieldsToDay(int32_t year, int32_t month, int32_t dom) {

void Grego::dayToFields(int32_t day, int32_t& year, int8_t& month,
int8_t& dom, int8_t& dow, int16_t& doy, UErrorCode& status) {
year = dayToYear(day, doy, status);
UBool isLeap = isLeapYear(year);

if (U_FAILURE(status)) return;
// Convert from 1970 CE epoch to 1 CE epoch (Gregorian calendar)
if (uprv_add32_overflow(day, JULIAN_1970_CE - JULIAN_1_CE, &day)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}

// Convert from the day number to the multiple radix
// representation. We use 400-year, 100-year, and 4-year cycles.
// For example, the 4-year cycle has 4 years + 1 leap day; giving
// 1461 == 365*4 + 1 days.
int32_t doy32;
int32_t n400 = ClockMath::floorDivide(day, 146097, &doy32); // 400-year cycle length
int32_t n100 = ClockMath::floorDivide(doy32, 36524, &doy32); // 100-year cycle length
int32_t n4 = ClockMath::floorDivide(doy32, 1461, &doy32); // 4-year cycle length
int32_t n1 = ClockMath::floorDivide(doy32, 365, &doy32);
year = 400*n400 + 100*n100 + 4*n4 + n1;
if (n100 == 4 || n1 == 4) {
doy = 365; // Dec 31 at end of 4- or 400-year cycle
} else {
doy = doy32;
++year;
}

UBool isLeap = isLeapYear(year);

// Gregorian day zero is a Monday.
dow = (day + 1) % 7;
dow += (dow < 0) ? (UCAL_SUNDAY + 7) : UCAL_SUNDAY;

// Common Julian/Gregorian calculation
int32_t correction = 0;
int32_t march1 = isLeap ? 60 : 59; // zero-based DOY for March 1
if (doy >= march1) {
if (doy > march1) {
correction = isLeap ? 1 : 2;
}
month = (12 * (doy + correction) + 6) / 367; // zero-based month
dom = doy - DAYS_BEFORE[month + (isLeap ? 12 : 0)] + 1; // one-based DOM
doy++; // one-based doy
month = (12 * (doy - 1 + correction) + 6) / 367; // zero-based month
dom = doy - DAYS_BEFORE[month + (isLeap ? 12 : 0)]; // one-based DOM
}

int32_t Grego::dayToYear(int32_t day, UErrorCode& status) {
int16_t unusedDOY;
return dayToYear(day, unusedDOY, status);
}

int32_t Grego::dayToYear(int32_t day, int16_t& doy, UErrorCode& status) {
if (U_FAILURE(status)) return 0;
// Convert from 1970 CE epoch to 1 CE epoch (Gregorian calendar)
if (uprv_add32_overflow(day, JULIAN_1970_CE - JULIAN_1_CE, &day)) {
Expand All @@ -173,17 +159,22 @@ int32_t Grego::dayToYear(int32_t day, UErrorCode& status) {
// representation. We use 400-year, 100-year, and 4-year cycles.
// For example, the 4-year cycle has 4 years + 1 leap day; giving
// 1461 == 365*4 + 1 days.
int32_t doy;
int32_t n400 = ClockMath::floorDivide(day, 146097, &doy); // 400-year cycle length
int32_t n100 = ClockMath::floorDivide(doy, 36524, &doy); // 100-year cycle length
int32_t n4 = ClockMath::floorDivide(doy, 1461, &doy); // 4-year cycle length
int32_t n1 = ClockMath::floorDivide(doy, 365, &doy);
int32_t doy32;
int32_t n400 = ClockMath::floorDivide(day, 146097, &doy32); // 400-year cycle length
int32_t n100 = ClockMath::floorDivide(doy32, 36524, &doy32); // 100-year cycle length
int32_t n4 = ClockMath::floorDivide(doy32, 1461, &doy32); // 4-year cycle length
int32_t n1 = ClockMath::floorDivide(doy32, 365, &doy32);
int32_t year = 400*n400 + 100*n100 + 4*n4 + n1;
if (!(n100 == 4 || n1 == 4)) {
if (n100 == 4 || n1 == 4) {
doy = 365; // Dec 31 at end of 4- or 400-year cycle
} else {
doy = doy32;
++year;
}
doy++; // one-based doy
return year;
}

void Grego::dayToFields(int32_t day, int32_t& year, int8_t& month,
int8_t& dom, int8_t& dow, UErrorCode& status) {
int16_t unusedDOY;
Expand Down
8 changes: 8 additions & 0 deletions icu4c/source/i18n/gregoimp.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ class Grego {
* @return year.
*/
static int32_t dayToYear(int32_t day, UErrorCode& status);
/**
* Convert a 1970-epoch day number to proleptic Gregorian year.
* @param day 1970-epoch day
* @param doy output parameter to receive day-of-year (1-based)
* @param status error code.
* @return year.
*/
static int32_t dayToYear(int32_t day, int16_t& doy, UErrorCode& status);

/**
* Convert a 1970-epoch milliseconds to proleptic Gregorian year,
Expand Down

0 comments on commit e700e4a

Please sign in to comment.