From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mimir.eigenstate.org ([206.124.132.107]) by ewsd; Mon Jun 15 02:02:53 EDT 2020 Received: from abbatoir.fios-router.home (pool-74-101-2-6.nycmny.fios.verizon.net [74.101.2.6]) by mimir.eigenstate.org (OpenSMTPD) with ESMTPSA id ba0cee25 (TLSv1.2:ECDHE-RSA-AES256-SHA:256:NO); Sun, 14 Jun 2020 23:02:37 -0700 (PDT) Message-ID: <93898413D555D17F598826842E67E234@eigenstate.org> To: ori@eigenstate.org, 9front@9front.org Subject: Re: [9front] libc: date handling improvements Date: Sun, 14 Jun 2020 23:02:36 -0700 From: ori@eigenstate.org In-Reply-To: <364F2C1F2CDA34BA1E40FA14CDBA8767@eigenstate.org> MIME-Version: 1.0 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit List-ID: <9front.9front.org> List-Help: X-Glyph: ➈ X-Bullshit: flexible realtime service service >> 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 :) >> > > Committed -- please let me know if you notice any > breakage. One more attempt -- and now, because this shit is annoying enough to test, I started writing some regression tests. Hoping to add other shit to the tests, too, as I start touching other parts of the system. https://git.sr.ht/~ori/regress Attached, with a few bugfixes: - Fix a missed case in the change from 0-based years to 1900-based years. - Fix a bug with negative modulo for years before 1970. - Add a few more common formats to seconds(1), to match the documentation. - Make seconds(1) more flexible in the formats accepted: when possible, build up dates from parts. (Perf is fine, in spite of a lot more dates tried.) diff -r 6055167dc76a sys/include/libc.h --- a/sys/include/libc.h Mon Jun 15 00:12:57 2020 +0200 +++ b/sys/include/libc.h Sun Jun 14 23:01:58 2020 -0700 @@ -314,22 +314,45 @@ /* * Time-of-day */ +typedef struct Tzone Tzone; +#pragma incomplete Tzone + typedef struct Tm { - int sec; - int min; - int hour; - int mday; - int mon; - int year; - int wday; - int yday; - char zone[4]; - int tzoff; + vlong abs; /* seconds since Jan 1 1970, GMT */ + int nsec; /* nseconds (range 0...1e9) */ + int sec; /* seconds (range 0..60) */ + int min; /* minutes (0..59) */ + int hour; /* hours (0..23) */ + int mday; /* day of the month (1..31) */ + int mon; /* month of the year (0..11) */ + int year; /* year A.D. */ + int wday; /* day of week (0..6, Sunday = 0) */ + int yday; /* day of year (0..365) */ + char zone[16]; /* time zone name */ + int tzoff; /* time zone delta from GMT */ + Tzone *tz; /* time zone associated with this date */ } Tm; +typedef +struct Tmfmt { + char *fmt; + Tm *tm; +} Tmfmt; + +#pragma varargck type "τ" Tmfmt + +extern Tzone* tmgetzone(char *name); +extern Tm* tmnow(Tm*, Tzone*); +extern Tm* tmtime(Tm*, vlong, Tzone*); +extern Tm* tmtimens(Tm*, vlong, int, Tzone*); +extern Tm* tmparse(Tm*, char*, char*, Tzone*); +extern Tm* tmnorm(Tm*); +extern Tmfmt tmfmt(Tm*, char*); +extern void tmfmtinstall(void); + extern Tm* gmtime(long); extern Tm* localtime(long); extern char* asctime(Tm*); diff -r 6055167dc76a sys/man/1/seconds --- a/sys/man/1/seconds Mon Jun 15 00:12:57 2020 +0200 +++ b/sys/man/1/seconds Sun Jun 14 23:01:58 2020 -0700 @@ -3,6 +3,9 @@ seconds \- convert human-readable date (and time) to seconds since epoch .SH SYNOPSIS .B seconds +[ +.I -f +] .I date \&... .SH DESCRIPTION @@ -18,16 +21,15 @@ it will usually be necessary to enclose it in quotes. .PP .I Seconds -accepts a somewhat wider range of input than just output from +accepts a superset of a number of well known formats. .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 +Among these are RFC5322, RFC822, RFC2822, RFC850, +RFC1123, and several other commmonly used formats. + +.PP +The -f flag allows using a specific format, specified +in the syntax of +.IR tmdate (2). .IR ±hhmm . Case is ignored. .SH EXAMPLES diff -r 6055167dc76a sys/src/cmd/seconds.c --- a/sys/src/cmd/seconds.c Mon Jun 15 00:12:57 2020 +0200 +++ b/sys/src/cmd/seconds.c Sun Jun 14 23:01:58 2020 -0700 @@ -1,236 +1,56 @@ -/* - * seconds absolute_date ... - convert absolute_date to seconds since epoch - */ - #include #include -#include -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 *knownfmt[] = { + /* asctime */ + "W MMM DD hh:mm:ss ?Z YYYY", + /* RFC3339 */ + "YYYY-MM-DD[T]hh:mm:ss[Z]?Z", + "YYYY-MM-DD[T]hh:mm:ss[Z]?Z", + "YYYY-MM-DD[T]hh:mm:ss ?Z", + "YYYY-MM-DD[T]hh:mm:ss?Z", }; -/* - * 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 */ +char *datefmt[] = { + /* RFC5322 */ + "?W ?DD ?MMM ?YYYY", + "?W, DD-?MM-YY", + /* RFC822/RFC2822 */ + "DD MMM YYYY", + "DD MMM YY", + /* RFC850 */ + "W, DD-MMM-YY", + /* RFC1123 */ + "WW, DD MMM YYYY", + /* RFC 3339 and human-readable variants */ + "YYYY-MM-DD", + "YYYY-MM-DD [@] ", + /* random formats */ + "?DD ?MM ?YYYY", + "MMM ?DD ?YYYY", + "YYYY ?MM ?DD", + "YYYY ?DD ?MM", + "YYYY/MM?/DD?", + "MMM YYYY ?DD", + "?DD YYYY MMM", + "MM/DD/YYYY", + nil +}; -/* keep this struct small since we have an array of them */ -typedef struct { - char token[Maxtok]; - char type; - schar value; -} Datetok; +char *timefmt[] = { + " hh:mm:ss", + " hh:mm", + " hh", + "", + nil, +}; -int dtok_numparsed; +char *zonefmt[] = { + " ?Z", + "", + nil, +}; -/* 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) @@ -239,228 +59,44 @@ exits("usage"); } +/* + * seconds absolute_date ... - convert absolute_date to seconds since epoch + */ void main(int argc, char **argv) { - int i, sts; + char **f, **df, **tf, **zf, *fmt, buf[256]; + Tm tm; + 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(&tm, fmt, argv[i], nil) != nil) + goto Found; + }else{ + for(f = knownfmt; *f != nil; f++) + if(tmparse(&tm, *f, argv[i], nil) != nil) + goto Found; + for(df = datefmt; *df; df++) + for(tf = timefmt; *tf; tf++) + for(zf = zonefmt; *zf; zf++){ + snprint(buf, sizeof(buf), "%s%s%s", *df, *tf, *zf); + if(tmparse(&tm, buf, 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); diff -r 6055167dc76a sys/src/libc/9sys/ctime.c --- a/sys/src/libc/9sys/ctime.c Mon Jun 15 00:12:57 2020 +0200 +++ b/sys/src/libc/9sys/ctime.c Sun Jun 14 23:01:58 2020 -0700 @@ -33,269 +33,34 @@ #include #include -static char dmsize[12] = +Tm* +localtime(long tim) { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 -}; + static Tm tm; + Tzone *tz; -/* - * The following table is used for 1974 and 1975 and - * gives the day number of the first day after the Sunday of the - * change. - */ + /* No error checking: the API doesn't allow it. */ + tz = tmgetzone("local"); + tmtime(&tm, tim, tz); + return &tm; -static int dysize(int); -static void ct_numb(char*, int); - -#define TZSIZE 150 -static void readtimezone(void); -static int rd_name(char**, char*); -static int rd_long(char**, long*); -static -struct -{ - char stname[4]; - char dlname[4]; - long stdiff; - long dldiff; - long dlpairs[TZSIZE]; -} timezone; - -char* -ctime(long t) -{ - return asctime(localtime(t)); } Tm* -localtime(long tim) +gmtime(long abs) { - Tm *ct; - long t, *p; - int dlflag; - - if(timezone.stname[0] == 0) - readtimezone(); - t = tim + timezone.stdiff; - dlflag = 0; - for(p = timezone.dlpairs; *p; p += 2) - if(t >= p[0]) - if(t < p[1]) { - t = tim + timezone.dldiff; - dlflag++; - break; - } - ct = gmtime(t); - if(dlflag){ - strcpy(ct->zone, timezone.dlname); - ct->tzoff = timezone.dldiff; - } else { - strcpy(ct->zone, timezone.stname); - ct->tzoff = timezone.stdiff; - } - return ct; -} - -Tm* -gmtime(long tim) -{ - int d0, d1; - long hms, day; - static Tm xtime; - - /* - * break initial number into days - */ - hms = tim % 86400L; - day = tim / 86400L; - if(hms < 0) { - hms += 86400L; - day -= 1; - } - - /* - * generate hours:minutes:seconds - */ - xtime.sec = hms % 60; - d1 = hms / 60; - xtime.min = d1 % 60; - d1 /= 60; - xtime.hour = d1; - - /* - * day is the day number. - * generate day of the week. - * The addend is 4 mod 7 (1/1/1970 was Thursday) - */ - - xtime.wday = (day + 7340036L) % 7; - - /* - * year number - */ - if(day >= 0) - for(d1 = 1970; day >= dysize(d1); d1++) - day -= dysize(d1); - else - for (d1 = 1970; day < 0; d1--) - day += dysize(d1-1); - xtime.year = d1-1900; - xtime.yday = d0 = day; - - /* - * generate month - */ - - if(dysize(d1) == 366) - dmsize[1] = 29; - for(d1 = 0; d0 >= dmsize[d1]; d1++) - d0 -= dmsize[d1]; - dmsize[1] = 28; - xtime.mday = d0 + 1; - xtime.mon = d1; - strcpy(xtime.zone, "GMT"); - return &xtime; + static Tm tm; + return tmtime(&tm, abs, nil); } char* -asctime(Tm *t) +ctime(long abs) { - char *ncp; - static char cbuf[30]; + Tzone *tz; + Tm tm; - strcpy(cbuf, "Thu Jan 01 00:00:00 GMT 1970\n"); - ncp = &"SunMonTueWedThuFriSat"[t->wday*3]; - cbuf[0] = *ncp++; - cbuf[1] = *ncp++; - cbuf[2] = *ncp; - ncp = &"JanFebMarAprMayJunJulAugSepOctNovDec"[t->mon*3]; - cbuf[4] = *ncp++; - cbuf[5] = *ncp++; - cbuf[6] = *ncp; - ct_numb(cbuf+8, t->mday); - ct_numb(cbuf+11, t->hour+100); - ct_numb(cbuf+14, t->min+100); - ct_numb(cbuf+17, t->sec+100); - ncp = t->zone; - cbuf[20] = *ncp++; - cbuf[21] = *ncp++; - cbuf[22] = *ncp; - ct_numb(cbuf+24, (t->year+1900) / 100 + 100); - ct_numb(cbuf+26, t->year+100); - return cbuf; + /* No error checking: the API doesn't allow it. */ + tz = tmgetzone("local"); + tmtime(&tm, abs, tz); + return asctime(&tm); } - -static -dysize(int y) -{ - - if(y%4 == 0 && (y%100 != 0 || y%400 == 0)) - return 366; - return 365; -} - -static -void -ct_numb(char *cp, int n) -{ - - cp[0] = ' '; - if(n >= 10) - cp[0] = (n/10)%10 + '0'; - cp[1] = n%10 + '0'; -} - -static -void -readtimezone(void) -{ - char buf[TZSIZE*11+30], *p; - int i; - - memset(buf, 0, sizeof(buf)); - i = open("/env/timezone", 0); - if(i < 0) - goto error; - if(read(i, buf, sizeof(buf)) >= sizeof(buf)){ - close(i); - goto error; - } - close(i); - p = buf; - if(rd_name(&p, timezone.stname)) - goto error; - if(rd_long(&p, &timezone.stdiff)) - goto error; - if(rd_name(&p, timezone.dlname)) - goto error; - if(rd_long(&p, &timezone.dldiff)) - goto error; - for(i=0; i '9') - return 1; - l = l*10 + c-'0'; - c = *(*f)++; - } - if(s) - l = -l; - *p = l; - return 0; -} diff -r 6055167dc76a sys/src/libc/9sys/tm2sec.c --- a/sys/src/libc/9sys/tm2sec.c Mon Jun 15 00:12:57 2020 +0200 +++ b/sys/src/libc/9sys/tm2sec.c Sun Jun 14 23:01:58 2020 -0700 @@ -1,202 +1,9 @@ #include #include -#define TZSIZE 150 -static void readtimezone(void); -static int rd_name(char**, char*); -static int rd_long(char**, long*); -static -struct -{ - char stname[4]; - char dlname[4]; - long stdiff; - long dldiff; - long dlpairs[TZSIZE]; -} timezone; - -#define SEC2MIN 60L -#define SEC2HOUR (60L*SEC2MIN) -#define SEC2DAY (24L*SEC2HOUR) - -/* - * days per month plus days/year - */ -static int dmsize[] = -{ - 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 -}; -static int ldmsize[] = -{ - 366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 -}; - -/* - * return the days/month for the given year - */ -static int * -yrsize(int y) -{ - if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0)) - return ldmsize; - else - return dmsize; -} - -/* - * compute seconds since Jan 1 1970 GMT - * and convert to our timezone. - */ long tm2sec(Tm *tm) { - long secs, *p; - int i, yday, year, *d2m; - - if(strcmp(tm->zone, "GMT") != 0 && timezone.stname[0] == 0) - readtimezone(); - secs = 0; - - /* - * seconds per year - */ - year = tm->year + 1900; - for(i = 1970; i < year; i++){ - d2m = yrsize(i); - secs += d2m[0] * SEC2DAY; - } - - /* - * if mday is set, use mon and mday to compute yday - */ - if(tm->mday){ - yday = 0; - d2m = yrsize(year); - for(i=0; imon; i++) - yday += d2m[i+1]; - yday += tm->mday-1; - }else{ - yday = tm->yday; - } - secs += yday * SEC2DAY; - - /* - * hours, minutes, seconds - */ - secs += tm->hour * SEC2HOUR; - secs += tm->min * SEC2MIN; - secs += tm->sec; - - /* - * Only handles zones mentioned in /env/timezone, - * but things get too ambiguous otherwise. - */ - if(strcmp(tm->zone, timezone.stname) == 0) - secs -= timezone.stdiff; - else if(strcmp(tm->zone, timezone.dlname) == 0) - secs -= timezone.dldiff; - else if(tm->zone[0] == 0){ - secs -= timezone.dldiff; - for(p = timezone.dlpairs; *p; p += 2) - if(secs >= p[0] && secs < p[1]) - break; - if(*p == 0){ - secs += timezone.dldiff; - secs -= timezone.stdiff; - } - } - return secs; + tmnorm(tm); + return tm->abs; } - -static -void -readtimezone(void) -{ - char buf[TZSIZE*11+30], *p; - int i; - - memset(buf, 0, sizeof(buf)); - i = open("/env/timezone", 0); - if(i < 0) - goto error; - if(read(i, buf, sizeof(buf)) >= sizeof(buf)) - goto error; - close(i); - p = buf; - if(rd_name(&p, timezone.stname)) - goto error; - if(rd_long(&p, &timezone.stdiff)) - goto error; - if(rd_name(&p, timezone.dlname)) - goto error; - if(rd_long(&p, &timezone.dldiff)) - goto error; - for(i=0; i '9') - return 1; - l = l*10 + c-'0'; - c = *(*f)++; - } - if(s) - l = -l; - *p = l; - return 0; -} diff -r 6055167dc76a sys/src/libc/port/mkfile --- a/sys/src/libc/port/mkfile Mon Jun 15 00:12:57 2020 +0200 +++ b/sys/src/libc/port/mkfile Sun Jun 14 23:01:58 2020 -0700 @@ -20,6 +20,7 @@ cleanname.c\ crypt.c\ ctype.c\ + date.c\ encodefmt.c\ execl.c\ exits.c\