From: ori@eigenstate.org
To: ori@eigenstate.org, 9front@9front.org
Subject: Re: [9front] libc: date handling improvements
Date: Sat, 30 May 2020 21:57:30 -0700 [thread overview]
Message-ID: <865929461018EA402453EDE353D7838B@eigenstate.org> (raw)
In-Reply-To: <AE583BF0F480F9D2360D51764C6A37CC@eigenstate.org>
> I implemented libdate -- but the right place to put
> it is in libc, replacing our ctime and gmtime apis.
>
> Follow ups will replace seconds(1), upas time parsing
> code, and whatever other instances of hand rolled date
> parsing and formatting can be found.
>
> Here's a patch that does this. Testing appreciated,
> and follow ups that I haven't done yet would also be
> great :)
And here's a rough replacement of seconds(1). Testing
needed, especially since it's likely we no longer parse
some date formats that we used to. The old code was
hacky and heuristic filled, and the grammar wasn't very
clear.
If there's a date format this no longer parses for you,
let me know and I'll add it.
diff -r d45cfe9072cd sys/src/cmd/seconds.c
--- a/sys/src/cmd/seconds.c Sat May 30 13:03:05 2020 +0200
+++ b/sys/src/cmd/seconds.c Sat May 30 21:56:13 2020 -0700
@@ -1,237 +1,38 @@
+#include <u.h>
+#include <libc.h>
+#include <date.h>
+
/*
* 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,
+char *formats[] = {
+ /* asctime */
+ "W MMM DD hh:mm:ss ZZZ YYYY",
+ /* RFC5322 */
+ "?W ?DD ?MMM ?YYYY hh:mm:ss ?Z",
+ "?W, DD-?MM-YY hh:mm:ss ?Z",
+ /* RFC822/RFC8222 */
+ "DD MMM YY hh:mm ZZZ",
+ "DD MMM YY hh:mm Z",
+ /* RFC850 */
+ "W, DD-MMM-YY hh:mm:ss MST",
+ /* RFC1123 */
+ "WW, DD MMM YYYY hh:mm:ss ZZZ",
+ /* RFC1123Z */
+ "WW, DD MMM YYYY hh:mm:ss ZZ",
+ /* RFC3339 */
+ "YYYY-MM-DD[T]hh:mm:ss[Z]ZZ",
+ "YYYY-MM-DD[T]hh:mm:ss[Z]Z",
+ "YYYY-MM-DD[T]hh:mm:ss ZZ",
+ "YYYY-MM-DD[T]hh:mm:ss Z",
+ /* RFC 3339 and human-readable variants */
+ "YYYY-MM-DD hh:mm:ss",
+ "YYYY-MM-DD hh:mm:ss ?Z",
+ "YYYY-MM-DD [@] hh:mm:ss",
+ "YYYY-MM-DD [@] hh:mm:ss ?Z",
+ nil
};
-/*
- * 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;
- 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:
- /* tm2sec mangles timezones, so we do our own handling */
- tm->tzoff = FROMVAL(tp);
- snprint(tm->zone, sizeof(tm->zone), "GMT");
- 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) - 60*date.tzoff: -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)
{
@@ -242,225 +43,31 @@
void
main(int argc, char **argv)
{
- int i, sts;
+ Tmd tm;
+ char **f, *fmt;
+ int i;
- sts = 0;
+ fmt = nil;
ARGBEGIN{
+ case 'f':
+ fmt = EARGF(usage());
+ break;
default:
usage();
- }ARGEND
- if (argc == 0)
- usage();
- for (i = 0; i < argc; i++)
- sts |= convert(argv[i]);
- exits(sts != 0? "bad": 0);
+ }ARGEND;
+
+ for(i = 0; i < argc; i++){
+ if(fmt != nil){
+ if(tmparse(fmt, argv[i], nil, &tm) != nil)
+ goto Found;
+ }else{
+ for(f = formats; *f != nil; f++)
+ if(tmparse(&tm, *f, argv[i], nil) != nil)
+ goto Found;
+ }
+ sysfatal("tmparse: %r");
+Found:
+ print("%lld\n", tm.abs);
+ }
+ exits(nil);
}
-
-/*
- * 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);
next prev parent reply other threads:[~2020-05-31 4:57 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-05-31 3:39 ori
2020-05-31 4:57 ` ori [this message]
2020-06-06 20:56 ` [9front] " ori
2020-06-13 19:40 ` ori
2020-06-15 6:02 ` ori
2020-06-15 6:03 ori
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=865929461018EA402453EDE353D7838B@eigenstate.org \
--to=ori@eigenstate.org \
--cc=9front@9front.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).