v27i207: strftime-6.0 - yet another strftime(3) and date(1), Part01/01

Monty Solomon monty at roscom.COM
Wed Feb 23 22:17:43 UTC 1994



Begin forwarded message:

Path: decwrl!vixie!vixie!not-for-mail
From: arnold at skeeve.atl.ga.us (Arnold Robbins)
Newsgroups: comp.sources.unix
Subject: v27i207: strftime-6.0 - yet another strftime(3) and date(1), Part01/01
Date: 22 Feb 1994 17:21:29 -0800
Organization: Vixie Enterprises
Lines: 1245
Sender: vixie at vix.com
Approved: vixie at gw.home.vix.com
Nntp-Posting-Host: gw.home.vix.com
To: unix-sources at pa.dec.com

Submitted-By: arnold at skeeve.atl.ga.us (Arnold Robbins)
Posting-Number: Volume 27, Issue 207
Archive-Name: strftime-6.0/part01

[ this recurring package is the cleanest, nicest date(1) and strftime(3)
  i have ever seen, including all free or vendor systems.	--vix ]

It seems to be an annual event, something in the circling of the stars
overhead, that requires me to post this, at least once a year. (:-)

This version fixes bugs in the calculations for %U, %W, and %V and output
formats of several of the others (they now have leading zeros if necessary).
It also fixes a small bug in date.c.

Although this is only the fourth version posted c.s.u, it is the sixth
one to go to the net, the first two were in comp.sources.misc. So I don't
know if this is strftime 4.0 or 6.0, you can decide.

