seconds: use new libc date api
This commit is contained in:
parent
6d3a2e6531
commit
c834f535b7
1 changed files with 87 additions and 439 deletions
|
@ -1,466 +1,114 @@
|
|||
/*
|
||||
* 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 *knownfmt[] = {
|
||||
/* asctime */
|
||||
"WW 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",
|
||||
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 */
|
||||
char *datefmt[] = {
|
||||
/* RFC5322 */
|
||||
"?W ?DD ?MMM ?YYYY",
|
||||
"?W, DD-?MM-YY",
|
||||
/* RFC822/RFC2822 */
|
||||
"DD MMM YYYY",
|
||||
"DD MMM YY",
|
||||
/* RFC850 */
|
||||
"WW, DD-MMM-YY",
|
||||
/* RFC1123 */
|
||||
"WWW, DD MMM YYYY",
|
||||
/* RFC 3339 and human-readable variants */
|
||||
"YYYY-MM-DD",
|
||||
"YYYY-MM-DD [@] ",
|
||||
/* random formats */
|
||||
"?W ?MMM ?DD ?YYYY",
|
||||
"?MMM ?DD ?YYYY",
|
||||
"?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",
|
||||
" hh:mm:ss ?A",
|
||||
" hh:mm ?A",
|
||||
" hh ?A",
|
||||
"",
|
||||
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)
|
||||
{
|
||||
fprint(2, "usage: %s date-time ...\n", argv0);
|
||||
fprint(2, "usage: %s [-f fmt] date-time/win m...\n", argv0);
|
||||
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, *ep, buf[256];
|
||||
Tzone *tz;
|
||||
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;
|
||||
|
||||
/*
|
||||
* 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((tz = tzload("local")) == nil)
|
||||
sysfatal("bad local time: %r");
|
||||
for(i = 0; i < argc; i++){
|
||||
if(fmt != nil){
|
||||
if(tmparse(&tm, fmt, argv[i], tz, &ep) != nil && *ep == 0)
|
||||
goto Found;
|
||||
}else{
|
||||
for(f = knownfmt; *f != nil; f++)
|
||||
if(tmparse(&tm, *f, argv[i], tz, &ep) != nil && *ep == 0)
|
||||
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], tz, &ep) != nil && *ep == 0)
|
||||
goto Found;
|
||||
}
|
||||
}
|
||||
if (cmp < 0)
|
||||
last = pos - 1;
|
||||
if(*ep == 0)
|
||||
sysfatal("tmparse: %r");
|
||||
else
|
||||
base = pos + 1;
|
||||
sysfatal("tmparse: trailing junk");
|
||||
Found:
|
||||
print("%lld\n", tmnorm(&tm));
|
||||
}
|
||||
return 0;
|
||||
exits(nil);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
Loading…
Reference in a new issue