[alt.sources] beta release, pd ANSI strftime routine

arnold@audiofax.com (Arnold Robbins) (01/26/91)

The other day I wanted to use the ANSI C routine strftime.  It's not
in my C library, unfortunately.  So I cobbled one up.  Here it is. Please
try it out, and let me know if you find any bugs.  If there are none
reported in the next week or so, I'll ship it out over one of the moderated
source groups.

As this is alt.sources, there is no man page or makefile.  This compiles
fine with both gcc and cc on my ESIX V.3.2 rev D system.

It does assume System V for the timezone information.
---------------- cut here ---------------------
/*
 * strftime.c
 *
 * Public-domain relatively quick-and-dirty implemenation of
 * ANSI library routine for System V Unix systems.
 *
 * It's written in old-style C for maximal portability.
 *
 * The code for %c, %x, and %X is my best guess as to what's "appropriate".
 * This version ignores LOCALE information.
 * It also doesn't worry about multi-byte characters.
 * So there.
 *
 * Arnold Robbins
 * January, 1991
 */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>

#ifndef NULL
#define NULL	0	/* the real programmer's definition */
#endif

#ifndef __STDC__
#define const	/**/
#endif

#ifndef __STDC__
extern void tzset();
extern int abs();
static int weeknumber();
#else
extern void tzset(void);
extern int abs(int val);
static int weeknumber(const struct tm *timeptr, int firstweekday);
#endif

extern char *tzname[2];
extern int daylight;

#define L_DAYA	3	/* length of an abbreviated day name */
#define L_MONA	3	/* length of an abbreviated month name */


/* strftime --- produce formatted time */

