[net.sources] fdate: a program to format the date in various ways

kre@munnari.OZ (Robert Elz) (09/21/84)

echo x - "READ_ME" 2>&1
sed "s/^X//" >"READ_ME" <<'!The!End!'
X
X    Spurred by the recent discussion in net.unix-wizards that produced
X    a suggestion on how to set the date over the network, but relied
X    upon facilities of the Bell System V date command, I submit for
X    your perusal, and possible use, the following old program of mine
X    that performs a similar function.  (Note: this is NOT one of the
X    system 5 rewrites!)
X
X    This is a sh archive, you should remove the mail header, and run
X    sh on the body to extract 3 files.
X
X    READ_ME	(that you are now reading)
X    fdate.1	A manual page
X    fdate.c	The source
X
X    On System V systems, this should be compiled as
X
X	    cc -O -o fdate -DSysV fdate.c
X
X    4.*bsd systems and version 7 should use
X
X	    cc -O -o fdate fdate.c
X
X    Other systems can take pot luck!		Sorry.
X
X    To use it to get the date in the form required as the arg of the
X    date(1) command, you should use:
X
X		date "`fdate %02y%02m%02d%02H%02M.%02S`"
X
X    I hope that there is someone out there who might find this useful,
X    otherwise I just wasted quite a lot of network time ...
X
X    Robert Elz.				decvax!mulga!kre
X
X
!The!End!

