Subject: bin/6961: strftime(3) gives wrong week number (patch supplied)
To: None <gnats-bugs@gnats.netbsd.org>
From: Wolfgang Helbig <helbig@Informatik.BA-Stuttgart.DE>
List: netbsd-bugs
Date: 02/07/1999 17:53:48
>Number: 6961
>Category: bin
>Synopsis: strftime(3) gives wrong week number (patch supplied)
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: bin-bug-people (Utility Bug People)
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Sun Feb 7 09:05:00 1999
>Last-Modified:
>Originator: Wolfgang Helbig
>Organization:
>Release: NetBSD-current Fri Feb 5 18:29:06 CET 1999
>Environment:
System: NetBSD rvc1 1.3I NetBSD 1.3I (RVC1) #4: Sun Feb 7 10:59:47 CET 1999 helbig@rvc1:/usr/src/sys/arch/i386/compile/RVC1 i386
>Description:
The %V conversion specifier produces a wrong week number, e. g.
according to ISO 8601 the week number of 31 Dec 1991 is 1 but
strftime says it is 53.
>How-To-Repeat:
>Fix:
I merged in the algorithm used in the latest version of
strftime from elsi.nci.nih.gov. It corrects the `%V'
conversion specifier and provides two new ones, `%G' and
`%g', which expand to the year which contains the greater
part of the week. Following is a patcht to strftime.c and
strftime.3 (two mention the new specifiers).
--- /usr/src/lib/libc/time/strftime.c Wed Dec 2 13:11:34 1998
+++ strftime.c Sun Feb 7 16:58:52 1999
@@ -227,32 +227,94 @@
pt, ptlim))
return (0);
continue;
- case 'V':
+ case 'V': /* ISO 8601 week number */
+ case 'G': /* ISO 8601 year (four digits) */
+ case 'g': /* ISO 8601 year (two digits) */
+/*
+** From Arnold Robbins' strftime version 3.0: "the week number of the
+** year (the first Monday as the first day of week 1) as a decimal number
+** (01-53)."
+** (ado, 1993-05-24)
+**
+** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
+** "Week 01 of a year is per definition the first week which has the
+** Thursday in this year, which is equivalent to the week which contains
+** the fourth day of January. In other words, the first week of a new year
+** is the week which has the majority of its days in the new year. Week 01
+** might also contain days from the previous year and the week before week
+** 01 of a year is the last week (52 or 53) of the previous year even if
+** it contains days from the new year. A week starts with Monday (day 1)
+** and ends with Sunday (day 7). For example, the first week of the year
+** 1997 lasts from 1996-12-30 to 1997-01-05..."
+** (ado, 1996-01-02)
+*/
{
- /* ISO 8601 Week Of Year:
- * If the week (Monday - Sunday) containing
- * January 1 has four or more days in the new
- * year, then it is week 1; otherwise it is
- * week 53 of the previous year and the next
- * week is week one.
- */
+ int year;
+ int yday;
+ int wday;
+ int w;
- int week = MON_WEEK(t);
+ year = t->tm_year + TM_YEAR_BASE;
+ yday = t->tm_yday;
+ wday = t->tm_wday;
+ for ( ; ; ) {
+ int len;
+ int bot;
+ int top;
- int days = (((t)->tm_yday + 7 -
- ((t)->tm_wday ? (t)->tm_wday - 1 : 6)) % 7);
-
-
- if (days >= 4) {
- week++;
- } else if (week == 0) {
- week = 53;
+ len = isleap(year) ?
+ DAYSPERLYEAR :
+ DAYSPERNYEAR;
+ /*
+ ** What yday (-3 ... 3) does
+ ** the ISO year begin on?
+ */
+ bot = ((yday + 11 - wday) %
+ DAYSPERWEEK) - 3;
+ /*
+ ** What yday does the NEXT
+ ** ISO year begin on?
+ */
+ top = bot -
+ (len % DAYSPERWEEK);
+ if (top < -3)
+ top += DAYSPERWEEK;
+ top += len;
+ if (yday >= top) {
+ ++year;
+ w = 1;
+ break;
+ }
+ if (yday >= bot) {
+ w = 1 + ((yday - bot) /
+ DAYSPERWEEK);
+ break;
+ }
+ --year;
+ yday += isleap(year) ?
+ 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') {
+ if (!_conv(w, 2, '0',
+ pt, ptlim))
+ return (0);
+ } else if (*format == 'g') {
+ if (!_conv(year % 100, 2, '0',
+ pt, ptlim))
+ return (0);
+ } else if (!_conv(year, 4, '0',
+ pt, ptlim))
+ return (0);
}
-
- if (!_conv(week, 2, '0', pt, ptlim))
- return (0);
continue;
- }
case 'W':
if (!_conv(MON_WEEK(t), 2, '0', pt, ptlim))
return (0);
--- /usr/src/lib/libc/time/strftime.3 Thu Feb 12 15:05:20 1998
+++ strftime.3 Sun Feb 7 17:31:27 1999
@@ -102,6 +102,16 @@
.It Cm \&%e
is replaced by the day of month as a decimal number [1,31];
single digits are preceded by a blank.
+.It Cm \&%G
+is replaced by the ISO 8601 year with century as a decimal number.
+.TP
+.It Cm \&%g
+is replaced by the ISO 8601 year without century as a decimal number (00-99).
+This is the year that includes the greater part of the week. (Monday as the
+first day of a week). See also the
+.Ql \&%V
+conversion specification.
+.TP
.It Cm \&%H
is replaced by the hour (24-hour clock) as a decimal number [00,23].
.It Cm \&%I
@@ -149,9 +159,12 @@
as a decimal number [1,7].
.It Cm \&%V
is replaced by the week number of the year (Monday as the first day of
-the week) as a decimal number [01,53]. If the week containing January
-1 has four or more days in the new year, then it is week 1; otherwise
-it is week 53 of the previous year, and the next week is week 1.
+the week) as a decimal number [01,53]. According to ISO 8601 the week
+containing January 1 is week 1 if it has four or more days in the new year,
+otherwise it is week 53 of the previous year, and the next week is week 1.
+The year is given by the
+.Ql \&%G
+conversion specification.
.It Cm \&%W
is replaced by the week number of the year (Monday as the first day of
the week) as a decimal number [00,53].
@@ -188,6 +201,8 @@
.Ql \&%C ,
.Ql \&%D ,
.Ql \&%e ,
+.Ql \&%g ,
+.Ql \&%G ,
.Ql \&%h ,
.Ql \&%k ,
.Ql \&%l ,
>Audit-Trail:
>Unformatted: