Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
/*
2
 * seconds absolute_date ... - convert absolute_date to seconds since epoch
3
 */
4
 
5
#include <u.h>
6
#include <libc.h>
7
#include <ctype.h>
8
 
9
typedef ulong Time;
10
 
11
enum {
12
	AM, PM, HR24,
13
 
14
	/* token types */
15
	Month = 1,
16
	Year,
17
	Day,
18
	Timetok,
19
	Tz,
20
	Dtz,
21
	Ignore,
22
	Ampm,
23
 
24
	Maxtok		= 6, /* only this many chars are stored in datetktbl */
25
	Maxdateflds	= 25,
26
};
27
 
28
/*
29
 * macros for squeezing values into low 7 bits of "value".
30
 * all timezones we care about are divisible by 10, and the largest value
31
 * (780) when divided is 78.
32
 */
33
#define TOVAL(tp, v)	((tp)->value = (v) / 10)
34
#define FROMVAL(tp)	((tp)->value * 10)	/* uncompress */
35
 
36
/* keep this struct small since we have an array of them */
37
typedef struct {
38
	char	token[Maxtok];
39
	char	type;
40
	schar	value;
41
} Datetok;
42
 
43
int dtok_numparsed;
44
 
45
/* forwards */
46
Datetok	*datetoktype(char *s, int *bigvalp);
47
 
48
static Datetok datetktbl[];
49
static unsigned szdatetktbl;
50
 
51
/* parse 1- or 2-digit number, advance *cpp past it */
52
static int
53
eatnum(char **cpp)
54
{
55
	int c, x;
56
	char *cp;
57
 
58
	cp = *cpp;
59
	c = *cp;
60
	if (!isascii(c) || !isdigit(c))
61
		return -1;
62
	x = c - '0';
63
 
64
	c = *++cp;
65
	if (isascii(c) && isdigit(c)) {
66
		x = 10*x + c - '0';
67
		cp++;
68
	}
69
	*cpp = cp;
70
	return x;
71
}
72
 
73
/* return -1 on failure */
74
int
75
parsetime(char *time, Tm *tm)
76
{
77
	tm->hour = eatnum(&time);
78
	if (tm->hour == -1 || *time++ != ':')
79
		return -1;			/* only hour; too short */
80
 
81
	tm->min = eatnum(&time);
82
	if (tm->min == -1)
83
		return -1;
84
	if (*time++ != ':') {
85
		tm->sec = 0;
86
		return 0;			/* no seconds; okay */
87
	}
88
 
89
	tm->sec = eatnum(&time);
90
	if (tm->sec == -1)
91
		return -1;
92
 
93
	/* this may be considered too strict.  garbage at end of time? */
94
	return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
95
}
96
 
97
/*
98
 * try to parse pre-split timestr in fields as an absolute date
99
 */
100
int
101
tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
102
{
103
	int i, mer = HR24, bigval = -1;
104
	long flg = 0, ty;
105
	char *p;
106
	Datetok *tp;
107
 
108
	now = localtime(time(0));	/* default to local time (zone) */
109
	tm->tzoff = now->tzoff;
110
	strncpy(tm->zone, now->zone, sizeof tm->zone - 1);
111
	tm->zone[sizeof tm->zone - 1] = '\0';
112
 
113
	tm->mday = tm->mon = tm->year = -1;	/* mandatory */
114
	tm->hour = tm->min = tm->sec = 0;
115
	dtok_numparsed = 0;
116
 
117
	for (i = 0; i < nf; i++) {
118
		if (fields[i][0] == '\0')
119
			continue;
120
		tp = datetoktype(fields[i], &bigval);
121
		ty = (1L << tp->type) & ~(1L << Ignore);
122
		if (flg & ty)
123
			return -1;		/* repeated type */
124
		flg |= ty;
125
		switch (tp->type) {
126
		case Year:
127
			tm->year = bigval;
128
			if (tm->year < 1970 || tm->year > 2106)
129
				return -1;	/* can't represent in ulong */
130
			/* convert 4-digit year to 1900 origin */
131
			if (tm->year >= 1900)
132
				tm->year -= 1900;
133
			break;
134
		case Day:
135
			tm->mday = bigval;
136
			break;
137
		case Month:
138
			tm->mon = tp->value - 1; /* convert to zero-origin */
139
			break;
140
		case Timetok:
141
			if (parsetime(fields[i], tm) < 0)
142
				return -1;
143
			break;
144
		case Dtz:
145
		case Tz:
146
			tm->tzoff = FROMVAL(tp);
147
			/* tm2sec needs the name in upper case */
148
			strncpy(tm->zone, fields[i], sizeof tm->zone - 1);
149
			tm->zone[sizeof tm->zone - 1] = '\0';
150
			for (p = tm->zone; *p; p++)
151
				if (isascii(*p) && islower(*p))
152
					*p = toupper(*p);
153
			break;
154
		case Ignore:
155
			break;
156
		case Ampm:
157
			mer = tp->value;
158
			break;
159
		default:
160
			return -1;	/* bad token type: CANTHAPPEN */
161
		}
162
	}
163
	if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
164
		return -1;		/* missing component */
165
	if (mer == PM)
166
		tm->hour += 12;
167
	return 0;
168
}
169
 
170
int
171
prsabsdate(char *timestr, Tm *now, Tm *tm)
172
{
173
	int nf;
174
	char *fields[Maxdateflds];
175
	static char delims[] = "- \t\n/,";
176
 
177
	nf = gettokens(timestr, fields, nelem(fields), delims+1);
178
	if (nf > nelem(fields))
179
		return -1;
180
	if (tryabsdate(fields, nf, now, tm) < 0) {
181
		char *p = timestr;
182
 
183
		/*
184
		 * could be a DEC-date; glue it all back together, split it
185
		 * with dash as a delimiter and try again.  Yes, this is a
186
		 * hack, but so are DEC-dates.
187
		 */
188
		while (--nf > 0) {
189
			while (*p++ != '\0')
190
				;
191
			p[-1] = ' ';
192
		}
193
		nf = gettokens(timestr, fields, nelem(fields), delims);
194
		if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
195
			return -1;
196
	}
197
	return 0;
198
}
199
 
200
int
201
validtm(Tm *tm)
202
{
203
	if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
204
	    tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
205
	    tm->min < 0 || tm->min > 59 ||
206
	    tm->sec < 0 || tm->sec > 61)	/* allow 2 leap seconds */
207
		return 0;
208
	return 1;
209
}
210
 
211
Time
212
seconds(char *timestr)
213
{
214
	Tm date;
215
 
216
	memset(&date, 0, sizeof date);
217
	if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
218
		return -1;
219
	return validtm(&date)? tm2sec(&date): -1;
220
}
221
 
222
int
223
convert(char *timestr)
224
{
225
	char *copy;
226
	Time tstime;
227
 
228
	copy = strdup(timestr);
229
	if (copy == nil)
230
		sysfatal("out of memory");
231
	tstime = seconds(copy);
232
	free(copy);
233
	if (tstime == -1) {
234
		fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
235
		return 1;
236
	}
237
	print("%lud\n", tstime);
238
	return 0;
239
}
240
 
241
static void
242
usage(void)
243
{
244
	fprint(2, "usage: %s date-time ...\n", argv0);
245
	exits("usage");
246
}
247
 
248
void
249
main(int argc, char **argv)
250
{
251
	int i, sts;
252
 
253
	sts = 0;
254
	ARGBEGIN{
255
	default:
256
		usage();
257
	}ARGEND
258
	if (argc == 0)
259
		usage();
260
	for (i = 0; i < argc; i++)
261
		sts |= convert(argv[i]);
262
	exits(sts != 0? "bad": 0);
263
}
264
 
265
/*
266
 * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
267
 * is WAY faster than the generic bsearch().
268
 */
269
Datetok *
270
datebsearch(char *key, Datetok *base, unsigned nel)
271
{
272
	int cmp;
273
	Datetok *last = base + nel - 1, *pos;
274
 
275
	while (last >= base) {
276
		pos = base + ((last - base) >> 1);
277
		cmp = key[0] - pos->token[0];
278
		if (cmp == 0) {
279
			cmp = strncmp(key, pos->token, Maxtok);
280
			if (cmp == 0)
281
				return pos;
282
		}
283
		if (cmp < 0)
284
			last = pos - 1;
285
		else
286
			base = pos + 1;
287
	}
288
	return 0;
289
}
290
 