I sincerely hope this will be it (at least 'til next year! :-)

Arnold Robbins -- The Basement Computer		| Laundry increases
Internet: arnold at skeeve.ATL.GA.US		| exponentially in the
UUCP:	emory!skeeve!arnold			| number of children.
Bitnet:	Forget it. Get on a real network.	|    -- Miriam Robbins

#! /bin/sh
echo - 'README'
cat << 'EOF' > 'README'
Tue Feb 15 08:26:04 EST 1994

This package implements the Posix 1003.2 date command, as a wrapper around
an extended version of the ANSI strftime(3) library routine.
Everything in it is public domain.

Arnold Robbins -- The Basement Computer		| Laundry increases
Internet: arnold at skeeve.ATL.GA.US		| exponentially in the
UUCP:	{ gatech, emory }!skeeve!arnold		| number of children.
Bitnet:	Forget it. Get on a real network.	|    -- Miriam Robbins
EOF
echo - 'Makefile'
cat << 'EOF' > 'Makefile'
# Makefile for PD date and strftime

SRCS= date.c strftime.c
OBJS= date.o strftime.o
DOCS= date.1 strftime.3

# Uncomment the define of HAVE_TZNAME if your system has the tzname[] array.
# Uncomment the define of TM_IN_SYS_TIME if struct tm is in <sys/time.h>
CFLAGS= -O #-DHAVE_TZNAME #-DTM_IN_SYS_TIME

date: $(OBJS)
	$(CC) $(CFLAGS) $(OBJS) -o $@

date.o: date.c

strftime.o: strftime.c
EOF
echo - 'date.1'
cat << 'EOF' > 'date.1'
.TH DATE 1
.SH NAME
date \- write the date and time
.SH SYNOPSIS
.B date
[
.B \-u
] [
.RI + format
]
.SH DESCRIPTION
.I Date
writes the current date and time to the standard output.
It is intended to be compliant with draft 11 of the Posix
1003.2 Command Language and Utilities standard.
Therefore, it is purposely
.I not
usable by the super-user for setting the system time.
.LP
.I Date
accepts one option:
.TP
.B \-u
Perform operations as if the
.B TZ
environment variable was set to
.BR GMT0 .
.LP
If an argument to 

.I date
is given that begins with a ``+'',
then the output is controlled by the contents of the rest of
the string.  Normal text is output unmodified, while field descriptors
in the format string are substituted for.
.LP
The
.I date
program is essentially a wrapper around
.IR strftime (3);
see there for a description of the available formatting options.
.LP
If no format string is given, or if it does not begin with a ``+'',
then the default format of \fB"%a %b %e %H:%M:%S %Z %Y"\fR will
be used.  This produces the traditional style of output, such as
``Sun Mar 17 10:32:47 EST 1991''.
.SH SEE ALSO
time(2), strftime(3), localtime(3)
.SH BUGS
This version only works for the POSIX locale.
.SH AUTHOR
.nf
Arnold Robbins
.sp
INTERNET: arnold at skeeve.atl.ga.us
UUCP:     emory!skeeve!arnold
Phone:    +1 404 248 9324
.fi
EOF
echo - 'date.c'
cat << 'EOF' > 'date.c'
/*
 * date.c
 *
 * Public domain implementation of Posix 1003.2
 * date command.  Lets strftime() do the dirty work.
 *
 * Arnold Robbins
 * arnold at skeeve.atl.ga.us
 * April, 1991
 *
 * Bug fix courtesy of Chris Ritson (C.R.Ritson at newcastle.ac.uk),
 * February, 1994.
 */

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

extern char *malloc();
extern size_t strftime();
extern int getopt();
extern int optind;

int
main(argc, argv)
int argc;
char **argv;
{
	time_t clock;
	struct tm *now;
	int c, size, ret;
	char *defhow = "%a %b %e %H:%M:%S %Z %Y";
	char *howto = defhow;
	char *buf;

	while ((c = getopt(argc, argv, "u")) != -1)
		switch (c) {
		case 'u':
			putenv("TZ=GMT0");
			break;
		default:
			fprintf(stderr, "usage: %s [-u] [+format_str]\n",
				argv[0]);
			exit(1);
		}

	time(& clock);
	now = localtime(& clock);

	if (optind < argc && argv[optind][0] == '+')
		howto = & argv[optind][1];

	size = strlen(howto) * 10;
	if (size < 26)
		size = 26;
	if ((buf = malloc(size)) == NULL) {
		perror("not enough memory");
		exit(1);
	}

	ret = strftime(buf, size, howto, now);
	if (ret != 0)
		printf("%s\n", buf);
	else {
		fprintf(stderr, "conversion failed\n");
		exit(1);
	}
	
	exit(0);
}
EOF
echo - 'strftime.3'
cat << 'EOF' > 'strftime.3'
.TH STRFTIME 3
.SH NAME
strftime \- generate formatted time information
.SH SYNOPSIS
.ft B
.nf
#include <sys/types.h>
#include <time.h>
.sp
size_t strftime(char *s, size_t maxsize, const char *format,
	const struct tm *timeptr);
.SH DESCRIPTION
The following description is transcribed verbatim from the December 7, 1988
draft standard for ANSI C.
This draft is essentially identical in technical content
to the final version of the standard.
.LP
The
.B strftime
function places characters into the array pointed to by
.B s
as controlled by the string pointed to by
.BR format .
The format shall be a multibyte character sequence, beginning and ending in
its initial shift state.
The
.B format
string consists of zero or more conversion specifiers and ordinary
multibyte characters.  A conversion specifier consists of a
.B %
character followed by a character that determines the behavior of the
conversion specifier.
All ordinary multibyte characters (including the terminating null
character) are copied unchanged into the array.
If copying takes place between objects that overlap the behavior is undefined.
No more than
.B maxsize
characters are placed into the array.
Each conversion specifier is replaced by appropriate characters as described
in the following list.
The appropriate characters are determined by the
.B LC_TIME
category of the current locale and by the values contained in the
structure pointed to by
.BR timeptr .
.TP
.B %a
is replaced by the locale's abbreviated weekday name.
.TP
.B %A
is replaced by the locale's full weekday name.
.TP
.B %b
is replaced by the locale's abbreviated month name.
.TP
.B %B
is replaced by the locale's full month name.
.TP
.B %c
is replaced by the locale's appropriate date and time representation.
.TP
.B %d
is replaced by the day of the month as a decimal number
.RB ( 01 - 31 ).
.TP
.B %H
is replaced by the hour (24-hour clock) as a decimal number
.RB ( 00 - 23 ).
.TP
.B %I
is replaced by the hour (12-hour clock) as a decimal number
.RB ( 01 - 12 ).
.TP
.B %j
is replaced by the day of the year as a decimal number
.RB ( 001 - 366 ).
.TP
.B %m
is replaced by the month as a decimal number
.RB ( 01 - 12 ).
.TP
.B %M
is replaced by the minute as a decimal number
.RB ( 00 - 59 ).
.TP
.B %p
is replaced by the locale's equivalent of the AM/PM designations associated
with a 12-hour clock.
.TP
.B %S
is replaced by the second as a decimal number
.RB ( 00 - 61 ).
.TP
.B %U
is replaced by the week number of the year (the first Sunday as the first
day of week 1) as a decimal number
.RB ( 00 - 53 ).
.TP
.B %w
is replaced by the weekday as a decimal number
.RB [ "0 " (Sunday)- 6 ].
.TP
.B %W
is replaced by the week number of the year (the first Monday as the first
day of week 1) as a decimal number
.RB ( 00 - 53 ).
.TP
.B %x
is replaced by the locale's appropriate date representation.
.TP
.B %X
is replaced by the locale's appropriate time representation.
.TP
.B %y
is replaced by the year without century as a decimal number
.RB ( 00 - 99 ).
.TP
.B %Y
is replaced by the year with century as a decimal number.
.TP
.B %Z
is replaced by the time zone name or abbreviation, or by no characters if
no time zone is determinable.
.TP
.B %%
is replaced by
.BR % .
.LP
If a conversion specifier is not one of the above, the behavior is
undefined.
.SH RETURNS
If the total number of resulting characters including the terminating null
character is not more than
.BR maxsize ,
the
.B strftime
function returns the number of characters placed into the array pointed to
by
.B s
not including the terminating null character.
Otherwise, zero is returned and the contents of the array are indeterminate.
.SH NON-ANSI EXTENSIONS
If
.B SYSV_EXT
is defined when the routine is compiled, then the following additional
conversions will be available.
These are borrowed from the System V
.IR cftime (3)
and
.IR ascftime (3)
routines.
.TP
.B %D
is equivalent to specifying
.BR %m/%d/%y .
.TP
.B %e
is replaced by the day of the month,
padded with a blank if it is only one digit.
.TP
.B %h
is equivalent to
.BR %b ,
above.
.TP
.B %n
is replaced with a newline character (\s-1ASCII LF\s+1).
.TP
.B %r
is equivalent to specifying
.BR "%I:%M:%S %p" .
.TP
.B %R
is equivalent to specifying
.BR %H:%M .
.TP
.B %T
is equivalent to specifying
.BR %H:%M:%S .
.TP
.B %t
is replaced with a \s-1TAB\s+1 character.
.PP
If
.B SUNOS_EXT
is defined when the routine is compiled, then the following additional
conversions will be available.
These are borrowed from the SunOS version of
.IR strftime .
.TP
.B %k
is replaced by the hour (24-hour clock) as a decimal number
.RB ( 0 - 23 ).
Single digit numbers are padded with a blank.
.TP
.B %l
is replaced by the hour (12-hour clock) as a decimal number
.RB ( 1 - 12 ).
Single digit numbers are padded with a blank.
.SH POSIX 1003.2 EXTENSIONS
If
.B POSIX2_DATE
is defined, then all of the conversions available with
.B SYSV_EXT
and
.B SUNOS_EXT
are available, as well as the
following additional conversions:
.TP
.B %C
The century, as a number between 00 and 99.
.TP
.B %u
is replaced by the weekday as a decimal number
.RB [ "1 " (Monday)- 7 ].
.TP
.B %V
is replaced by the week number of the year (the first Monday as the first
day of week 1) as a decimal number
.RB ( 01 - 53 ).
The method for determining the week number is as specified by ISO 8601
(to wit: if the week containing January 1 has four or more days in the
new year, then it is week 1, otherwise it is week 53 of the previous year
and the next week is week 1).
.LP
The text of the POSIX standard for the
.I date
utility describes
.B %U
and
.B %W
this way:
.TP
.B %U
is replaced by the week number of the year (the first Sunday as the first
day of week 1) as a decimal number
.RB ( 00 - 53 ).
All days in a new year preceding the first Sunday are considered to be
in week 0.
.TP
.B %W
is replaced by the week number of the year (the first Monday as the first
day of week 1) as a decimal number
.RB ( 00 - 53 ).
All days in a new year preceding the first Monday are considered to be
in week 0.
.LP
In addition, the alternate representations
.BR %Ec ,
.BR %EC ,
.BR %Ex ,
.BR %Ey ,
.BR %EY ,
.BR %Od ,
.BR %Oe ,
.BR %OH ,
.BR %OI ,
.BR %Om ,
.BR %OM ,
.BR %OS ,
.BR %Ou ,
.BR %OU ,
.BR %OV ,
.BR %Ow ,
.BR %OW ,
and
.B %Oy
are recognized, but their normal representations are used.
.SH VMS EXTENSIONS
If
.B VMS_EXT
is defined, then the following additional conversion is available:
.TP
.B %v
The date in VMS format (e.g. 20-JUN-1991).
.SH SEE ALSO
.IR time (2),
.IR ctime (3),
.IR localtime (3),
.IR tzset (3)
.SH BUGS
This version does not handle multibyte characters or pay attention to the
setting of the
.B LC_TIME
environment variable.
.LP
It is not clear what is ``appropriate'' for the C locale; the values
returned are a best guess on the author's part.
.SH CAVEATS
The pre-processor symbol
.B POSIX_SEMANTICS
is automatically defined, which forces the code to call
.IR tzset (3)
whenever the
.B TZ
environment variable has changed.
If this routine will be used in an application that will not be changing
.BR TZ ,
then there may be some performance improvements by not defining
.BR POSIX_SEMANTICS .
.SH AUTHOR
.nf
Arnold Robbins
.sp
INTERNET: arnold at skeeve.atl.ga.us
UUCP:     emory!skeeve!arnold
Phone:    +1 404 248 9324
.fi
.SH ACKNOWLEDGEMENTS
Thanks to Geoff Clare <gwc at root.co.uk> for helping debug earlier
versions of this routine, and for advice about POSIX semantics.
Additional thanks to Arthur David Olsen <ado at elsie.nci.nih.gov>
for some code improvements.
Thanks also to Tor Lillqvist <tml at tik.vtt.fi>
for code fixes to the ISO 8601 code.
EOF
echo - 'strftime.c'
cat << 'EOF' > 'strftime.c'
/*
 * strftime.c
 *
 * Public-domain implementation of ANSI C library routine.
 *
 * It's written in old-style C for maximal portability.
 * However, since I'm used to prototypes, I've included them too.
 *
 * If you want stuff in the System V ascftime routine, add the SYSV_EXT define.
 * For extensions from SunOS, add SUNOS_EXT.
 * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE.
 * For VMS dates, add VMS_EXT.
 * For complete POSIX semantics, add POSIX_SEMANTICS.
 *
 * 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.
 *
 * This file is also shipped with GAWK (GNU Awk), gawk specific bits of
 * code are included if GAWK is defined.
 *
 * Arnold Robbins
 * January, February, March, 1991
 * Updated March, April 1992
 * Updated April, 1993
 * Updated February, 1994
 *
 * Fixes from ado at elsie.nci.nih.gov
 * February 1991, May 1992
 * Fixes from Tor Lillqvist tml at tik.vtt.fi
 * May, 1993
 * Further fixes from ado at elsie.nci.nih.gov
 * February 1994
 */

#ifndef GAWK
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#endif
#include <sys/types.h>
#if defined(TM_IN_SYS_TIME) || ! defined(GAWK)
#include <sys/time.h>
#endif

/* defaults: season to taste */
#define SYSV_EXT	1	/* stuff in System V ascftime routine */
#define SUNOS_EXT	1	/* stuff in SunOS strftime routine */
#define POSIX2_DATE	1	/* stuff in Posix 1003.2 date command */
#define VMS_EXT		1	/* include %v for VMS date format */
#ifndef GAWK
#define POSIX_SEMANTICS	1	/* call tzset() if TZ changes */
#endif

#if defined(POSIX2_DATE)
#if ! defined(SYSV_EXT)
#define SYSV_EXT	1
#endif
#if ! defined(SUNOS_EXT)
#define SUNOS_EXT	1
#endif
#endif

#if defined(POSIX2_DATE)
#define adddecl(stuff)	stuff
#else
#define adddecl(stuff)
#endif

#undef strchr	/* avoid AIX weirdness */

#ifndef __STDC__
#define const	/**/
extern void *malloc();
extern void *realloc();
extern void tzset();
extern char *strchr();
extern char *getenv();
static int weeknumber();
adddecl(static int iso8601wknum();)
#else
extern void *malloc(unsigned count);
extern void *realloc(void *ptr, unsigned count);
extern void tzset(void);
extern char *strchr(const char *str, int ch);
extern char *getenv(const char *v);
static int weeknumber(const struct tm *timeptr, int firstweekday);
adddecl(static int iso8601wknum(const struct tm *timeptr);)
#endif

#ifdef __GNUC__
#define inline	__inline__
#else
#define inline	/**/
#endif

#define range(low, item, hi)	max(low, min(item, hi))

#if !defined(OS2) && !defined(MSDOS) && defined(HAVE_TZNAME)
extern char *tzname[2];
extern int daylight;
#endif

/* min --- return minimum of two numbers */

#ifndef __STDC__
static inline int
min(a, b)
int a, b;
#else
static inline int
min(int a, int b)
#endif
{
	return (a < b ? a : b);
}

/* max --- return maximum of two numbers */

#ifndef __STDC__
static inline int
max(a, b)
int a, b;
#else
static inline int
max(int a, int b)
#endif
{
	return (a > b ? a : b);
}

/* 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;
	auto char tbuf[100];
	int i;
	static short first = 1;
#ifdef POSIX_SEMANTICS
	static char *savetz = NULL;
	static int savetzlen = 0;
	char *tz;
#endif /* POSIX_SEMANTICS */

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

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

	/* quick check if we even need to bother */
	if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
		return 0;

#ifndef POSIX_SEMANTICS
	if (first) {
		tzset();
		first = 0;
	}
#else	/* POSIX_SEMANTICS */
	tz = getenv("TZ");
	if (first) {
		if (tz != NULL) {
			int tzlen = strlen(tz);

			savetz = (char *) malloc(tzlen + 1);
			if (savetz != NULL) {
				savetzlen = tzlen + 1;
				strcpy(savetz, tz);
			}
		}
		tzset();
		first = 0;
	}
	/* if we have a saved TZ, and it is different, recapture and reset */
	if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) {
		i = strlen(tz) + 1;
		if (i > savetzlen) {
			savetz = (char *) realloc(savetz, i);
			if (savetz) {
				savetzlen = i;
				strcpy(savetz, tz);
			}
		} else
			strcpy(savetz, tz);
		tzset();
	}
