asctime.c

Paul Eggert eggert at CS.UCLA.EDU
Tue Jul 27 18:52:29 UTC 2004


"Olson, Arthur David (NIH/NCI)" <olsona at dc37a.nci.nih.gov> writes:

> use... %4ld for years to avoid problems if a year isn't four digits long

That "%4ld" doesn't conform to the C standard, which says that leading
zeros must not be printed for years.  So the following program:

   #include <time.h>
   #include <stdio.h>
   int
   main (void)
   {
     struct tm tm;
     tm.tm_year = 999 - 1900;
     tm.tm_mon = 12 - 1;
     tm.tm_mday = 1;
     tm.tm_wday = 1;
     tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
     puts (asctime (&tm));
     return 0;
   }

which is strictly conforming even on 32-bit time_t hosts, must print
"Mon Dec  1 00:00:00 999", without a leading zero before the 999.

Also, that proposal still assumes that EOVERFLOW is defined by
<errno.h>.  (Perhaps you're defining it in private.h if it's not
already defined?  That would explain this.)

Finally, there's still a regression in that asctime now sometimes
return NULL when it used to return a valid string.  It's fairly common
for programs to assume that asctime always succeeds.  zdump.c itself
is one such program (I've submitted patches for that but we're working
on asctime first).  Admittedly such programs are unportable, but I
don't see why we should have them dump core when it's easy to have
them succeed and return a valid string.

Anyway, here's a proposal that incorporates your changes to avoid
snprintf entirely, along with comments along the lines that I
suggested in my earlier message today, and a few other comments about
the regression noted above.

/*
** This file is in the public domain, so clarified as of
** 1996-06-05 by Arthur David Olson (arthur_david_olson at nih.gov).
*/

#ifndef lint
#ifndef NOID
static char	elsieid[] = "@(#)asctime.c	7.15";
#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).  The above example assumes 32-bit int,
** but the same idea applies to all int widths.
**
** The year is printed as a long int, but it can't possibly take more
** digits than the number of digits printed in an int, because it is
** the sum of 1900 and an int value.  Even if the sum overflows past
** INT_MAX, it will add at most one digit to the print width, and that
** extra byte is already accounted for by the width of INT_MIN (which
** has a leading minus sign).
*/
#define MAX_ASCTIME_SIZE (2 * 3 + 5 * INT_STRLEN_MAXIMUM(int) + 3 + 2 + 1 + 1)

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;
	char			result[MAX_ASCTIME_SIZE];

	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"
	** Some systems only handle "%.2d"; others only handle "%02d";
	** "%02.2d" makes (most) everybody happy.
	*/
	/*
	** We avoid using snprintf since it's not available on all systems.
	*/
	(void) sprintf(result, "%.3s %.3s%3d %02.2d:%02.2d:%02.2d %ld\n",
		wn, mn,
		timeptr->tm_mday, timeptr->tm_hour,
		timeptr->tm_min, timeptr->tm_sec,
		timeptr->tm_year + (long) TM_YEAR_BASE);
	if (strlen(result) >= size) {
		errno = EOVERFLOW;
		return NULL;
	} else {
		(void) strcpy(buf, result);
		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;
{
	/*
	** The standard requires only STANDARD_BUFFER_SIZE bytes in
	** this static buffer.  However, make it longer so that
	** asctime never returns a null pointer.  This supports the
	** many (admittedly unportable) programs that assume that
	** asctime never fails.
	*/
	static char		result[MAX_ASCTIME_SIZE];

	return asctime_rn(timeptr, result, sizeof result);
}



More information about the tz mailing list