wcsftime(3) computes %V (ISO 8601 week number) incorrectly
Serhiy Storchaka <[email protected]> Wed, 22 Jul 2026 12:41:31 +0300
| Newsgroups | gmane.os.openbsd.bugs |
|---|---|
| Message-ID | <[email protected]> |
>Synopsis: wcsftime(3) computes %V (ISO 8601 week number) incorrectly >Category: library >Environment: System : OpenBSD 7.9 Details : OpenBSD 7.9 (GENERIC.MP) #449: Wed May 6 13:17:25 MDT 2026 [email protected]:/usr/src/sys/arch/amd64/compile/GENERIC.MP Architecture: OpenBSD.amd64 Machine : amd64 >Description: wcsftime(3) returns 53 for %V whenever the ISO 8601 week belongs to a different year than tm_year. strftime(3) returns the correct value for the same struct tm. %G and %u are correct in both. Both functions contain the same XPG4-1994 override, applied after the loop that has already computed the ISO week number correctly: if ((w == 52 && t->tm_mon == TM_JANUARY) || (w == 1 && t->tm_mon == TM_DECEMBER)) w = 53; In strftime.c it is wrapped in #ifdef XPG4_1994_04_09, so it is dead code. In wcsftime.c the guard is missing, so it always runs and clobbers exactly the year-boundary cases. XPG4_1994_04_09 is not defined anywhere in the tree; it occurs only in strftime.c, in the #ifdef and #endif lines themselves. The guard was already absent when wcsftime.c was added in 2011 -- the file was adapted from strftime.c and the #ifdef/#endif lines were dropped while the code between them was kept -- so this has been wrong since the file was created. This was reported once before, in 2018, from a downstream Python bug: https://marc.info/?l=openbsd-bugs&m=153728102618747 >How-To-Repeat: Compile and run the program below. On OpenBSD 7.9: 1905-01-01 strftime 1904-52 wcsftime 1904-53 expected 1904-52 <-- WRONG 1906-12-31 strftime 1907-01 wcsftime 1907-53 expected 1907-01 <-- WRONG 2008-12-29 strftime 2009-01 wcsftime 2009-53 expected 2009-01 <-- WRONG 2009-01-01 strftime 2009-01 wcsftime 2009-01 expected 2009-01 2010-01-03 strftime 2009-53 wcsftime 2009-53 expected 2009-53 3 of 5 wrong #include <stdio.h> #include <string.h> #include <time.h> #include <wchar.h> /* %V is the ISO 8601 week number. strftime() and wcsftime() must agree. */ static int check(int year, int mon, int mday, const wchar_t *expected) { struct tm tm; char sbuf[16]; wchar_t wbuf[16]; int bad; memset(&tm, 0, sizeof tm); tm.tm_year = year - 1900; tm.tm_mon = mon - 1; tm.tm_mday = mday; tm.tm_isdst = -1; mktime(&tm); strftime(sbuf, sizeof sbuf, "%G-%V", &tm); wcsftime(wbuf, sizeof wbuf / sizeof *wbuf, L"%G-%V", &tm); bad = wcscmp(wbuf, expected) != 0; printf("%04d-%02d-%02d strftime %s wcsftime %ls expected %ls%s\n", year, mon, mday, sbuf, wbuf, expected, bad ? " <-- WRONG" : ""); return bad; } int main(void) { int bad = 0; bad += check(1905, 1, 1, L"1904-52"); bad += check(1906, 12, 31, L"1907-01"); bad += check(2008, 12, 29, L"2009-01"); bad += check(2009, 1, 1, L"2009-01"); bad += check(2010, 1, 3, L"2009-53"); printf("%d of 5 wrong\n", bad); return bad != 0; } >Fix: Restore the guard, so that wcsftime() matches strftime(). I compiled lib/libc/time/wcsftime.c with this diff applied and ran the test above against it: 0 of 5 wrong, and it agrees with strftime(3) on every input. Index: lib/libc/time/wcsftime.c =================================================================== RCS file: /cvs/src/lib/libc/time/wcsftime.c,v retrieving revision 1.7 diff -u -p -r1.7 wcsftime.c --- lib/libc/time/wcsftime.c 12 May 2019 12:49:52 -0000 1.7 +++ lib/libc/time/wcsftime.c 22 Jul 2026 09:30:00 -0000 @@ -386,9 +386,11 @@ label: DAYSPERLYEAR : DAYSPERNYEAR; } +#ifdef XPG4_1994_04_09 if ((w == 52 && t->tm_mon == TM_JANUARY) || (w == 1 && t->tm_mon == TM_DECEMBER)) w = 53; +#endif /* defined XPG4_1994_04_09 */ if (*format == 'V') pt = _conv(w, L"%02d", pt, ptlim); else if (*format == 'g') {