parsedate.y revision 1.3 1 1.1 christos %{
2 1.1 christos /*
3 1.1 christos ** Originally written by Steven M. Bellovin <smb (at) research.att.com> while
4 1.1 christos ** at the University of North Carolina at Chapel Hill. Later tweaked by
5 1.1 christos ** a couple of people on Usenet. Completely overhauled by Rich $alz
6 1.1 christos ** <rsalz (at) bbn.com> and Jim Berets <jberets (at) bbn.com> in August, 1990;
7 1.1 christos **
8 1.1 christos ** This grammar has 10 shift/reduce conflicts.
9 1.1 christos **
10 1.1 christos ** This code is in the public domain and has no copyright.
11 1.1 christos */
12 1.1 christos /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
13 1.1 christos /* SUPPRESS 288 on yyerrlab *//* Label unused */
14 1.1 christos
15 1.1 christos #include <stdio.h>
16 1.1 christos #include <ctype.h>
17 1.1 christos #include <string.h>
18 1.1 christos #include <time.h>
19 1.1 christos #include <util.h>
20 1.3 drochner #include <stdlib.h>
21 1.1 christos
22 1.1 christos /* NOTES on rebuilding parsedate.c (particularly for inclusion in CVS
23 1.1 christos releases):
24 1.1 christos
25 1.1 christos We don't want to mess with all the portability hassles of alloca.
26 1.1 christos In particular, most (all?) versions of bison will use alloca in
27 1.1 christos their parser. If bison works on your system (e.g. it should work
28 1.1 christos with gcc), then go ahead and use it, but the more general solution
29 1.1 christos is to use byacc instead of bison, which should generate a portable
30 1.1 christos parser. I played with adding "#define alloca dont_use_alloca", to
31 1.1 christos give an error if the parser generator uses alloca (and thus detect
32 1.1 christos unportable parsedate.c's), but that seems to cause as many problems
33 1.1 christos as it solves. */
34 1.1 christos
35 1.1 christos #define yyparse parsedate_yyparse
36 1.1 christos #define yylex parsedate_yylex
37 1.1 christos #define yyerror parsedate_yyerror
38 1.1 christos
39 1.1 christos #define EPOCH 1970
40 1.1 christos #define HOUR(x) ((time_t)(x) * 60)
41 1.1 christos #define SECSPERDAY (24L * 60L * 60L)
42 1.1 christos
43 1.1 christos
44 1.1 christos /*
45 1.1 christos ** An entry in the lexical lookup table.
46 1.1 christos */
47 1.1 christos typedef struct _TABLE {
48 1.1 christos const char *name;
49 1.1 christos int type;
50 1.1 christos time_t value;
51 1.1 christos } TABLE;
52 1.1 christos
53 1.1 christos
54 1.1 christos /*
55 1.1 christos ** Daylight-savings mode: on, off, or not yet known.
56 1.1 christos */
57 1.1 christos typedef enum _DSTMODE {
58 1.1 christos DSTon, DSToff, DSTmaybe
59 1.1 christos } DSTMODE;
60 1.1 christos
61 1.1 christos /*
62 1.1 christos ** Meridian: am, pm, or 24-hour style.
63 1.1 christos */
64 1.1 christos typedef enum _MERIDIAN {
65 1.1 christos MERam, MERpm, MER24
66 1.1 christos } MERIDIAN;
67 1.1 christos
68 1.1 christos
69 1.1 christos /*
70 1.1 christos ** Global variables. We could get rid of most of these by using a good
71 1.1 christos ** union as the yacc stack. (This routine was originally written before
72 1.1 christos ** yacc had the %union construct.) Maybe someday; right now we only use
73 1.1 christos ** the %union very rarely.
74 1.1 christos */
75 1.1 christos static const char *yyInput;
76 1.1 christos static DSTMODE yyDSTmode;
77 1.1 christos static time_t yyDayOrdinal;
78 1.1 christos static time_t yyDayNumber;
79 1.1 christos static int yyHaveDate;
80 1.1 christos static int yyHaveDay;
81 1.1 christos static int yyHaveRel;
82 1.1 christos static int yyHaveTime;
83 1.1 christos static int yyHaveZone;
84 1.1 christos static time_t yyTimezone;
85 1.1 christos static time_t yyDay;
86 1.1 christos static time_t yyHour;
87 1.1 christos static time_t yyMinutes;
88 1.1 christos static time_t yyMonth;
89 1.1 christos static time_t yySeconds;
90 1.1 christos static time_t yyYear;
91 1.1 christos static MERIDIAN yyMeridian;
92 1.1 christos static time_t yyRelMonth;
93 1.1 christos static time_t yyRelSeconds;
94 1.1 christos
95 1.1 christos %}
96 1.1 christos
97 1.1 christos %union {
98 1.1 christos time_t Number;
99 1.1 christos enum _MERIDIAN Meridian;
100 1.1 christos }
101 1.1 christos
102 1.1 christos %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
103 1.1 christos %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
104 1.1 christos
105 1.1 christos %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
106 1.1 christos %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
107 1.1 christos %type <Meridian> tMERIDIAN o_merid
108 1.1 christos
109 1.1 christos %%
110 1.1 christos
111 1.1 christos spec : /* NULL */
112 1.1 christos | spec item
113 1.1 christos ;
114 1.1 christos
115 1.1 christos item : time {
116 1.1 christos yyHaveTime++;
117 1.1 christos }
118 1.1 christos | zone {
119 1.1 christos yyHaveZone++;
120 1.1 christos }
121 1.1 christos | date {
122 1.1 christos yyHaveDate++;
123 1.1 christos }
124 1.1 christos | day {
125 1.1 christos yyHaveDay++;
126 1.1 christos }
127 1.1 christos | rel {
128 1.1 christos yyHaveRel++;
129 1.1 christos }
130 1.1 christos | cvsstamp {
131 1.1 christos yyHaveTime++;
132 1.1 christos yyHaveDate++;
133 1.1 christos yyHaveZone++;
134 1.1 christos }
135 1.1 christos | number
136 1.1 christos ;
137 1.1 christos
138 1.1 christos cvsstamp: tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER {
139 1.1 christos yyYear = $1;
140 1.1 christos if (yyYear < 100) yyYear += 1900;
141 1.1 christos yyMonth = $3;
142 1.1 christos yyDay = $5;
143 1.1 christos yyHour = $7;
144 1.1 christos yyMinutes = $9;
145 1.1 christos yySeconds = $11;
146 1.1 christos yyDSTmode = DSToff;
147 1.1 christos yyTimezone = 0;
148 1.1 christos }
149 1.1 christos ;
150 1.1 christos
151 1.1 christos time : tUNUMBER tMERIDIAN {
152 1.1 christos yyHour = $1;
153 1.1 christos yyMinutes = 0;
154 1.1 christos yySeconds = 0;
155 1.1 christos yyMeridian = $2;
156 1.1 christos }
157 1.1 christos | tUNUMBER ':' tUNUMBER o_merid {
158 1.1 christos yyHour = $1;
159 1.1 christos yyMinutes = $3;
160 1.1 christos yySeconds = 0;
161 1.1 christos yyMeridian = $4;
162 1.1 christos }
163 1.1 christos | tUNUMBER ':' tUNUMBER tSNUMBER {
164 1.1 christos yyHour = $1;
165 1.1 christos yyMinutes = $3;
166 1.1 christos yyMeridian = MER24;
167 1.1 christos yyDSTmode = DSToff;
168 1.1 christos yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
169 1.1 christos }
170 1.1 christos | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
171 1.1 christos yyHour = $1;
172 1.1 christos yyMinutes = $3;
173 1.1 christos yySeconds = $5;
174 1.1 christos yyMeridian = $6;
175 1.1 christos }
176 1.1 christos | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
177 1.1 christos yyHour = $1;
178 1.1 christos yyMinutes = $3;
179 1.1 christos yySeconds = $5;
180 1.1 christos yyMeridian = MER24;
181 1.1 christos yyDSTmode = DSToff;
182 1.1 christos yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
183 1.1 christos }
184 1.1 christos ;
185 1.1 christos
186 1.1 christos zone : tZONE {
187 1.1 christos yyTimezone = $1;
188 1.1 christos yyDSTmode = DSToff;
189 1.1 christos }
190 1.1 christos | tDAYZONE {
191 1.1 christos yyTimezone = $1;
192 1.1 christos yyDSTmode = DSTon;
193 1.1 christos }
194 1.1 christos |
195 1.1 christos tZONE tDST {
196 1.1 christos yyTimezone = $1;
197 1.1 christos yyDSTmode = DSTon;
198 1.1 christos }
199 1.1 christos ;
200 1.1 christos
201 1.1 christos day : tDAY {
202 1.1 christos yyDayOrdinal = 1;
203 1.1 christos yyDayNumber = $1;
204 1.1 christos }
205 1.1 christos | tDAY ',' {
206 1.1 christos yyDayOrdinal = 1;
207 1.1 christos yyDayNumber = $1;
208 1.1 christos }
209 1.1 christos | tUNUMBER tDAY {
210 1.1 christos yyDayOrdinal = $1;
211 1.1 christos yyDayNumber = $2;
212 1.1 christos }
213 1.1 christos ;
214 1.1 christos
215 1.1 christos date : tUNUMBER '/' tUNUMBER {
216 1.1 christos yyMonth = $1;
217 1.1 christos yyDay = $3;
218 1.1 christos }
219 1.1 christos | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
220 1.1 christos if ($1 >= 100) {
221 1.1 christos yyYear = $1;
222 1.1 christos yyMonth = $3;
223 1.1 christos yyDay = $5;
224 1.1 christos } else {
225 1.1 christos yyMonth = $1;
226 1.1 christos yyDay = $3;
227 1.1 christos yyYear = $5;
228 1.1 christos }
229 1.1 christos }
230 1.1 christos | tUNUMBER tSNUMBER tSNUMBER {
231 1.1 christos /* ISO 8601 format. yyyy-mm-dd. */
232 1.1 christos yyYear = $1;
233 1.1 christos yyMonth = -$2;
234 1.1 christos yyDay = -$3;
235 1.1 christos }
236 1.1 christos | tUNUMBER tMONTH tSNUMBER {
237 1.1 christos /* e.g. 17-JUN-1992. */
238 1.1 christos yyDay = $1;
239 1.1 christos yyMonth = $2;
240 1.1 christos yyYear = -$3;
241 1.1 christos }
242 1.1 christos | tMONTH tUNUMBER {
243 1.1 christos yyMonth = $1;
244 1.1 christos yyDay = $2;
245 1.1 christos }
246 1.1 christos | tMONTH tUNUMBER ',' tUNUMBER {
247 1.1 christos yyMonth = $1;
248 1.1 christos yyDay = $2;
249 1.1 christos yyYear = $4;
250 1.1 christos }
251 1.1 christos | tUNUMBER tMONTH {
252 1.1 christos yyMonth = $2;
253 1.1 christos yyDay = $1;
254 1.1 christos }
255 1.1 christos | tUNUMBER tMONTH tUNUMBER {
256 1.1 christos yyMonth = $2;
257 1.1 christos yyDay = $1;
258 1.1 christos yyYear = $3;
259 1.1 christos }
260 1.1 christos ;
261 1.1 christos
262 1.1 christos rel : relunit tAGO {
263 1.1 christos yyRelSeconds = -yyRelSeconds;
264 1.1 christos yyRelMonth = -yyRelMonth;
265 1.1 christos }
266 1.1 christos | relunit
267 1.1 christos ;
268 1.1 christos
269 1.1 christos relunit : tUNUMBER tMINUTE_UNIT {
270 1.1 christos yyRelSeconds += $1 * $2 * 60L;
271 1.1 christos }
272 1.1 christos | tSNUMBER tMINUTE_UNIT {
273 1.1 christos yyRelSeconds += $1 * $2 * 60L;
274 1.1 christos }
275 1.1 christos | tMINUTE_UNIT {
276 1.1 christos yyRelSeconds += $1 * 60L;
277 1.1 christos }
278 1.1 christos | tSNUMBER tSEC_UNIT {
279 1.1 christos yyRelSeconds += $1;
280 1.1 christos }
281 1.1 christos | tUNUMBER tSEC_UNIT {
282 1.1 christos yyRelSeconds += $1;
283 1.1 christos }
284 1.1 christos | tSEC_UNIT {
285 1.1 christos yyRelSeconds++;
286 1.1 christos }
287 1.1 christos | tSNUMBER tMONTH_UNIT {
288 1.1 christos yyRelMonth += $1 * $2;
289 1.1 christos }
290 1.1 christos | tUNUMBER tMONTH_UNIT {
291 1.1 christos yyRelMonth += $1 * $2;
292 1.1 christos }
293 1.1 christos | tMONTH_UNIT {
294 1.1 christos yyRelMonth += $1;
295 1.1 christos }
296 1.1 christos ;
297 1.1 christos
298 1.1 christos number : tUNUMBER {
299 1.1 christos if (yyHaveTime && yyHaveDate && !yyHaveRel)
300 1.1 christos yyYear = $1;
301 1.1 christos else {
302 1.1 christos if($1>10000) {
303 1.1 christos yyHaveDate++;
304 1.1 christos yyDay= ($1)%100;
305 1.1 christos yyMonth= ($1/100)%100;
306 1.1 christos yyYear = $1/10000;
307 1.1 christos }
308 1.1 christos else {
309 1.1 christos yyHaveTime++;
310 1.1 christos if ($1 < 100) {
311 1.1 christos yyHour = $1;
312 1.1 christos yyMinutes = 0;
313 1.1 christos }
314 1.1 christos else {
315 1.1 christos yyHour = $1 / 100;
316 1.1 christos yyMinutes = $1 % 100;
317 1.1 christos }
318 1.1 christos yySeconds = 0;
319 1.1 christos yyMeridian = MER24;
320 1.1 christos }
321 1.1 christos }
322 1.1 christos }
323 1.1 christos ;
324 1.1 christos
325 1.1 christos o_merid : /* NULL */ {
326 1.1 christos $$ = MER24;
327 1.1 christos }
328 1.1 christos | tMERIDIAN {
329 1.1 christos $$ = $1;
330 1.1 christos }
331 1.1 christos ;
332 1.1 christos
333 1.1 christos %%
334 1.1 christos
335 1.1 christos /* Month and day table. */
336 1.1 christos static TABLE const MonthDayTable[] = {
337 1.1 christos { "january", tMONTH, 1 },
338 1.1 christos { "february", tMONTH, 2 },
339 1.1 christos { "march", tMONTH, 3 },
340 1.1 christos { "april", tMONTH, 4 },
341 1.1 christos { "may", tMONTH, 5 },
342 1.1 christos { "june", tMONTH, 6 },
343 1.1 christos { "july", tMONTH, 7 },
344 1.1 christos { "august", tMONTH, 8 },
345 1.1 christos { "september", tMONTH, 9 },
346 1.1 christos { "sept", tMONTH, 9 },
347 1.1 christos { "october", tMONTH, 10 },
348 1.1 christos { "november", tMONTH, 11 },
349 1.1 christos { "december", tMONTH, 12 },
350 1.1 christos { "sunday", tDAY, 0 },
351 1.1 christos { "monday", tDAY, 1 },
352 1.1 christos { "tuesday", tDAY, 2 },
353 1.1 christos { "tues", tDAY, 2 },
354 1.1 christos { "wednesday", tDAY, 3 },
355 1.1 christos { "wednes", tDAY, 3 },
356 1.1 christos { "thursday", tDAY, 4 },
357 1.1 christos { "thur", tDAY, 4 },
358 1.1 christos { "thurs", tDAY, 4 },
359 1.1 christos { "friday", tDAY, 5 },
360 1.1 christos { "saturday", tDAY, 6 },
361 1.1 christos { NULL, 0, 0 }
362 1.1 christos };
363 1.1 christos
364 1.1 christos /* Time units table. */
365 1.1 christos static TABLE const UnitsTable[] = {
366 1.1 christos { "year", tMONTH_UNIT, 12 },
367 1.1 christos { "month", tMONTH_UNIT, 1 },
368 1.1 christos { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
369 1.1 christos { "week", tMINUTE_UNIT, 7 * 24 * 60 },
370 1.1 christos { "day", tMINUTE_UNIT, 1 * 24 * 60 },
371 1.1 christos { "hour", tMINUTE_UNIT, 60 },
372 1.1 christos { "minute", tMINUTE_UNIT, 1 },
373 1.1 christos { "min", tMINUTE_UNIT, 1 },
374 1.1 christos { "second", tSEC_UNIT, 1 },
375 1.1 christos { "sec", tSEC_UNIT, 1 },
376 1.1 christos { NULL, 0, 0 }
377 1.1 christos };
378 1.1 christos
379 1.1 christos /* Assorted relative-time words. */
380 1.1 christos static TABLE const OtherTable[] = {
381 1.1 christos { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
382 1.1 christos { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
383 1.1 christos { "today", tMINUTE_UNIT, 0 },
384 1.1 christos { "now", tMINUTE_UNIT, 0 },
385 1.1 christos { "last", tUNUMBER, -1 },
386 1.1 christos { "this", tMINUTE_UNIT, 0 },
387 1.1 christos { "next", tUNUMBER, 2 },
388 1.1 christos { "first", tUNUMBER, 1 },
389 1.1 christos /* { "second", tUNUMBER, 2 }, */
390 1.1 christos { "third", tUNUMBER, 3 },
391 1.1 christos { "fourth", tUNUMBER, 4 },
392 1.1 christos { "fifth", tUNUMBER, 5 },
393 1.1 christos { "sixth", tUNUMBER, 6 },
394 1.1 christos { "seventh", tUNUMBER, 7 },
395 1.1 christos { "eighth", tUNUMBER, 8 },
396 1.1 christos { "ninth", tUNUMBER, 9 },
397 1.1 christos { "tenth", tUNUMBER, 10 },
398 1.1 christos { "eleventh", tUNUMBER, 11 },
399 1.1 christos { "twelfth", tUNUMBER, 12 },
400 1.1 christos { "ago", tAGO, 1 },
401 1.1 christos { NULL, 0, 0 }
402 1.1 christos };
403 1.1 christos
404 1.1 christos /* The timezone table. */
405 1.1 christos /* Some of these are commented out because a time_t can't store a float. */
406 1.1 christos static TABLE const TimezoneTable[] = {
407 1.1 christos { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
408 1.1 christos { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
409 1.1 christos { "utc", tZONE, HOUR( 0) },
410 1.1 christos { "wet", tZONE, HOUR( 0) }, /* Western European */
411 1.1 christos { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
412 1.1 christos { "wat", tZONE, HOUR( 1) }, /* West Africa */
413 1.1 christos { "at", tZONE, HOUR( 2) }, /* Azores */
414 1.1 christos #if 0
415 1.1 christos /* For completeness. BST is also British Summer, and GST is
416 1.1 christos * also Guam Standard. */
417 1.1 christos { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
418 1.1 christos { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
419 1.1 christos #endif
420 1.1 christos #if 0
421 1.1 christos { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
422 1.1 christos { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
423 1.1 christos { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
424 1.1 christos #endif
425 1.1 christos { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
426 1.1 christos { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
427 1.1 christos { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
428 1.1 christos { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
429 1.1 christos { "cst", tZONE, HOUR( 6) }, /* Central Standard */
430 1.1 christos { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
431 1.1 christos { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
432 1.1 christos { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
433 1.1 christos { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
434 1.1 christos { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
435 1.1 christos { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
436 1.1 christos { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
437 1.1 christos { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
438 1.1 christos { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
439 1.1 christos { "cat", tZONE, HOUR(10) }, /* Central Alaska */
440 1.1 christos { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
441 1.1 christos { "nt", tZONE, HOUR(11) }, /* Nome */
442 1.1 christos { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
443 1.1 christos { "cet", tZONE, -HOUR(1) }, /* Central European */
444 1.1 christos { "met", tZONE, -HOUR(1) }, /* Middle European */
445 1.1 christos { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
446 1.1 christos { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
447 1.1 christos { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
448 1.1 christos { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
449 1.1 christos { "fwt", tZONE, -HOUR(1) }, /* French Winter */
450 1.1 christos { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
451 1.1 christos { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
452 1.1 christos { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
453 1.1 christos #if 0
454 1.1 christos { "it", tZONE, -HOUR(3.5) },/* Iran */
455 1.1 christos #endif
456 1.1 christos { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
457 1.1 christos { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
458 1.1 christos #if 0
459 1.1 christos { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
460 1.1 christos #endif
461 1.1 christos { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
462 1.1 christos #if 0
463 1.1 christos /* For completeness. NST is also Newfoundland Stanard, and SST is
464 1.1 christos * also Swedish Summer. */
465 1.1 christos { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
466 1.1 christos { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
467 1.1 christos #endif /* 0 */
468 1.1 christos { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
469 1.1 christos { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
470 1.1 christos #if 0
471 1.1 christos { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
472 1.1 christos #endif
473 1.1 christos { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
474 1.1 christos { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
475 1.1 christos #if 0
476 1.1 christos { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
477 1.1 christos { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
478 1.1 christos #endif
479 1.1 christos { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
480 1.1 christos { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
481 1.1 christos { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
482 1.1 christos { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
483 1.1 christos { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
484 1.1 christos { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
485 1.1 christos { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
486 1.1 christos { NULL, 0, 0 }
487 1.1 christos };
488 1.1 christos
489 1.1 christos /* Military timezone table. */
490 1.1 christos static TABLE const MilitaryTable[] = {
491 1.1 christos { "a", tZONE, HOUR( 1) },
492 1.1 christos { "b", tZONE, HOUR( 2) },
493 1.1 christos { "c", tZONE, HOUR( 3) },
494 1.1 christos { "d", tZONE, HOUR( 4) },
495 1.1 christos { "e", tZONE, HOUR( 5) },
496 1.1 christos { "f", tZONE, HOUR( 6) },
497 1.1 christos { "g", tZONE, HOUR( 7) },
498 1.1 christos { "h", tZONE, HOUR( 8) },
499 1.1 christos { "i", tZONE, HOUR( 9) },
500 1.1 christos { "k", tZONE, HOUR( 10) },
501 1.1 christos { "l", tZONE, HOUR( 11) },
502 1.1 christos { "m", tZONE, HOUR( 12) },
503 1.1 christos { "n", tZONE, HOUR(- 1) },
504 1.1 christos { "o", tZONE, HOUR(- 2) },
505 1.1 christos { "p", tZONE, HOUR(- 3) },
506 1.1 christos { "q", tZONE, HOUR(- 4) },
507 1.1 christos { "r", tZONE, HOUR(- 5) },
508 1.1 christos { "s", tZONE, HOUR(- 6) },
509 1.1 christos { "t", tZONE, HOUR(- 7) },
510 1.1 christos { "u", tZONE, HOUR(- 8) },
511 1.1 christos { "v", tZONE, HOUR(- 9) },
512 1.1 christos { "w", tZONE, HOUR(-10) },
513 1.1 christos { "x", tZONE, HOUR(-11) },
514 1.1 christos { "y", tZONE, HOUR(-12) },
515 1.1 christos { "z", tZONE, HOUR( 0) },
516 1.1 christos { NULL, 0, 0 }
517 1.1 christos };
518 1.1 christos
519 1.1 christos
520 1.1 christos
522 1.1 christos
523 1.1 christos /* ARGSUSED */
524 1.2 christos static int
525 1.1 christos yyerror(const char *s __unused)
526 1.1 christos {
527 1.1 christos return 0;
528 1.1 christos }
529 1.1 christos
530 1.1 christos
531 1.1 christos static time_t
532 1.1 christos ToSeconds(
533 1.1 christos time_t Hours,
534 1.1 christos time_t Minutes,
535 1.1 christos time_t Seconds,
536 1.1 christos MERIDIAN Meridian
537 1.1 christos )
538 1.1 christos {
539 1.1 christos if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
540 1.1 christos return -1;
541 1.1 christos switch (Meridian) {
542 1.1 christos case MER24:
543 1.1 christos if (Hours < 0 || Hours > 23)
544 1.1 christos return -1;
545 1.1 christos return (Hours * 60L + Minutes) * 60L + Seconds;
546 1.1 christos case MERam:
547 1.1 christos if (Hours < 1 || Hours > 12)
548 1.1 christos return -1;
549 1.1 christos if (Hours == 12)
550 1.1 christos Hours = 0;
551 1.1 christos return (Hours * 60L + Minutes) * 60L + Seconds;
552 1.1 christos case MERpm:
553 1.1 christos if (Hours < 1 || Hours > 12)
554 1.1 christos return -1;
555 1.1 christos if (Hours == 12)
556 1.1 christos Hours = 0;
557 1.1 christos return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
558 1.1 christos default:
559 1.1 christos abort ();
560 1.1 christos }
561 1.1 christos /* NOTREACHED */
562 1.1 christos }
563 1.1 christos
564 1.1 christos
565 1.1 christos /* Year is either
566 1.1 christos * A negative number, which means to use its absolute value (why?)
567 1.1 christos * A number from 0 to 99, which means a year from 1900 to 1999, or
568 1.1 christos * The actual year (>=100). */
569 1.1 christos static time_t
570 1.1 christos Convert(
571 1.1 christos time_t Month,
572 1.1 christos time_t Day,
573 1.1 christos time_t Year,
574 1.1 christos time_t Hours,
575 1.1 christos time_t Minutes,
576 1.1 christos time_t Seconds,
577 1.1 christos MERIDIAN Meridian,
578 1.1 christos DSTMODE DSTmode
579 1.1 christos )
580 1.1 christos {
581 1.1 christos static int DaysInMonth[12] = {
582 1.1 christos 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
583 1.1 christos };
584 1.1 christos time_t tod;
585 1.1 christos time_t Julian;
586 1.1 christos int i;
587 1.1 christos
588 1.1 christos if (Year < 0)
589 1.1 christos Year = -Year;
590 1.1 christos if (Year < 69)
591 1.1 christos Year += 2000;
592 1.1 christos else if (Year < 100)
593 1.1 christos Year += 1900;
594 1.1 christos DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
595 1.1 christos ? 29 : 28;
596 1.1 christos /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
597 1.1 christos I'm too lazy to try to check for time_t overflow in another way. */
598 1.1 christos if (Year < EPOCH || Year > 2038
599 1.1 christos || Month < 1 || Month > 12
600 1.1 christos /* Lint fluff: "conversion from long may lose accuracy" */
601 1.1 christos || Day < 1 || Day > DaysInMonth[(int)--Month])
602 1.1 christos /* FIXME:
603 1.1 christos * It would be nice to set a global error string here.
604 1.1 christos * "February 30 is not a valid date" is much more informative than
605 1.1 christos * "Can't parse date/time: 100 months" when the user input was
606 1.1 christos * "100 months" and addition resolved that to February 30, for
607 1.1 christos * example. See rcs2-7 in src/sanity.sh for more. */
608 1.1 christos return -1;
609 1.1 christos
610 1.1 christos for (Julian = Day - 1, i = 0; i < Month; i++)
611 1.1 christos Julian += DaysInMonth[i];
612 1.1 christos for (i = EPOCH; i < Year; i++)
613 1.1 christos Julian += 365 + (i % 4 == 0);
614 1.1 christos Julian *= SECSPERDAY;
615 1.1 christos Julian += yyTimezone * 60L;
616 1.1 christos if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
617 1.1 christos return -1;
618 1.1 christos Julian += tod;
619 1.1 christos if (DSTmode == DSTon
620 1.1 christos || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
621 1.1 christos Julian -= 60 * 60;
622 1.1 christos return Julian;
623 1.1 christos }
624 1.1 christos
625 1.1 christos
626 1.1 christos static time_t
627 1.1 christos DSTcorrect(
628 1.1 christos time_t Start,
629 1.1 christos time_t Future
630 1.1 christos )
631 1.1 christos {
632 1.1 christos time_t StartDay;
633 1.1 christos time_t FutureDay;
634 1.1 christos
635 1.1 christos StartDay = (localtime(&Start)->tm_hour + 1) % 24;
636 1.1 christos FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
637 1.1 christos return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
638 1.1 christos }
639 1.1 christos
640 1.1 christos
641 1.1 christos static time_t
642 1.1 christos RelativeDate(
643 1.1 christos time_t Start,
644 1.1 christos time_t DayOrdinal,
645 1.1 christos time_t DayNumber
646 1.1 christos )
647 1.1 christos {
648 1.1 christos struct tm *tm;
649 1.1 christos time_t now;
650 1.1 christos
651 1.1 christos now = Start;
652 1.1 christos tm = localtime(&now);
653 1.1 christos now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
654 1.1 christos now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
655 1.1 christos return DSTcorrect(Start, now);
656 1.1 christos }
657 1.1 christos
658 1.1 christos
659 1.1 christos static time_t
660 1.1 christos RelativeMonth(
661 1.1 christos time_t Start,
662 1.1 christos time_t RelMonth
663 1.1 christos )
664 1.1 christos {
665 1.1 christos struct tm *tm;
666 1.1 christos time_t Month;
667 1.1 christos time_t Year;
668 1.1 christos
669 1.1 christos if (RelMonth == 0)
670 1.1 christos return 0;
671 1.1 christos tm = localtime(&Start);
672 1.1 christos Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
673 1.1 christos Year = Month / 12;
674 1.1 christos Month = Month % 12 + 1;
675 1.1 christos return DSTcorrect(Start,
676 1.1 christos Convert(Month, (time_t)tm->tm_mday, Year,
677 1.1 christos (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
678 1.1 christos MER24, DSTmaybe));
679 1.1 christos }
680 1.1 christos
681 1.1 christos
682 1.1 christos static int
683 1.1 christos LookupWord(char *buff)
684 1.1 christos {
685 1.1 christos register char *p;
686 1.1 christos register char *q;
687 1.1 christos register const TABLE *tp;
688 1.1 christos int i;
689 1.1 christos int abbrev;
690 1.1 christos
691 1.1 christos /* Make it lowercase. */
692 1.1 christos for (p = buff; *p; p++)
693 1.1 christos if (isupper((unsigned char)*p))
694 1.1 christos *p = tolower((unsigned char)*p);
695 1.1 christos
696 1.1 christos if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
697 1.1 christos yylval.Meridian = MERam;
698 1.1 christos return tMERIDIAN;
699 1.1 christos }
700 1.1 christos if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
701 1.1 christos yylval.Meridian = MERpm;
702 1.1 christos return tMERIDIAN;
703 1.1 christos }
704 1.1 christos
705 1.1 christos /* See if we have an abbreviation for a month. */
706 1.1 christos if (strlen(buff) == 3)
707 1.1 christos abbrev = 1;
708 1.1 christos else if (strlen(buff) == 4 && buff[3] == '.') {
709 1.1 christos abbrev = 1;
710 1.1 christos buff[3] = '\0';
711 1.1 christos }
712 1.1 christos else
713 1.1 christos abbrev = 0;
714 1.1 christos
715 1.1 christos for (tp = MonthDayTable; tp->name; tp++) {
716 1.1 christos if (abbrev) {
717 1.1 christos if (strncmp(buff, tp->name, 3) == 0) {
718 1.1 christos yylval.Number = tp->value;
719 1.1 christos return tp->type;
720 1.1 christos }
721 1.1 christos }
722 1.1 christos else if (strcmp(buff, tp->name) == 0) {
723 1.1 christos yylval.Number = tp->value;
724 1.1 christos return tp->type;
725 1.1 christos }
726 1.1 christos }
727 1.1 christos
728 1.1 christos for (tp = TimezoneTable; tp->name; tp++)
729 1.1 christos if (strcmp(buff, tp->name) == 0) {
730 1.1 christos yylval.Number = tp->value;
731 1.1 christos return tp->type;
732 1.1 christos }
733 1.1 christos
734 1.1 christos if (strcmp(buff, "dst") == 0)
735 1.1 christos return tDST;
736 1.1 christos
737 1.1 christos for (tp = UnitsTable; tp->name; tp++)
738 1.1 christos if (strcmp(buff, tp->name) == 0) {
739 1.1 christos yylval.Number = tp->value;
740 1.1 christos return tp->type;
741 1.1 christos }
742 1.1 christos
743 1.1 christos /* Strip off any plural and try the units table again. */
744 1.1 christos i = strlen(buff) - 1;
745 1.1 christos if (buff[i] == 's') {
746 1.1 christos buff[i] = '\0';
747 1.1 christos for (tp = UnitsTable; tp->name; tp++)
748 1.1 christos if (strcmp(buff, tp->name) == 0) {
749 1.1 christos yylval.Number = tp->value;
750 1.1 christos return tp->type;
751 1.1 christos }
752 1.1 christos buff[i] = 's'; /* Put back for "this" in OtherTable. */
753 1.1 christos }
754 1.1 christos
755 1.1 christos for (tp = OtherTable; tp->name; tp++)
756 1.1 christos if (strcmp(buff, tp->name) == 0) {
757 1.1 christos yylval.Number = tp->value;
758 1.1 christos return tp->type;
759 1.1 christos }
760 1.1 christos
761 1.1 christos /* Military timezones. */
762 1.1 christos if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
763 1.1 christos for (tp = MilitaryTable; tp->name; tp++)
764 1.1 christos if (strcmp(buff, tp->name) == 0) {
765 1.1 christos yylval.Number = tp->value;
766 1.1 christos return tp->type;
767 1.1 christos }
768 1.1 christos }
769 1.1 christos
770 1.1 christos /* Drop out any periods and try the timezone table again. */
771 1.1 christos for (i = 0, p = q = buff; *q; q++)
772 1.1 christos if (*q != '.')
773 1.1 christos *p++ = *q;
774 1.1 christos else
775 1.1 christos i++;
776 1.1 christos *p = '\0';
777 1.1 christos if (i)
778 1.1 christos for (tp = TimezoneTable; tp->name; tp++)
779 1.1 christos if (strcmp(buff, tp->name) == 0) {
780 1.1 christos yylval.Number = tp->value;
781 1.1 christos return tp->type;
782 1.1 christos }
783 1.1 christos
784 1.1 christos return tID;
785 1.1 christos }
786 1.1 christos
787 1.1 christos
788 1.1 christos static int
789 1.1 christos yylex(void)
790 1.1 christos {
791 1.1 christos register char c;
792 1.1 christos register char *p;
793 1.1 christos char buff[20];
794 1.1 christos int Count;
795 1.1 christos int sign;
796 1.1 christos
797 1.1 christos for ( ; ; ) {
798 1.1 christos while (isspace((unsigned char)*yyInput))
799 1.1 christos yyInput++;
800 1.1 christos
801 1.1 christos if (isdigit((unsigned char)(c = *yyInput)) || c == '-' || c == '+') {
802 1.1 christos if (c == '-' || c == '+') {
803 1.1 christos sign = c == '-' ? -1 : 1;
804 1.1 christos if (!isdigit((unsigned char)*++yyInput))
805 1.1 christos /* skip the '-' sign */
806 1.1 christos continue;
807 1.1 christos }
808 1.1 christos else
809 1.1 christos sign = 0;
810 1.1 christos for (yylval.Number = 0; isdigit((unsigned char)(c = *yyInput++)); )
811 1.1 christos yylval.Number = 10 * yylval.Number + c - '0';
812 1.1 christos yyInput--;
813 1.1 christos if (sign < 0)
814 1.1 christos yylval.Number = -yylval.Number;
815 1.1 christos return sign ? tSNUMBER : tUNUMBER;
816 1.1 christos }
817 1.1 christos if (isalpha((unsigned char)c)) {
818 1.1 christos for (p = buff; isalpha((unsigned char)(c = *yyInput++)) || c == '.'; )
819 1.1 christos if (p < &buff[sizeof buff - 1])
820 1.1 christos *p++ = c;
821 1.1 christos *p = '\0';
822 1.1 christos yyInput--;
823 1.1 christos return LookupWord(buff);
824 1.1 christos }
825 1.1 christos if (c != '(')
826 1.1 christos return *yyInput++;
827 1.1 christos Count = 0;
828 1.1 christos do {
829 1.1 christos c = *yyInput++;
830 1.1 christos if (c == '\0')
831 1.1 christos return c;
832 1.1 christos if (c == '(')
833 1.1 christos Count++;
834 1.1 christos else if (c == ')')
835 1.1 christos Count--;
836 1.1 christos } while (Count > 0);
837 1.1 christos }
838 1.1 christos }
839 1.1 christos
840 1.1 christos #define TM_YEAR_ORIGIN 1900
841 1.1 christos
842 1.1 christos /* Yield A - B, measured in seconds. */
843 1.1 christos static long
844 1.1 christos difftm (struct tm *a, struct tm *b)
845 1.1 christos {
846 1.1 christos int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
847 1.1 christos int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
848 1.1 christos int days = (
849 1.1 christos /* difference in day of year */
850 1.1 christos a->tm_yday - b->tm_yday
851 1.1 christos /* + intervening leap days */
852 1.1 christos + ((ay >> 2) - (by >> 2))
853 1.1 christos - (ay/100 - by/100)
854 1.1 christos + ((ay/100 >> 2) - (by/100 >> 2))
855 1.1 christos /* + difference in years * 365 */
856 1.1 christos + (long)(ay-by) * 365
857 1.1 christos );
858 1.1 christos return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
859 1.1 christos + (a->tm_min - b->tm_min))
860 1.1 christos + (a->tm_sec - b->tm_sec));
861 1.1 christos }
862 1.1 christos
863 1.1 christos time_t
864 1.1 christos parsedate(const char *p, const time_t *now, const int *zone)
865 1.1 christos {
866 1.1 christos struct tm gmt, local, *gmt_ptr, *tm;
867 1.1 christos time_t nowt;
868 1.1 christos int zonet;
869 1.1 christos time_t Start;
870 1.1 christos time_t tod;
871 1.1 christos
872 1.1 christos yyInput = p;
873 1.1 christos if (now == NULL || zone == NULL) {
874 1.1 christos now = &nowt;
875 1.1 christos zone = &zonet;
876 1.1 christos (void)time(&nowt);
877 1.1 christos
878 1.1 christos gmt_ptr = gmtime_r(now, &gmt);
879 1.1 christos if ((tm = localtime_r(now, &local)) == NULL)
880 1.1 christos return -1;
881 1.1 christos
882 1.1 christos if (gmt_ptr != NULL)
883 1.1 christos zonet = difftm(&gmt, &local) / 60;
884 1.1 christos else
885 1.1 christos /* We are on a system like VMS, where the system clock is
886 1.1 christos in local time and the system has no concept of timezones.
887 1.1 christos Hopefully we can fake this out (for the case in which the
888 1.1 christos user specifies no timezone) by just saying the timezone
889 1.1 christos is zero. */
890 1.1 christos zonet = 0;
891 1.1 christos
892 1.1 christos if (local.tm_isdst)
893 1.1 christos zonet += 60;
894 1.1 christos } else {
895 1.1 christos if ((tm = localtime_r(now, &local)) == NULL)
896 1.1 christos return -1;
897 1.1 christos }
898 1.1 christos yyYear = tm->tm_year + 1900;
899 1.1 christos yyMonth = tm->tm_mon + 1;
900 1.1 christos yyDay = tm->tm_mday;
901 1.1 christos yyTimezone = *zone;
902 1.1 christos yyDSTmode = DSTmaybe;
903 1.1 christos yyHour = 0;
904 1.1 christos yyMinutes = 0;
905 1.1 christos yySeconds = 0;
906 1.1 christos yyMeridian = MER24;
907 1.1 christos yyRelSeconds = 0;
908 1.1 christos yyRelMonth = 0;
909 1.1 christos yyHaveDate = 0;
910 1.1 christos yyHaveDay = 0;
911 1.1 christos yyHaveRel = 0;
912 1.1 christos yyHaveTime = 0;
913 1.1 christos yyHaveZone = 0;
914 1.1 christos
915 1.1 christos if (yyparse()
916 1.1 christos || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
917 1.1 christos return -1;
918 1.1 christos
919 1.1 christos if (yyHaveDate || yyHaveTime || yyHaveDay) {
920 1.1 christos Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
921 1.1 christos yyMeridian, yyDSTmode);
922 1.1 christos if (Start < 0)
923 1.1 christos return -1;
924 1.1 christos }
925 1.1 christos else {
926 1.1 christos Start = *now;
927 1.1 christos if (!yyHaveRel)
928 1.1 christos Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
929 1.1 christos }
930 1.1 christos
931 1.1 christos Start += yyRelSeconds;
932 1.1 christos Start += RelativeMonth(Start, yyRelMonth);
933 1.1 christos
934 1.1 christos if (yyHaveDay && !yyHaveDate) {
935 1.1 christos tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
936 1.1 christos Start += tod;
937 1.1 christos }
938 1.1 christos
939 1.1 christos /* Have to do *something* with a legitimate -1 so it's distinguishable
940 1.1 christos * from the error return value. (Alternately could set errno on error.) */
941 1.1 christos return Start == -1 ? 0 : Start;
942 1.1 christos }
943 1.1 christos
944 1.1 christos
945 1.1 christos #if defined(TEST)
946 1.1 christos
947 1.1 christos /* ARGSUSED */
948 1.1 christos int
949 1.1 christos main(ac, av)
950 1.1 christos int ac;
951 1.1 christos char *av[];
952 1.1 christos {
953 1.1 christos char buff[128];
954 1.1 christos time_t d;
955 1.1 christos
956 1.1 christos (void)printf("Enter date, or blank line to exit.\n\t> ");
957 1.1 christos (void)fflush(stdout);
958 1.1 christos while (gets(buff) && buff[0]) {
959 1.1 christos d = parsedate(buff, NULL, NULL);
960 1.1 christos if (d == -1)
961 1.1 christos (void)printf("Bad format - couldn't convert.\n");
962 1.1 christos else
963 1.1 christos (void)printf("%s", ctime(&d));
964 1.1 christos (void)printf("\t> ");
965 1.1 christos (void)fflush(stdout);
966 1.1 christos }
967 1.1 christos exit(0);
968 1.1 christos /* NOTREACHED */
969 1.1 christos }
970 #endif /* defined(TEST) */
971