shithub: riscv

Download patch

ref: f5dd576a50a1266291dfc99f8a55373bc5f53d01
parent: b5565b0403d206e7d020c68b8c0ad42726125897
author: cinap_lenrek <[email protected]>
date: Tue May 22 01:16:23 EDT 2012

add seconds(1) command

--- a/sys/man/1/mtime
+++ b/sys/man/1/mtime
@@ -11,3 +11,6 @@
 .IR file .
 .SH SOURCE
 .B /sys/src/cmd/mtime.c
+.SH SEE ALSO
+.IR du (1),
+.IR seconds (1)
--- /dev/null
+++ b/sys/man/1/seconds
@@ -1,0 +1,48 @@
+.TH SECONDS 1 
+.SH NAME
+seconds \- convert human-readable date (and time) to seconds since epoch
+.SH SYNOPSIS
+.B seconds
+.I date
+\&...
+.SH DESCRIPTION
+.I Seconds
+prints the number of seconds since 1 Jan 1970
+corresponding to one or more human-readable
+.IR date s.
+Each
+.I date
+must be
+.I one
+argument;
+it will usually be necessary to enclose it in quotes.
+.PP
+.I Seconds
+accepts a somewhat wider range of input than just output from
+.IR date (1).
+The main requirement is that the date must be fully specified,
+with a day of month, month and year
+in any order.
+The month must be an English name (or abbreviation),
+not a number, and the year must contain 4 digits.
+Unambiguous time-zone names are understood (i.e., not
+.LR IST )
+or time zones may be written as
+.IR ±hhmm .
+Case is ignored.
+.SH EXAMPLES
+Print the names of all files under
+.L \&.
+modified since the start of 23 May 2011.
+.IP
+.EX
+du -ta | awk '$1 >= '^`{seconds '23 may 2011'}^' {print $2}'
+.EE
+.SH SEE ALSO
+.IR date (1),
+.IR du (1),
+.IR mtime (1),
+.IR ctime (2)
+.SH BUGS
+All-numeric dates, popular in the USA, are simply ambiguous,
+more so if the year is truncated to 2 digits.
--- /dev/null
+++ b/sys/src/cmd/seconds.c
@@ -1,0 +1,472 @@
+/*
+ * seconds absolute_date ... - convert absolute_date to seconds since epoch
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+typedef ulong Time;
+
+enum {
+	AM, PM, HR24,
+
+	/* token types */
+	Month = 1,
+	Year,
+	Day,
+	Timetok,
+	Tz,
+	Dtz,
+	Ignore,
+	Ampm,
+
+	Maxtok		= 6, /* only this many chars are stored in datetktbl */
+	Maxdateflds	= 25,
+};
+
+/*
+ * macros for squeezing values into low 7 bits of "value".
+ * all timezones we care about are divisible by 10, and the largest value
+ * (780) when divided is 78.
+ */
+#define TOVAL(tp, v)	((tp)->value = (v) / 10)
+#define FROMVAL(tp)	((tp)->value * 10)	/* uncompress */
+
+/* keep this struct small since we have an array of them */
+typedef struct {
+	char	token[Maxtok];
+	char	type;
+	schar	value;
+} Datetok;
+
+int dtok_numparsed;
+
+/* forwards */
+Datetok	*datetoktype(char *s, int *bigvalp);
+
+static Datetok datetktbl[];
+static unsigned szdatetktbl;
+
+/* parse 1- or 2-digit number, advance *cpp past it */
+static int
+eatnum(char **cpp)
+{
+	int c, x;
+	char *cp;
+
+	cp = *cpp;
+	c = *cp;
+	if (!isascii(c) || !isdigit(c))
+		return -1;
+	x = c - '0';
+
+	c = *++cp;
+	if (isascii(c) && isdigit(c)) {
+		x = 10*x + c - '0';
+		cp++;
+	}
+	*cpp = cp;
+	return x;
+}
+
+/* return -1 on failure */
+int
+parsetime(char *time, Tm *tm)
+{
+	tm->hour = eatnum(&time);
+	if (tm->hour == -1 || *time++ != ':')
+		return -1;			/* only hour; too short */
+
+	tm->min = eatnum(&time);
+	if (tm->min == -1)
+		return -1;
+	if (*time++ != ':') {
+		tm->sec = 0;
+		return 0;			/* no seconds; okay */
+	}
+
+	tm->sec = eatnum(&time);
+	if (tm->sec == -1)
+		return -1;
+
+	/* this may be considered too strict.  garbage at end of time? */
+	return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
+}
+
+/*
+ * try to parse pre-split timestr in fields as an absolute date
+ */
+int
+tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
+{
+	int i, mer = HR24, bigval = -1;
+	long flg = 0, ty;
+	char *p;
+	char upzone[32];
+	Datetok *tp;
+
+	now = localtime(time(0));	/* default to local time (zone) */
+	tm->tzoff = now->tzoff;
+	strncpy(tm->zone, now->zone, sizeof tm->zone);
+
+	tm->mday = tm->mon = tm->year = -1;	/* mandatory */
+	tm->hour = tm->min = tm->sec = 0;
+	dtok_numparsed = 0;
+
+	for (i = 0; i < nf; i++) {
+		if (fields[i][0] == '\0')
+			continue;
+		tp = datetoktype(fields[i], &bigval);
+		ty = (1L << tp->type) & ~(1L << Ignore);
+		if (flg & ty)
+			return -1;		/* repeated type */
+		flg |= ty;
+		switch (tp->type) {
+		case Year:
+			tm->year = bigval;
+			if (tm->year < 1970 || tm->year > 2106)
+				return -1;	/* can't represent in ulong */
+			/* convert 4-digit year to 1900 origin */
+			if (tm->year >= 1900)
+				tm->year -= 1900;
+			break;
+		case Day:
+			tm->mday = bigval;
+			break;
+		case Month:
+			tm->mon = tp->value - 1; /* convert to zero-origin */
+			break;
+		case Timetok:
+			if (parsetime(fields[i], tm) < 0)
+				return -1;
+			break;
+		case Dtz:
+		case Tz:
+			tm->tzoff = FROMVAL(tp);
+			/* tm2sec needs the name in upper case */
+			strcpy(upzone, fields[i]);
+			for (p = upzone; *p; p++)
+				if (isascii(*p) && islower(*p))
+					*p = toupper(*p);
+			strncpy(tm->zone, upzone, sizeof tm->zone);
+			break;
+		case Ignore:
+			break;
+		case Ampm:
+			mer = tp->value;
+			break;
+		default:
+			return -1;	/* bad token type: CANTHAPPEN */
+		}
+	}
+	if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
+		return -1;		/* missing component */
+	if (mer == PM)
+		tm->hour += 12;
+	return 0;
+}
+
+int
+prsabsdate(char *timestr, Tm *now, Tm *tm)
+{
+	int nf;
+	char *fields[Maxdateflds];
+	static char delims[] = "- \t\n/,";
+
+	nf = gettokens(timestr, fields, nelem(fields), delims+1);
+	if (nf > nelem(fields))
+		return -1;
+	if (tryabsdate(fields, nf, now, tm) < 0) {
+		char *p = timestr;
+
+		/*
+		 * could be a DEC-date; glue it all back together, split it
+		 * with dash as a delimiter and try again.  Yes, this is a
+		 * hack, but so are DEC-dates.
+		 */
+		while (--nf > 0) {
+			while (*p++ != '\0')
+				;
+			p[-1] = ' ';
+		}
+		nf = gettokens(timestr, fields, nelem(fields), delims);
+		if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+int
+validtm(Tm *tm)
+{
+	if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
+	    tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
+	    tm->min < 0 || tm->min > 59 ||
+	    tm->sec < 0 || tm->sec > 61)	/* allow 2 leap seconds */
+		return 0;
+	return 1;
+}
+
+Time
+seconds(char *timestr)
+{
+	Tm date;
+
+	memset(&date, 0, sizeof date);
+	if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
+		return -1;
+	return validtm(&date)? tm2sec(&date): -1;
+}
+
+int
+convert(char *timestr)
+{
+	char *copy;
+	Time tstime;
+
+	copy = strdup(timestr);
+	if (copy == nil)
+		sysfatal("out of memory");
+	tstime = seconds(copy);
+	free(copy);
+	if (tstime == -1) {
+		fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
+		return 1;
+	}
+	print("%lud\n", tstime);
+	return 0;
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s date-time ...\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, sts;
+
+	sts = 0;
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+	if (argc == 0)
+		usage();
+	for (i = 0; i < argc; i++)
+		sts |= convert(argv[i]);
+	exits(sts != 0? "bad": 0);
+}
+
+/*
+ * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
+ * is WAY faster than the generic bsearch().
+ */
+Datetok *
+datebsearch(char *key, Datetok *base, unsigned nel)
+{
+	int cmp;
+	Datetok *last = base + nel - 1, *pos;
+
+	while (last >= base) {
+		pos = base + ((last - base) >> 1);
+		cmp = key[0] - pos->token[0];
+		if (cmp == 0) {
+			cmp = strncmp(key, pos->token, Maxtok);
+			if (cmp == 0)
+				return pos;
+		}
+		if (cmp < 0)
+			last = pos - 1;
+		else
+			base = pos + 1;
+	}
+	return 0;
+}
+
+Datetok *
+datetoktype(char *s, int *bigvalp)
+{
+	char *cp = s;
+	char c = *cp;
+	static Datetok t;
+	Datetok *tp = &t;
+
+	if (isascii(c) && isdigit(c)) {
+		int len = strlen(cp);
+
+		if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
+			tp->type = Timetok;
+		else {
+			if (bigvalp != nil)
+				*bigvalp = atoi(cp); /* won't fit in tp->value */
+			if (len == 4)
+				tp->type = Year;
+			else if (++dtok_numparsed == 1)
+				tp->type = Day;
+			else
+				tp->type = Year;
+		}
+	} else if (c == '-' || c == '+') {
+		int val = atoi(cp + 1);
+		int hr =  val / 100;
+		int min = val % 100;
+
+		val = hr*60 + min;
+		TOVAL(tp, c == '-'? -val: val);
+		tp->type = Tz;
+	} else {
+		char lowtoken[Maxtok+1];
+		char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
+
+		/* copy to lowtoken to avoid modifying s */
+		while ((c = *cp++) != '\0' && ltp < endltp)
+			*ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
+		*ltp = '\0';
+		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
+		if (tp == nil) {
+			tp = &t;
+			tp->type = Ignore;
+		}
+	}
+	return tp;
+}
+
+
+/*
+ * to keep this table reasonably small, we divide the lexval for Tz and Dtz
+ * entries by 10 and truncate the text field at MAXTOKLEN characters.
+ * the text field is not guaranteed to be NUL-terminated.
+ */
+static Datetok datetktbl[] = {
+/*	text		token	lexval */
+	"acsst",	Dtz,	63,	/* Cent. Australia */
+	"acst",		Tz,	57,	/* Cent. Australia */
+	"adt",		Dtz,	-18,	/* Atlantic Daylight Time */
+	"aesst",	Dtz,	66,	/* E. Australia */
+	"aest",		Tz,	60,	/* Australia Eastern Std Time */
+	"ahst",		Tz,	60,	/* Alaska-Hawaii Std Time */
+	"am",		Ampm,	AM,
+	"apr",		Month,	4,
+	"april",	Month,	4,
+	"ast",		Tz,	-24,	/* Atlantic Std Time (Canada) */
+	"at",		Ignore,	0,	/* "at" (throwaway) */
+	"aug",		Month,	8,
+	"august",	Month,	8,
+	"awsst",	Dtz,	54,	/* W. Australia */
+	"awst",		Tz,	48,	/* W. Australia */
+	"bst",		Tz,	6,	/* British Summer Time */
+	"bt",		Tz,	18,	/* Baghdad Time */
+	"cadt",		Dtz,	63,	/* Central Australian DST */
+	"cast",		Tz,	57,	/* Central Australian ST */
+	"cat",		Tz,	-60,	/* Central Alaska Time */
+	"cct",		Tz,	48,	/* China Coast */
+	"cdt",		Dtz,	-30,	/* Central Daylight Time */
+	"cet",		Tz,	6,	/* Central European Time */
+	"cetdst",	Dtz,	12,	/* Central European Dayl.Time */
+	"cst",		Tz,	-36,	/* Central Standard Time */
+	"dec",		Month,	12,
+	"decemb",	Month,	12,
+	"dnt",		Tz,	6,	/* Dansk Normal Tid */
+	"dst",		Ignore,	0,
+	"east",		Tz,	-60,	/* East Australian Std Time */
+	"edt",		Dtz,	-24,	/* Eastern Daylight Time */
+	"eet",		Tz,	12,	/* East. Europe, USSR Zone 1 */
+	"eetdst",	Dtz,	18,	/* Eastern Europe */
+	"est",		Tz,	-30,	/* Eastern Standard Time */
+	"feb",		Month,	2,
+	"februa",	Month,	2,
+	"fri",		Ignore,	5,
+	"friday",	Ignore,	5,
+	"fst",		Tz,	6,	/* French Summer Time */
+	"fwt",		Dtz,	12,	/* French Winter Time  */
+	"gmt",		Tz,	0,	/* Greenwish Mean Time */
+	"gst",		Tz,	60,	/* Guam Std Time, USSR Zone 9 */
+	"hdt",		Dtz,	-54,	/* Hawaii/Alaska */
+	"hmt",		Dtz,	18,	/* Hellas ? ? */
+	"hst",		Tz,	-60,	/* Hawaii Std Time */
+	"idle",		Tz,	72,	/* Intl. Date Line, East */
+	"idlw",		Tz,	-72,	/* Intl. Date Line, West */
+	"ist",		Tz,	12,	/* Israel */
+	"it",		Tz,	22,	/* Iran Time */
+	"jan",		Month,	1,
+	"januar",	Month,	1,
+	"jst",		Tz,	54,	/* Japan Std Time,USSR Zone 8 */
+	"jt",		Tz,	45,	/* Java Time */
+	"jul",		Month,	7,
+	"july",		Month,	7,
+	"jun",		Month,	6,
+	"june",		Month,	6,
+	"kst",		Tz,	54,	/* Korea Standard Time */
+	"ligt",		Tz,	60,	/* From Melbourne, Australia */
+	"mar",		Month,	3,
+	"march",	Month,	3,
+	"may",		Month,	5,
+	"mdt",		Dtz,	-36,	/* Mountain Daylight Time */
+	"mest",		Dtz,	12,	/* Middle Europe Summer Time */
+	"met",		Tz,	6,	/* Middle Europe Time */
+	"metdst",	Dtz,	12,	/* Middle Europe Daylight Time*/
+	"mewt",		Tz,	6,	/* Middle Europe Winter Time */
+	"mez",		Tz,	6,	/* Middle Europe Zone */
+	"mon",		Ignore,	1,
+	"monday",	Ignore,	1,
+	"mst",		Tz,	-42,	/* Mountain Standard Time */
+	"mt",		Tz,	51,	/* Moluccas Time */
+	"ndt",		Dtz,	-15,	/* Nfld. Daylight Time */
+	"nft",		Tz,	-21,	/* Newfoundland Standard Time */
+	"nor",		Tz,	6,	/* Norway Standard Time */
+	"nov",		Month,	11,
+	"novemb",	Month,	11,
+	"nst",		Tz,	-21,	/* Nfld. Standard Time */
+	"nt",		Tz,	-66,	/* Nome Time */
+	"nzdt",		Dtz,	78,	/* New Zealand Daylight Time */
+	"nzst",		Tz,	72,	/* New Zealand Standard Time */
+	"nzt",		Tz,	72,	/* New Zealand Time */
+	"oct",		Month,	10,
+	"octobe",	Month,	10,
+	"on",		Ignore,	0,	/* "on" (throwaway) */
+	"pdt",		Dtz,	-42,	/* Pacific Daylight Time */
+	"pm",		Ampm,	PM,
+	"pst",		Tz,	-48,	/* Pacific Standard Time */
+	"sadt",		Dtz,	63,	/* S. Australian Dayl. Time */
+	"sast",		Tz,	57,	/* South Australian Std Time */
+	"sat",		Ignore,	6,
+	"saturd",	Ignore,	6,
+	"sep",		Month,	9,
+	"sept",		Month,	9,
+	"septem",	Month,	9,
+	"set",		Tz,	-6,	/* Seychelles Time ?? */
+	"sst",		Dtz,	12,	/* Swedish Summer Time */
+	"sun",		Ignore,	0,
+	"sunday",	Ignore,	0,
+	"swt",		Tz,	6,	/* Swedish Winter Time  */
+	"thu",		Ignore,	4,
+	"thur",		Ignore,	4,
+	"thurs",	Ignore,	4,
+	"thursd",	Ignore,	4,
+	"tue",		Ignore,	2,
+	"tues",		Ignore,	2,
+	"tuesda",	Ignore,	2,
+	"ut",		Tz,	0,
+	"utc",		Tz,	0,
+	"wadt",		Dtz,	48,	/* West Australian DST */
+	"wast",		Tz,	42,	/* West Australian Std Time */
+	"wat",		Tz,	-6,	/* West Africa Time */
+	"wdt",		Dtz,	54,	/* West Australian DST */
+	"wed",		Ignore,	3,
+	"wednes",	Ignore,	3,
+	"weds",		Ignore,	3,
+	"wet",		Tz,	0,	/* Western Europe */
+	"wetdst",	Dtz,	6,	/* Western Europe */
+	"wst",		Tz,	48,	/* West Australian Std Time */
+	"ydt",		Dtz,	-48,	/* Yukon Daylight Time */
+	"yst",		Tz,	-54,	/* Yukon Standard Time */
+	"zp4",		Tz,	-24,	/* GMT +4  hours. */
+	"zp5",		Tz,	-30,	/* GMT +5  hours. */
+	"zp6",		Tz,	-36,	/* GMT +6  hours. */
+};
+static unsigned szdatetktbl = nelem(datetktbl);