"Clive D.W. Feather" <clive@demon.net> writes:
Note, by the way, that strftime is only supposed to work when the relevant fields are in their "normal range". No such range is given for tm_year.
I intepret this to mean that strftime is supposed to work regardless of the value of tm_year. However, the standard says that %C always generates a value in the range [00,99] so it would appear there's an inconsistency here. I suppose one could argue that %C has undefined behavior for years outside the range [0, 9999]. But this appears to me to be a defect in the standard -- at least, things are quite unclear here. I would prefer it if strftime were required to handle all tm_year values. There is no similar restriction on the range for %Y, which suggests that strftime %Y must handle all tm_year values. For %y the range is [00,99], which argues for using modulus rather than remainder.
It would be interesting to see what they do with %C as well:
Solaris interprets %C completely differently: it treats it as a request to output the same string that the "date" command outputs by default. The strftime man page says that there is a "standard-conforming" strftime somewhere but doesn't say how to get it. I couldn't figure it out so I gave up looking for it.
The glibc and OpenBSD behaviours appear to be using the % operator.
glibc uses %, but adjusts negative remainders to make them positive, so that it's actually using modulus. I think OpenBSD uses plain %.
As for Solaris, my best guess is that it's calculating: '0' + tm_year / 10 % 10 '0' + tm_year % 10
Yes, that sounds plausible, as Unix Version 7 does something similar. Solaris also mishandles %Y for negative and/or large years. For example, strftime %Y prints the year -1 as "000/", and prints the year 2**31 (i.e., tm_year == 2**31 - 1900) as "-*,(". This is consistent with your theory. Here's a test program you can use to try out your implementation. It's not strictly conforming code (it relies on floating point) but it should work on all practical platforms. Only glibc "passes" the test, in then sense that it produces a coherent set of values for all inputs (it always uses modulus for %y, and for %C it always truncates towards minus infinity). Solaris botches %C entirely, and mishandles %y for years before 1900, mishandles %Y for years before 0. OpenBSD uses signed remainder for negative years, though I'd argue that having %y generate "-" is bogus. OpenBSD and Solaris both clearly mishandle tm_year values close to INT_MAX. #include <string.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <time.h> static void process (int tm_year) { struct tm tm; char y[1000]; char C[1000]; char Y[1000]; tm.tm_year = tm_year; strftime (y + 1, sizeof y - 2, "%y", &tm); y[0] = '"'; strcat (y, "\""); strftime (C + 1, sizeof C - 2, "%C", &tm); C[0] = '"'; strcat (C, "\""); strftime (Y + 1, sizeof Y - 2, "%Y", &tm); Y[0] = '"'; strcat (Y, "\""); printf ("%13d %13.0f %13s %13s %13s\n", tm_year, tm_year + 1900.0, y, C, Y); } int main (int argc, char **argv) { printf ("%13s %13s %13s %13s %13s\n", "tm_year", "year", "%y", "%C", "%Y"); if (argc <= 1) { #define near(x) (x) - 1900, (x) - 1900 + 1, (x) - 1900 + 2 static int test[] = { near (INT_MIN + 1900), near (-1001), near (-101), near (-11), near (-1), near (9), near (99), near (999), near (1899), near (1969), near (1999), near (2099), near (INT_MAX - 1), near (INT_MAX + 1900.0 - 2) }; int i; for (i = 0; i < sizeof test / sizeof *test; i++) { if (i == 0 || test[i - 1] + 1 != test[i]) printf ("\n"); process (test[i]); } } else while (*++argv) process (atoi (*argv)); return 0; }