472 lines · 11.0 KB · C
RawDownload
/* * 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 inteatnum(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 */intparsetime(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 */inttryabsdate(char **fields, int nf, Tm *now, Tm *tm){	int i, mer = HR24, bigval = -1;	long flg = 0, ty;	char *p;	Datetok *tp; 	now = localtime(time(0));	/* default to local time (zone) */	tm->tzoff = now->tzoff;	strncpy(tm->zone, now->zone, sizeof tm->zone - 1);	tm->zone[sizeof tm->zone - 1] = '\0'; 	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 */			strncpy(tm->zone, fields[i], sizeof tm->zone - 1);			tm->zone[sizeof tm->zone - 1] = '\0';			for (p = tm->zone; *p; p++)				if (isascii(*p) && islower(*p))					*p = toupper(*p);			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;} intprsabsdate(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;} intvalidtm(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;} Timeseconds(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;} intconvert(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 voidusage(void){	fprint(2, "usage: %s date-time ...\n", argv0);	exits("usage");} voidmain(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);