3 * March 2005: Further modified and simplified by Tim Kientzle:
4 * Eliminate minutes-based calculations (just do everything in
5 * seconds), have lexer only recognize unsigned integers (handle '+'
6 * and '-' characters in grammar), combine tables into one table with
7 * explicit abbreviation notes, do am/pm adjustments in the grammar
8 * (eliminate some state variables and post-processing). Among other
9 * things, these changes eliminated two shift/reduce conflicts. (Went
11 * All of Tim Kientzle's changes to this file are public domain.
15 ** Originally written by Steven M. Bellovin <smb@research.att.com> while
16 ** at the University of North Carolina at Chapel Hill. Later tweaked by
17 ** a couple of people on Usenet. Completely overhauled by Rich $alz
18 ** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
20 ** This grammar has 10 shift/reduce conflicts.
22 ** This code is in the public domain and has no copyright.
24 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
25 /* SUPPRESS 288 on yyerrlab *//* Label unused */
28 #include <sys/cdefs.h>
29 __FBSDID
("$FreeBSD: src/usr.bin/tar/getdate.y,v 1.9 2007/07/20 01:27:50 kientzle Exp $");
38 #define yyparse getdate_yyparse
39 #define yylex getdate_yylex
40 #define yyerror getdate_yyerror
42 static int yyparse(void);
43 static int yylex(void);
44 static int yyerror(const char *);
46 time_t get_date
(char *);
49 #define HOUR(x) ((time_t)(x) * 60)
50 #define SECSPERDAY (24L * 60L * 60L)
53 ** Daylight-savings mode: on, off, or not yet known.
55 typedef
enum _DSTMODE
{
56 DSTon
, DSToff
, DSTmaybe
60 ** Meridian: am or pm.
65 ** Global variables. We could get rid of most of these by using a good
66 ** union as the yacc stack. (This routine was originally written before
67 ** yacc had the %union construct.) Maybe someday; right now we only use
68 ** the %union very rarely.
72 static DSTMODE yyDSTmode
;
73 static time_t yyDayOrdinal
;
74 static time_t yyDayNumber
;
75 static int yyHaveDate
;
78 static int yyHaveTime
;
79 static int yyHaveZone
;
80 static time_t yyTimezone
;
83 static time_t yyMinutes
;
84 static time_t yyMonth
;
85 static time_t yySeconds
;
87 static time_t yyRelMonth
;
88 static time_t yyRelSeconds
;
96 %token tAGO tDAY tDAYZONE tAMPM tMONTH tMONTH_UNIT tSEC_UNIT tUNUMBER
99 %type
<Number
> tDAY tDAYZONE tMONTH tMONTH_UNIT
100 %type
<Number
> tSEC_UNIT tUNUMBER tZONE tAMPM
108 item
: time
{ yyHaveTime
++; }
109 | zone
{ yyHaveZone
++; }
110 | date
{ yyHaveDate
++; }
111 | day
{ yyHaveDay
++; }
112 | rel
{ yyHaveRel
++; }
116 time
: tUNUMBER tAMPM
{
127 /* "7:12:18" "19:17" */
130 /* "7:12pm", "12:20:13am" */
136 | bare_time
'+' tUNUMBER
{
139 yyTimezone
= - ($3 %
100 + ($3 / 100) * 60);
141 | bare_time
'-' tUNUMBER
{
142 /* "19:14:12-0530" */
144 yyTimezone
= + ($3 %
100 + ($3 / 100) * 60);
148 bare_time
: tUNUMBER
':' tUNUMBER
{
153 | tUNUMBER
':' tUNUMBER
':' tUNUMBER
{
179 /* "tue," "wednesday," */
184 /* "second tues" "3 wed" */
190 date
: tUNUMBER
'/' tUNUMBER
{
195 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
{
197 /* First number is big: 2004/01/29, 99/02/17 */
201 } else if
(($5 >= 13) ||
($3 >= 13)) {
202 /* Last number is big: 01/07/98 */
203 /* Middle number is big: 01/29/04 */
208 /* No significant clues: 02/03/04 */
214 | tUNUMBER
'-' tUNUMBER
'-' tUNUMBER
{
215 /* ISO 8601 format. yyyy-mm-dd. */
220 | tUNUMBER
'-' tMONTH
'-' tUNUMBER
{
222 /* e.g. 1992-Jun-17 */
227 /* e.g. 17-JUN-1992. */
238 | tMONTH tUNUMBER
',' tUNUMBER
{
239 /* "June 17, 2001" */
249 | tUNUMBER tMONTH tUNUMBER
{
258 yyRelSeconds
= -yyRelSeconds
;
259 yyRelMonth
= -yyRelMonth
;
264 relunit
: '-' tUNUMBER tSEC_UNIT
{
266 yyRelSeconds
-= $2 * $3;
268 |
'+' tUNUMBER tSEC_UNIT
{
270 yyRelSeconds
+= $2 * $3;
272 | tUNUMBER tSEC_UNIT
{
274 yyRelSeconds
+= $1 * $2;
280 |
'-' tUNUMBER tMONTH_UNIT
{
282 yyRelMonth
-= $2 * $3;
284 |
'+' tUNUMBER tMONTH_UNIT
{
286 yyRelMonth
+= $2 * $3;
288 | tUNUMBER tMONTH_UNIT
{
290 yyRelMonth
+= $1 * $2;
299 if
(yyHaveTime
&& yyHaveDate
&& !yyHaveRel
)
306 yyMonth
= ($1/100)%
100;
310 /* "513" is same as "5:13" */
318 yyMinutes
= $1 %
100;
329 static struct TABLE
{
334 } const TimeWords
[] = {
336 { 0, "am", tAMPM
, tAM
},
337 { 0, "pm", tAMPM
, tPM
},
340 { 3, "january", tMONTH
, 1 },
341 { 3, "february", tMONTH
, 2 },
342 { 3, "march", tMONTH
, 3 },
343 { 3, "april", tMONTH
, 4 },
344 { 3, "may", tMONTH
, 5 },
345 { 3, "june", tMONTH
, 6 },
346 { 3, "july", tMONTH
, 7 },
347 { 3, "august", tMONTH
, 8 },
348 { 3, "september", tMONTH
, 9 },
349 { 3, "october", tMONTH
, 10 },
350 { 3, "november", tMONTH
, 11 },
351 { 3, "december", tMONTH
, 12 },
353 /* Days of the week. */
354 { 2, "sunday", tDAY
, 0 },
355 { 3, "monday", tDAY
, 1 },
356 { 2, "tuesday", tDAY
, 2 },
357 { 3, "wednesday", tDAY
, 3 },
358 { 2, "thursday", tDAY
, 4 },
359 { 2, "friday", tDAY
, 5 },
360 { 2, "saturday", tDAY
, 6 },
362 /* Timezones: Offsets are in minutes. */
363 { 0, "gmt", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
364 { 0, "ut", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
365 { 0, "utc", tZONE
, HOUR
( 0) },
366 { 0, "wet", tZONE
, HOUR
( 0) }, /* Western European */
367 { 0, "bst", tDAYZONE
, HOUR
( 0) }, /* British Summer */
368 { 0, "wat", tZONE
, HOUR
( 1) }, /* West Africa */
369 { 0, "at", tZONE
, HOUR
( 2) }, /* Azores */
370 /* { 0, "bst", tZONE, HOUR( 3) }, */ /* Brazil Standard: Conflict */
371 /* { 0, "gst", tZONE, HOUR( 3) }, */ /* Greenland Standard: Conflict*/
372 { 0, "nft", tZONE
, HOUR
(3)+30 }, /* Newfoundland */
373 { 0, "nst", tZONE
, HOUR
(3)+30 }, /* Newfoundland Standard */
374 { 0, "ndt", tDAYZONE
, HOUR
(3)+30 }, /* Newfoundland Daylight */
375 { 0, "ast", tZONE
, HOUR
( 4) }, /* Atlantic Standard */
376 { 0, "adt", tDAYZONE
, HOUR
( 4) }, /* Atlantic Daylight */
377 { 0, "est", tZONE
, HOUR
( 5) }, /* Eastern Standard */
378 { 0, "edt", tDAYZONE
, HOUR
( 5) }, /* Eastern Daylight */
379 { 0, "cst", tZONE
, HOUR
( 6) }, /* Central Standard */
380 { 0, "cdt", tDAYZONE
, HOUR
( 6) }, /* Central Daylight */
381 { 0, "mst", tZONE
, HOUR
( 7) }, /* Mountain Standard */
382 { 0, "mdt", tDAYZONE
, HOUR
( 7) }, /* Mountain Daylight */
383 { 0, "pst", tZONE
, HOUR
( 8) }, /* Pacific Standard */
384 { 0, "pdt", tDAYZONE
, HOUR
( 8) }, /* Pacific Daylight */
385 { 0, "yst", tZONE
, HOUR
( 9) }, /* Yukon Standard */
386 { 0, "ydt", tDAYZONE
, HOUR
( 9) }, /* Yukon Daylight */
387 { 0, "hst", tZONE
, HOUR
(10) }, /* Hawaii Standard */
388 { 0, "hdt", tDAYZONE
, HOUR
(10) }, /* Hawaii Daylight */
389 { 0, "cat", tZONE
, HOUR
(10) }, /* Central Alaska */
390 { 0, "ahst", tZONE
, HOUR
(10) }, /* Alaska-Hawaii Standard */
391 { 0, "nt", tZONE
, HOUR
(11) }, /* Nome */
392 { 0, "idlw", tZONE
, HOUR
(12) }, /* Intl Date Line West */
393 { 0, "cet", tZONE
, -HOUR
(1) }, /* Central European */
394 { 0, "met", tZONE
, -HOUR
(1) }, /* Middle European */
395 { 0, "mewt", tZONE
, -HOUR
(1) }, /* Middle European Winter */
396 { 0, "mest", tDAYZONE
, -HOUR
(1) }, /* Middle European Summer */
397 { 0, "swt", tZONE
, -HOUR
(1) }, /* Swedish Winter */
398 { 0, "sst", tDAYZONE
, -HOUR
(1) }, /* Swedish Summer */
399 { 0, "fwt", tZONE
, -HOUR
(1) }, /* French Winter */
400 { 0, "fst", tDAYZONE
, -HOUR
(1) }, /* French Summer */
401 { 0, "eet", tZONE
, -HOUR
(2) }, /* Eastern Eur, USSR Zone 1 */
402 { 0, "bt", tZONE
, -HOUR
(3) }, /* Baghdad, USSR Zone 2 */
403 { 0, "it", tZONE
, -HOUR
(3)-30 },/* Iran */
404 { 0, "zp4", tZONE
, -HOUR
(4) }, /* USSR Zone 3 */
405 { 0, "zp5", tZONE
, -HOUR
(5) }, /* USSR Zone 4 */
406 { 0, "ist", tZONE
, -HOUR
(5)-30 },/* Indian Standard */
407 { 0, "zp6", tZONE
, -HOUR
(6) }, /* USSR Zone 5 */
408 /* { 0, "nst", tZONE, -HOUR(6.5) }, */ /* North Sumatra: Conflict */
409 /* { 0, "sst", tZONE, -HOUR(7) }, */ /* So Sumatra, USSR 6: Conflict */
410 { 0, "wast", tZONE
, -HOUR
(7) }, /* West Australian Standard */
411 { 0, "wadt", tDAYZONE
, -HOUR
(7) }, /* West Australian Daylight */
412 { 0, "jt", tZONE
, -HOUR
(7)-30 },/* Java (3pm in Cronusland!)*/
413 { 0, "cct", tZONE
, -HOUR
(8) }, /* China Coast, USSR Zone 7 */
414 { 0, "jst", tZONE
, -HOUR
(9) }, /* Japan Std, USSR Zone 8 */
415 { 0, "cast", tZONE
, -HOUR
(9)-30 },/* Central Australian Std */
416 { 0, "cadt", tDAYZONE
, -HOUR
(9)-30 },/* Central Australian Daylt */
417 { 0, "east", tZONE
, -HOUR
(10) }, /* Eastern Australian Std */
418 { 0, "eadt", tDAYZONE
, -HOUR
(10) }, /* Eastern Australian Daylt */
419 { 0, "gst", tZONE
, -HOUR
(10) }, /* Guam Std, USSR Zone 9 */
420 { 0, "nzt", tZONE
, -HOUR
(12) }, /* New Zealand */
421 { 0, "nzst", tZONE
, -HOUR
(12) }, /* New Zealand Standard */
422 { 0, "nzdt", tDAYZONE
, -HOUR
(12) }, /* New Zealand Daylight */
423 { 0, "idle", tZONE
, -HOUR
(12) }, /* Intl Date Line East */
425 { 0, "dst", tDST
, 0 },
428 { 4, "years", tMONTH_UNIT
, 12 },
429 { 5, "months", tMONTH_UNIT
, 1 },
430 { 9, "fortnights", tSEC_UNIT
, 14 * 24 * 60 * 60 },
431 { 4, "weeks", tSEC_UNIT
, 7 * 24 * 60 * 60 },
432 { 3, "days", tSEC_UNIT
, 1 * 24 * 60 * 60 },
433 { 4, "hours", tSEC_UNIT
, 60 * 60 },
434 { 3, "minutes", tSEC_UNIT
, 60 },
435 { 3, "seconds", tSEC_UNIT
, 1 },
437 /* Relative-time words. */
438 { 0, "tomorrow", tSEC_UNIT
, 1 * 24 * 60 * 60 },
439 { 0, "yesterday", tSEC_UNIT
, -1 * 24 * 60 * 60 },
440 { 0, "today", tSEC_UNIT
, 0 },
441 { 0, "now", tSEC_UNIT
, 0 },
442 { 0, "last", tUNUMBER
, -1 },
443 { 0, "this", tSEC_UNIT
, 0 },
444 { 0, "next", tUNUMBER
, 2 },
445 { 0, "first", tUNUMBER
, 1 },
446 { 0, "1st", tUNUMBER
, 1 },
447 /* { 0, "second", tUNUMBER, 2 }, */
448 { 0, "2nd", tUNUMBER
, 2 },
449 { 0, "third", tUNUMBER
, 3 },
450 { 0, "3rd", tUNUMBER
, 3 },
451 { 0, "fourth", tUNUMBER
, 4 },
452 { 0, "4th", tUNUMBER
, 4 },
453 { 0, "fifth", tUNUMBER
, 5 },
454 { 0, "5th", tUNUMBER
, 5 },
455 { 0, "sixth", tUNUMBER
, 6 },
456 { 0, "seventh", tUNUMBER
, 7 },
457 { 0, "eighth", tUNUMBER
, 8 },
458 { 0, "ninth", tUNUMBER
, 9 },
459 { 0, "tenth", tUNUMBER
, 10 },
460 { 0, "eleventh", tUNUMBER
, 11 },
461 { 0, "twelfth", tUNUMBER
, 12 },
462 { 0, "ago", tAGO
, 1 },
464 /* Military timezones. */
465 { 0, "a", tZONE
, HOUR
( 1) },
466 { 0, "b", tZONE
, HOUR
( 2) },
467 { 0, "c", tZONE
, HOUR
( 3) },
468 { 0, "d", tZONE
, HOUR
( 4) },
469 { 0, "e", tZONE
, HOUR
( 5) },
470 { 0, "f", tZONE
, HOUR
( 6) },
471 { 0, "g", tZONE
, HOUR
( 7) },
472 { 0, "h", tZONE
, HOUR
( 8) },
473 { 0, "i", tZONE
, HOUR
( 9) },
474 { 0, "k", tZONE
, HOUR
( 10) },
475 { 0, "l", tZONE
, HOUR
( 11) },
476 { 0, "m", tZONE
, HOUR
( 12) },
477 { 0, "n", tZONE
, HOUR
(- 1) },
478 { 0, "o", tZONE
, HOUR
(- 2) },
479 { 0, "p", tZONE
, HOUR
(- 3) },
480 { 0, "q", tZONE
, HOUR
(- 4) },
481 { 0, "r", tZONE
, HOUR
(- 5) },
482 { 0, "s", tZONE
, HOUR
(- 6) },
483 { 0, "t", tZONE
, HOUR
(- 7) },
484 { 0, "u", tZONE
, HOUR
(- 8) },
485 { 0, "v", tZONE
, HOUR
(- 9) },
486 { 0, "w", tZONE
, HOUR
(-10) },
487 { 0, "x", tZONE
, HOUR
(-11) },
488 { 0, "y", tZONE
, HOUR
(-12) },
489 { 0, "z", tZONE
, HOUR
( 0) },
500 yyerror(const char *s
)
507 ToSeconds
(time_t Hours
, time_t Minutes
, time_t Seconds
)
509 if
(Minutes
< 0 || Minutes
> 59 || Seconds
< 0 || Seconds
> 59)
511 if
(Hours
< 0 || Hours
> 23)
513 return
(Hours
* 60L + Minutes
) * 60L + Seconds
;
518 * A number from 0 to 99, which means a year from 1970 to 2069, or
519 * The actual year (>=100). */
521 Convert
(time_t Month
, time_t Day
, time_t Year
,
522 time_t Hours
, time_t Minutes
, time_t Seconds
, DSTMODE DSTmode
)
524 static int DaysInMonth
[12] = {
525 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
535 DaysInMonth
[1] = Year %
4 == 0 && (Year %
100 != 0 || Year %
400 == 0)
537 /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
538 I'm too lazy to try to check for time_t overflow in another way. */
539 if
(Year
< EPOCH || Year
> 2038
540 || Month
< 1 || Month
> 12
541 /* Lint fluff: "conversion from long may lose accuracy" */
542 || Day
< 1 || Day
> DaysInMonth
[(int)--Month
])
546 for
(i
= 0; i
< Month
; i
++)
547 Julian
+= DaysInMonth
[i
];
548 for
(i
= EPOCH
; i
< Year
; i
++)
549 Julian
+= 365 + (i %
4 == 0);
550 Julian
*= SECSPERDAY
;
551 Julian
+= yyTimezone
* 60L;
552 if
((tod
= ToSeconds
(Hours
, Minutes
, Seconds
)) < 0)
556 ||
(DSTmode
== DSTmaybe
&& localtime
(&Julian
)->tm_isdst
))
563 DSTcorrect
(time_t Start
, time_t Future
)
568 StartDay
= (localtime
(&Start
)->tm_hour
+ 1) %
24;
569 FutureDay
= (localtime
(&Future
)->tm_hour
+ 1) %
24;
570 return
(Future
- Start
) + (StartDay
- FutureDay
) * 60L * 60L;
575 RelativeDate
(time_t Start
, time_t DayOrdinal
, time_t DayNumber
)
581 tm
= localtime
(&now
);
582 now
+= SECSPERDAY
* ((DayNumber
- tm
->tm_wday
+ 7) %
7);
583 now
+= 7 * SECSPERDAY
* (DayOrdinal
<= 0 ? DayOrdinal
: DayOrdinal
- 1);
584 return DSTcorrect
(Start
, now
);
589 RelativeMonth
(time_t Start
, time_t RelMonth
)
597 tm
= localtime
(&Start
);
598 Month
= 12 * (tm
->tm_year
+ 1900) + tm
->tm_mon
+ RelMonth
;
600 Month
= Month %
12 + 1;
601 return DSTcorrect
(Start
,
602 Convert
(Month
, (time_t)tm
->tm_mday
, Year
,
603 (time_t)tm
->tm_hour
, (time_t)tm
->tm_min
, (time_t)tm
->tm_sec
,
614 while
(isspace
((unsigned char)*yyInput
))
617 /* Skip parenthesized comments. */
618 if
(*yyInput
== '(') {
632 /* Try the next token in the word table first. */
633 /* This allows us to match "2nd", for example. */
636 const struct TABLE
*tp
;
639 /* Force to lowercase and strip '.' characters. */
641 && (isalnum
((unsigned char)*src
) ||
*src
== '.')
642 && i
< sizeof
(buff
)-1) {
644 if
(isupper
((unsigned char)*src
))
645 buff
[i
++] = tolower
((unsigned char)*src
);
654 * Find the first match. If the word can be
655 * abbreviated, make sure we match at least
656 * the minimum abbreviation.
658 for
(tp
= TimeWords
; tp
->name
; tp
++) {
659 size_t abbrev
= tp
->abbrev
;
661 abbrev
= strlen
(tp
->name
);
662 if
(strlen
(buff
) >= abbrev
663 && strncmp
(tp
->name
, buff
, strlen
(buff
))
665 /* Skip over token. */
667 /* Return the match. */
668 yylval.Number
= tp
->value
;
675 * Not in the word table, maybe it's a number. Note:
676 * Because '-' and '+' have other special meanings, I
677 * don't deal with signed numbers here.
679 if
(isdigit
((unsigned char)(c
= *yyInput
))) {
680 for
(yylval.Number
= 0; isdigit
((unsigned char)(c
= *yyInput
++)); )
681 yylval.Number
= 10 * yylval.Number
+ c
- '0';
690 #define TM_YEAR_ORIGIN 1900
692 /* Yield A - B, measured in seconds. */
694 difftm
(struct tm
*a
, struct tm
*b
)
696 int ay
= a
->tm_year
+ (TM_YEAR_ORIGIN
- 1);
697 int by
= b
->tm_year
+ (TM_YEAR_ORIGIN
- 1);
699 /* difference in day of year */
700 a
->tm_yday
- b
->tm_yday
701 /* + intervening leap days */
702 + ((ay
>> 2) - (by
>> 2))
704 + ((ay
/100 >> 2) - (by
/100 >> 2))
705 /* + difference in years * 365 */
706 + (long)(ay
-by
) * 365
708 return
(60*(60*(24*days
+ (a
->tm_hour
- b
->tm_hour
))
709 + (a
->tm_min
- b
->tm_min
))
710 + (a
->tm_sec
- b
->tm_sec
));
717 struct tm gmt
, *gmt_ptr
;
723 memset
(&gmt
, 0, sizeof
(gmt
));
726 (void)time
(&nowtime
);
728 gmt_ptr
= gmtime
(&nowtime
);
729 if
(gmt_ptr
!= NULL
) {
730 /* Copy, in case localtime and gmtime use the same buffer. */
734 if
(! (tm
= localtime
(&nowtime
)))
738 tzone
= difftm
(&gmt
, tm
) / 60;
740 /* This system doesn't understand timezones; fake it. */
745 yyYear
= tm
->tm_year
+ 1900;
746 yyMonth
= tm
->tm_mon
+ 1;
749 yyDSTmode
= DSTmaybe
;
762 || yyHaveTime
> 1 || yyHaveZone
> 1
763 || yyHaveDate
> 1 || yyHaveDay
> 1)
766 if
(yyHaveDate || yyHaveTime || yyHaveDay
) {
767 Start
= Convert
(yyMonth
, yyDay
, yyYear
,
768 yyHour
, yyMinutes
, yySeconds
, yyDSTmode
);
774 Start
-= ((tm
->tm_hour
* 60L + tm
->tm_min
) * 60L) + tm
->tm_sec
;
777 Start
+= yyRelSeconds
;
778 Start
+= RelativeMonth
(Start
, yyRelMonth
);
780 if
(yyHaveDay
&& !yyHaveDate
) {
781 tod
= RelativeDate
(Start
, yyDayOrdinal
, yyDayNumber
);
785 /* Have to do *something* with a legitimate -1 so it's
786 * distinguishable from the error return value. (Alternately
787 * could set errno on error.) */
788 return Start
== -1 ?
0 : Start
;
796 main
(int argc
, char **argv
)
800 while
(*++argv
!= NULL
) {
801 (void)printf
("Input: %s\n", *argv
);
804 (void)printf
("Bad format - couldn't convert.\n");
806 (void)printf
("Output: %s\n", ctime
(&d
));
811 #endif /* defined(TEST) */