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