#endif	/* POSIX_SEMANTICS */

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

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

		case 'a':	/* abbreviated weekday name */
			if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
				strcpy(tbuf, "?");
			else
				strcpy(tbuf, days_a[timeptr->tm_wday]);
			break;

		case 'A':	/* full weekday name */
			if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
				strcpy(tbuf, "?");
			else
				strcpy(tbuf, days_l[timeptr->tm_wday]);
			break;

#ifdef SYSV_EXT
		case 'h':	/* abbreviated month name */
#endif
		case 'b':	/* abbreviated month name */
			if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
				strcpy(tbuf, "?");
			else
				strcpy(tbuf, months_a[timeptr->tm_mon]);
			break;

		case 'B':	/* full month name */
			if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
				strcpy(tbuf, "?");
			else
				strcpy(tbuf, months_l[timeptr->tm_mon]);
			break;

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

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

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

		case 'I':	/* hour, 12-hour clock, 01 - 12 */
			i = range(0, timeptr->tm_hour, 23);
			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 */
			i = range(0, timeptr->tm_mon, 11);
			sprintf(tbuf, "%02d", i + 1);
			break;

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

		case 'p':	/* am or pm based on 12-hour clock */
			i = range(0, timeptr->tm_hour, 23);
			if (i < 12)
				strcpy(tbuf, ampm[0]);
			else
				strcpy(tbuf, ampm[1]);
			break;

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

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

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

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

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

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

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

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

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

