From ccc1b7f56076eba000a016993d35156971ed8ac6 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Wed, 19 Mar 2025 02:48:22 -0400 Subject: [PATCH] strftime: ISO-8601 leap weeks --- src/header/time/strftime.rs | 35 ++++++++++++ .../bins_dynamic/time/strftime.stdout | 5 ++ .../expected/bins_static/time/strftime.stdout | 5 ++ tests/time/strftime.c | 53 +++++++++++++++++-- 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/src/header/time/strftime.rs b/src/header/time/strftime.rs index 8bce391ac9..d7ab2cce55 100644 --- a/src/header/time/strftime.rs +++ b/src/header/time/strftime.rs @@ -208,6 +208,9 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const // Sunday-based week of year: %U b'U' => w!("{}", ((*t).tm_yday + 7 - (*t).tm_wday) / 7), + // ISO-8601 week of year + b'V' => w!("{}", week_of_year(unsafe { &*t })), + // Weekday (0-6, Sunday=0): %w b'w' => w!("{}", (*t).tm_wday), @@ -246,3 +249,35 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const } cw.written } + +/// Calculate number of weeks in a year as defined by ISO 8601 +/// +/// ## Source +/// https://en.wikipedia.org/wiki/ISO_week_date +fn weeks_per_year(year: c_int) -> c_int { + let year = year as f64; + // TODO: This function can be made const on newer Rust compilers but this line prevents it on + // our version in CI + let p_y = (year + (year / 4.) - (year / 100.) + (year / 400.)) as c_int % 7; + if p_y == 4 { + 53 + } else { + 52 + } +} + +/// Calculate the week of the year accounting for leap weeks (ISO 8601) +/// +/// ## Source +/// https://en.wikipedia.org/wiki/ISO_week_date +fn week_of_year(time: &tm) -> c_int { + let week = (10 + time.tm_yday - time.tm_wday) / 7; + + if week <= 1 { + weeks_per_year(time.tm_year - 1) + } else if week > weeks_per_year(time.tm_year) { + 1 + } else { + week + } +} diff --git a/tests/expected/bins_dynamic/time/strftime.stdout b/tests/expected/bins_dynamic/time/strftime.stdout index 1317c413d0..83638a54bb 100644 --- a/tests/expected/bins_dynamic/time/strftime.stdout +++ b/tests/expected/bins_dynamic/time/strftime.stdout @@ -7,3 +7,8 @@ 6: 197 28 28: Tue Jul 17 15:00:00 UTC 2018 0: Tue Aug 07 19:17:11 UTC 2018Tue Aug 07 19:17:11 U +53 +52 +52 +1 +53 diff --git a/tests/expected/bins_static/time/strftime.stdout b/tests/expected/bins_static/time/strftime.stdout index 1317c413d0..83638a54bb 100644 --- a/tests/expected/bins_static/time/strftime.stdout +++ b/tests/expected/bins_static/time/strftime.stdout @@ -7,3 +7,8 @@ 6: 197 28 28: Tue Jul 17 15:00:00 UTC 2018 0: Tue Aug 07 19:17:11 UTC 2018Tue Aug 07 19:17:11 U +53 +52 +52 +1 +53 diff --git a/tests/time/strftime.c b/tests/time/strftime.c index c9299e0b21..898fcfdebf 100644 --- a/tests/time/strftime.c +++ b/tests/time/strftime.c @@ -4,11 +4,53 @@ #include "test_helpers.h" +#define PRINT_BUFSZ 50 +#define V_BUFSZ 3 + void print(time_t timestamp, char* fmt) { - char* out = malloc(50); - size_t n = strftime(out, 50, fmt, localtime(×tamp)); + char out[PRINT_BUFSZ] = {0}; + size_t n = strftime(out, PRINT_BUFSZ, fmt, localtime(×tamp)); printf("%zu: %s\n", n, out); - free(out); +} + +void test_v(void) { + char buf[V_BUFSZ] = {0}; + struct tm time = {0}; + + // Example dates copied from Wikipedia + // Saturday 2005-01-01 + time.tm_yday = 0; + time.tm_wday = 6; + time.tm_year = 2005; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); + + // Saturday 2005-12-31 + time.tm_yday = 365; + time.tm_wday = 6; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); + + // Sunday 2006-01-01 + time.tm_yday = 0; + time.tm_wday = 0; + time.tm_year = 2006; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); + + // Sunday 2008-12-28 + time.tm_yday = 362; + time.tm_wday = 0; + time.tm_year = 2008; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); + + // Friday 2010-01-01 + time.tm_yday = 0; + time.tm_wday = 5; + time.tm_year = 2010; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); } int main(void) { @@ -21,4 +63,9 @@ int main(void) { print(1531839600, "%j %U"); print(1531839600, "%+"); print(1533669431, "%+%+%+%+%+"); // will overflow 50 characters + + // ISO-8601 tests + test_v(); + + return EXIT_SUCCESS; }