
snprintf's behavior is to return the number of characters that would have been generated had the output buffer not been limited. That being true, I think the asctime.c below does what Paul intended. Does anyone see problems with using this in the next bundle? --ado /* ** This file is in the public domain, so clarified as of ** 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov). */ #ifndef lint #ifndef NOID static char elsieid[] = "@(#)asctime.c 7.13"; #endif /* !defined NOID */ #endif /* !defined lint */ /*LINTLIBRARY*/ #include "private.h" #include "tzfile.h" #define STANDARD_BUFFER_SIZE 26 /* ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, 2004 Edition. */ char * asctime_r(timeptr, buf) register const struct tm * timeptr; char * buf; { static const char wday_name[][3] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char mon_name[][3] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; register const char * wn; register const char * mn; register int result; if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK) wn = "???"; else wn = wday_name[timeptr->tm_wday]; if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR) mn = "???"; else mn = mon_name[timeptr->tm_mon]; /* ** The format used in the (2004) standard is ** "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n" ** Use "%02d", as it is a bit more portable than "%.2d". */ result = snprintf(buf, STANDARD_BUFFER_SIZE, "%.3s %.3s%3d %02d:%02d:%02d %ld\n", wn, mn, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, timeptr->tm_year + (long) TM_YEAR_BASE); if (result < 0 || result >= STANDARD_BUFFER_SIZE) { errno = EOVERFLOW; return NULL; } return buf; } /* ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, 2004 Edition, ** with core dump avoidance. */ char * asctime(timeptr) register const struct tm * timeptr; { static char result[STANDARD_BUFFER_SIZE]; return asctime_r(timeptr, result); }

On Mon, Jul 26, 2004 at 03:51:23PM -0400, Olson, Arthur David (NIH/NCI) wrote:
snprintf's behavior is to return the number of characters that would have been generated had the output buffer not been limited. That being true, I think the asctime.c below does what Paul intended. Does anyone see problems with using this in the next bundle?
The main difference I see is that Paul's can compile and work on old systems which lack a snprintf(). His code *does* use the system's snprintf() in just the way you describe, if the HAVE_SNPRINTF preprocessor feature-test variable claims that it is available. --Ken Pizzini

"Olson, Arthur David (NIH/NCI)" <olsona@dc37a.nci.nih.gov> writes:
snprintf's behavior is to return the number of characters that would have been generated had the output buffer not been limited.
Good catch. Sorry, I misused snprintf. (Wouldn't be the first time. :-)
Does anyone see problems with using this in the next bundle?
Here are the problems I see: * There's a regression for out-of-range inputs. The current asctime always returns a valid, well-defined string. With the proposed change, asctime will fail and set errno = EOVERFLOW in some (but not in all) cases when the inputs are outside their traditional ranges. This problem is not limited to 64-bit hosts: it can also occur with 32-bit hosts, e.g, if tm_mday is out of range then the proposed asctime might fail (but it might not, depending on tm_year's value). The standard doesn't require asctime to succeed in all these cases, but I'd rather keep the current behavior: it's simpler to explain, is more useful in practice, and is less likely to crash (admittedly poorly-written) user programs. * If snprintf returns a negative value (this shouldn't happen -- perhaps if memory exhausted though?), asctime should probably leave errno set to whatever snprintf set it to, rather than setting it to EOVERFLOW. * A few portability assumptions, which aren't true on older hosts. These aren't that big a deal, I suppose.... - The code assumes that snprintf works. - The code assumes <errno.h> defines EOVERFLOW. Here's a proposed version to address the above issues. It merges the changes that you circulated, and it assumes the private.h and Makefile (comment) changes I proposed previously. /* ** This file is in the public domain, so clarified as of ** 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov). */ #ifndef lint #ifndef NOID static char elsieid[] = "@(#)asctime.c 7.13"; #endif /* !defined NOID */ #endif /* !defined lint */ /*LINTLIBRARY*/ #include "private.h" #include "tzfile.h" #define STANDARD_BUFFER_SIZE 26 #ifndef EOVERFLOW # define EOVERFLOW EINVAL #endif /* ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, 2004 Edition. */ /* ** Big enough for something such as ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n ** (two three-character abbreviations, five strings denoting integers, ** three explicit spaces, two explicit colons, a newline, ** and a trailing ASCII nul). */ #define MAX_ASCTIME_SIZE (3 * 2 + 5 * INT_STRLEN_MAXIMUM(int) + 3 + 2 + 1 + 1) #if !HAVE_SNPRINTF /* ** A substitute for snprintf that is good enough for asctime. */ static int snprintf(buf, size, format, mday, hour, min, sec, year) char * buf; size_t size; const char * format; int mday, hour, min, sec; long year; { char tbuf[MAX_ASCTIME_SIZE]; size_t len; (void) sprintf(tbuf, buf, size, format, mday, hour, min, sec, year); len = strlen(tbuf); if (len < size) { (void) strcpy(buf, tbuf); } else { (void) memcpy(buf, tbuf, size - 1); buf[size - 1] = '\0'; } return len; } #endif static char * asctime_rn(timeptr, buf, size) register const struct tm * timeptr; char * buf; size_t size; { static const char wday_name[][3] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char mon_name[][3] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; register const char * wn; register const char * mn; register int result; if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK) wn = "???"; else wn = wday_name[timeptr->tm_wday]; if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR) mn = "???"; else mn = mon_name[timeptr->tm_mon]; /* ** The format used in the (2004) standard is ** "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n" ** Use "%02d", as it is a bit more portable than "%.2d". */ result = snprintf(buf, STANDARD_BUFFER_SIZE, "%.3s %.3s%3d %02d:%02d:%02d %ld\n", wn, mn, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, timeptr->tm_year + (long) TM_YEAR_BASE); if (result < 0) return NULL; if (result >= size) { errno = EOVERFLOW; return NULL; } return buf; } char * asctime_r(timeptr, buf) register const struct tm * timeptr; char * buf; { return asctime_rn(timeptr, buf, STANDARD_BUFFER_SIZE); } /* ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, 2004 Edition, ** with core dump avoidance. */ char * asctime(timeptr) register const struct tm * timeptr; { static char result[MAX_ASCTIME_SIZE]; return asctime_rn(timeptr, result, sizeof result); }