#ifdef SYSV_EXT
		case 'n':	/* same as \n */
			tbuf[0] = '\n';
			tbuf[1] = '\0';
			break;

		case 't':	/* same as \t */
			tbuf[0] = '\t';
			tbuf[1] = '\0';
			break;

		case 'D':	/* date as %m/%d/%y */
			strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr);
			break;

		case 'e':	/* day of month, blank padded */
			sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31));
			break;

		case 'r':	/* time as %I:%M:%S %p */
			strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr);
			break;

		case 'R':	/* time as %H:%M */
			strftime(tbuf, sizeof tbuf, "%H:%M", timeptr);
			break;

		case 'T':	/* time as %H:%M:%S */
			strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);
			break;
#endif

#ifdef SUNOS_EXT
		case 'k':	/* hour, 24-hour clock, blank pad */
			sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23));
			break;

		case 'l':	/* hour, 12-hour clock, 1 - 12, blank pad */
			i = range(0, timeptr->tm_hour, 23);
			if (i == 0)
				i = 12;
			else if (i > 12)
				i -= 12;
			sprintf(tbuf, "%2d", i);
			break;
#endif


#ifdef VMS_EXT
		case 'v':	/* date as dd-bbb-YYYY */
			sprintf(tbuf, "%02d-%3.3s-%4d",
				range(1, timeptr->tm_mday, 31),
				months_a[range(0, timeptr->tm_mon, 11)],
				timeptr->tm_year + 1900);
			for (i = 3; i < 6; i++)
				if (islower(tbuf[i]))
					tbuf[i] = toupper(tbuf[i]);
			break;
