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 static time_t 577 ToSeconds( 578 time_t Hours, 579 time_t Minutes, 580 time_t Seconds, 581 MERIDIAN Meridian 582 ) 583 { 584 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) 585 return -1; 586 switch (Meridian) { 587 case MER24: 588 if (Hours < 0 || Hours > 23) 589 return -1; 590 return (Hours * 60L + Minutes) * 60L + Seconds; 591 case MERam: 592 if (Hours < 1 || Hours > 12) 593 return -1; 594 if (Hours == 12) 595 Hours = 0; 596 return (Hours * 60L + Minutes) * 60L + Seconds; 597 case MERpm: 598 if (Hours < 1 || Hours > 12) 599 return -1; 600 if (Hours == 12) 601 Hours = 0; 602 return ((Hours + 12) * 60L + Minutes) * 60L + Seconds; 603 default: 604 abort (); 605 } 606 /* NOTREACHED */ 607 } 608 609 static int 610 isLeap(int year) 611 { 612 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); 613 } 614 615 616 /* Year is either 617 * A negative number, which means to use its absolute value (why?) 618 * A number from 0 to 99, which means a year from 1900 to 1999, or 619 * The actual year (>=100). */ 620 static time_t 621 Convert( 622 time_t Month, 623 time_t Day, 624 time_t Year, 625 time_t Hours, 626 time_t Minutes, 627 time_t Seconds, 628 time_t Timezone, 629 MERIDIAN Meridian, 630 DSTMODE DSTmode 631 ) 632 { 633 static int DaysInMonth[12] = { 634 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 635 }; 636 time_t tod; 637 time_t Julian, oJulian; 638 int i; 639 640 /* XXX Y2K */ 641 if (Year < 0) 642 Year = -Year; 643 if (Year < 70) 644 Year += 2000; 645 else if (Year < 100) 646 Year += 1900; 647 DaysInMonth[1] = isLeap(Year) ? 29 : 28; 648 if (Year < EPOCH || Month < 1 || Month > 12 649 /* Lint fluff: "conversion from long may lose accuracy" */ 650 || Day < 1 || Day > DaysInMonth[(int)--Month]) 651 /* FIXME: 652 * It would be nice to set a global error string here. 653 * "February 30 is not a valid date" is much more informative than 654 * "Can't parse date/time: 100 months" when the user input was 655 * "100 months" and addition resolved that to February 30, for 656 * example. See rcs2-7 in src/sanity.sh for more. */ 657 return -1; 658 659 for (Julian = Day - 1, i = 0; i < Month; i++) 660 Julian += DaysInMonth[i]; 661 662 oJulian = Julian; 663 for (i = EPOCH; i < Year; i++) { 664 Julian += 365 + isLeap(i); 665 if (oJulian > Julian) 666 return -1; 667 oJulian = Julian; 668 } 669 670 Julian *= SECSPERDAY; 671 if (oJulian > Julian) 672 return -1; 673 oJulian = Julian; 674 Julian += Timezone * 60L; 675 if (Timezone > 0 && oJulian > Julian) 676 return -1; 677 oJulian = Julian; 678 679 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0) 680 return -1; 681 682 Julian += tod; 683 if (oJulian > Julian) 684 return -1; 685 686 if (DSTmode == DSTon || (DSTmode == DSTmaybe)) { 687 struct tm *tm; 688 if ((tm = localtime(&Julian)) == NULL) 689 return -1; 690 if (tm->tm_isdst) 691 Julian -= 60 * 60; 692 } 693 return Julian; 694 } 695 696 697 static time_t 698 DSTcorrect( 699 time_t Start, 700 time_t Future 701 ) 702 { 703 time_t StartDay; 704 time_t FutureDay; 705 struct tm *tm; 706 707 if ((tm = localtime(&Start)) == NULL) 708 return -1; 709 StartDay = (tm->tm_hour + 1) % 24; 710 711 if ((tm = localtime(&Future)) == NULL) 712 return -1; 713 FutureDay = (tm->tm_hour + 1) % 24; 714 715 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; 716 } 717 718 719 static time_t 720 RelativeDate( 721 time_t Start, 722 time_t DayOrdinal, 723 time_t DayNumber 724 ) 725 { 726 struct tm *tm; 727 time_t now; 728 729 now = Start; 730 tm = localtime(&now); 731 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7); 732 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); 733 return DSTcorrect(Start, now); 734 } 735 736 737 static time_t 738 RelativeMonth( 739 time_t Start, 740 time_t RelMonth, 741 time_t Timezone 742 ) 743 { 744 struct tm *tm; 745 time_t Month; 746 time_t Year; 747 748 if (RelMonth == 0) 749 return 0; 750 tm = localtime(&Start); 751 if (tm == NULL) 752 return -1; 753 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth; 754 Year = Month / 12; 755 Month = Month % 12 + 1; 756 return DSTcorrect(Start, 757 Convert(Month, (time_t)tm->tm_mday, Year, 758 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec, 759 Timezone, MER24, DSTmaybe)); 760 } 761 762 763 static int 764 LookupWord(YYSTYPE *yylval, char *buff) 765 { 766 register char *p; 767 register char *q; 768 register const TABLE *tp; 769 int i; 770 int abbrev; 771 772 /* Make it lowercase. */ 773 for (p = buff; *p; p++) 774 if (isupper((unsigned char)*p)) 775 *p = tolower((unsigned char)*p); 776 777 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) { 778 yylval->Meridian = MERam; 779 return tMERIDIAN; 780 } 781 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) { 782 yylval->Meridian = MERpm; 783 return tMERIDIAN; 784 } 785 786 /* See if we have an abbreviation for a month. */ 787 if (strlen(buff) == 3) 788 abbrev = 1; 789 else if (strlen(buff) == 4 && buff[3] == '.') { 790 abbrev = 1; 791 buff[3] = '\0'; 792 } 793 else 794 abbrev = 0; 795 796 for (tp = MonthDayTable; tp->name; tp++) { 797 if (abbrev) { 798 if (strncmp(buff, tp->name, 3) == 0) { 799 yylval->Number = tp->value; 800 return tp->type; 801 } 802 } 803 else if (strcmp(buff, tp->name) == 0) { 804 yylval->Number = tp->value; 805 return tp->type; 806 } 807 } 808 809 for (tp = TimezoneTable; tp->name; tp++) 810 if (strcmp(buff, tp->name) == 0) { 811 yylval->Number = tp->value; 812 return tp->type; 813 } 814 815 if (strcmp(buff, "dst") == 0) 816 return tDST; 817 818 for (tp = UnitsTable; tp->name; tp++) 819 if (strcmp(buff, tp->name) == 0) { 820 yylval->Number = tp->value; 821 return tp->type; 822 } 823 824 /* Strip off any plural and try the units table again. */ 825 i = strlen(buff) - 1; 826 if (buff[i] == 's') { 827 buff[i] = '\0'; 828 for (tp = UnitsTable; tp->name; tp++) 829 if (strcmp(buff, tp->name) == 0) { 830 yylval->Number = tp->value; 831 return tp->type; 832 } 833 buff[i] = 's'; /* Put back for "this" in OtherTable. */ 834 } 835 836 for (tp = OtherTable; tp->name; tp++) 837 if (strcmp(buff, tp->name) == 0) { 838 yylval->Number = tp->value; 839 return tp->type; 840 } 841 842 /* Military timezones. */ 843 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) { 844 for (tp = MilitaryTable; tp->name; tp++) 845 if (strcmp(buff, tp->name) == 0) { 846 yylval->Number = tp->value; 847 return tp->type; 848 } 849 } 850 851 /* Drop out any periods and try the timezone table again. */ 852 for (i = 0, p = q = buff; *q; q++) 853 if (*q != '.') 854 *p++ = *q; 855 else 856 i++; 857 *p = '\0'; 858 if (i) 859 for (tp = TimezoneTable; tp->name; tp++) 860 if (strcmp(buff, tp->name) == 0) { 861 yylval->Number = tp->value; 862 return tp->type; 863 } 864 865 return tID; 866 } 867 868 869 static int 870 yylex(YYSTYPE *yylval, const char **yyInput) 871 { 872 register char c; 873 register char *p; 874 char buff[20]; 875 int Count; 876 int sign; 877 const char *inp = *yyInput; 878 879 for ( ; ; ) { 880 while (isspace((unsigned char)*inp)) 881 inp++; 882 883 if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') { 884 if (c == '-' || c == '+') { 885 sign = c == '-' ? -1 : 1; 886 if (!isdigit((unsigned char)*++inp)) 887 /* skip the '-' sign */ 888 continue; 889 } 890 else 891 sign = 0; 892 for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); ) 893 yylval->Number = 10 * yylval->Number + c - '0'; 894 if (sign < 0) 895 yylval->Number = -yylval->Number; 896 *yyInput = --inp; 897 return sign ? tSNUMBER : tUNUMBER; 898 } 899 if (isalpha((unsigned char)c)) { 900 for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; ) 901 if (p < &buff[sizeof buff - 1]) 902 *p++ = c; 903 *p = '\0'; 904 *yyInput = --inp; 905 return LookupWord(yylval, buff); 906 } 907 if (c == '@') { 908 *yyInput = ++inp; 909 return AT_SIGN; 910 } 911 if (c != '(') { 912 *yyInput = ++inp; 913 return c; 914 } 915 Count = 0; 916 do { 917 c = *inp++; 918 if (c == '\0') 919 return c; 920 if (c == '(') 921 Count++; 922 else if (c == ')') 923 Count--; 924 } while (Count > 0); 925 } 926 } 927 928 #define TM_YEAR_ORIGIN 1900 929 930 /* Yield A - B, measured in seconds. */ 931 static time_t 932 difftm (struct tm *a, struct tm *b) 933 { 934 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); 935 int by = b->tm_year + (TM_YEAR_ORIGIN - 1); 936 int days = ( 937 /* difference in day of year */ 938 a->tm_yday - b->tm_yday 939 /* + intervening leap days */ 940 + ((ay >> 2) - (by >> 2)) 941 - (ay/100 - by/100) 942 + ((ay/100 >> 2) - (by/100 >> 2)) 943 /* + difference in years * 365 */ 944 + (long)(ay-by) * 365 945 ); 946 return ((time_t)60*(60*(24*days + (a->tm_hour - b->tm_hour)) 947 + (a->tm_min - b->tm_min)) 948 + (a->tm_sec - b->tm_sec)); 949 } 950 951 time_t 952 parsedate(const char *p, const time_t *now, const int *zone) 953 { 954 struct tm gmt, local, *gmt_ptr, *tm; 955 time_t nowt; 956 int zonet; 957 time_t Start; 958 time_t tod, rm; 959 struct dateinfo param; 960 961 if (now == NULL || zone == NULL) { 962 now = &nowt; 963 zone = &zonet; 964 (void)time(&nowt); 965 966 gmt_ptr = gmtime_r(now, &gmt); 967 if ((tm = localtime_r(now, &local)) == NULL) 968 return -1; 969 970 if (gmt_ptr != NULL) 971 zonet = difftm(&gmt, &local) / 60; 972 else 973 /* We are on a system like VMS, where the system clock is 974 in local time and the system has no concept of timezones. 975 Hopefully we can fake this out (for the case in which the 976 user specifies no timezone) by just saying the timezone 977 is zero. */ 978 zonet = 0; 979 980 if (local.tm_isdst) 981 zonet += 60; 982 } else { 983 if ((tm = localtime_r(now, &local)) == NULL) 984 return -1; 985 } 986 param.yyYear = tm->tm_year + 1900; 987 param.yyMonth = tm->tm_mon + 1; 988 param.yyDay = tm->tm_mday; 989 param.yyTimezone = *zone; 990 param.yyDSTmode = DSTmaybe; 991 param.yyHour = 0; 992 param.yyMinutes = 0; 993 param.yySeconds = 0; 994 param.yyMeridian = MER24; 995 param.yyRelSeconds = 0; 996 param.yyRelMonth = 0; 997 param.yyHaveDate = 0; 998 param.yyHaveDay = 0; 999 param.yyHaveRel = 0; 1000 param.yyHaveTime = 0; 1001 param.yyHaveZone = 0; 1002 1003 if (yyparse(¶m, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 || 1004 param.yyHaveDate > 1 || param.yyHaveDay > 1) 1005 return -1; 1006 1007 if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) { 1008 Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour, 1009 param.yyMinutes, param.yySeconds, param.yyTimezone, 1010 param.yyMeridian, param.yyDSTmode); 1011 if (Start < 0) 1012 return -1; 1013 } 1014 else { 1015 Start = *now; 1016 if (!param.yyHaveRel) 1017 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; 1018 } 1019 1020 Start += param.yyRelSeconds; 1021 rm = RelativeMonth(Start, param.yyRelMonth, param.yyTimezone); 1022 if (rm == -1) 1023 return -1; 1024 Start += rm; 1025 1026 if (param.yyHaveDay && !param.yyHaveDate) { 1027 tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber); 1028 Start += tod; 1029 } 1030 1031 return Start; 1032 } 1033 1034 1035 #if defined(TEST) 1036 1037 /* ARGSUSED */ 1038 int 1039 main(ac, av) 1040 int ac; 1041 char *av[]; 1042 { 1043 char buff[128]; 1044 time_t d; 1045 1046 (void)printf("Enter date, or blank line to exit.\n\t> "); 1047 (void)fflush(stdout); 1048 while (gets(buff) && buff[0]) { 1049 d = parsedate(buff, NULL, NULL); 1050 if (d == -1) 1051 (void)printf("Bad format - couldn't convert.\n"); 1052 else 1053 (void)printf("%s", ctime(&d)); 1054 (void)printf("\t> "); 1055 (void)fflush(stdout); 1056 } 1057 exit(0); 1058 /* NOTREACHED */ 1059 } 1060 #endif /* defined(TEST) */ 1061