#ifndef __STDC__
size_t
strftime(s, maxsize, format, timeptr)
char *s;
size_t maxsize;
const char *format;
const struct tm *timeptr;
#else
size_t
strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr)
#endif
{
	char *endp = s + maxsize;
	char *start = s;
	char tbuf[100];
	int i;
	static short first = 1;

	/* various tables, useful in North America */
	static char *days_a[] = {
		"Sun", "Mon", "Tue", "Wed",
		"Thu", "Fri", "Sat",
	};
	static char *days_l[] = {
		"Sunday", "Monday", "Tuesday", "Wednesday",
		"Thursday", "Friday", "Saturday",
	};
	static char *months_a[] = {
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
	};
	static char *months_l[] = {
		"January", "February", "March", "April",
		"May", "June", "July", "August", "September",
		"October", "November", "December",
	};

	if (s == NULL || format == NULL || timeptr == NULL || maxsize <= 0)
		return 0;

	if (first) {
		first = 0;
		tzset();
	}

	for (; *format && s < endp - 1; format++) {
		tbuf[0] = '\0';
		if (*format != '%') {
			*s++ = *format;
			continue;
		}
		switch (*++format) {
		case '\0':
			break;

		case '%':
			*s++ = '%';
			continue;

		case 'a':	/* abbreviated weekday name */
			if (s + L_DAYA < endp - 1) {
				*s++ = days_a[timeptr->tm_wday][0];
				*s++ = days_a[timeptr->tm_wday][1];
				*s++ = days_a[timeptr->tm_wday][2];
			} else
				goto out;
			break;

		case 'A':	/* full weekday name */
			i = strlen(days_l[timeptr->tm_wday]);
			if (s + i < endp - 1) {
				strcpy(s, days_l[timeptr->tm_wday]);
				s += i;
			} else
				goto out;
			break;

		case 'b':	/* abbreviated month name */
			if (s + L_MONA < endp - 1) {
				*s++ = months_a[timeptr->tm_mon][0];
				*s++ = months_a[timeptr->tm_mon][1];
				*s++ = months_a[timeptr->tm_mon][2];
			} else
				goto out;
			break;

		case 'B':	/* full month name */
			i = strlen(months_l[timeptr->tm_mon]);
			if (s + i < endp - 1) {
				strcpy(s, months_l[timeptr->tm_mon]);
				s += i;
			} else
				goto out;
			break;

		case 'c':	/* appropriate date and time representation */
			sprintf(tbuf, "%s %s %2d %02d:%02d:%02d %d",
				days_a[timeptr->tm_wday],
				months_a[timeptr->tm_mon],
				timeptr->tm_mday,
				timeptr->tm_hour,
				timeptr->tm_min,
				timeptr->tm_sec,
				timeptr->tm_year + 1900);
			break;

		case 'd':	/* day of the month, 01 - 31 */
			sprintf(tbuf, "%02d", timeptr->tm_mday);
			break;

		case 'H':	/* hour, 24-hour clock, 00 - 23 */
			sprintf(tbuf, "%02d", timeptr->tm_hour);
			break;

		case 'I':	/* hour, 12-hour clock, 01 - 12 */
			i = timeptr->tm_hour;
			if (i == 0)
				i = 12;
			else if (i > 12)
				i -= 12;
			sprintf(tbuf, "%02d", i);
			break;

		case 'j':	/* day of the year, 001 - 366 */
			sprintf(tbuf, "%03d", timeptr->tm_yday + 1);
			break;

		case 'm':	/* month, 01 - 12 */
			sprintf(tbuf, "%02d", timeptr->tm_mon + 1);
			break;

		case 'M':	/* minute, 00 - 59 */
			sprintf(tbuf, "%02d", timeptr->tm_min);
			break;

		case 'p':	/* am or pm based on 12-hour clock */
			if (timeptr->tm_hour < 12)
				strcpy(tbuf, "a.m.");
			else
				strcpy(tbuf, "p.m.");
			break;

		case 'S':	/* second, 00 - 61 */
			sprintf(tbuf, "%02d", timeptr->tm_sec);
			break;

		case 'U':	/* week of year, Sunday is first day of week */
			sprintf(tbuf, "%d", weeknumber(timeptr, 0));
			break;

		case 'w':	/* weekday, Sunday == 0, 0 - 6 */
			sprintf(tbuf, "%d", timeptr->tm_wday);
			break;

		case 'W':	/* week of year, Monday is first day of week */
			sprintf(tbuf, "%d", weeknumber(timeptr, 1));
			break;

		case 'x':	/* appropriate date representation */
			sprintf(tbuf, "%s %s %2d %d",
				days_a[timeptr->tm_wday],
				months_a[timeptr->tm_mon],
				timeptr->tm_mday,
				timeptr->tm_year + 1900);
			break;

		case 'X':	/* appropriate time representation */
			sprintf(tbuf, "%02d:%02d:%02d",
				timeptr->tm_hour,
				timeptr->tm_min,
				timeptr->tm_sec);
			break;

		case 'y':	/* year without a century, 00 - 99 */
			i = timeptr->tm_year % 100;
			sprintf(tbuf, "%d", i);
			break;

		case 'Y':	/* year with century */
			sprintf(tbuf, "%d", 1900 + timeptr->tm_year);
			break;

		case 'Z':	/* time zone name or abbrevation */
			i = 0;
			if (daylight && timeptr->tm_isdst)
				i = 1;
			strcpy(tbuf, tzname[i]);
			break;

		default:
			/* do something reasonable */
			if (s + 2 < endp - 1) {
				*s++ = '%';
				*s++ = *format;
			} else
				goto out;
			continue;
		}
		i = strlen(tbuf);
		if (i)
			if (s + i < endp - 1) {
				strcpy(s, tbuf);
				s += i;
			} else
				break;	/* the for loop */
	}
out:
	if (s < endp) {
		*s = '\0';
		return (s - start - 1);
	} else
		return 0;
}

/* weeknumber --- figure how many weeks into the year */

#ifndef __STDC__
static int
weeknumber(timeptr, firstweekday)
const struct tm *timeptr;
int firstweekday;
#else
static int
weeknumber(const struct tm *timeptr, int firstweekday)
#endif
{
	int m, drift, r;

	/* days into the current 7-day period */
	m = timeptr->tm_yday % 7;

	/* special case the first week */
	if (m == timeptr->tm_yday)
		if (timeptr->tm_wday >= firstweekday)
			return 1;
		else
			return 0;

	/* how many days away from Sunday or Monday the year started */
	drift = timeptr->tm_wday - m + firstweekday;
	drift = abs(drift);

	r = (timeptr->tm_yday - drift) / 7;

	return r;
}
-- 
Arnold Robbins				AudioFAX, Inc. | Laundry increases
2000 Powers Ferry Road, #200 / Marietta, GA. 30067     | exponentially in the
INTERNET: arnold@audiofax.com Phone:   +1 404 933 7612 | number of children.
UUCP:	  emory!audfax!arnold Fax-box: +1 404 618 4581 |   -- Miriam Robbins