From 106f60b5c01fea14cbbb86a07940c2e3a36c881d Mon Sep 17 00:00:00 2001 From: Laurence Withers Date: Sun, 15 Oct 2006 12:10:16 +0100 Subject: [PATCH] Add from weekdate functions. --- src/libiso8601/calc.c | 93 +++++++++++++++++++++++++++++++++++++- src/libiso8601/functions.h | 2 + src/libiso8601/print.c | 20 +++++--- src/tests/calconv.c | 40 ++++++++++++++++ 4 files changed, 148 insertions(+), 7 deletions(-) diff --git a/src/libiso8601/calc.c b/src/libiso8601/calc.c index 4455bb7..6381274 100644 --- a/src/libiso8601/calc.c +++ b/src/libiso8601/calc.c @@ -127,7 +127,7 @@ void iso8601_to_cal(int* year, int* month, int* day, const struct iso8601_date* mc = iso8601_isleap(*year) ? _days_in_month_leap : _days_in_month_common; *month = 1; while(ndays >= mc->days) { - ++*month; + *month += 1; ndays -= mc->days; ++mc; } @@ -149,6 +149,97 @@ void iso8601_to_ord(int* year, int* oday, const struct iso8601_date* date) +static int _weekday_of_year(int year) +{ + int w = 6; + + /* Algorithm notes: + * - 0 = sun, 1 = mon, ..., 6 = sat + * - 0000-001 is a Saturday, day 6 + * - every year we pass gives us one additional day (364 is divisible by 7) + * - but of course every leap year we pass gives us a further day + * - so for every 400 years, we add 497 (400 common years, 97 leap years); 497 % 7 = 0 + */ + year %= 400; + if(year < 0) year += 400; // end up with between 0-399 years left + w += year; // excluding leap years, we increase by 1 day a year + w += (year + 3) / 4; // there is one leap year for every four years + w -= (year - 1) / 100; // but one less for every century over 0 + w %= 7; + + return w; +} + + + +void iso8601_to_week(int* year, int* week, int* wday, const struct iso8601_date* date) +{ + int ndays, w, has53 = 0; + div_t d; + + // perform year and weekday calculation + _to_year(year, &ndays, date); + w = _weekday_of_year(*year); + + // find out what day jan 1 was; from there, we can find the ISO week and year number + switch(w) { + case 4: // W01 starts XXXY-12-28 + w += 7; + has53 = 1; // years starting Thursday have 53 weeks + break; + + case 5: // W01 starts XXXZ-01-03 + case 6: // W01 starts XXXZ-01-02 + break; + + case 0: // W01 starts XXXZ-01-01 + case 1: // W01 starts XXXY-12-31 + case 2: // W01 starts XXXY-12-30 + w += 7; // for week calculation + break; + + case 3: // W01 starts XXXY-12-29 + w += 7; + if(iso8601_isleap(*year)) has53 = 1; // leap years starting Wednesday have 53 weeks + } + + // now we simply add the number of days elapsed since the start of the year, and % 7 + w += ndays; + d = div(w, 7); // w can never be 0 + + // do Sunday correction + if(!d.rem) { + d.rem = 7; + --d.quot; + } + + *wday = d.rem; + if(d.quot) { + if(d.quot == 53 && !has53) { + d.quot = 1; + *year += 1; + } + *week = d.quot; + } else { + *year -= 1; + switch(_weekday_of_year(*year)) { + case 3: + *week = iso8601_isleap(*year) ? 53 : 52; + break; + + case 4: + *week = 53; + break; + + default: + *week = 52; + break; + } + } +} + + + int _from_year(struct iso8601_date* date, int year) { div_t qr; diff --git a/src/libiso8601/functions.h b/src/libiso8601/functions.h index d03d219..8e7c57d 100644 --- a/src/libiso8601/functions.h +++ b/src/libiso8601/functions.h @@ -20,6 +20,8 @@ void iso8601_to_time_t(time_t* t, const struct iso8601_date* date); int iso8601_isleap(int year); void iso8601_to_cal(int* year, int* month, int* day, const struct iso8601_date* date); int iso8601_from_cal(struct iso8601_date* date, int year, int month, int day); +void iso8601_to_ord(int* year, int* oday, const struct iso8601_date* date); +void iso8601_to_week(int* year, int* week, int* wday, const struct iso8601_date* date); /* print.c */ void iso8601_print(char* str, int amt, const struct iso8601_date* date, diff --git a/src/libiso8601/print.c b/src/libiso8601/print.c index 5e91723..1c08aac 100644 --- a/src/libiso8601/print.c +++ b/src/libiso8601/print.c @@ -19,7 +19,7 @@ static const struct iso8601_details _default_details = { void iso8601_print(char* str, int amt, const struct iso8601_date* date, const struct iso8601_details* details) { - int y, m, d, ret, extended; + int y, m, d, ret = 0, extended; struct iso8601_date dttz; double frac; @@ -41,13 +41,11 @@ void iso8601_print(char* str, int amt, const struct iso8601_date* date, return; case iso8601_prec_month: - iso8601_to_cal(&y, &m, &d, &dttz); if(y < 0) snprintf(str, amt, "%05d-%02d", y, m); else snprintf(str, amt, extended ? "%04d-%02d" : "%04d%02d", y, m); return; case iso8601_prec_day: - iso8601_to_cal(&y, &m, &d, &dttz); if(y < 0) ret = snprintf(str, amt, "%05d-%02d-%02d", y, m, d); else ret = snprintf(str, amt, extended ? "%04d-%02d-%02d" : "%04d%02d%02d", y, m, d); break; @@ -58,9 +56,19 @@ void iso8601_print(char* str, int amt, const struct iso8601_date* date, else ret = snprintf(str, amt, extended ? "%04d-%03d" : "%04d%03d", y, d); break; - default: - fprintf(stderr, "<<< NOT IMPLEMENTED >>>\n"); - abort(); + case iso8601_prec_week: + iso8601_to_week(&y, &m, &d, &dttz); + extended = y < 0 || y > 9999 || details->extended; // ISO year is different + if(y < 0) snprintf(str, amt, "%05d-W%02d", y, m); + else snprintf(str, amt, extended ? "%04d-W%02d" : "%04dW%02d", y, m); + return; + + case iso8601_prec_wday: + iso8601_to_week(&y, &m, &d, &dttz); + extended = y < 0 || y > 9999 || details->extended; // ISO year is different + if(y < 0) ret = snprintf(str, amt, "%05d-W%02d-%d", y, m, d); + else ret = snprintf(str, amt, extended ? "%04d-W%02d-%d" : "%04dW%02d%d", y, m, d); + break; } if(ret < 1 || ret >= amt) return; diff --git a/src/tests/calconv.c b/src/tests/calconv.c index 30efc79..5ed360b 100644 --- a/src/tests/calconv.c +++ b/src/tests/calconv.c @@ -123,6 +123,43 @@ int try_neg_year(int year) +int try_weeks(void) +{ + // starting position + int weekcount = 52, weekday = 5, isoyear = -1; + int y, wk, wd, ret = 0; + struct iso8601_date dt; + + for(dt.day = 0; dt.day < MAX_YEAR_COUNT * 366; ++dt.day) { + iso8601_to_week(&y, &wk, &wd, &dt); + if(wd == 1) { + if(weekday != 7) goto discont; + ++weekcount; + if(wk == 1) { + if(weekcount != 53 && weekcount != 54) goto discont; + weekcount = 1; + ++isoyear; + } + } + if(wk != weekcount || y != isoyear) goto discont; + + weekday = wd; + continue; + + discont: + printf("[weekdiscont ] %04d-W%02d-%d ==> %04d-W%02d-%d (%d)\n", + isoyear, weekcount, weekday, y, wk, wd, dt.day); + isoyear = y; + weekcount = wk; + weekday = wd; + if(++ret > 20) return ret; + } + + return ret; +} + + + int main(int argc, char* argv[]) { int ret = 0, year; @@ -142,6 +179,9 @@ int main(int argc, char* argv[]) if(ret > 20) return ret; } + ret += try_weeks(); + if(ret > 20) return ret; + return ret; }