291
Datetok *
292
datetoktype(char *s, int *bigvalp)
293
{
294
	char *cp = s;
295
	char c = *cp;
296
	static Datetok t;
297
	Datetok *tp = &t;
298
 
299
	if (isascii(c) && isdigit(c)) {
300
		int len = strlen(cp);
301
 
302
		if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
303
			tp->type = Timetok;
304
		else {
305
			if (bigvalp != nil)
306
				*bigvalp = atoi(cp); /* won't fit in tp->value */
307
			if (len == 4)
308
				tp->type = Year;
309
			else if (++dtok_numparsed == 1)
310
				tp->type = Day;
311
			else
312
				tp->type = Year;
313
		}
314
	} else if (c == '-' || c == '+') {
315
		int val = atoi(cp + 1);
316
		int hr =  val / 100;
317
		int min = val % 100;
318
 
319
		val = hr*60 + min;
320
		TOVAL(tp, c == '-'? -val: val);
321
		tp->type = Tz;
322
	} else {
323
		char lowtoken[Maxtok+1];
324
		char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
325
 
326
		/* copy to lowtoken to avoid modifying s */
327
		while ((c = *cp++) != '\0' && ltp < endltp)
328
			*ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
329
		*ltp = '\0';
330
		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
331
		if (tp == nil) {
332
			tp = &t;
333
			tp->type = Ignore;
334
		}
335
	}
336
	return tp;
337
}
338
 
339
 
340
/*
341
 * to keep this table reasonably small, we divide the lexval for Tz and Dtz
342
 * entries by 10 and truncate the text field at MAXTOKLEN characters.
343
 * the text field is not guaranteed to be NUL-terminated.
344
 */