#endif


#ifdef POSIX2_DATE
		case 'C':
			sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100);
			break;


		case 'E':
		case 'O':
			/* POSIX locale extensions, ignored for now */
			goto again;

		case 'V':	/* week of year according ISO 8601 */
#if defined(GAWK) && defined(VMS_EXT)
		{
			extern int do_lint;
			extern void warning();
			static int warned = 0;

			if (! warned && do_lint) {
				warned = 1;
				warning(
	"conversion %%V added in P1003.2; for VMS style date, use %%v");
			}
		}
#endif
			sprintf(tbuf, "%02d", iso8601wknum(timeptr));
			break;

		case 'u':
		/* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
			sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 :
					timeptr->tm_wday);
			break;
#endif	/* POSIX2_DATE */
		default:
			tbuf[0] = '%';
			tbuf[1] = *format;
			tbuf[2] = '\0';
			break;
		}
		i = strlen(tbuf);
		if (i) {
			if (s + i < endp - 1) {
				strcpy(s, tbuf);
				s += i;
			} else
				return 0;
		}
	}
out:
	if (s < endp && *format == '\0') {
		*s = '\0';
		return (s - start);
	} else
		return 0;
}

#ifdef POSIX2_DATE
/* iso8601wknum --- compute week number according to ISO 8601 */

