asctime.c

Paul Eggert eggert at CS.UCLA.EDU
Tue Jul 27 07:37:12 UTC 2004


"Olson, Arthur David (NIH/NCI)" <olsona at 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 at 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);
}



More information about the tz mailing list