parsedate.y revision 1.40 1 %{
2 /*
3 ** Originally written by Steven M. Bellovin <smb (at) 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 (at) bbn.com> and Jim Berets <jberets (at) bbn.com> in August, 1990;
7 **
8 ** This code is in the public domain and has no copyright.
9 */
10 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
11 /* SUPPRESS 288 on yyerrlab *//* Label unused */
12
13 #include <sys/cdefs.h>
14 #ifdef __RCSID
15 __RCSID("$NetBSD: parsedate.y,v 1.40 2024/05/02 14:19:56 christos Exp $");
16 #endif
17
18 #include <stdio.h>
19 #include <ctype.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <string.h>
23 #include <time.h>
24 #include <util.h>
25 #include <stdlib.h>
26
27 /* NOTES on rebuilding parsedate.c (particularly for inclusion in CVS
28 releases):
29
30 We don't want to mess with all the portability hassles of alloca.
31 In particular, most (all?) versions of bison will use alloca in
32 their parser. If bison works on your system (e.g. it should work
33 with gcc), then go ahead and use it, but the more general solution
34 is to use byacc instead of bison, which should generate a portable
35 parser. I played with adding "#define alloca dont_use_alloca", to
36 give an error if the parser generator uses alloca (and thus detect
37 unportable parsedate.c's), but that seems to cause as many problems
38 as it solves. */
39
40 #define EPOCH 1970
41 #define HOUR(x) ((time_t)((x) * 60))
42 #define SECSPERDAY (24L * 60L * 60L)
43
44 #define MAXREL 16 /* hours mins secs days weeks months years - maybe twice each ...*/
45
46 #define USE_LOCAL_TIME 99999 /* special case for Convert() and yyTimezone */
47
48 /*
49 ** An entry in the lexical lookup table.
50 */
51 typedef struct _TABLE {
52 const char *name;
53 int type;
54 time_t value;
55 } TABLE;
56
57
58 /*
59 ** Daylight-savings mode: on, off, or not yet known.
60 */
61 typedef enum _DSTMODE {
62 DSTon, DSToff, DSTmaybe
63 } DSTMODE;
64
65 /*
66 ** Meridian: am, pm, or 24-hour style (plus "noon" and "midnight").
67 */
68 typedef enum _MERIDIAN {
69 MERam, MERpm, MER24, MER_NOON, MER_MN
70 } MERIDIAN;
71
72
73 struct dateinfo {
74 DSTMODE yyDSTmode; /* DST on/off/maybe */
75 time_t yyDayOrdinal;
76 time_t yyDayNumber;
77 int yyHaveDate;
78 int yyHaveFullYear; /* if true, year is not abbreviated. */
79 /* if false, need to call AdjustYear(). */
80 int yyHaveDay;
81 int yyHaveRel;
82 int yyHaveTime;
83 int yyHaveZone;
84 time_t yyTimezone; /* Timezone as minutes ahead/east of UTC */
85 time_t yyDay; /* Day of month [1-31] */
86 time_t yyHour; /* Hour of day [0-24] or [1-12] */
87 time_t yyMinutes; /* Minute of hour [0-59] */
88 time_t yyMonth; /* Month of year [1-12] */
89 time_t yySeconds; /* Second of minute [0-60] */
90 time_t yyYear; /* Year, see also yyHaveFullYear */
91 MERIDIAN yyMeridian; /* Interpret yyHour as AM/PM/24 hour clock */
92 struct {
93 time_t yyRelVal;
94 int yyRelMonth;
95 } yyRel[MAXREL];
96 };
97
98 static int RelVal(struct dateinfo *, time_t, time_t, int, int);
99
100 #define CheckRelVal(a, b, c, d, e) do { \
101 if (!RelVal((a), (b), (c), (d), (e))) { \
102 YYREJECT; \
103 } \
104 } while (0)
105
106 %}
107
108 %union {
109 time_t Number;
110 enum _MERIDIAN Meridian;
111 }
112
113 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
114 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST AT_SIGN tTIME
115
116 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
117 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE tTIME
118 %type <Meridian> tMERIDIAN
119
120 %type <Number> at_number
121 %type <Meridian> o_merid
122
123 %parse-param { struct dateinfo *param }
124 %parse-param { const char **yyInput }
125 %lex-param { const char **yyInput }
126 %pure-parser
127
128 %%
129
130 spec:
131 /* empty */
132 | spec item
133 ;
134
135 item:
136 time { param->yyHaveTime++; }
137 | time_numericzone { param->yyHaveTime++; param->yyHaveZone++; }
138 | zone { param->yyHaveZone++; }
139 | date { param->yyHaveDate++; }
140 | day { param->yyHaveDay++; }
141 | rel { param->yyHaveRel++; }
142 | cvsstamp { param->yyHaveTime++; param->yyHaveDate++;
143 param->yyHaveZone++; }
144 | epochdate { param->yyHaveTime++; param->yyHaveDate++;
145 param->yyHaveZone++; }
146 | number
147 ;
148
149 cvsstamp:
150 tUNUMBER '.' tUNUMBER '.' tUNUMBER '.'
151 tUNUMBER '.' tUNUMBER '.' tUNUMBER {
152 param->yyYear = $1;
153 if (param->yyYear < 100) {
154 param->yyYear += 1900;
155 }
156 param->yyHaveFullYear = 1;
157 param->yyMonth = $3;
158 param->yyDay = $5;
159 param->yyHour = $7;
160 param->yyMinutes = $9;
161 param->yySeconds = $11;
162 param->yyDSTmode = DSToff;
163 param->yyTimezone = 0;
164 }
165 ;
166
167 epochdate:
168 AT_SIGN at_number {
169 time_t when = $2;
170 struct tm tmbuf;
171
172 if (gmtime_r(&when, &tmbuf) != NULL) {
173 param->yyYear = tmbuf.tm_year + 1900;
174 param->yyMonth = tmbuf.tm_mon + 1;
175 param->yyDay = tmbuf.tm_mday;
176
177 param->yyHour = tmbuf.tm_hour;
178 param->yyMinutes = tmbuf.tm_min;
179 param->yySeconds = tmbuf.tm_sec;
180 } else {
181 param->yyYear = EPOCH;
182 param->yyMonth = 1;
183 param->yyDay = 1;
184
185 param->yyHour = 0;
186 param->yyMinutes = 0;
187 param->yySeconds = 0;
188 }
189 param->yyHaveFullYear = 1;
190 param->yyDSTmode = DSToff;
191 param->yyTimezone = 0;
192 }
193 ;
194
195 at_number:
196 tUNUMBER
197 | tSNUMBER
198 ;
199
200 time:
201 tUNUMBER tMERIDIAN {
202 if ($1 > 24)
203 YYREJECT;
204 param->yyMinutes = 0;
205 param->yySeconds = 0;
206 if ($2 == MER_NOON || $2 == MER_MN) {
207 if ($1 == 12) {
208 switch ($2) {
209 case MER_NOON: param->yyHour = 12; break;
210 case MER_MN : param->yyHour = 0; break;
211 default: /* impossible */; break;
212 }
213 param->yyMeridian = MER24;
214 } else
215 YYREJECT;
216 } else {
217 param->yyHour = $1;
218 param->yyMeridian = $2;
219 }
220 }
221 | tUNUMBER ':' tUNUMBER o_merid {
222 if ($1 > 24 || $3 >= 60)
223 YYREJECT;
224 param->yyMinutes = $3;
225 param->yySeconds = 0;
226 if ($4 == MER_NOON || $4 == MER_MN) {
227 if ($1 == 12 && $3 == 0) {
228 switch ($4) {
229 case MER_NOON: param->yyHour = 12; break;
230 case MER_MN : param->yyHour = 0; break;
231 default: /* impossible */; break;
232 }
233 param->yyMeridian = MER24;
234 } else
235 YYREJECT;
236 } else {
237 param->yyHour = $1;
238 param->yyMeridian = $4;
239 }
240 }
241 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
242 if ($1 > 24 || $3 >= 60 || $5 > 60)
243 YYREJECT;
244 param->yyMinutes = $3;
245 param->yySeconds = $5;
246 if ($6 == MER_NOON || $6 == MER_MN) {
247 if ($1 == 12 && $3 == 0 && $5 == 0) {
248 switch ($6) {
249 case MER_NOON: param->yyHour = 12; break;
250 case MER_MN : param->yyHour = 0; break;
251 default: /* impossible */; break;
252 }
253 param->yyMeridian = MER24;
254 } else
255 YYREJECT;
256 } else {
257 param->yyHour = $1;
258 param->yyMeridian = $6;
259 }
260 }
261 | tUNUMBER ':' tUNUMBER ':' tUNUMBER '.' tUNUMBER {
262 if ($1 > 24 || $3 >= 60 || $5 > 60)
263 YYREJECT;
264 param->yyHour = $1;
265 param->yyMinutes = $3;
266 param->yySeconds = $5;
267 param->yyMeridian = MER24;
268 /* XXX: Do nothing with fractional secs ($7) */
269 }
270 | tUNUMBER ':' tUNUMBER ':' tUNUMBER ',' tUNUMBER {
271 if ($1 > 24 || $3 >= 60 || $5 > 60)
272 YYREJECT;
273 param->yyHour = $1;
274 param->yyMinutes = $3;
275 param->yySeconds = $5;
276 param->yyMeridian = MER24;
277 /* XXX: Do nothing with fractional seconds ($7) */
278 }
279 | tTIME {
280 param->yyHour = $1;
281 param->yyMinutes = 0;
282 param->yySeconds = 0;
283 param->yyMeridian = MER24;
284 /* Tues midnight --> Weds 00:00, midnight Tues -> Tues 00:00 */
285 if ($1 == 0 && param->yyHaveDay)
286 param->yyDayNumber++;
287 }
288 | tUNUMBER tTIME {
289 if ($1 == 12 && ($2 == 0 || $2 == 12)) {
290 param->yyHour = $2;
291 param->yyMinutes = 0;
292 param->yySeconds = 0;
293 param->yyMeridian = MER24;
294 } else
295 YYREJECT;
296 }
297 ;
298
299 time_numericzone:
300 tUNUMBER ':' tUNUMBER tSNUMBER {
301 if ($4 < -(47 * 100 + 59) || $4 > (47 * 100 + 59))
302 YYREJECT;
303 if ($1 > 24 || $3 > 59)
304 YYREJECT;
305 param->yyHour = $1;
306 param->yyMinutes = $3;
307 param->yyMeridian = MER24;
308 param->yyDSTmode = DSToff;
309 param->yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
310 }
311 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
312 if ($6 < -(47 * 100 + 59) || $6 > (47 * 100 + 59))
313 YYREJECT;
314 if ($1 > 24 || $3 > 59 || $5 > 60)
315 YYREJECT;
316 param->yyHour = $1;
317 param->yyMinutes = $3;
318 param->yySeconds = $5;
319 param->yyMeridian = MER24;
320 param->yyDSTmode = DSToff;
321 param->yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
322 }
323 ;
324
325 zone:
326 tZONE { param->yyTimezone = $1; param->yyDSTmode = DSToff; }
327 | tDAYZONE { param->yyTimezone = $1; param->yyDSTmode = DSTon; }
328 | tZONE tDST { param->yyTimezone = $1; param->yyDSTmode = DSTon; }
329 | tSNUMBER {
330 if (param->yyHaveDate == 0 && param->yyHaveTime == 0)
331 YYREJECT;
332 if ($1 < -(47 * 100 + 59) || $1 > (47 * 100 + 59))
333 YYREJECT;
334 param->yyTimezone = - ($1 % 100 + ($1 / 100) * 60);
335 param->yyDSTmode = DSTmaybe;
336 }
337 ;
338
339 day:
340 tDAY { param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
341 | tDAY ',' { param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
342 | tUNUMBER tDAY { param->yyDayOrdinal = $1; param->yyDayNumber = $2; }
343 ;
344
345 date:
346 tUNUMBER '/' tUNUMBER {
347 if ($1 > 12 || $3 > 31 || $1 == 0 || $3 == 0)
348 YYREJECT;
349 param->yyMonth = $1;
350 param->yyDay = $3;
351 }
352 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
353 if ($1 >= 100) {
354 if ($3 > 12 || $5 > 31 || $3 == 0 || $5 == 0)
355 YYREJECT;
356 param->yyYear = $1;
357 param->yyMonth = $3;
358 param->yyDay = $5;
359 } else {
360 if ($1 > 12 || $3 > 31 || $1 == 0 || $3 == 0)
361 YYREJECT;
362 param->yyMonth = $1;
363 param->yyDay = $3;
364 param->yyYear = $5;
365 }
366 }
367 | tUNUMBER tSNUMBER tSNUMBER {
368 /* ISO 8601 format. yyyy-mm-dd. */
369 if ($2 >= 0 || $2 < -12 || $3 >= 0 || $3 < -31)
370 YYREJECT;
371 param->yyYear = $1;
372 param->yyHaveFullYear = 1;
373 param->yyMonth = -$2;
374 param->yyDay = -$3;
375 }
376 | tUNUMBER tMONTH tSNUMBER {
377 if ($3 > 0 || $1 == 0 || $1 > 31)
378 YYREJECT;
379 /* e.g. 17-JUN-1992. */
380 param->yyDay = $1;
381 param->yyMonth = $2;
382 param->yyYear = -$3;
383 }
384 | tMONTH tUNUMBER {
385 if ($2 == 0 || $2 > 31)
386 YYREJECT;
387 param->yyMonth = $1;
388 param->yyDay = $2;
389 }
390 | tMONTH tUNUMBER ',' tUNUMBER {
391 if ($2 == 0 || $2 > 31)
392 YYREJECT;
393 param->yyMonth = $1;
394 param->yyDay = $2;
395 param->yyYear = $4;
396 }
397 | tUNUMBER tMONTH {
398 if ($1 == 0 || $1 > 31)
399 YYREJECT;
400 param->yyMonth = $2;
401 param->yyDay = $1;
402 }
403 | tUNUMBER tMONTH tUNUMBER {
404 if ($1 > 31 && $3 > 31)
405 YYREJECT;
406 if ($1 < 35) {
407 if ($1 == 0)
408 YYREJECT;
409 param->yyDay = $1;
410 param->yyYear = $3;
411 } else {
412 if ($3 == 0)
413 YYREJECT;
414 param->yyDay = $3;
415 param->yyYear = $1;
416 }
417 param->yyMonth = $2;
418 }
419 ;
420
421 rel:
422 relunit
423 | relunit tAGO {
424 param->yyRel[param->yyHaveRel].yyRelVal =
425 -param->yyRel[param->yyHaveRel].yyRelVal;
426 }
427 ;
428
429 relunit:
430 tUNUMBER tMINUTE_UNIT { CheckRelVal(param, $1, $2, 60, 0); }
431 | tSNUMBER tMINUTE_UNIT { CheckRelVal(param, $1, $2, 60, 0); }
432 | tMINUTE_UNIT { CheckRelVal(param, 1, $1, 60, 0); }
433 | tSNUMBER tSEC_UNIT { CheckRelVal(param, $1, 1, 1, 0); }
434 | tUNUMBER tSEC_UNIT { CheckRelVal(param, $1, 1, 1, 0); }
435 | tSEC_UNIT { CheckRelVal(param, 1, 1, 1, 0); }
436 | tSNUMBER tMONTH_UNIT { CheckRelVal(param, $1, $2, 1, 1); }
437 | tUNUMBER tMONTH_UNIT { CheckRelVal(param, $1, $2, 1, 1); }
438 | tMONTH_UNIT { CheckRelVal(param, 1, $1, 1, 1); }
439 ;
440
441 number:
442 tUNUMBER {
443 if (param->yyHaveTime && param->yyHaveDate &&
444 !param->yyHaveRel) {
445 param->yyYear = $1;
446 } else {
447 if ($1 > 10000) {
448 param->yyHaveDate++;
449 param->yyDay = ($1)%100;
450 param->yyMonth = ($1/100)%100;
451 param->yyYear = $1/10000;
452 }
453 else {
454 param->yyHaveTime++;
455 if ($1 < 100) {
456 param->yyHour = $1;
457 param->yyMinutes = 0;
458 }
459 else {
460 param->yyHour = $1 / 100;
461 param->yyMinutes = $1 % 100;
462 }
463 param->yySeconds = 0;
464 param->yyMeridian = MER24;
465 }
466 }
467 }
468 ;
469
470 o_merid:
471 /* empty */ { $$ = MER24; }
472 | tMERIDIAN { $$ = $1; }
473 | tTIME { $$ = $1 == 0 ? MER_MN : MER_NOON; }
474 ;
475
476 %%
477
478 static short DaysInMonth[12] = {
479 31, 28, 31, 30, 31, 30,
480 31, 31, 30, 31, 30, 31
481 };
482
483 /*
484 * works with tm.tm_year (ie: rel to 1900)
485 */
486 #define isleap(yr) (((yr) & 3) == 0 && (((yr) % 100) != 0 || \
487 ((1900+(yr)) % 400) == 0))
488
489 /* Month and day table. */
490 static const TABLE MonthDayTable[] = {
491 { "january", tMONTH, 1 },
492 { "february", tMONTH, 2 },
493 { "march", tMONTH, 3 },
494 { "april", tMONTH, 4 },
495 { "may", tMONTH, 5 },
496 { "june", tMONTH, 6 },
497 { "july", tMONTH, 7 },
498 { "august", tMONTH, 8 },
499 { "september", tMONTH, 9 },
500 { "sept", tMONTH, 9 },
501 { "october", tMONTH, 10 },
502 { "november", tMONTH, 11 },
503 { "december", tMONTH, 12 },
504 { "sunday", tDAY, 0 },
505 { "su", tDAY, 0 },
506 { "monday", tDAY, 1 },
507 { "mo", tDAY, 1 },
508 { "tuesday", tDAY, 2 },
509 { "tues", tDAY, 2 },
510 { "tu", tDAY, 2 },
511 { "wednesday", tDAY, 3 },
512 { "wednes", tDAY, 3 },
513 { "weds", tDAY, 3 },
514 { "we", tDAY, 3 },
515 { "thursday", tDAY, 4 },
516 { "thurs", tDAY, 4 },
517 { "thur", tDAY, 4 },
518 { "th", tDAY, 4 },
519 { "friday", tDAY, 5 },
520 { "fr", tDAY, 5 },
521 { "saturday", tDAY, 6 },
522 { "sa", tDAY, 6 },
523 { NULL, 0, 0 }
524 };
525
526 /* Time units table. */
527 static const TABLE UnitsTable[] = {
528 { "year", tMONTH_UNIT, 12 },
529 { "month", tMONTH_UNIT, 1 },
530 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
531 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
532 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
533 { "hour", tMINUTE_UNIT, 60 },
534 { "minute", tMINUTE_UNIT, 1 },
535 { "min", tMINUTE_UNIT, 1 },
536 { "second", tSEC_UNIT, 1 },
537 { "sec", tSEC_UNIT, 1 },
538 { NULL, 0, 0 }
539 };
540
541 /* Assorted relative-time words. */
542 static const TABLE OtherTable[] = {
543 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
544 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
545 { "today", tMINUTE_UNIT, 0 },
546 { "now", tMINUTE_UNIT, 0 },
547 { "last", tUNUMBER, -1 },
548 { "this", tMINUTE_UNIT, 0 },
549 { "next", tUNUMBER, 2 }, /* it is more useful this way */
550 { "first", tUNUMBER, 1 },
551 { "one", tUNUMBER, 1 },
552 /* { "second", tUNUMBER, 2 }, */
553 { "two", tUNUMBER, 2 },
554 { "third", tUNUMBER, 3 },
555 { "three", tUNUMBER, 3 },
556 { "fourth", tUNUMBER, 4 },
557 { "four", tUNUMBER, 4 },
558 { "fifth", tUNUMBER, 5 },
559 { "five", tUNUMBER, 5 },
560 { "sixth", tUNUMBER, 6 },
561 { "six", tUNUMBER, 6 },
562 { "seventh", tUNUMBER, 7 },
563 { "seven", tUNUMBER, 7 },
564 { "eighth", tUNUMBER, 8 },
565 { "eight", tUNUMBER, 8 },
566 { "ninth", tUNUMBER, 9 },
567 { "nine", tUNUMBER, 9 },
568 { "tenth", tUNUMBER, 10 },
569 { "ten", tUNUMBER, 10 },
570 { "eleventh", tUNUMBER, 11 },
571 { "eleven", tUNUMBER, 11 },
572 { "twelfth", tUNUMBER, 12 },
573 { "twelve", tUNUMBER, 12 },
574 { "ago", tAGO, 1 },
575 { NULL, 0, 0 }
576 };
577
578 /* The timezone table. */
579 /* Some of these are commented out because a time_t can't store a float. */
580 static const TABLE TimezoneTable[] = {
581 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
582 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
583 { "utc", tZONE, HOUR( 0) },
584 { "wet", tZONE, HOUR( 0) }, /* Western European */
585 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
586 { "wat", tZONE, HOUR( 1) }, /* West Africa */
587 { "at", tZONE, HOUR( 2) }, /* Azores */
588 #if 0
589 /* For completeness. BST is also British Summer, and GST is
590 * also Guam Standard. */
591 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
592 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
593 #endif
594 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
595 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
596 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
597 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
598 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
599 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
600 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
601 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
602 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
603 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
604 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
605 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
606 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
607 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
608 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
609 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
610 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
611 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
612 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
613 { "nt", tZONE, HOUR(11) }, /* Nome */
614 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
615 { "cet", tZONE, -HOUR(1) }, /* Central European */
616 { "met", tZONE, -HOUR(1) }, /* Middle European */
617 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
618 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
619 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
620 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
621 { "fwt", tZONE, -HOUR(1) }, /* French Winter */
622 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
623 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
624 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
625 { "it", tZONE, -HOUR(3.5) },/* Iran */
626 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
627 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
628 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
629 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
630 #if 0
631 /* For completeness. NST is also Newfoundland Stanard, and SST is
632 * also Swedish Summer. */
633 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
634 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
635 #endif /* 0 */
636 { "ict", tZONE, -HOUR(7) }, /* Indo China Time (Thai) */
637 #if 0 /* this one looks to be bogus */
638 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
639 #endif
640 { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
641 { "awst", tZONE, -HOUR(8) }, /* West Australian Standard */
642 { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
643 { "awdt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
644 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
645 { "sgt", tZONE, -HOUR(8) }, /* Singapore */
646 { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
647 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
648 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
649 { "acst", tZONE, -HOUR(9.5) },/* Central Australian Standard */
650 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
651 { "acdt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
652 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
653 { "aest", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
654 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
655 { "aedt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
656 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
657 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
658 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
659 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
660 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
661 { NULL, 0, 0 }
662 };
663
664 /* Military timezone table. */
665 static const TABLE MilitaryTable[] = {
666 { "a", tZONE, HOUR( 1) },
667 { "b", tZONE, HOUR( 2) },
668 { "c", tZONE, HOUR( 3) },
669 { "d", tZONE, HOUR( 4) },
670 { "e", tZONE, HOUR( 5) },
671 { "f", tZONE, HOUR( 6) },
672 { "g", tZONE, HOUR( 7) },
673 { "h", tZONE, HOUR( 8) },
674 { "i", tZONE, HOUR( 9) },
675 { "k", tZONE, HOUR( 10) },
676 { "l", tZONE, HOUR( 11) },
677 { "m", tZONE, HOUR( 12) },
678 { "n", tZONE, HOUR(- 1) },
679 { "o", tZONE, HOUR(- 2) },
680 { "p", tZONE, HOUR(- 3) },
681 { "q", tZONE, HOUR(- 4) },
682 { "r", tZONE, HOUR(- 5) },
683 { "s", tZONE, HOUR(- 6) },
684 { "t", tZONE, HOUR(- 7) },
685 { "u", tZONE, HOUR(- 8) },
686 { "v", tZONE, HOUR(- 9) },
687 { "w", tZONE, HOUR(-10) },
688 { "x", tZONE, HOUR(-11) },
689 { "y", tZONE, HOUR(-12) },
690 { "z", tZONE, HOUR( 0) },
691 { NULL, 0, 0 }
692 };
693
694 static const TABLE TimeNames[] = {
695 { "midnight", tTIME, 0 },
696 { "mn", tTIME, 0 },
697 { "noon", tTIME, 12 },
698 { "midday", tTIME, 12 },
699 { NULL, 0, 0 }
700 };
701
702
703
705 /* ARGSUSED */
706 static int
707 yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
708 {
709 return 0;
710 }
711
712 /*
713 * Save a relative value, if it fits
714 */
715 static int
716 RelVal(struct dateinfo *param, time_t num, time_t unit, int scale, int type)
717 {
718 int i;
719 time_t v;
720 uintmax_t m;
721 int sign = 1;
722
723 if ((i = param->yyHaveRel) >= MAXREL)
724 return 0;
725
726 if (num < 0) {
727 sign = -sign;
728 num = -num;
729 }
730 if (unit < 0) {
731 sign = -sign;
732 unit = -unit;
733 }
734 /* scale is always positive */
735
736 m = LLONG_MAX; /* TIME_T_MAX */
737 if (scale > 1)
738 m /= scale;
739 if (unit > 1)
740 m /= unit;
741 if ((uintmax_t)num > m)
742 return 0;
743
744 m = num * unit * scale;
745 v = (time_t) m;
746 if (v < 0 || (uintmax_t)v != m)
747 return 0;
748 if (sign < 0)
749 v = -v;
750
751 param->yyRel[i].yyRelMonth = type;
752 param->yyRel[i].yyRelVal = v;
753
754 return 1;
755 }
756
757 /*
758 * Adjust year from a value that might be abbreviated, to a full value.
759 * e.g. convert 70 to 1970.
760 * Input Year is either:
761 * - A negative number, which means to use its absolute value (why?)
762 * - A number from 0 to 68, which means a year from 2000 to 2068,
763 * - A number from 69 to 99, which means a year from 1969 to 1999, or
764 * - The actual year (>=100).
765 * Returns the full year.
766 */
767 static time_t
768 AdjustYear(time_t Year)
769 {
770 /* XXX Y2K */
771 if (Year < 0)
772 Year = -Year;
773 if (Year < 69) /* POSIX compliant, 0..68 is 2000's, 69-99 1900's */
774 Year += 2000;
775 else if (Year < 100)
776 Year += 1900;
777 return Year;
778 }
779
780 static time_t
781 Convert(
782 time_t Month, /* month of year [1-12] */
783 time_t Day, /* day of month [1-31] */
784 time_t Year, /* year, not abbreviated in any way */
785 time_t Hours, /* Hour of day [0-24] */
786 time_t Minutes, /* Minute of hour [0-59] */
787 time_t Seconds, /* Second of minute [0-60] */
788 time_t Timezone, /* Timezone as minutes east of UTC,
789 * or USE_LOCAL_TIME special case */
790 MERIDIAN Meridian, /* Hours are am/pm/24 hour clock */
791 DSTMODE DSTmode /* DST on/off/maybe */
792 )
793 {
794 struct tm tm = {.tm_sec = 0};
795 struct tm otm;
796 time_t result;
797
798 tm.tm_sec = Seconds;
799 tm.tm_min = Minutes;
800 tm.tm_hour = ((Hours == 12 && Meridian != MER24) ? 0 : Hours) +
801 (Meridian == MERpm ? 12 : 0);
802
803 tm.tm_mday = Day;
804 tm.tm_mon = Month - 1;
805 tm.tm_year = Year - 1900;
806 if ((time_t)tm.tm_year + 1900 != Year) {
807 errno = EOVERFLOW;
808 return -1;
809 }
810 if (Timezone == USE_LOCAL_TIME) {
811 switch (DSTmode) {
812 case DSTon: tm.tm_isdst = 1; break;
813 case DSToff: tm.tm_isdst = 0; break;
814 default: tm.tm_isdst = -1; break;
815 }
816 otm = tm;
817 result = mktime(&tm);
818 } else {
819 /* We rely on mktime_z(NULL, ...) working in UTC */
820 tm.tm_isdst = 0; /* hence cannot be summer time */
821 otm = tm;
822 errno = 0;
823 result = mktime_z(NULL, &tm);
824 if (result != -1 || errno == 0) {
825 result += Timezone * 60;
826 if (DSTmode == DSTon) /* if specified sumer time */
827 result -= 3600; /* UTC is 1 hour earlier XXX */
828 }
829 }
830
831 #if PARSEDATE_DEBUG
832 fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
833 " mer=%d DST=%d)",
834 __func__,
835 (intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
836 (intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
837 (intmax_t)Timezone, (int)Meridian, (int)DSTmode);
838 fprintf(stderr, " -> %jd", (intmax_t)result);
839 fprintf(stderr, " %s", ctime(&result));
840 #endif
841
842 #define TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld)
843 if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) ||
844 TM_NE(hour) || TM_NE(min) || TM_NE(sec)) {
845 /* mktime() "corrected" our tm, so it must have been invalid */
846 result = -1;
847 errno = EAGAIN;
848 }
849 #undef TM_NE
850
851 return result;
852 }
853
854
855 static time_t
856 DSTcorrect(
857 time_t Start,
858 time_t Future
859 )
860 {
861 time_t StartDay;
862 time_t FutureDay;
863 struct tm tm;
864
865 if (localtime_r(&Start, &tm) == NULL)
866 return -1;
867 StartDay = (tm.tm_hour + 1) % 24;
868
869 if (localtime_r(&Future, &tm) == NULL)
870 return -1;
871 FutureDay = (tm.tm_hour + 1) % 24;
872
873 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
874 }
875
876
877 static time_t
878 RelativeDate(
879 time_t Start,
880 time_t DayOrdinal,
881 time_t DayNumber
882 )
883 {
884 struct tm tm;
885 time_t now;
886 time_t change;
887
888 now = Start;
889 if (localtime_r(&now, &tm) == NULL)
890 return -1;
891
892 /* should be using TIME_T_MAX but there is no such thing, so just "know" */
893 if (llabs(DayOrdinal) >= LLONG_MAX / (7 * SECSPERDAY)) {
894 errno = EOVERFLOW;
895 return -1;
896 }
897
898 change = SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7);
899 change += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
900
901 /* same here for _MAX and _MIN */
902 if ((change > 0 && LLONG_MAX - change < now) ||
903 (change < 0 && LLONG_MIN - change > now)) {
904 errno = EOVERFLOW;
905 return -1;
906 }
907
908 now += change;
909 return DSTcorrect(Start, now);
910 }
911
912
913 static time_t
914 RelativeMonth(
915 time_t Start,
916 time_t RelMonth,
917 time_t Timezone
918 )
919 {
920 struct tm tm;
921 time_t Month;
922 time_t Then;
923 int Day;
924
925 if (RelMonth == 0)
926 return 0;
927 /*
928 * It doesn't matter what timezone we use to do this computation,
929 * as long as we use the same one to reassemble the time that we
930 * used to disassemble it. So always use localtime and mktime. In
931 * particular, don't use Convert() to reassemble, because it will
932 * not only reassemble with the wrong timezone but it will also
933 * fail if we do e.g. three months from March 31 yielding July 1.
934 */
935 (void)Timezone;
936
937 if (localtime_r(&Start, &tm) == NULL)
938 return -1;
939
940 if (RelMonth >= LLONG_MAX - 12*((time_t)tm.tm_year + 1900) - tm.tm_mon) {
941 errno = EOVERFLOW;
942 return -1;
943 }
944 Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
945 tm.tm_year = (Month / 12) - 1900;
946 /* check for tm_year (an int) overflow */
947 if (((time_t)tm.tm_year + 1900) != Month/12) {
948 errno = EOVERFLOW;
949 return -1;
950 }
951 tm.tm_mon = Month % 12;
952 if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] +
953 ((tm.tm_mon==1) ? isleap(tm.tm_year) : 0)))
954 tm.tm_mday = Day;
955 errno = 0;
956 Then = mktime(&tm);
957 if (Then == -1 && errno != 0)
958 return -1;
959 return DSTcorrect(Start, Then);
960 }
961
962
963 static int
964 LookupWord(YYSTYPE *yylval, char *buff)
965 {
966 register char *p;
967 register char *q;
968 register const TABLE *tp;
969 int i;
970 int abbrev;
971
972 /* Make it lowercase. */
973 for (p = buff; *p; p++)
974 if (isupper((unsigned char)*p))
975 *p = tolower((unsigned char)*p);
976
977 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
978 yylval->Meridian = MERam;
979 return tMERIDIAN;
980 }
981 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
982 yylval->Meridian = MERpm;
983 return tMERIDIAN;
984 }
985
986 /* See if we have an abbreviation for a month. */
987 if (strlen(buff) == 3)
988 abbrev = 1;
989 else if (strlen(buff) == 4 && buff[3] == '.') {
990 abbrev = 1;
991 buff[3] = '\0';
992 }
993 else
994 abbrev = 0;
995
996 for (tp = MonthDayTable; tp->name; tp++) {
997 if (abbrev) {
998 if (strncmp(buff, tp->name, 3) == 0) {
999 yylval->Number = tp->value;
1000 return tp->type;
1001 }
1002 }
1003 else if (strcmp(buff, tp->name) == 0) {
1004 yylval->Number = tp->value;
1005 return tp->type;
1006 }
1007 }
1008
1009 for (tp = TimezoneTable; tp->name; tp++)
1010 if (strcmp(buff, tp->name) == 0) {
1011 yylval->Number = tp->value;
1012 return tp->type;
1013 }
1014
1015 if (strcmp(buff, "dst") == 0)
1016 return tDST;
1017
1018 for (tp = TimeNames; tp->name; tp++)
1019 if (strcmp(buff, tp->name) == 0) {
1020 yylval->Number = tp->value;
1021 return tp->type;
1022 }
1023
1024 for (tp = UnitsTable; tp->name; tp++)
1025 if (strcmp(buff, tp->name) == 0) {
1026 yylval->Number = tp->value;
1027 return tp->type;
1028 }
1029
1030 /* Strip off any plural and try the units table again. */
1031 i = strlen(buff) - 1;
1032 if (buff[i] == 's') {
1033 buff[i] = '\0';
1034 for (tp = UnitsTable; tp->name; tp++)
1035 if (strcmp(buff, tp->name) == 0) {
1036 yylval->Number = tp->value;
1037 return tp->type;
1038 }
1039 buff[i] = 's'; /* Put back for "this" in OtherTable. */
1040 }
1041
1042 for (tp = OtherTable; tp->name; tp++)
1043 if (strcmp(buff, tp->name) == 0) {
1044 yylval->Number = tp->value;
1045 return tp->type;
1046 }
1047
1048 /* Military timezones. */
1049 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
1050 for (tp = MilitaryTable; tp->name; tp++)
1051 if (strcmp(buff, tp->name) == 0) {
1052 yylval->Number = tp->value;
1053 return tp->type;
1054 }
1055 }
1056
1057 /* Drop out any periods and try the timezone table again. */
1058 for (i = 0, p = q = buff; *q; q++)
1059 if (*q != '.')
1060 *p++ = *q;
1061 else
1062 i++;
1063 *p = '\0';
1064 if (i)
1065 for (tp = TimezoneTable; tp->name; tp++)
1066 if (strcmp(buff, tp->name) == 0) {
1067 yylval->Number = tp->value;
1068 return tp->type;
1069 }
1070
1071 return tID;
1072 }
1073
1074
1075 static int
1076 yylex(YYSTYPE *yylval, const char **yyInput)
1077 {
1078 register char c;
1079 register char *p;
1080 char buff[20];
1081 int Count;
1082 int sign;
1083 const char *inp = *yyInput;
1084
1085 for ( ; ; ) {
1086 while (isspace((unsigned char)*inp))
1087 inp++;
1088
1089 if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
1090 if (c == '-' || c == '+') {
1091 sign = c == '-' ? -1 : 1;
1092 if (!isdigit((unsigned char)*++inp))
1093 /* skip the '-' sign */
1094 continue;
1095 }
1096 else
1097 sign = 0;
1098 for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); ) {
1099 time_t v;
1100
1101 v = yylval->Number;
1102 if (v > LLONG_MAX/10 ||
1103 (v == LLONG_MAX/10 && (v * 10 > LLONG_MAX - (c - '0'))))
1104 yylval->Number = LLONG_MAX;
1105 else
1106 yylval->Number = 10 * yylval->Number + c - '0';
1107 }
1108 if (sign < 0)
1109 yylval->Number = -yylval->Number;
1110 *yyInput = --inp;
1111 return sign ? tSNUMBER : tUNUMBER;
1112 }
1113 if (isalpha((unsigned char)c)) {
1114 for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
1115 if (p < &buff[sizeof buff - 1])
1116 *p++ = c;
1117 *p = '\0';
1118 *yyInput = --inp;
1119 return LookupWord(yylval, buff);
1120 }
1121 if (c == '@') {
1122 *yyInput = ++inp;
1123 return AT_SIGN;
1124 }
1125 if (c != '(') {
1126 *yyInput = ++inp;
1127 return c;
1128 }
1129 Count = 0;
1130 do {
1131 c = *inp++;
1132 if (c == '\0')
1133 return c;
1134 if (c == '(')
1135 Count++;
1136 else if (c == ')')
1137 Count--;
1138 } while (Count > 0);
1139 }
1140 }
1141
1142 #define TM_YEAR_ORIGIN 1900
1143
1144 time_t
1145 parsedate(const char *p, const time_t *now, const int *zone)
1146 {
1147 struct tm local, *tm;
1148 time_t nowt;
1149 int zonet;
1150 time_t Start;
1151 time_t tod, rm;
1152 struct dateinfo param;
1153 int saved_errno;
1154 int i;
1155
1156 saved_errno = errno;
1157 errno = 0;
1158
1159 if (now == NULL) {
1160 now = &nowt;
1161 (void)time(&nowt);
1162 }
1163 if (zone == NULL) {
1164 zone = &zonet;
1165 zonet = USE_LOCAL_TIME;
1166 if ((tm = localtime_r(now, &local)) == NULL)
1167 return -1;
1168 } else {
1169 /*
1170 * Should use the specified zone, not localtime.
1171 * Fake it using gmtime and arithmetic.
1172 * This is good enough because we use only the year/month/day,
1173 * not other fields of struct tm.
1174 */
1175 time_t fake = *now + (*zone * 60);
1176 if ((tm = gmtime_r(&fake, &local)) == NULL)
1177 return -1;
1178 }
1179 param.yyYear = tm->tm_year + 1900;
1180 param.yyMonth = tm->tm_mon + 1;
1181 param.yyDay = tm->tm_mday;
1182 param.yyTimezone = *zone;
1183 param.yyDSTmode = DSTmaybe;
1184 param.yyHour = 0;
1185 param.yyMinutes = 0;
1186 param.yySeconds = 0;
1187 param.yyMeridian = MER24;
1188 param.yyHaveDate = 0;
1189 param.yyHaveFullYear = 0;
1190 param.yyHaveDay = 0;
1191 param.yyHaveRel = 0;
1192 param.yyHaveTime = 0;
1193 param.yyHaveZone = 0;
1194
1195 /*
1196 * This one is too hard to parse using a grammar (the lexer would
1197 * confuse the 'T' with the Mil format timezone designator)
1198 * so handle it as a special case.
1199 */
1200 do {
1201 const unsigned char *pp = (const unsigned char *)p;
1202 char *ep; /* starts as "expected, becomes "end ptr" */
1203 static char format[] = "-dd-ddTdd:dd:dd";
1204 time_t yr;
1205
1206 while (isdigit(*pp))
1207 pp++;
1208
1209 if (pp == (const unsigned char *)p)
1210 break;
1211
1212 for (ep = format; *ep; ep++, pp++) {
1213 switch (*ep) {
1214 case 'd':
1215 if (isdigit(*pp))
1216 continue;
1217 break;
1218 case 'T':
1219 if (*pp == 'T' || *pp == 't' || *pp == ' ')
1220 continue;
1221 break;
1222 default:
1223 if (*pp == *ep)
1224 continue;
1225 break;
1226 }
1227 break;
1228 }
1229 if (*ep != '\0')
1230 break;
1231 if (*pp == '.' || *pp == ',') {
1232 if (!isdigit(pp[1]))
1233 break;
1234 while (isdigit(*++pp))
1235 continue;
1236 }
1237 if (*pp == 'Z' || *pp == 'z')
1238 pp++;
1239 else if (isdigit(*pp))
1240 break;
1241
1242 if (*pp != '\0' && !isspace(*pp))
1243 break;
1244
1245 errno = 0;
1246 yr = (time_t)strtol(p, &ep, 10);
1247 if (errno != 0) /* out of range (can be big number) */
1248 break; /* the ones below are all 2 digits */
1249
1250 /*
1251 * This is good enough to commit to there being an ISO format
1252 * timestamp leading the input string. We permit standard
1253 * parsedate() modifiers to follow but not precede this string.
1254 */
1255 param.yyHaveTime = 1;
1256 param.yyHaveDate = 1;
1257 param.yyHaveFullYear = 1;
1258
1259 if (pp[-1] == 'Z' || pp[-1] == 'z') {
1260 param.yyTimezone = 0;
1261 param.yyHaveZone = 1;
1262 }
1263
1264 param.yyYear = yr;
1265 param.yyMonth = (time_t)strtol(ep + 1, &ep, 10);
1266 param.yyDay = (time_t)strtol(ep + 1, &ep, 10);
1267 param.yyHour = (time_t)strtol(ep + 1, &ep, 10);
1268 param.yyMinutes = (time_t)strtol(ep + 1, &ep, 10);
1269 param.yySeconds = (time_t)strtol(ep + 1, &ep, 10);
1270 /* ignore any fractional seconds, no way to return them in a time_t */
1271
1272 param.yyMeridian = MER24;
1273
1274 p = (const char *)pp;
1275 } while (0);
1276
1277 if (yyparse(¶m, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
1278 param.yyHaveDate > 1 || param.yyHaveDay > 1) {
1279 errno = EINVAL;
1280 return -1;
1281 }
1282
1283 if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
1284 if (! param.yyHaveFullYear) {
1285 param.yyYear = AdjustYear(param.yyYear);
1286 param.yyHaveFullYear = 1;
1287 }
1288 errno = 0;
1289 Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
1290 param.yyMinutes, param.yySeconds, param.yyTimezone,
1291 param.yyMeridian, param.yyDSTmode);
1292 if (Start == -1 && errno != 0)
1293 return -1;
1294 }
1295 else {
1296 Start = *now;
1297 if (!param.yyHaveRel)
1298 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
1299 }
1300
1301 if (param.yyHaveRel > MAXREL) {
1302 errno = EINVAL;
1303 return -1;
1304 }
1305 for (i = 0; i < param.yyHaveRel; i++) {
1306 if (param.yyRel[i].yyRelMonth) {
1307 errno = 0;
1308 rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone);
1309 if (rm == -1 && errno != 0)
1310 return -1;
1311 Start += rm;
1312 } else
1313 Start += param.yyRel[i].yyRelVal;
1314 }
1315
1316 if (param.yyHaveDay && !param.yyHaveDate) {
1317 errno = 0;
1318 tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
1319 if (tod == -1 && errno != 0)
1320 return -1;
1321 Start += tod;
1322 }
1323
1324 errno = saved_errno;
1325 return Start;
1326 }
1327
1328
1329 #if defined(TEST)
1330
1331 /* ARGSUSED */
1332 int
1333 main(int ac, char *av[])
1334 {
1335 char buff[128];
1336 time_t d;
1337
1338 (void)printf("Enter date, or blank line to exit.\n\t> ");
1339 (void)fflush(stdout);
1340 while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
1341 errno = 0;
1342 d = parsedate(buff, NULL, NULL);
1343 if (d == -1 && errno != 0)
1344 (void)printf("Bad format - couldn't convert: %s\n",
1345 strerror(errno));
1346 else
1347 (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
1348 (void)printf("\t> ");
1349 (void)fflush(stdout);
1350 }
1351 exit(0);
1352 /* NOTREACHED */
1353 }
1354 #endif /* defined(TEST) */
1355