345
static Datetok datetktbl[] = {
346
/*	text		token	lexval */
347
	"acsst",	Dtz,	63,	/* Cent. Australia */
348
	"acst",		Tz,	57,	/* Cent. Australia */
349
	"adt",		Dtz,	-18,	/* Atlantic Daylight Time */
350
	"aesst",	Dtz,	66,	/* E. Australia */
351
	"aest",		Tz,	60,	/* Australia Eastern Std Time */
352
	"ahst",		Tz,	60,	/* Alaska-Hawaii Std Time */
353
	"am",		Ampm,	AM,
354
	"apr",		Month,	4,
355
	"april",	Month,	4,
356
	"ast",		Tz,	-24,	/* Atlantic Std Time (Canada) */
357
	"at",		Ignore,	0,	/* "at" (throwaway) */
358
	"aug",		Month,	8,
359
	"august",	Month,	8,
360
	"awsst",	Dtz,	54,	/* W. Australia */
361
	"awst",		Tz,	48,	/* W. Australia */
362
	"bst",		Tz,	6,	/* British Summer Time */
363
	"bt",		Tz,	18,	/* Baghdad Time */
364
	"cadt",		Dtz,	63,	/* Central Australian DST */
365
	"cast",		Tz,	57,	/* Central Australian ST */
366
	"cat",		Tz,	-60,	/* Central Alaska Time */
367
	"cct",		Tz,	48,	/* China Coast */
368
	"cdt",		Dtz,	-30,	/* Central Daylight Time */
369
	"cet",		Tz,	6,	/* Central European Time */
370
	"cetdst",	Dtz,	12,	/* Central European Dayl.Time */
371
	"cst",		Tz,	-36,	/* Central Standard Time */
372
	"dec",		Month,	12,
373
	"decemb",	Month,	12,
374
	"dnt",		Tz,	6,	/* Dansk Normal Tid */
375
	"dst",		Ignore,	0,
376
	"east",		Tz,	-60,	/* East Australian Std Time */
377
	"edt",		Dtz,	-24,	/* Eastern Daylight Time */
378
	"eet",		Tz,	12,	/* East. Europe, USSR Zone 1 */
379
	"eetdst",	Dtz,	18,	/* Eastern Europe */
380
	"est",		Tz,	-30,	/* Eastern Standard Time */
381
	"feb",		Month,	2,
382
	"februa",	Month,	2,
383
	"fri",		Ignore,	5,
384
	"friday",	Ignore,	5,
385
	"fst",		Tz,	6,	/* French Summer Time */
386
	"fwt",		Dtz,	12,	/* French Winter Time  */
387
	"gmt",		Tz,	0,	/* Greenwish Mean Time */
388
	"gst",		Tz,	60,	/* Guam Std Time, USSR Zone 9 */
389
	"hdt",		Dtz,	-54,	/* Hawaii/Alaska */
390
	"hmt",		Dtz,	18,	/* Hellas ? ? */
391
	"hst",		Tz,	-60,	/* Hawaii Std Time */
392
	"idle",		Tz,	72,	/* Intl. Date Line, East */
393
	"idlw",		Tz,	-72,	/* Intl. Date Line, West */
394
	"ist",		Tz,	12,	/* Israel */
395
	"it",		Tz,	22,	/* Iran Time */
396
	"jan",		Month,	1,
397
	"januar",	Month,	1,
398
	"jst",		Tz,	54,	/* Japan Std Time,USSR Zone 8 */
399
	"jt",		Tz,	45,	/* Java Time */
400
	"jul",		Month,	7,
401
	"july",		Month,	7,
402
	"jun",		Month,	6,
403
	"june",		Month,	6,
404
	"kst",		Tz,	54,	/* Korea Standard Time */
405
	"ligt",		Tz,	60,	/* From Melbourne, Australia */
406
	"mar",		Month,	3,
407
	"march",	Month,	3,
408
	"may",		Month,	5,
409
	"mdt",		Dtz,	-36,	/* Mountain Daylight Time */
410
	"mest",		Dtz,	12,	/* Middle Europe Summer Time */
411
	"met",		Tz,	6,	/* Middle Europe Time */
412
	"metdst",	Dtz,	12,	/* Middle Europe Daylight Time*/
413
	"mewt",		Tz,	6,	/* Middle Europe Winter Time */
414
	"mez",		Tz,	6,	/* Middle Europe Zone */
415
	"mon",		Ignore,	1,
416
	"monday",	Ignore,	1,
417
	"mst",		Tz,	-42,	/* Mountain Standard Time */
418
	"mt",		Tz,	51,	/* Moluccas Time */
419
	"ndt",		Dtz,	-15,	/* Nfld. Daylight Time */
420
	"nft",		Tz,	-21,	/* Newfoundland Standard Time */
421
	"nor",		Tz,	6,	/* Norway Standard Time */
422
	"nov",		Month,	11,
423
	"novemb",	Month,	11,
424
	"nst",		Tz,	-21,	/* Nfld. Standard Time */
425
	"nt",		Tz,	-66,	/* Nome Time */
426
	"nzdt",		Dtz,	78,	/* New Zealand Daylight Time */
427
	"nzst",		Tz,	72,	/* New Zealand Standard Time */
428
	"nzt",		Tz,	72,	/* New Zealand Time */
429
	"oct",		Month,	10,
430
	"octobe",	Month,	10,
431
	"on",		Ignore,	0,	/* "on" (throwaway) */
432
	"pdt",		Dtz,	-42,	/* Pacific Daylight Time */
433
	"pm",		Ampm,	PM,
434
	"pst",		Tz,	-48,	/* Pacific Standard Time */
435
	"sadt",		Dtz,	63,	/* S. Australian Dayl. Time */
436
	"sast",		Tz,	57,	/* South Australian Std Time */
437
	"sat",		Ignore,	6,
438
	"saturd",	Ignore,	6,
439
	"sep",		Month,	9,
440
	"sept",		Month,	9,
441
	"septem",	Month,	9,
442
	"set",		Tz,	-6,	/* Seychelles Time ?? */
443
	"sst",		Dtz,	12,	/* Swedish Summer Time */
444
	"sun",		Ignore,	0,
445
	"sunday",	Ignore,	0,
446
	"swt",		Tz,	6,	/* Swedish Winter Time  */
447
	"thu",		Ignore,	4,
448
	"thur",		Ignore,	4,
449
	"thurs",	Ignore,	4,
450
	"thursd",	Ignore,	4,
451
	"tue",		Ignore,	2,
452
	"tues",		Ignore,	2,
453
	"tuesda",	Ignore,	2,
454
	"ut",		Tz,	0,
455
	"utc",		Tz,	0,
456
	"wadt",		Dtz,	48,	/* West Australian DST */
457
	"wast",		Tz,	42,	/* West Australian Std Time */
458
	"wat",		Tz,	-6,	/* West Africa Time */
459
	"wdt",		Dtz,	54,	/* West Australian DST */
460
	"wed",		Ignore,	3,
461
	"wednes",	Ignore,	3,
462
	"weds",		Ignore,	3,
463
	"wet",		Tz,	0,	/* Western Europe */
464
	"wetdst",	Dtz,	6,	/* Western Europe */
465
	"wst",		Tz,	48,	/* West Australian Std Time */
466
	"ydt",		Dtz,	-48,	/* Yukon Daylight Time */
467
	"yst",		Tz,	-54,	/* Yukon Standard Time */
468
	"zp4",		Tz,	-24,	/* GMT +4  hours. */
469
	"zp5",		Tz,	-30,	/* GMT +5  hours. */
470
	"zp6",		Tz,	-36,	/* GMT +6  hours. */
471
};
472
static unsigned szdatetktbl = nelem(datetktbl);