On Tue, Jul 27, 2004 at 12:37:12AM -0700, Paul Eggert wrote:
snprintf's behavior is to return the number of characters that would have been generated had the output buffer not been limited.
Good catch. Sorry, I misused snprintf. (Wouldn't be the first time. :-)
Ah; I didn't read the previous code carefully enough when I claimed that Eggert did it right last time. :-P [...]
* If snprintf returns a negative value (this shouldn't happen -- perhaps if memory exhausted though?), asctime should probably leave errno set to whatever snprintf set it to, rather than setting it to EOVERFLOW.
There is a portability issue where some older snprintf() implementations (e.g., glibc-2.0.6) returned -1 for the "buffer too small" conditon, instead of the C99 specified behavior that Olson is mentioning. I'm not sure that errno is given a useful value in this situation (I don't have old glibc documentation handy, and it may vary with other implementations anyway), so it might still make sense to force errno=EOVERFLOW. --Ken Pizzini

Date: Tue, 27 Jul 2004 00:37:12 -0700 From: Paul Eggert <eggert@CS.UCLA.EDU> Message-ID: <87zn5lew9j.fsf@penguin.cs.ucla.edu> None of my messages (these days) ever make it to the list - I suspect some absurd anti-spam "protection" killing all of my mail. But never mind that for now. | /* | ** The format used in the (2004) standard is | ** "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n" | ** Use "%02d", as it is a bit more portable than "%.2d". | */ The most postable format is %02.2d - that works on every printf I've ever seen that has any method to make a 2 column zero-filled numeric field. Nothing else works on everything (the %02d version is original Research unix/BSD variants, the %.2d version is original Sys III type stuff I think - for a 2 character field, it probably doesn't matter, except possibly for the value 0 (where a system that likes %.2d might produce just " 0" if given %02d - the "0" in the format (SysIII variant) requires a leading 0 in the result, or something like that, so for 1, you should get 01, but for 0 ...) | result = snprintf(buf, STANDARD_BUFFER_SIZE, | "%.3s %.3s%3d %02d:%02d:%02d %ld\n", Please make that %4ld - it never mattered before, because the year was always between 1900 and 21xx, so there were always 4 digits anyway. But now if you're going to allow for year 10, etc, that 4 digit field needs to be forced to be 4 characters wide. It really is supposed to be that, not just "the rest of the line". The \n should be in buf[25] and absolutely nowhere else (for correct historical compatibility). That is, doing buf[25] = '\0'; to the result of asctime() is the "time honoured" way of printing the result without the trailing \n (that or using "%.24s" which amounts to the same thing). kre

Robert Elz <kre@munnari.oz.au> writes:
The \n should be in buf[25] and absolutely nowhere else (for correct historical compatibility).
Here (as with the %4ld issue) we have a disagreement between Unix Version 7 and the C standard. V7 asctime always generated 26 bytes of output (including the terminating null), and always generated 4 bytes for the year, no matter what. It also generated only positive numbers, even when (say) tm_sec was -1. But the C standard says that sometimes fewer bytes of output must be generated (in particular, if the year is in the range -99 through 999), and that a negative number must be printed in some cases (e.g., when tm_sec is -1). I'd prefer to conform to the current standard when it disagrees with tradition, particularly when these are cases that don't really matter for practical programs (only standards nerds like us will care about them :-).
The most postable format is %02.2d
Yes, I suppose this is the most portable to ancient hosts, but unfortunately GCC complains about it: warning: `0' flag ignored with precision and `%d' printf format (I'm using GCC 3.4.1.) But this doesn't prevent compilation from succeeding, so I guess it's OK.

Date: Tue, 27 Jul 2004 12:13:14 -0700 From: Paul Eggert <eggert@CS.UCLA.EDU> Message-ID: <87fz7db6wl.fsf@penguin.cs.ucla.edu> | I'd prefer to conform to the current standard when it disagrees with | tradition, particularly when these are cases that don't really matter | for practical programs (only standards nerds like us will care about | them :-). If the standard actually says what you say (I don't have anything to do with it) then the standard is broken, and someone should file a defect report. This one isn't just of academic interest, there's lots of code that does stuff like printf("The date is: %.24s today\n", asctime(tm)); and expects that there cannot be a newline between the date and the word "today". This is not something to break, failing to adhere to the standard in a minor way is a trivial price to pay for this compatibility. This one is important. kre

Robert Elz said:
If the standard actually says what you say
It does.
This one isn't just of academic interest, there's lots of code that does stuff like
printf("The date is: %.24s today\n", asctime(tm));
and expects that there cannot be a newline between the date and the word "today".
Then that code is broken. End of story. -- Clive D.W. Feather | Work: <clive@demon.net> | Tel: +44 20 8495 6138 Internet Expert | Home: <clive@davros.org> | Fax: +44 870 051 9937 Demon Internet | WWW: http://www.davros.org | Mobile: +44 7973 377646 Thus plc | |

Date: Wed, 28 Jul 2004 05:54:42 +0100 From: "Clive D.W. Feather" <clive@demon.net> Message-ID: <20040728045442.GC71827@finch-staff-1.thus.net> | > This one isn't just of academic interest, there's lots of code that | > does stuff like | > | > printf("The date is: %.24s today\n", asctime(tm)); | > | > and expects that there cannot be a newline between the date and the | > word "today". | | Then that code is broken. End of story. Nonsense (for this point anyway - ignore the possibility that asctime() might return NULL, that could be tested before the value is used). The standard should be documenting C as it exists, not reinventing it. The standard (any standard) has meaning only as long as it is actually implemented, if it is ignored, it is just so much worthless paper (or bits). Implementors must (both economically, and morally) keep on implementing what existing code uses, otherwise no-one will care about, or use, their implementation, the users will just find something that does what they expect. Here, the tzlib library is the implementation, and it needs to keep supporting all the old code that was written way back before anyone even thought about having a standard for C, using this perfectly well specified, and consistently implemented, interface. If the standard chooses to stick its head in the mud and ignore what the written code actually does, it is our responsibility to make sure the standard gets ignored. kre

Paul Eggert said:
The most postable format is %02.2d Yes, I suppose this is the most portable to ancient hosts, but unfortunately GCC complains about it: warning: `0' flag ignored with precision and `%d' printf format
That's right: the C Standard says that 0 is ignored if there's a precision. -- Clive D.W. Feather | Work: <clive@demon.net> | Tel: +44 20 8495 6138 Internet Expert | Home: <clive@davros.org> | Fax: +44 870 051 9937 Demon Internet | WWW: http://www.davros.org | Mobile: +44 7973 377646 Thus plc | |
participants (5)
-
Clive D.W. Feather
-
Ken Pizzini
-
Olson, Arthur David (NIH/NCI)
-
Paul Eggert
-
Robert Elz