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