]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/seconds.c
libregexp: improve the transition to next available thread, instruction, and generation
[plan9front.git] / sys / src / cmd / seconds.c
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         char upzone[32];
107         Datetok *tp;
108
109         now = localtime(time(0));       /* default to local time (zone) */
110         tm->tzoff = now->tzoff;
111         strncpy(tm->zone, now->zone, sizeof tm->zone);
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                         strcpy(upzone, fields[i]);
149                         for (p = upzone; *p; p++)
150                                 if (isascii(*p) && islower(*p))
151                                         *p = toupper(*p);
152                         strncpy(tm->zone, upzone, sizeof tm->zone);
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);