#ifndef __STDC__
static int
iso8601wknum(timeptr)
const struct tm *timeptr;
#else
static int
iso8601wknum(const struct tm *timeptr)
#endif
{
	/*
	 * From 1003.2:
	 *	If the week (Monday to Sunday) containing January 1
	 *	has four or more days in the new year, then it is week 1;
	 *	otherwise it is week 53 of the previous year, and the
	 *	next week is week 1.
	 *
	 * ADR: This means if Jan 1 was Monday through Thursday,
	 *	it was week 1, otherwise week 53.
	 */

	int weeknum, jan1day, diff;

	/* get week number, Monday as first day of the week */
	weeknum = weeknumber(timeptr, 1);

	/*
	 * With thanks and tip of the hatlo to tml at tik.vtt.fi
	 *
	 * What day of the week does January 1 fall on?
	 * We know that
	 *	(timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
	 *		(timeptr->tm_wday - jan1.tm_wday) MOD 7
	 * and that
	 * 	jan1.tm_yday == 0
	 * and that
	 * 	timeptr->tm_wday MOD 7 == timeptr->tm_wday
	 * from which it follows that. . .
 	 */
	jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);
	if (jan1day < 0)
		jan1day += 7;

	/*
	 * If Jan 1 was a Monday through Thursday, it was in
	 * week 1.  Otherwise it was last year's week 53, which is
	 * this year's week 0.
	 *
	 * What does that mean?
	 * If Jan 1 was Monday, the week number is exactly right, it can
	 *	never be 0.
	 * If it was Tuesday through Thursday, the weeknumber is one
	 *	less than it should be, so we add one.
	 * Otherwise, Friday, Saturday or Sunday, the week number is
	 * OK, but if it is 0, it needs to be 53.
	 */
	switch (jan1day) {
	case 1:		/* Monday */
		break;
	case 2:		/* Tuesday */
	case 3:		/* Wednedsday */
	case 4:		/* Thursday */
		weeknum++;
		break;
	case 5:		/* Friday */
	case 6:		/* Saturday */
	case 0:		/* Sunday */
		if (weeknum == 0)
			weeknum = 53;
		break;
	}
	return weeknum;
}
#endif

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

