etc/services - sync with NetBSD-8
[minix.git] / lib / libutil / parsedate.y
blobf282fd6f98e939dafefa84dd48ab911ed71f1f70
1 %{
2 /*
3 ** Originally written by Steven M. Bellovin <smb@research.att.com> while
4 ** at the University of North Carolina at Chapel Hill. Later tweaked by
5 ** a couple of people on Usenet. Completely overhauled by Rich $alz
6 ** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
7 **
8 ** This grammar has 10 shift/reduce conflicts.
9 **
10 ** This code is in the public domain and has no copyright.
12 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
13 /* SUPPRESS 288 on yyerrlab *//* Label unused */
15 #include <sys/cdefs.h>
16 #ifdef __RCSID
17 __RCSID("$NetBSD: parsedate.y,v 1.20 2014/10/08 17:38:28 apb Exp $");
18 #endif
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <time.h>
25 #include <util.h>
26 #include <stdlib.h>
28 /* NOTES on rebuilding parsedate.c (particularly for inclusion in CVS
29 releases):
31 We don't want to mess with all the portability hassles of alloca.
32 In particular, most (all?) versions of bison will use alloca in
33 their parser. If bison works on your system (e.g. it should work
34 with gcc), then go ahead and use it, but the more general solution
35 is to use byacc instead of bison, which should generate a portable
36 parser. I played with adding "#define alloca dont_use_alloca", to
37 give an error if the parser generator uses alloca (and thus detect
38 unportable parsedate.c's), but that seems to cause as many problems
39 as it solves. */
41 #define EPOCH 1970
42 #define HOUR(x) ((time_t)(x) * 60)
43 #define SECSPERDAY (24L * 60L * 60L)
45 #define USE_LOCAL_TIME 99999 /* special case for Convert() and yyTimezone */
48 ** An entry in the lexical lookup table.
50 typedef struct _TABLE {
51 const char *name;
52 int type;
53 time_t value;
54 } TABLE;
58 ** Daylight-savings mode: on, off, or not yet known.
60 typedef enum _DSTMODE {
61 DSTon, DSToff, DSTmaybe
62 } DSTMODE;
65 ** Meridian: am, pm, or 24-hour style.
67 typedef enum _MERIDIAN {
68 MERam, MERpm, MER24
69 } MERIDIAN;
72 struct dateinfo {
73 DSTMODE yyDSTmode; /* DST on/off/maybe */
74 time_t yyDayOrdinal;
75 time_t yyDayNumber;
76 int yyHaveDate;
77 int yyHaveFullYear; /* if true, year is not abbreviated. */
78 /* if false, need to call AdjustYear(). */
79 int yyHaveDay;
80 int yyHaveRel;
81 int yyHaveTime;
82 int yyHaveZone;
83 time_t yyTimezone; /* Timezone as minutes ahead/east of UTC */
84 time_t yyDay; /* Day of month [1-31] */
85 time_t yyHour; /* Hour of day [0-24] or [1-12] */
86 time_t yyMinutes; /* Minute of hour [0-59] */
87 time_t yyMonth; /* Month of year [1-12] */
88 time_t yySeconds; /* Second of minute [0-60] */
89 time_t yyYear; /* Year, see also yyHaveFullYear */
90 MERIDIAN yyMeridian; /* Interpret yyHour as AM/PM/24 hour clock */
91 time_t yyRelMonth;
92 time_t yyRelSeconds;
96 %union {
97 time_t Number;
98 enum _MERIDIAN Meridian;
101 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
102 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST AT_SIGN
104 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
105 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
106 %type <Meridian> tMERIDIAN o_merid
108 %parse-param { struct dateinfo *param }
109 %parse-param { const char **yyInput }
110 %lex-param { const char **yyInput }
111 %pure-parser
115 spec : /* NULL */
116 | spec item
119 item : time {
120 param->yyHaveTime++;
122 | time_numericzone {
123 param->yyHaveTime++;
124 param->yyHaveZone++;
126 | zone {
127 param->yyHaveZone++;
129 | date {
130 param->yyHaveDate++;
132 | day {
133 param->yyHaveDay++;
135 | rel {
136 param->yyHaveRel++;
138 | cvsstamp {
139 param->yyHaveTime++;
140 param->yyHaveDate++;
141 param->yyHaveZone++;
143 | epochdate {
144 param->yyHaveTime++;
145 param->yyHaveDate++;
146 param->yyHaveZone++;
148 | number
151 cvsstamp: tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER {
152 param->yyYear = $1;
153 if (param->yyYear < 100) param->yyYear += 1900;
154 param->yyHaveFullYear = 1;
155 param->yyMonth = $3;
156 param->yyDay = $5;
157 param->yyHour = $7;
158 param->yyMinutes = $9;
159 param->yySeconds = $11;
160 param->yyDSTmode = DSToff;
161 param->yyTimezone = 0;
165 epochdate: AT_SIGN at_number {
166 time_t when = $<Number>2;
167 struct tm tmbuf;
168 if (gmtime_r(&when, &tmbuf) != NULL) {
169 param->yyYear = tmbuf.tm_year + 1900;
170 param->yyMonth = tmbuf.tm_mon + 1;
171 param->yyDay = tmbuf.tm_mday;
173 param->yyHour = tmbuf.tm_hour;
174 param->yyMinutes = tmbuf.tm_min;
175 param->yySeconds = tmbuf.tm_sec;
176 } else {
177 param->yyYear = EPOCH;
178 param->yyMonth = 1;
179 param->yyDay = 1;
181 param->yyHour = 0;
182 param->yyMinutes = 0;
183 param->yySeconds = 0;
185 param->yyHaveFullYear = 1;
186 param->yyDSTmode = DSToff;
187 param->yyTimezone = 0;
191 at_number : tUNUMBER | tSNUMBER ;
193 time : tUNUMBER tMERIDIAN {
194 param->yyHour = $1;
195 param->yyMinutes = 0;
196 param->yySeconds = 0;
197 param->yyMeridian = $2;
199 | tUNUMBER ':' tUNUMBER o_merid {
200 param->yyHour = $1;
201 param->yyMinutes = $3;
202 param->yySeconds = 0;
203 param->yyMeridian = $4;
205 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
206 param->yyHour = $1;
207 param->yyMinutes = $3;
208 param->yySeconds = $5;
209 param->yyMeridian = $6;
211 | tUNUMBER ':' tUNUMBER ':' tUNUMBER '.' tUNUMBER {
212 param->yyHour = $1;
213 param->yyMinutes = $3;
214 param->yySeconds = $5;
215 param->yyMeridian = MER24;
216 /* XXX: Do nothing with millis */
220 time_numericzone : tUNUMBER ':' tUNUMBER tSNUMBER {
221 param->yyHour = $1;
222 param->yyMinutes = $3;
223 param->yyMeridian = MER24;
224 param->yyDSTmode = DSToff;
225 param->yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
227 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
228 param->yyHour = $1;
229 param->yyMinutes = $3;
230 param->yySeconds = $5;
231 param->yyMeridian = MER24;
232 param->yyDSTmode = DSToff;
233 param->yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
237 zone : tZONE {
238 param->yyTimezone = $1;
239 param->yyDSTmode = DSToff;
241 | tDAYZONE {
242 param->yyTimezone = $1;
243 param->yyDSTmode = DSTon;
246 tZONE tDST {
247 param->yyTimezone = $1;
248 param->yyDSTmode = DSTon;
252 day : tDAY {
253 param->yyDayOrdinal = 1;
254 param->yyDayNumber = $1;
256 | tDAY ',' {
257 param->yyDayOrdinal = 1;
258 param->yyDayNumber = $1;
260 | tUNUMBER tDAY {
261 param->yyDayOrdinal = $1;
262 param->yyDayNumber = $2;
266 date : tUNUMBER '/' tUNUMBER {
267 param->yyMonth = $1;
268 param->yyDay = $3;
270 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
271 if ($1 >= 100) {
272 param->yyYear = $1;
273 param->yyMonth = $3;
274 param->yyDay = $5;
275 } else {
276 param->yyMonth = $1;
277 param->yyDay = $3;
278 param->yyYear = $5;
281 | tUNUMBER tSNUMBER tSNUMBER {
282 /* ISO 8601 format. yyyy-mm-dd. */
283 param->yyYear = $1;
284 param->yyHaveFullYear = 1;
285 param->yyMonth = -$2;
286 param->yyDay = -$3;
288 | tUNUMBER tMONTH tSNUMBER {
289 /* e.g. 17-JUN-1992. */
290 param->yyDay = $1;
291 param->yyMonth = $2;
292 param->yyYear = -$3;
294 | tMONTH tUNUMBER {
295 param->yyMonth = $1;
296 param->yyDay = $2;
298 | tMONTH tUNUMBER ',' tUNUMBER {
299 param->yyMonth = $1;
300 param->yyDay = $2;
301 param->yyYear = $4;
303 | tUNUMBER tMONTH {
304 param->yyMonth = $2;
305 param->yyDay = $1;
307 | tUNUMBER tMONTH tUNUMBER {
308 param->yyMonth = $2;
309 param->yyDay = $1;
310 param->yyYear = $3;
314 rel : relunit tAGO {
315 param->yyRelSeconds = -param->yyRelSeconds;
316 param->yyRelMonth = -param->yyRelMonth;
318 | relunit
321 relunit : tUNUMBER tMINUTE_UNIT {
322 param->yyRelSeconds += $1 * $2 * 60L;
324 | tSNUMBER tMINUTE_UNIT {
325 param->yyRelSeconds += $1 * $2 * 60L;
327 | tMINUTE_UNIT {
328 param->yyRelSeconds += $1 * 60L;
330 | tSNUMBER tSEC_UNIT {
331 param->yyRelSeconds += $1;
333 | tUNUMBER tSEC_UNIT {
334 param->yyRelSeconds += $1;
336 | tSEC_UNIT {
337 param->yyRelSeconds++;
339 | tSNUMBER tMONTH_UNIT {
340 param->yyRelMonth += $1 * $2;
342 | tUNUMBER tMONTH_UNIT {
343 param->yyRelMonth += $1 * $2;
345 | tMONTH_UNIT {
346 param->yyRelMonth += $1;
350 number : tUNUMBER {
351 if (param->yyHaveTime && param->yyHaveDate && !param->yyHaveRel)
352 param->yyYear = $1;
353 else {
354 if($1>10000) {
355 param->yyHaveDate++;
356 param->yyDay= ($1)%100;
357 param->yyMonth= ($1/100)%100;
358 param->yyYear = $1/10000;
360 else {
361 param->yyHaveTime++;
362 if ($1 < 100) {
363 param->yyHour = $1;
364 param->yyMinutes = 0;
366 else {
367 param->yyHour = $1 / 100;
368 param->yyMinutes = $1 % 100;
370 param->yySeconds = 0;
371 param->yyMeridian = MER24;
377 o_merid : /* NULL */ {
378 $$ = MER24;
380 | tMERIDIAN {
381 $$ = $1;
387 /* Month and day table. */
388 static const TABLE MonthDayTable[] = {
389 { "january", tMONTH, 1 },
390 { "february", tMONTH, 2 },
391 { "march", tMONTH, 3 },
392 { "april", tMONTH, 4 },
393 { "may", tMONTH, 5 },
394 { "june", tMONTH, 6 },
395 { "july", tMONTH, 7 },
396 { "august", tMONTH, 8 },
397 { "september", tMONTH, 9 },
398 { "sept", tMONTH, 9 },
399 { "october", tMONTH, 10 },
400 { "november", tMONTH, 11 },
401 { "december", tMONTH, 12 },
402 { "sunday", tDAY, 0 },
403 { "monday", tDAY, 1 },
404 { "tuesday", tDAY, 2 },
405 { "tues", tDAY, 2 },
406 { "wednesday", tDAY, 3 },
407 { "wednes", tDAY, 3 },
408 { "thursday", tDAY, 4 },
409 { "thur", tDAY, 4 },
410 { "thurs", tDAY, 4 },
411 { "friday", tDAY, 5 },
412 { "saturday", tDAY, 6 },
413 { NULL, 0, 0 }
416 /* Time units table. */
417 static const TABLE UnitsTable[] = {
418 { "year", tMONTH_UNIT, 12 },
419 { "month", tMONTH_UNIT, 1 },
420 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
421 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
422 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
423 { "hour", tMINUTE_UNIT, 60 },
424 { "minute", tMINUTE_UNIT, 1 },
425 { "min", tMINUTE_UNIT, 1 },
426 { "second", tSEC_UNIT, 1 },
427 { "sec", tSEC_UNIT, 1 },
428 { NULL, 0, 0 }
431 /* Assorted relative-time words. */
432 static const TABLE OtherTable[] = {
433 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
434 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
435 { "today", tMINUTE_UNIT, 0 },
436 { "now", tMINUTE_UNIT, 0 },
437 { "last", tUNUMBER, -1 },
438 { "this", tMINUTE_UNIT, 0 },
439 { "next", tUNUMBER, 2 },
440 { "first", tUNUMBER, 1 },
441 { "one", tUNUMBER, 1 },
442 /* { "second", tUNUMBER, 2 }, */
443 { "two", tUNUMBER, 2 },
444 { "third", tUNUMBER, 3 },
445 { "three", tUNUMBER, 3 },
446 { "fourth", tUNUMBER, 4 },
447 { "four", tUNUMBER, 4 },
448 { "fifth", tUNUMBER, 5 },
449 { "five", tUNUMBER, 5 },
450 { "sixth", tUNUMBER, 6 },
451 { "six", tUNUMBER, 6 },
452 { "seventh", tUNUMBER, 7 },
453 { "seven", tUNUMBER, 7 },
454 { "eighth", tUNUMBER, 8 },
455 { "eight", tUNUMBER, 8 },
456 { "ninth", tUNUMBER, 9 },
457 { "nine", tUNUMBER, 9 },
458 { "tenth", tUNUMBER, 10 },
459 { "ten", tUNUMBER, 10 },
460 { "eleventh", tUNUMBER, 11 },
461 { "eleven", tUNUMBER, 11 },
462 { "twelfth", tUNUMBER, 12 },
463 { "twelve", tUNUMBER, 12 },
464 { "ago", tAGO, 1 },
465 { NULL, 0, 0 }
468 /* The timezone table. */
469 /* Some of these are commented out because a time_t can't store a float. */
470 static const TABLE TimezoneTable[] = {
471 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
472 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
473 { "utc", tZONE, HOUR( 0) },
474 { "wet", tZONE, HOUR( 0) }, /* Western European */
475 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
476 { "wat", tZONE, HOUR( 1) }, /* West Africa */
477 { "at", tZONE, HOUR( 2) }, /* Azores */
478 #if 0
479 /* For completeness. BST is also British Summer, and GST is
480 * also Guam Standard. */
481 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
482 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
483 #endif
484 #if 0
485 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
486 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
487 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
488 #endif
489 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
490 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
491 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
492 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
493 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
494 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
495 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
496 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
497 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
498 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
499 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
500 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
501 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
502 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
503 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
504 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
505 { "nt", tZONE, HOUR(11) }, /* Nome */
506 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
507 { "cet", tZONE, -HOUR(1) }, /* Central European */
508 { "met", tZONE, -HOUR(1) }, /* Middle European */
509 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
510 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
511 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
512 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
513 { "fwt", tZONE, -HOUR(1) }, /* French Winter */
514 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
515 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
516 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
517 #if 0
518 { "it", tZONE, -HOUR(3.5) },/* Iran */
519 #endif
520 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
521 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
522 #if 0
523 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
524 #endif
525 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
526 #if 0
527 /* For completeness. NST is also Newfoundland Stanard, and SST is
528 * also Swedish Summer. */
529 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
530 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
531 #endif /* 0 */
532 { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
533 { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
534 #if 0
535 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
536 #endif
537 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
538 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
539 #if 0
540 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
541 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
542 #endif
543 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
544 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
545 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
546 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
547 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
548 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
549 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
550 { NULL, 0, 0 }
553 /* Military timezone table. */
554 static const TABLE MilitaryTable[] = {
555 { "a", tZONE, HOUR( 1) },
556 { "b", tZONE, HOUR( 2) },
557 { "c", tZONE, HOUR( 3) },
558 { "d", tZONE, HOUR( 4) },
559 { "e", tZONE, HOUR( 5) },
560 { "f", tZONE, HOUR( 6) },
561 { "g", tZONE, HOUR( 7) },
562 { "h", tZONE, HOUR( 8) },
563 { "i", tZONE, HOUR( 9) },
564 { "k", tZONE, HOUR( 10) },
565 { "l", tZONE, HOUR( 11) },
566 { "m", tZONE, HOUR( 12) },
567 { "n", tZONE, HOUR(- 1) },
568 { "o", tZONE, HOUR(- 2) },
569 { "p", tZONE, HOUR(- 3) },
570 { "q", tZONE, HOUR(- 4) },
571 { "r", tZONE, HOUR(- 5) },
572 { "s", tZONE, HOUR(- 6) },
573 { "t", tZONE, HOUR(- 7) },
574 { "u", tZONE, HOUR(- 8) },
575 { "v", tZONE, HOUR(- 9) },
576 { "w", tZONE, HOUR(-10) },
577 { "x", tZONE, HOUR(-11) },
578 { "y", tZONE, HOUR(-12) },
579 { "z", tZONE, HOUR( 0) },
580 { NULL, 0, 0 }
586 /* ARGSUSED */
587 static int
588 yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
590 return 0;
594 /* Adjust year from a value that might be abbreviated, to a full value.
595 * e.g. convert 70 to 1970.
596 * Input Year is either:
597 * - A negative number, which means to use its absolute value (why?)
598 * - A number from 0 to 99, which means a year from 1900 to 1999, or
599 * - The actual year (>=100).
600 * Returns the full year. */
601 static time_t
602 AdjustYear(time_t Year)
604 /* XXX Y2K */
605 if (Year < 0)
606 Year = -Year;
607 if (Year < 70)
608 Year += 2000;
609 else if (Year < 100)
610 Year += 1900;
611 return Year;
614 static time_t
615 Convert(
616 time_t Month, /* month of year [1-12] */
617 time_t Day, /* day of month [1-31] */
618 time_t Year, /* year, not abbreviated in any way */
619 time_t Hours, /* Hour of day [0-24] */
620 time_t Minutes, /* Minute of hour [0-59] */
621 time_t Seconds, /* Second of minute [0-60] */
622 time_t Timezone, /* Timezone as minutes east of UTC,
623 * or USE_LOCAL_TIME special case */
624 MERIDIAN Meridian, /* Hours are am/pm/24 hour clock */
625 DSTMODE DSTmode /* DST on/off/maybe */
628 struct tm tm = {.tm_sec = 0};
629 time_t result;
631 tm.tm_sec = Seconds;
632 tm.tm_min = Minutes;
633 tm.tm_hour = Hours + (Meridian == MERpm ? 12 : 0);
634 tm.tm_mday = Day;
635 tm.tm_mon = Month - 1;
636 tm.tm_year = Year - 1900;
637 switch (DSTmode) {
638 case DSTon: tm.tm_isdst = 1; break;
639 case DSToff: tm.tm_isdst = 0; break;
640 default: tm.tm_isdst = -1; break;
643 if (Timezone == USE_LOCAL_TIME) {
644 result = mktime(&tm);
645 } else {
646 /* We rely on mktime_z(NULL, ...) working in UTC */
647 result = mktime_z(NULL, &tm);
648 result += Timezone * 60;
651 #if PARSEDATE_DEBUG
652 fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
653 " mer=%d DST=%d)",
654 __func__,
655 (intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
656 (intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
657 (intmax_t)Timezone, (int)Meridian, (int)DSTmode);
658 fprintf(stderr, " -> %jd", (intmax_t)result);
659 fprintf(stderr, " %s", ctime(&result));
660 #endif
662 return result;
666 static time_t
667 DSTcorrect(
668 time_t Start,
669 time_t Future
672 time_t StartDay;
673 time_t FutureDay;
674 struct tm *tm;
676 if ((tm = localtime(&Start)) == NULL)
677 return -1;
678 StartDay = (tm->tm_hour + 1) % 24;
680 if ((tm = localtime(&Future)) == NULL)
681 return -1;
682 FutureDay = (tm->tm_hour + 1) % 24;
684 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
688 static time_t
689 RelativeDate(
690 time_t Start,
691 time_t DayOrdinal,
692 time_t DayNumber
695 struct tm *tm;
696 time_t now;
698 now = Start;
699 tm = localtime(&now);
700 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
701 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
702 return DSTcorrect(Start, now);
706 static time_t
707 RelativeMonth(
708 time_t Start,
709 time_t RelMonth,
710 time_t Timezone
713 struct tm *tm;
714 time_t Month;
715 time_t Year;
717 if (RelMonth == 0)
718 return 0;
719 tm = localtime(&Start);
720 if (tm == NULL)
721 return -1;
722 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
723 Year = Month / 12;
724 Month = Month % 12 + 1;
725 return DSTcorrect(Start,
726 Convert(Month, (time_t)tm->tm_mday, Year,
727 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
728 Timezone, MER24, DSTmaybe));
732 static int
733 LookupWord(YYSTYPE *yylval, char *buff)
735 register char *p;
736 register char *q;
737 register const TABLE *tp;
738 int i;
739 int abbrev;
741 /* Make it lowercase. */
742 for (p = buff; *p; p++)
743 if (isupper((unsigned char)*p))
744 *p = tolower((unsigned char)*p);
746 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
747 yylval->Meridian = MERam;
748 return tMERIDIAN;
750 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
751 yylval->Meridian = MERpm;
752 return tMERIDIAN;
755 /* See if we have an abbreviation for a month. */
756 if (strlen(buff) == 3)
757 abbrev = 1;
758 else if (strlen(buff) == 4 && buff[3] == '.') {
759 abbrev = 1;
760 buff[3] = '\0';
762 else
763 abbrev = 0;
765 for (tp = MonthDayTable; tp->name; tp++) {
766 if (abbrev) {
767 if (strncmp(buff, tp->name, 3) == 0) {
768 yylval->Number = tp->value;
769 return tp->type;
772 else if (strcmp(buff, tp->name) == 0) {
773 yylval->Number = tp->value;
774 return tp->type;
778 for (tp = TimezoneTable; tp->name; tp++)
779 if (strcmp(buff, tp->name) == 0) {
780 yylval->Number = tp->value;
781 return tp->type;
784 if (strcmp(buff, "dst") == 0)
785 return tDST;
787 for (tp = UnitsTable; tp->name; tp++)
788 if (strcmp(buff, tp->name) == 0) {
789 yylval->Number = tp->value;
790 return tp->type;
793 /* Strip off any plural and try the units table again. */
794 i = strlen(buff) - 1;
795 if (buff[i] == 's') {
796 buff[i] = '\0';
797 for (tp = UnitsTable; tp->name; tp++)
798 if (strcmp(buff, tp->name) == 0) {
799 yylval->Number = tp->value;
800 return tp->type;
802 buff[i] = 's'; /* Put back for "this" in OtherTable. */
805 for (tp = OtherTable; tp->name; tp++)
806 if (strcmp(buff, tp->name) == 0) {
807 yylval->Number = tp->value;
808 return tp->type;
811 /* Military timezones. */
812 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
813 for (tp = MilitaryTable; tp->name; tp++)
814 if (strcmp(buff, tp->name) == 0) {
815 yylval->Number = tp->value;
816 return tp->type;
820 /* Drop out any periods and try the timezone table again. */
821 for (i = 0, p = q = buff; *q; q++)
822 if (*q != '.')
823 *p++ = *q;
824 else
825 i++;
826 *p = '\0';
827 if (i)
828 for (tp = TimezoneTable; tp->name; tp++)
829 if (strcmp(buff, tp->name) == 0) {
830 yylval->Number = tp->value;
831 return tp->type;
834 return tID;
838 static int
839 yylex(YYSTYPE *yylval, const char **yyInput)
841 register char c;
842 register char *p;
843 char buff[20];
844 int Count;
845 int sign;
846 const char *inp = *yyInput;
848 for ( ; ; ) {
849 while (isspace((unsigned char)*inp))
850 inp++;
852 if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
853 if (c == '-' || c == '+') {
854 sign = c == '-' ? -1 : 1;
855 if (!isdigit((unsigned char)*++inp))
856 /* skip the '-' sign */
857 continue;
859 else
860 sign = 0;
861 for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
862 yylval->Number = 10 * yylval->Number + c - '0';
863 if (sign < 0)
864 yylval->Number = -yylval->Number;
865 *yyInput = --inp;
866 return sign ? tSNUMBER : tUNUMBER;
868 if (isalpha((unsigned char)c)) {
869 for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
870 if (p < &buff[sizeof buff - 1])
871 *p++ = c;
872 *p = '\0';
873 *yyInput = --inp;
874 return LookupWord(yylval, buff);
876 if (c == '@') {
877 *yyInput = ++inp;
878 return AT_SIGN;
880 if (c != '(') {
881 *yyInput = ++inp;
882 return c;
884 Count = 0;
885 do {
886 c = *inp++;
887 if (c == '\0')
888 return c;
889 if (c == '(')
890 Count++;
891 else if (c == ')')
892 Count--;
893 } while (Count > 0);
897 #define TM_YEAR_ORIGIN 1900
899 time_t
900 parsedate(const char *p, const time_t *now, const int *zone)
902 struct tm local, *tm;
903 time_t nowt;
904 int zonet;
905 time_t Start;
906 time_t tod, rm;
907 struct dateinfo param;
908 int saved_errno;
910 saved_errno = errno;
911 errno = 0;
913 if (now == NULL) {
914 now = &nowt;
915 (void)time(&nowt);
917 if (zone == NULL) {
918 zone = &zonet;
919 zonet = USE_LOCAL_TIME;
920 if ((tm = localtime_r(now, &local)) == NULL)
921 return -1;
922 } else {
924 * Should use the specified zone, not localtime.
925 * Fake it using gmtime and arithmetic.
926 * This is good enough because we use only the year/month/day,
927 * not other fields of struct tm.
929 time_t fake = *now + (*zone * 60);
930 if ((tm = gmtime_r(&fake, &local)) == NULL)
931 return -1;
933 param.yyYear = tm->tm_year + 1900;
934 param.yyMonth = tm->tm_mon + 1;
935 param.yyDay = tm->tm_mday;
936 param.yyTimezone = *zone;
937 param.yyDSTmode = DSTmaybe;
938 param.yyHour = 0;
939 param.yyMinutes = 0;
940 param.yySeconds = 0;
941 param.yyMeridian = MER24;
942 param.yyRelSeconds = 0;
943 param.yyRelMonth = 0;
944 param.yyHaveDate = 0;
945 param.yyHaveFullYear = 0;
946 param.yyHaveDay = 0;
947 param.yyHaveRel = 0;
948 param.yyHaveTime = 0;
949 param.yyHaveZone = 0;
951 if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
952 param.yyHaveDate > 1 || param.yyHaveDay > 1) {
953 errno = EINVAL;
954 return -1;
957 if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
958 if (! param.yyHaveFullYear) {
959 param.yyYear = AdjustYear(param.yyYear);
960 param.yyHaveFullYear = 1;
962 Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
963 param.yyMinutes, param.yySeconds, param.yyTimezone,
964 param.yyMeridian, param.yyDSTmode);
965 if (Start == -1 && errno != 0)
966 return -1;
968 else {
969 Start = *now;
970 if (!param.yyHaveRel)
971 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
974 Start += param.yyRelSeconds;
975 rm = RelativeMonth(Start, param.yyRelMonth, param.yyTimezone);
976 if (rm == -1 && errno != 0)
977 return -1;
978 Start += rm;
980 if (param.yyHaveDay && !param.yyHaveDate) {
981 tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
982 Start += tod;
985 if (errno == 0)
986 errno = saved_errno;
987 return Start;
991 #if defined(TEST)
993 /* ARGSUSED */
995 main(int ac, char *av[])
997 char buff[128];
998 time_t d;
1000 (void)printf("Enter date, or blank line to exit.\n\t> ");
1001 (void)fflush(stdout);
1002 while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
1003 errno = 0;
1004 d = parsedate(buff, NULL, NULL);
1005 if (d == -1 && errno != 0)
1006 (void)printf("Bad format - couldn't convert: %s\n",
1007 strerror(errno));
1008 else
1009 (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
1010 (void)printf("\t> ");
1011 (void)fflush(stdout);
1013 exit(0);
1014 /* NOTREACHED */
1016 #endif /* defined(TEST) */