echo x - "fdate.1" 2>&1
sed "s/^X//" >"fdate.1" <<'!The!End!'
X.TH FDATE 1
X.if n .ds lq '
X.if n .ds rq '
X.if t .ds lq `
X.if t .ds rq '
X.MU
X.SH NAME
Xfdate \- format date to standard output
X.SH SYNOPSIS
X.B
Xfdate [-n]
X.BR [-F c ]
Xargs
X.SH DESCRIPTION
X.I Fdate
Xwrites its arguments to standard output
Xjust like
X.IR echo (1).
XWith the option
X.B \-n
Xthe newline normally supplied at the end is supressed.
X.PP
XHowever, unlike echo,
X.I fdate
Xrecognizes certain patterns in its arguments, and replaces them
Xby various parts of the current date and time.
X.PP
XEach conversion specification is introduced by
Xthe character
X.BR % .
X(If the option
X.BR \-F c
Xis used, then the character
X.I c
Xreplaces the
X.BR % .)
XAny other characters are simply copied to the output.
XFollowing the
X.BR % ,
Xthere may be
X.TP
X\-
Xan optional minus sign
X.RB "\*(lq" "\-" "\*(rq"
Xwhich specifies
X.I "left adjustment"
Xof the converted value
Xin the
Xindicated field;
X.TP
X\-
Xan optional digit string specifying a
X.I "field width;"
Xif the converted value has fewer characters
Xthan the field width
Xit will be blank-padded on the left (or right,
Xif the left-adjustment indicator has been
Xgiven) to make up the field width;
Xif the field width begins with a zero,
Xzero-padding will be done instead of blank-padding;
X.TP
X\-
Xan optional period
X.RB \&\*(lq . \&\*(rq
Xwhich serves to
Xseparate the field width from the
Xnext digit string;
X.TP
X\-
Xan optional digit string
Xspecifying a
X.I maximum
X.I width
Xwhich specifies
Xthe maximum number of characters
Xto be printed from this conversion;
X.TP
X\-
Xan optional plus sign
X.RB \*(lq + \*(rq
Xwhich serves to introduce the next digit string;
X.TP
X\-
Xan optional digit string
Xspecifying an increment to be applied to the current time.
XThe increment is in units indicated by the conversion character.
XOnly
X.RB \*(lq y \*(rq,
X.RB \*(lq m \*(rq,
X.RB \*(lq d \*(rq,
X.RB \*(lq H \*(rq,
X.RB \*(lq M \*(rq,
Xand
X.RB \*(lq S \*(rq
Xmay have increments, others are ignored.
XIncrements are applied in increasing order of signifigance, and
Xall are applied before any output.
X.TP
X\-
Xan optional second plus sign
X.RB \*(lq + \*(rq
Xindicating that this conversion is to produce no output.
X.TP
X\-
Xa character which indicates the value to be inserted.
X.PP
XThe conversion characters
Xand the values that replace them are
X.TP
X.B d
XThe day of the month (an integer from 1 to 31).
X.TP
X.B m
XThe month of the year (an integer from 1 to 12).
X.TP
X.B y
XThe current year (relative to the beginning of the century).
X.TP
X.B D
XThe day, month and year in the format: dd/mm/yy. If zero fill
Xwas specified, then each of the three fields is guaranteed to
Xbe exactly two digits wide (with a leading zero if necessary).
X.TP
X.B Y
XThe day, month, and year in the format: yy/mm/dd. Zero fill operates
Xin the same way as for
X.BR %D .
X.TP
X.B H
XThe hour of the day.
X.TP
X.B M
XThe minute of the hour.
X.TP
X.B S
XThe second of the minute.
X.TP
X.B X
XAn approximation to the number of milliseconds past the second of
Xthe minute.
X.TP
X.B T
XThe current time in the format: hh:mm:ss. Zero fill operates in the
Xsame way as for
X.BR %D .
X.TP
X.B R
XThe hour of the day, using a 12 hour clock (an integer between
X1 and 12), in place of the 24 hour clock used by
X.BR %H .
X.TP
X.B r
XThe current time in the format: hh:mm:ss XM, where
X.I X
Xis either
X.B A
Xor
X.BR P .
XThis gives a twelve hour clock, in contrast to the 24 hour clock
Xobtained through
X.BR %T .
XZero fill works in the same manner as for
X.BR %D .
X.TP
X.B w
XThe day of the week (an integer between 0 and 6).
X.TP
X.B A
XThe name of the day of the week.
X.TP
X.B a
XThe first three characters of the name of the day of the week
X(ie: equivalent to
X.BR %.3A ).
X.TP
X.B L
XThe name of the current month.
X.TP
X.B l
XThe first three characters of the name of the current month.
X.TP
X.B j
XThe julian day (day of the year), an integer between 1 and 366.
X.TP
X.B z
XThe name of the local timezone (as produced by
X.BR timezone (3)).
X.TP
X.B C
XA 24 character string containing the current time as formatted by
X.BR ctime (3),
Xbut without the trailing newline or null.
X.TP
X.B p
XEither
X.B am
Xor
X.BR pm ,
Xas appropriate.
X.TP
X.B P
XEither
X.B AM
Xor
X.BR PM ,
Xas appropriate
X.RB ( %p 
Xin capitals).
X.TP
X.B h
XOne of the four strings: \*(lqst\*(rq, \*(lqnd\*(rq, \*(lqrd\*(rq, or \*(lqth\*(rq, whichever
Xis appropriate for the current day of the month.
X.TP
X.B %
XA \*(lq%\*(rq; the field width, justification, fill, and maximum characters
Xindications are ignored if specified.
X.TP
X.B n
XA newline (widths etc. are ignored).
X.TP
X.B t
XA tab (widths are ignored here too).
X.PP
XA small field width will never cause truncation of a value, however
Xa small maximum width value will. A maximum width parameter larger
Xthan the width of the value is ignored, but a large field width
Xcauses padding (on the left or right, with spaces or zeroes).
X.PP
X.B Examples
X.br
XTo print the date in the same format that
X.BR date (1)
Xuses:
X.IP
Xfdate %.19C %z 19%02y
X.PP
XTo make a C program segment, containing a character string
Xwith the current date (for compilation date stamping):
X.RS
X.nf
X.sp
Xfdate 'char version [\^] = {%n%t"prog version nn, %0Y %0T"%n};'
X.fi
X.RE
X.PP
XTo set a shell variable to the current hour of the day:
X.IP
Xvar=`fdate %H`
X.PP
XTo run some commands in an hour from now:
X.IP
Xat `fdate %02+1H%02M`
X.br
Xcommands
X.br
X^D
X.SH "SEE ALSO"
Xctime(3),
Xdate(1),
Xecho(1),
Xsh(1)
X.SH BUGS
XThere is no %Z for zodiac signs.
X.br
XThere is no way to go back in time.
X(Though if the day, month, etc. are not wanted,
Xgoing forward 23 hours and 55 minutes is close enough
Xto going back 5 minutes).
X.br
XGoing forward a month from January 31st
Xis not what everybody expects.
X.br
XGoing forward around about the time that daylight saving turns
Xoff and on could also be considered strange.
X.br
XOn Bell System V, the \fB%X\fP conversion always returns 0.
!The!End!

echo x - "fdate.c" 2>&1
sed "s/^X//" >"fdate.c" <<'!The!End!'
Xstatic char sccsid[] = "@(#)fdate.c	4.3 (Melbourne) 82/01/25";
X
X/*
X *	fdate - formatted date output
X */
X
X#include        <stdio.h>
X#include	<sys/types.h>
X#include	<time.h>
X
X#ifdef SysV
Xstruct timeb {
X	time_t	time;
X	int	millitm;
X	int	timezone;
X};
X#else
X#include	<sys/timeb.h>
X#endif
X
X#define	MONTH	itoa(tim->tm_mon+1,p)
X#define	DAY	itoa(tim->tm_mday,p)
X#define	YEAR	itoa(tim->tm_year,p)
X#define	HOUR	itoa(tim->tm_hour,p)
X#define	MINUTE	itoa(tim->tm_min,p)
X#define	SECOND	itoa(tim->tm_sec,p)
X#define	JULIAN	itoa(tim->tm_yday+1,p)
X#define	WEEKDAY	itoa(tim->tm_wday,p)
X#define	MODHOUR	itoa(h,p)
X#define	MILISEC	itoa(timeb.millitm,p)
X
X#define	FILL(num) {if (fill == '0' && tim->tm_/**/num < 10) *p++ = '0';}
X
Xchar	*month[] = {
X	"January",
X	"February",
X	"March",
X	"April",
X	"May",
X	"June",
X	"July",
X	"August",
X	"September",
X	"October",
X	"November",
X	"December",
X};
X
Xint	monthdays[12] = {
X	31,
X	28,
X	31,
X	30,
X	31,
X	30,
X	31,
X	31,
X	30,
X	31,
X	30,
X	31
X};
X
Xchar	*days[] = {
X	"Sunday",
X	"Monday",
X	"Tuesday",
X	"Wednesday",
X	"Thursday",
X	"Friday",
X	"Saturday",
X};
X
Xchar	*suffix[] = {
X	"th",
X	"st",
X	"nd",
X	"rd",
X	"th",
X	"th",
X	"th",
X	"th",
X	"th",
X	"th",
X};
X
Xchar	*itoa();
X#ifndef SysV
Xchar	*timezone();
X#endif
Xstruct	tm	*localtime();
Xchar	*asctime();
Xint	nonl;
X
Xstruct	tm	incr	= { 0 };
X
X
X
Xmain(argc, argv)
X	int	argc;
X	char	**argv;
X{
X	register char	*aptr, c;
X	struct	timeb	timeb;
X	register struct	tm  *tim;
X	register int i, h, hflag;
X	char *p;
X	int	ndig, nchar, ljust;
X	char	fill;
X	char	lead;
X	int	hadincr;
X	int	ac;
X	char	**av;
X	char	 buf[60];
X	extern	char	_sobuf[];
X
X	setbuf(stdout, _sobuf);
X
X#ifdef SysV
X	tzset();
X#endif
X
X	ftime(&timeb);
X	tim = localtime(&timeb.time);
X
X	lead = '%';
X
X	while (argc > 1 && argv[1][0] == '-') {
X		switch (argv[1][1]) {
X
X		case 'n':
X			if (argv[1][2] == '\0') {
X				nonl++;
X				argv++;
X				argc--;
X				continue;
X			}
X			break;
X
X		case 'F':
X			if (argv[1][2] != '\0' && argv[1][3] == '\0') {
X				lead = argv[1][2];
X				argv++;
X				argc--;
X				continue;
X			}
X			break;
X		}
X		break;
X	}
X
X	ac = argc;
X	av = argv;
X
X	hadincr = 0;
X	while (--ac > 0) {
X		aptr = *++av;
X
X		while (c = *aptr++) {
X			if (c != lead)
X				continue;
X
X			i = 0;
X
X			if (*aptr == '-')
X				aptr++;
X			while (*aptr >= '0' && *aptr <= '9')
X				aptr++;
X			if (*aptr == '.')
X				while (*++aptr >= '0' && *aptr <= '9')
X					;
X			if (*aptr == '+') {
X				while (*++aptr >= '0' && *aptr <= '9')
X					i *= 10, i += *aptr - '0';
X				if (*aptr == '+')
X					aptr++;
X			}
X			hadincr++;
X
X			switch (*aptr++) {
X
X			case 'd':	incr.tm_mday = i;	break;
X			case 'm':	incr.tm_mon = i;	break;
X			case 'y':	incr.tm_year = i;	break;
X
X			case 'H':	incr.tm_hour = i;	break;
X			case 'M':	incr.tm_min = i;	break;
X			case 'S':	incr.tm_sec = i;	break;
X
X			default:	hadincr--;		break;
X			}
X		}
X	}
X
X	if (hadincr) {
X		tim->tm_sec += incr.tm_sec;
X		while (tim->tm_sec >= 60) {
X			tim->tm_min++;
X			tim->tm_sec -= 60;
X		}
X
X		tim->tm_min += incr.tm_min;
X		while (tim->tm_min >= 60) {
X			tim->tm_hour++;
X			tim->tm_min -= 60;
X		}
X
X		tim->tm_hour += incr.tm_hour;
X		while (tim->tm_hour >= 24) {
X			tim->tm_mday++;
X			tim->tm_hour -= 24;
X		}
X
X		setyear(tim->tm_year);
X		tim->tm_mday += incr.tm_mday;
X		while (tim->tm_mday > monthdays[tim->tm_mon]) {
X			tim->tm_mday -= monthdays[tim->tm_mon];
X			if (++tim->tm_mon >= 12) {
X				tim->tm_mon -= 12;
X				setyear(++tim->tm_year);
X			}
X		}
X
X		tim->tm_mon += incr.tm_mon;
X		while (tim->tm_mon >= 12) {
X			tim->tm_year++;
X			tim->tm_mon -= 12;
X		}
X
X		tim->tm_year += incr.tm_year;
X		setyear(tim->tm_year);
X
X		if (tim->tm_mday > monthdays[tim->tm_mon])
X			tim->tm_mday = monthdays[tim->tm_mon];
X
X		timeb.time = 0;
X		tim->tm_year += 1900;
X
X		for (i = 1970; i < tim->tm_year; i++)
X			timeb.time += dysize(i);
X
X		while (tim->tm_mon)
X			timeb.time += monthdays[--tim->tm_mon];
X
X		timeb.time += tim->tm_mday - 1;
X		timeb.time *= 24;
X		timeb.time += tim->tm_hour;
X		timeb.time *= 60;
X		timeb.time += tim->tm_min;
X		timeb.time *= 60;
X		timeb.time += tim->tm_sec;
X
X		timeb.time += (long)timeb.timezone*60;
X		if (tim->tm_isdst)
X			timeb.time -= 60*60;
X		tim = localtime(&timeb.time);
X	}
X
X	while (--argc > 0) {
X		aptr = *++argv;
X
X		while (c = *aptr++) {
X			for (p = buf; p < &buf[60]; )
X				*p++ = '\0';
X
X			ndig = nchar = ljust = 0;
X			fill = ' ';
X			p = buf;
X			if (c == lead) {
X				if (*aptr == '-') {
X					aptr++;
X					ljust++;
X				}
X				if (*aptr == '0') {
X					fill = '0';
X					aptr++;
X				}
X				i = 0;
X				while (*aptr >= '0' && *aptr <= '9') {
X					i *= 10;
X					i += *aptr++ - '0';
X				}
X				ndig = i;
X				if (*aptr == '.') {
X					i = 0;
X					while (*++aptr >= '0' && *aptr <= '9')
X						i *= 10, i += *aptr - '0';
X					nchar = i;
X				}
X				if (*aptr == '+') {
X					while (*++aptr >= '0' && *aptr <= '9')
X						;
X					if (*aptr == '+') {
X						aptr++;
X						if (*aptr)
X							aptr++;
X						continue;
X					}
X				}
X				switch (*aptr++) {
X				case '%':
X					putchar('%');
X					continue;
X				case 'n':
X					putchar('\n');
X					continue;
X				case 't':
X					putchar('\t');
X					continue;
X				case 'p':
X					if (tim->tm_hour >= 12)
X						p = "pm";
X					else
X						p = "am";
X					strcpy(buf, p);
X					break;
X				case 'P':
X					if (tim->tm_hour >= 12)
X						p = "PM";
X					else
X						p = "AM";
X					strcpy(buf, p);
X					break;
X				case 'h':
X					i = tim->tm_mday;
X					if (i > 10 && i < 19)
X						i = 5;
X					i %= 10;
X					strcpy(buf, suffix[i]);
X					break;
X				case 'm' :
X					p = MONTH;
X					break;
X				case 'd':
X					p = DAY;
X					break;
X				case 'y' :
X					p = YEAR;
X					break;
X				case 'Y':
X					FILL(year);
X					p = YEAR;
X					*p++ = '/';
X					FILL(mon+1);
X					p = MONTH;
X					*p++ = '/';
X					FILL(mday);
X					p = DAY;
X					break;
X				case 'D':
X					FILL(mday);
X					p = DAY;
X					*p++ = '/';
X					FILL(mon+1);
X					p = MONTH;
X					*p++ = '/';
X					FILL(year);
X					p = YEAR;
X					break;
X				case 'H':
X					p = HOUR;
X					break;
X				case 'M':
X					p = MINUTE;
X					break;
X				case 'S':
X					p = SECOND;
X					break;
X				case 'T':
X					FILL(hour);
X					p = HOUR;
X					*p++ = ':';
X					FILL(min);
X					p = MINUTE;
X					*p++ = ':';
X					FILL(sec);
X					p = SECOND;
X					break;
X				case 'j':
X					p = JULIAN;
X					break;
X				case 'w':
X					p = WEEKDAY;
X					break;
X				case 'R':
X					h = tim->tm_hour;
X					if ((h %= 12) == 0)
X						h = 12;
X					p = MODHOUR;
X					break;
X				case 'r':
X					if ((h = tim->tm_hour) >= 12)
X						hflag++;
X					if ((h %= 12) == 0)
X						h = 12;
X					if (fill == '0' && h < 10)
X						*p++ = '0';
X					p = MODHOUR;
X					*p++ = ':';
X					FILL(min);
X					p = MINUTE;
X					*p++ = ':';
X					FILL(sec);
X					p = SECOND;
X					*p++ = ' ';
X					if (hflag)
X						*p++ = 'P';
X					else 
X						*p++ = 'A';
X					*p++ = 'M';
X					break;
X				case 'l':
X					if (nchar == 0 || nchar > 3)
X						nchar = 3;
X				case 'L':
X					p = month[tim->tm_mon];
X					strcpy(buf, p);
X					break;
X				case 'a':
X					if (nchar == 0 || nchar > 3)
X						nchar = 3;
X				case 'A':
X					p = days[tim->tm_wday];
X					strcpy(buf, p);
X					break;
X				case 'X':
X					p = MILISEC;
X					break;
X				case 'C':
X					if (nchar == 0 || nchar > 24)
X						nchar = 24;
X					p = asctime(tim);
X					strcpy(buf, p);
X					break;
X				case 'z':
X#ifdef SysV
X					p = tzname[tim->tm_isdst];
X#else
X					p = timezone(timeb.timezone,tim->tm_isdst);
X#endif
X					strcpy(buf, p);
X					break;
X				default:
X					if (c == lead) {
X						putchar(c);
X						break;
X					}
X					fprintf(stderr, "fdate: bad format character - %c\n", *--aptr);
X					exit(2);
X
X				}	/* endsw */
X			} else {
X				putchar(c);
X				continue;
X			}
X
X			h = strlen(buf);
X			if (nchar && nchar < h)
X				h = nchar;
X			i = ndig - h;
X			if (!ljust)
X				while (i-- > 0)
X					putchar(fill);
X			p = buf;
X			while (h-- > 0)
X				putchar(*p++);
X			while (i-- > 0)
X				putchar(fill);
X
X		}	/* endwh */
X
X		if (argc > 1)
X			putchar(' ');
X	}	/* endwh */
X
X	if (!nonl)
X		putchar('\n');
X	exit(0);
X}
X
Xchar *
Xitoa(i, ptr)
X	register  int	i;
X	register  char	*ptr;
X{
X	char	buf[8];
X	register	char *p = buf;
X	do {
X		*p++ = i % 10 + '0';
X		i /= 10;
X	} while(i);
X
X	while (p > buf)
X		*ptr++ = *--p;
X	*ptr = '\0';
X	return (ptr);
X}
X
Xsetyear(yr)
X	register yr;
X{
X	if (yr > 2100) {
X		fprintf(stderr, "fdate: time got too big\n");
X		exit(1);
X	}
X
X	if (dysize(yr) == 366)
X		monthdays[1] = 29;
X	else
X		monthdays[1] = 28;
X}
X
X#ifdef SysV
X
Xftime(p)
X	struct timeb *p;
X{
X	(void) time (&p->time);
X	p->millitm = 0;
X	p->timezone = timezone / 60;
X}
X
Xint
Xdysize(yr)
X	register yr;
X{
X	if (yr % 4)
X		return (365);
X	if (yr % 100)
X		return (366);
X	if (yr % 400)
X		return (365);
X	return (366);
X}
X
X#endif
!The!End!
exit