/* With thanks and tip of the hatlo to ado at elsie.nci.nih.gov */

#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 wday = timeptr->tm_wday;
	int ret;

	if (firstweekday == 1) {
		if (wday == 0)	/* sunday */
			wday = 6;
		else
			wday--;
	}
	ret = ((timeptr->tm_yday + 7 - wday) / 7);
	if (ret < 0)
		ret = 0;
	return ret;
}

#if 0
/* ADR --- I'm loathe to mess with ado's code ... */

Date:         Wed, 24 Apr 91 20:54:08 MDT
From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK>
To: arnold at audiofax.com

Hi Arnold,
in a process of fixing of strftime() in libraries on Atari ST I grabbed
some pieces of code from your own strftime.  When doing that it came
to mind that your weeknumber() function compiles a little bit nicer
in the following form:
/*
 * firstweekday is 0 if starting in Sunday, non-zero if in Monday
 */
{
    return (timeptr->tm_yday - timeptr->tm_wday +
	    (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7;
}
How nicer it depends on a compiler, of course, but always a tiny bit.

   Cheers,
   Michal
   ntomczak at vm.ucs.ualberta.ca
#endif

#ifdef	TEST_STRFTIME

/*
 * NAME:
 *	tst
 *
 * SYNOPSIS:
 *	tst
 *
 * DESCRIPTION:
 *	"tst" is a test driver for the function "strftime".
 *
 * OPTIONS:
 *	None.
 *
 * AUTHOR:
 *	Karl Vogel
 *	Control Data Systems, Inc.
 *	vogelke at c-17igp.wpafb.af.mil
 *
 * BUGS:
 *	None noticed yet.
 *
 * COMPILE:
 *	cc -o tst -DTEST_STRFTIME strftime.c
 */

/* ADR: I reformatted this to my liking, and deleted some unneeded code. */

#ifndef NULL
#include	<stdio.h>
#endif
#include	<sys/time.h>
#include	<string.h>

#define		MAXTIME		132

/*
 * Array of time formats.
 */

static char *array[] =
{
	"(%%A)      full weekday name, var length (Sunday..Saturday)  %A",
	"(%%B)       full month name, var length (January..December)  %B",
	"(%%C)                                               Century  %C",
	"(%%D)                                       date (%%m/%%d/%%y)  %D",
	"(%%E)                           Locale extensions (ignored)  %E",
	"(%%H)                          hour (24-hour clock, 00..23)  %H",
	"(%%I)                          hour (12-hour clock, 01..12)  %I",
	"(%%M)                                       minute (00..59)  %M",
	"(%%O)                           Locale extensions (ignored)  %O",
	"(%%R)                                 time, 24-hour (%%H:%%M)  %R",
	"(%%S)                                       second (00..61)  %S",
	"(%%T)                              time, 24-hour (%%H:%%M:%%S)  %T",
	"(%%U)    week of year, Sunday as first day of week (00..53)  %U",
	"(%%V)                    week of year according to ISO 8601  %V",
	"(%%W)    week of year, Monday as first day of week (00..53)  %W",
	"(%%X)     appropriate locale time representation (%H:%M:%S)  %X",
	"(%%Y)                           year with century (1970...)  %Y",
	"(%%Z) timezone (EDT), or blank if timezone not determinable  %Z",
	"(%%a)          locale's abbreviated weekday name (Sun..Sat)  %a",
	"(%%b)            locale's abbreviated month name (Jan..Dec)  %b",
	"(%%c)           full date (Sat Nov  4 12:02:33 1989)%n%t%t%t  %c",
	"(%%d)                             day of the month (01..31)  %d",
	"(%%e)               day of the month, blank-padded ( 1..31)  %e",
	"(%%h)                                should be same as (%%b)  %h",
	"(%%j)                            day of the year (001..366)  %j",
	"(%%k)               hour, 24-hour clock, blank pad ( 0..23)  %k",
	"(%%l)               hour, 12-hour clock, blank pad ( 0..12)  %l",
	"(%%m)                                        month (01..12)  %m",
	"(%%p)              locale's AM or PM based on 12-hour clock  %p",
	"(%%r)                   time, 12-hour (same as %%I:%%M:%%S %%p)  %r",
	"(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7]   %u",
	"(%%v)                                VAX date (dd-bbb-YYYY)  %v",
	"(%%w)                       day of week (0..6, Sunday == 0)  %w",
	"(%%x)                appropriate locale date representation  %x",
	"(%%y)                      last two digits of year (00..99)  %y",
	(char *) NULL
};

/* main routine. */

int
main(argc, argv)
int argc;
char **argv;
{
	long time();

	char *next;
	char string[MAXTIME];

	int k;
	int length;

	struct tm *tm;

	long clock;

	/* Call the function. */

	clock = time((long *) 0);
	tm = localtime(&clock);

	for (k = 0; next = array[k]; k++) {
		length = strftime(string, MAXTIME, next, tm);
		printf("%s\n", string);
	}

	exit(0);
}
#endif	/* TEST_STRFTIME */
EOF




More information about the tz mailing list