diff options
author | cinap_lenrek <cinap_lenrek@gmx.de> | 2012-05-22 05:16:23 +0200 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@gmx.de> | 2012-05-22 05:16:23 +0200 |
commit | f5dd576a50a1266291dfc99f8a55373bc5f53d01 (patch) | |
tree | 16d27c26ea9a2aad439c64322140ca5d1f2d356a | |
parent | b5565b0403d206e7d020c68b8c0ad42726125897 (diff) |
add seconds(1) command
-rw-r--r-- | sys/man/1/mtime | 3 | ||||
-rw-r--r-- | sys/man/1/seconds | 48 | ||||
-rw-r--r-- | sys/src/cmd/seconds.c | 472 |
3 files changed, 523 insertions, 0 deletions
diff --git a/sys/man/1/mtime b/sys/man/1/mtime index 5bfe4da32..abd831496 100644 --- a/sys/man/1/mtime +++ b/sys/man/1/mtime @@ -11,3 +11,6 @@ of each .IR file . .SH SOURCE .B /sys/src/cmd/mtime.c +.SH SEE ALSO +.IR du (1), +.IR seconds (1) diff --git a/sys/man/1/seconds b/sys/man/1/seconds new file mode 100644 index 000000000..2a8c301d3 --- /dev/null +++ b/sys/man/1/seconds @@ -0,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. diff --git a/sys/src/cmd/seconds.c b/sys/src/cmd/seconds.c new file mode 100644 index 000000000..d6718ffa7 --- /dev/null +++ b/sys/src/cmd/seconds.c @@ -0,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); |