From 582b960e0b8e79fbade87e8bc519e62617395137 Mon Sep 17 00:00:00 2001 From: Laurence Withers Date: Sun, 15 Oct 2006 19:41:04 +0100 Subject: [PATCH] Add from-week-date conversion functions --- src/libiso8601/calc.c | 36 ++++++++++++++++++++++++++++++------ src/tests/calconv.c | 12 ++++++++++-- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/libiso8601/calc.c b/src/libiso8601/calc.c index 3c0ac11..9db1d9e 100644 --- a/src/libiso8601/calc.c +++ b/src/libiso8601/calc.c @@ -16,6 +16,8 @@ * of the next day). */ +#define DAYS_IN_400_YEARS (146097) + int iso8601_isleap(int year) @@ -74,14 +76,14 @@ static void _to_year(int* year, int* days_left, const struct iso8601_date* date) div_t qr; int ndays = date->day; - // Each 400 years have 97 leap days, giving 365*400+97 = 146097 days in 400 years - qr = div(ndays, 146097); + // Each 400 years have 97 leap days, giving 365*400+97 = DAYS_IN_400_YEARS days in 400 years + qr = div(ndays, DAYS_IN_400_YEARS); *year = qr.quot * 400; ndays = qr.rem; // ensure that we always end up with between 0 and 146096 days remaining if(ndays < 0) { - ndays += 146097; + ndays += DAYS_IN_400_YEARS; *year -= 400; } @@ -250,14 +252,14 @@ int _from_year(struct iso8601_date* date, int year) return -1; } - // Each 400 years have 97 leap days, giving 365*400+97 = 146097 days in 400 years + // Each 400 years have 97 leap days, giving 365*400+97 = DAYS_IN_400_YEARS days in 400 years qr = div(year, 400); - date->day = qr.quot * 146097; + date->day = qr.quot * DAYS_IN_400_YEARS; year = qr.rem; // ensure we have between 0 and 399 years if(year < 0) { - date->day -= 146097; + date->day -= DAYS_IN_400_YEARS; year += 400; } @@ -316,6 +318,28 @@ int iso8601_from_ord(struct iso8601_date* date, int year, int oday) int iso8601_from_week(struct iso8601_date* date, int isoyear, int week, int wday) { + int day1off; + + _from_year(date, isoyear); + + // 400-year cycle; ensure we're between 0-400 years + isoyear %= 400; + if(isoyear < 0) isoyear += 400; + + /* Algorithm notes: + * We now compute the offset between the start day of the ISO year and the start day of the + * real year. Year 0000 starts on a Saturday, meaning the ISO year 0000 starts on 0000-003 + * (offset of +2 days). Each year reduces the offset by one day, and each leap year reduces it + * by a further day; the offset wraps from -3 to +3. + */ + day1off = 2 - isoyear; // reduce offset by 1 for each year + day1off += (isoyear - 1) / 100; // cancel out 1 leap year for each century past the first + day1off -= (isoyear + 3) / 4; // 1 leap year every 4 years + day1off %= 7; + if(day1off < -3) day1off += 7; + + // now simply add in the day offset and days/weeks elapsed + date->day += day1off + (week - 1) * 7 + wday - 1; } diff --git a/src/tests/calconv.c b/src/tests/calconv.c index 5ed360b..04e0d22 100644 --- a/src/tests/calconv.c +++ b/src/tests/calconv.c @@ -128,7 +128,7 @@ int try_weeks(void) // starting position int weekcount = 52, weekday = 5, isoyear = -1; int y, wk, wd, ret = 0; - struct iso8601_date dt; + struct iso8601_date dt, dt2; for(dt.day = 0; dt.day < MAX_YEAR_COUNT * 366; ++dt.day) { iso8601_to_week(&y, &wk, &wd, &dt); @@ -141,9 +141,17 @@ int try_weeks(void) ++isoyear; } } - if(wk != weekcount || y != isoyear) goto discont; + if(wk != weekcount || y != isoyear) goto discont; weekday = wd; + + iso8601_from_week(&dt2, isoyear, weekcount, weekday); + if(dt2.day != dt.day) { + printf("[weekmismatch ] %04d-W%02d-%d: should be %d, got %d\n", + isoyear, weekcount, weekday, dt.day, dt2.day); + if(++ret > 20) return ret; + } + continue; discont: