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