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 <sys/cdefs.h> 16 #ifdef __RCSID 17 __RCSID("$NetBSD: parsedate.y,v 1.32 2017/03/22 18:17:42 kre Exp $"); 18 #endif 19 20 #include <stdio.h> 21 #include <ctype.h> 22 #include <errno.h> 23 #include <string.h> 24 #include <time.h> 25 #include <util.h> 26 #include <stdlib.h> 27 28 /* NOTES on rebuilding parsedate.c (particularly for inclusion in CVS 29 releases): 30 31 We don't want to mess with all the portability hassles of alloca. 32 In particular, most (all?) versions of bison will use alloca in 33 their parser. If bison works on your system (e.g. it should work 34 with gcc), then go ahead and use it, but the more general solution 35 is to use byacc instead of bison, which should generate a portable 36 parser. I played with adding "#define alloca dont_use_alloca", to 37 give an error if the parser generator uses alloca (and thus detect 38 unportable parsedate.c's), but that seems to cause as many problems 39 as it solves. */ 40 41 #define EPOCH 1970 42 #define HOUR(x) ((time_t)((x) * 60)) 43 #define SECSPERDAY (24L * 60L * 60L) 44 45 #define MAXREL 16 /* hours mins secs days weeks months years - maybe twice each ...*/ 46 47 #define USE_LOCAL_TIME 99999 /* special case for Convert() and yyTimezone */ 48 49 /* 50 ** An entry in the lexical lookup table. 51 */ 52 typedef struct _TABLE { 53 const char *name; 54 int type; 55 time_t value; 56 } TABLE; 57 58 59 /* 60 ** Daylight-savings mode: on, off, or not yet known. 61 */ 62 typedef enum _DSTMODE { 63 DSTon, DSToff, DSTmaybe 64 } DSTMODE; 65 66 /* 67 ** Meridian: am, pm, or 24-hour style (plus "noon" and "midnight"). 68 */ 69 typedef enum _MERIDIAN { 70 MERam, MERpm, MER24, MER_NOON, MER_MN 71 } MERIDIAN; 72 73 74 struct dateinfo { 75 DSTMODE yyDSTmode; /* DST on/off/maybe */ 76 time_t yyDayOrdinal; 77 time_t yyDayNumber; 78 int yyHaveDate; 79 int yyHaveFullYear; /* if true, year is not abbreviated. */ 80 /* if false, need to call AdjustYear(). */ 81 int yyHaveDay; 82 int yyHaveRel; 83 int yyHaveTime; 84 int yyHaveZone; 85 time_t yyTimezone; /* Timezone as minutes ahead/east of UTC */ 86 time_t yyDay; /* Day of month [1-31] */ 87 time_t yyHour; /* Hour of day [0-24] or [1-12] */ 88 time_t yyMinutes; /* Minute of hour [0-59] */ 89 time_t yyMonth; /* Month of year [1-12] */ 90 time_t yySeconds; /* Second of minute [0-60] */ 91 time_t yyYear; /* Year, see also yyHaveFullYear */ 92 MERIDIAN yyMeridian; /* Interpret yyHour as AM/PM/24 hour clock */ 93 struct { 94 time_t yyRelVal; 95 int yyRelMonth; 96 } yyRel[MAXREL]; 97 }; 98 %} 99 100 %union { 101 time_t Number; 102 enum _MERIDIAN Meridian; 103 } 104 105 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT 106 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST AT_SIGN tTIME 107 108 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT 109 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE tTIME 110 %type <Meridian> tMERIDIAN 111 112 %type <Number> at_number 113 %type <Meridian> o_merid 114 115 %parse-param { struct dateinfo *param } 116 %parse-param { const char **yyInput } 117 %lex-param { const char **yyInput } 118 %pure-parser 119 120 %% 121 122 spec: 123 /* empty */ 124 | spec item 125 ; 126 127 item: 128 time { param->yyHaveTime++; } 129 | time_numericzone { param->yyHaveTime++; param->yyHaveZone++; } 130 | zone { param->yyHaveZone++; } 131 | date { param->yyHaveDate++; } 132 | day { param->yyHaveDay++; } 133 | rel { param->yyHaveRel++; } 134 | cvsstamp { param->yyHaveTime++; param->yyHaveDate++; 135 param->yyHaveZone++; } 136 | epochdate { param->yyHaveTime++; param->yyHaveDate++; 137 param->yyHaveZone++; } 138 | number 139 ; 140 141 cvsstamp: 142 tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' 143 tUNUMBER '.' tUNUMBER '.' tUNUMBER { 144 param->yyYear = $1; 145 if (param->yyYear < 100) { 146 param->yyYear += 1900; 147 } 148 param->yyHaveFullYear = 1; 149 param->yyMonth = $3; 150 param->yyDay = $5; 151 param->yyHour = $7; 152 param->yyMinutes = $9; 153 param->yySeconds = $11; 154 param->yyDSTmode = DSToff; 155 param->yyTimezone = 0; 156 } 157 ; 158 159 epochdate: 160 AT_SIGN at_number { 161 time_t when = $2; 162 struct tm tmbuf; 163 164 if (gmtime_r(&when, &tmbuf) != NULL) { 165 param->yyYear = tmbuf.tm_year + 1900; 166 param->yyMonth = tmbuf.tm_mon + 1; 167 param->yyDay = tmbuf.tm_mday; 168 169 param->yyHour = tmbuf.tm_hour; 170 param->yyMinutes = tmbuf.tm_min; 171 param->yySeconds = tmbuf.tm_sec; 172 } else { 173 param->yyYear = EPOCH; 174 param->yyMonth = 1; 175 param->yyDay = 1; 176 177 param->yyHour = 0; 178 param->yyMinutes = 0; 179 param->yySeconds = 0; 180 } 181 param->yyHaveFullYear = 1; 182 param->yyDSTmode = DSToff; 183 param->yyTimezone = 0; 184 } 185 ; 186 187 at_number: 188 tUNUMBER 189 | tSNUMBER 190 ; 191 192 time: 193 tUNUMBER tMERIDIAN { 194 param->yyMinutes = 0; 195 param->yySeconds = 0; 196 if ($2 == MER_NOON || $2 == MER_MN) { 197 if ($1 == 12) { 198 switch ($2) { 199 case MER_NOON: param->yyHour = 12; break; 200 case MER_MN : param->yyHour = 0; break; 201 default: /* impossible */; break; 202 } 203 param->yyMeridian = MER24; 204 } else 205 YYREJECT; 206 } else { 207 param->yyHour = $1; 208 param->yyMeridian = $2; 209 } 210 } 211 | tUNUMBER ':' tUNUMBER o_merid { 212 param->yyMinutes = $3; 213 param->yySeconds = 0; 214 if ($4 == MER_NOON || $4 == MER_MN) { 215 if ($1 == 12 && $3 == 0) { 216 switch ($4) { 217 case MER_NOON: param->yyHour = 12; break; 218 case MER_MN : param->yyHour = 0; break; 219 default: /* impossible */; break; 220 } 221 param->yyMeridian = MER24; 222 } else 223 YYREJECT; 224 } else { 225 param->yyHour = $1; 226 param->yyMeridian = $4; 227 } 228 } 229 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid { 230 param->yyMinutes = $3; 231 param->yySeconds = $5; 232 if ($6 == MER_NOON || $6 == MER_MN) { 233 if ($1 == 12 && $3 == 0 && $5 == 0) { 234 switch ($6) { 235 case MER_NOON: param->yyHour = 12; break; 236 case MER_MN : param->yyHour = 0; break; 237 default: /* impossible */; break; 238 } 239 param->yyMeridian = MER24; 240 } else 241 YYREJECT; 242 } else { 243 param->yyHour = $1; 244 param->yyMeridian = $6; 245 } 246 } 247 | tUNUMBER ':' tUNUMBER ':' tUNUMBER '.' tUNUMBER { 248 param->yyHour = $1; 249 param->yyMinutes = $3; 250 param->yySeconds = $5; 251 param->yyMeridian = MER24; 252 /* XXX: Do nothing with millis */ 253 } 254 | tTIME { 255 param->yyHour = $1; 256 param->yyMinutes = 0; 257 param->yySeconds = 0; 258 param->yyMeridian = MER24; 259 /* Tues midnight --> Weds 00:00, midnight Tues -> Tues 00:00 */ 260 if ($1 == 0 && param->yyHaveDay) 261 param->yyDayNumber++; 262 } 263 | tUNUMBER tTIME { 264 if ($1 == 12 && ($2 == 0 || $2 == 12)) { 265 param->yyHour = $2; 266 param->yyMinutes = 0; 267 param->yySeconds = 0; 268 param->yyMeridian = MER24; 269 } else 270 YYREJECT; 271 } 272 ; 273 274 time_numericzone: 275 tUNUMBER ':' tUNUMBER tSNUMBER { 276 param->yyHour = $1; 277 param->yyMinutes = $3; 278 param->yyMeridian = MER24; 279 param->yyDSTmode = DSToff; 280 param->yyTimezone = - ($4 % 100 + ($4 / 100) * 60); 281 } 282 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER { 283 param->yyHour = $1; 284 param->yyMinutes = $3; 285 param->yySeconds = $5; 286 param->yyMeridian = MER24; 287 param->yyDSTmode = DSToff; 288 param->yyTimezone = - ($6 % 100 + ($6 / 100) * 60); 289 } 290 ; 291 292 zone: 293 tZONE { param->yyTimezone = $1; param->yyDSTmode = DSToff; } 294 | tDAYZONE { param->yyTimezone = $1; param->yyDSTmode = DSTon; } 295 | tZONE tDST { param->yyTimezone = $1; param->yyDSTmode = DSTon; } 296 ; 297 298 day: 299 tDAY { param->yyDayOrdinal = 1; param->yyDayNumber = $1; } 300 | tDAY ',' { param->yyDayOrdinal = 1; param->yyDayNumber = $1; } 301 | tUNUMBER tDAY { param->yyDayOrdinal = $1; param->yyDayNumber = $2; } 302 ; 303 304 date: 305 tUNUMBER '/' tUNUMBER { 306 param->yyMonth = $1; 307 param->yyDay = $3; 308 } 309 | tUNUMBER '/' tUNUMBER '/' tUNUMBER { 310 if ($1 >= 100) { 311 param->yyYear = $1; 312 param->yyMonth = $3; 313 param->yyDay = $5; 314 } else { 315 param->yyMonth = $1; 316 param->yyDay = $3; 317 param->yyYear = $5; 318 } 319 } 320 | tUNUMBER tSNUMBER tSNUMBER { 321 /* ISO 8601 format. yyyy-mm-dd. */ 322 param->yyYear = $1; 323 param->yyHaveFullYear = 1; 324 param->yyMonth = -$2; 325 param->yyDay = -$3; 326 } 327 | tUNUMBER tMONTH tSNUMBER { 328 /* e.g. 17-JUN-1992. */ 329 param->yyDay = $1; 330 param->yyMonth = $2; 331 param->yyYear = -$3; 332 } 333 | tMONTH tUNUMBER { 334 param->yyMonth = $1; 335 param->yyDay = $2; 336 } 337 | tMONTH tUNUMBER ',' tUNUMBER { 338 param->yyMonth = $1; 339 param->yyDay = $2; 340 param->yyYear = $4; 341 } 342 | tUNUMBER tMONTH { 343 param->yyMonth = $2; 344 param->yyDay = $1; 345 } 346 | tUNUMBER tMONTH tUNUMBER { 347 param->yyMonth = $2; 348 if ($1 < 35) { 349 param->yyDay = $1; 350 param->yyYear = $3; 351 } else { 352 param->yyDay = $3; 353 param->yyYear = $1; 354 } 355 } 356 ; 357 358 rel: 359 relunit 360 | relunit tAGO { 361 param->yyRel[param->yyHaveRel].yyRelVal = 362 -param->yyRel[param->yyHaveRel].yyRelVal; 363 } 364 ; 365 366 relunit: 367 tUNUMBER tMINUTE_UNIT { RelVal(param, $1 * $2 * 60L, 0); } 368 | tSNUMBER tMINUTE_UNIT { RelVal(param, $1 * $2 * 60L, 0); } 369 | tMINUTE_UNIT { RelVal(param, $1 * 60L, 0); } 370 | tSNUMBER tSEC_UNIT { RelVal(param, $1, 0); } 371 | tUNUMBER tSEC_UNIT { RelVal(param, $1, 0); } 372 | tSEC_UNIT { RelVal(param, 1L, 0); } 373 | tSNUMBER tMONTH_UNIT { RelVal(param, $1 * $2, 1); } 374 | tUNUMBER tMONTH_UNIT { RelVal(param, $1 * $2, 1); } 375 | tMONTH_UNIT { RelVal(param, $1, 1); } 376 ; 377 378 number: 379 tUNUMBER { 380 if (param->yyHaveTime && param->yyHaveDate && 381 !param->yyHaveRel) { 382 param->yyYear = $1; 383 } else { 384 if ($1 > 10000) { 385 param->yyHaveDate++; 386 param->yyDay = ($1)%100; 387 param->yyMonth = ($1/100)%100; 388 param->yyYear = $1/10000; 389 } 390 else { 391 param->yyHaveTime++; 392 if ($1 < 100) { 393 param->yyHour = $1; 394 param->yyMinutes = 0; 395 } 396 else { 397 param->yyHour = $1 / 100; 398 param->yyMinutes = $1 % 100; 399 } 400 param->yySeconds = 0; 401 param->yyMeridian = MER24; 402 } 403 } 404 } 405 ; 406 407 o_merid: 408 /* empty */ { $$ = MER24; } 409 | tMERIDIAN { $$ = $1; } 410 | tTIME { $$ = $1 == 0 ? MER_MN : MER_NOON; } 411 ; 412 413 %% 414 415 static short DaysInMonth[12] = { 416 31, 28, 31, 30, 31, 30, 417 31, 31, 30, 31, 30, 31 418 }; 419 420 /* 421 * works with tm.tm_year (ie: rel to 1900) 422 */ 423 #define isleap(yr) (((yr) & 3) == 0 && (((yr) % 100) != 0 || \ 424 ((1900+(yr)) % 400) == 0)) 425 426 /* Month and day table. */ 427 static const TABLE MonthDayTable[] = { 428 { "january", tMONTH, 1 }, 429 { "february", tMONTH, 2 }, 430 { "march", tMONTH, 3 }, 431 { "april", tMONTH, 4 }, 432 { "may", tMONTH, 5 }, 433 { "june", tMONTH, 6 }, 434 { "july", tMONTH, 7 }, 435 { "august", tMONTH, 8 }, 436 { "september", tMONTH, 9 }, 437 { "sept", tMONTH, 9 }, 438 { "october", tMONTH, 10 }, 439 { "november", tMONTH, 11 }, 440 { "december", tMONTH, 12 }, 441 { "sunday", tDAY, 0 }, 442 { "su", tDAY, 0 }, 443 { "monday", tDAY, 1 }, 444 { "mo", tDAY, 1 }, 445 { "tuesday", tDAY, 2 }, 446 { "tues", tDAY, 2 }, 447 { "tu", tDAY, 2 }, 448 { "wednesday", tDAY, 3 }, 449 { "wednes", tDAY, 3 }, 450 { "weds", tDAY, 3 }, 451 { "we", tDAY, 3 }, 452 { "thursday", tDAY, 4 }, 453 { "thurs", tDAY, 4 }, 454 { "thur", tDAY, 4 }, 455 { "th", tDAY, 4 }, 456 { "friday", tDAY, 5 }, 457 { "fr", tDAY, 5 }, 458 { "saturday", tDAY, 6 }, 459 { "sa", tDAY, 6 }, 460 { NULL, 0, 0 } 461 }; 462 463 /* Time units table. */ 464 static const TABLE UnitsTable[] = { 465 { "year", tMONTH_UNIT, 12 }, 466 { "month", tMONTH_UNIT, 1 }, 467 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 }, 468 { "week", tMINUTE_UNIT, 7 * 24 * 60 }, 469 { "day", tMINUTE_UNIT, 1 * 24 * 60 }, 470 { "hour", tMINUTE_UNIT, 60 }, 471 { "minute", tMINUTE_UNIT, 1 }, 472 { "min", tMINUTE_UNIT, 1 }, 473 { "second", tSEC_UNIT, 1 }, 474 { "sec", tSEC_UNIT, 1 }, 475 { NULL, 0, 0 } 476 }; 477 478 /* Assorted relative-time words. */ 479 static const TABLE OtherTable[] = { 480 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 }, 481 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 }, 482 { "today", tMINUTE_UNIT, 0 }, 483 { "now", tMINUTE_UNIT, 0 }, 484 { "last", tUNUMBER, -1 }, 485 { "this", tMINUTE_UNIT, 0 }, 486 { "next", tUNUMBER, 2 }, 487 { "first", tUNUMBER, 1 }, 488 { "one", tUNUMBER, 1 }, 489 /* { "second", tUNUMBER, 2 }, */ 490 { "two", tUNUMBER, 2 }, 491 { "third", tUNUMBER, 3 }, 492 { "three", tUNUMBER, 3 }, 493 { "fourth", tUNUMBER, 4 }, 494 { "four", tUNUMBER, 4 }, 495 { "fifth", tUNUMBER, 5 }, 496 { "five", tUNUMBER, 5 }, 497 { "sixth", tUNUMBER, 6 }, 498 { "six", tUNUMBER, 6 }, 499 { "seventh", tUNUMBER, 7 }, 500 { "seven", tUNUMBER, 7 }, 501 { "eighth", tUNUMBER, 8 }, 502 { "eight", tUNUMBER, 8 }, 503 { "ninth", tUNUMBER, 9 }, 504 { "nine", tUNUMBER, 9 }, 505 { "tenth", tUNUMBER, 10 }, 506 { "ten", tUNUMBER, 10 }, 507 { "eleventh", tUNUMBER, 11 }, 508 { "eleven", tUNUMBER, 11 }, 509 { "twelfth", tUNUMBER, 12 }, 510 { "twelve", tUNUMBER, 12 }, 511 { "ago", tAGO, 1 }, 512 { NULL, 0, 0 } 513 }; 514 515 /* The timezone table. */ 516 /* Some of these are commented out because a time_t can't store a float. */ 517 static const TABLE TimezoneTable[] = { 518 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ 519 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ 520 { "utc", tZONE, HOUR( 0) }, 521 { "wet", tZONE, HOUR( 0) }, /* Western European */ 522 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ 523 { "wat", tZONE, HOUR( 1) }, /* West Africa */ 524 { "at", tZONE, HOUR( 2) }, /* Azores */ 525 #if 0 526 /* For completeness. BST is also British Summer, and GST is 527 * also Guam Standard. */ 528 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */ 529 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */ 530 #endif 531 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */ 532 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */ 533 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */ 534 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ 535 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ 536 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */ 537 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ 538 { "cst", tZONE, HOUR( 6) }, /* Central Standard */ 539 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ 540 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ 541 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ 542 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ 543 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ 544 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ 545 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ 546 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ 547 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ 548 { "cat", tZONE, HOUR(10) }, /* Central Alaska */ 549 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ 550 { "nt", tZONE, HOUR(11) }, /* Nome */ 551 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */ 552 { "cet", tZONE, -HOUR(1) }, /* Central European */ 553 { "met", tZONE, -HOUR(1) }, /* Middle European */ 554 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */ 555 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */ 556 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */ 557 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */ 558 { "fwt", tZONE, -HOUR(1) }, /* French Winter */ 559 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */ 560 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */ 561 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */ 562 { "it", tZONE, -HOUR(3.5) },/* Iran */ 563 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */ 564 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */ 565 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */ 566 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */ 567 #if 0 568 /* For completeness. NST is also Newfoundland Stanard, and SST is 569 * also Swedish Summer. */ 570 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */ 571 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */ 572 #endif /* 0 */ 573 { "ict", tZONE, -HOUR(7) }, /* Indo China Time (Thai) */ 574 #if 0 /* this one looks to be bogus */ 575 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */ 576 #endif 577 { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */ 578 { "awst", tZONE, -HOUR(8) }, /* West Australian Standard */ 579 { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */ 580 { "awdt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */ 581 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */ 582 { "sgt", tZONE, -HOUR(8) }, /* Singapore */ 583 { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */ 584 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */ 585 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */ 586 { "acst", tZONE, -HOUR(9.5) },/* Central Australian Standard */ 587 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */ 588 { "acdt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */ 589 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */ 590 { "aest", tZONE, -HOUR(10) }, /* Eastern Australian Standard */ 591 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */ 592 { "aedt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */ 593 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */ 594 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */ 595 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ 596 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ 597 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */ 598 { NULL, 0, 0 } 599 }; 600 601 /* Military timezone table. */ 602 static const TABLE MilitaryTable[] = { 603 { "a", tZONE, HOUR( 1) }, 604 { "b", tZONE, HOUR( 2) }, 605 { "c", tZONE, HOUR( 3) }, 606 { "d", tZONE, HOUR( 4) }, 607 { "e", tZONE, HOUR( 5) }, 608 { "f", tZONE, HOUR( 6) }, 609 { "g", tZONE, HOUR( 7) }, 610 { "h", tZONE, HOUR( 8) }, 611 { "i", tZONE, HOUR( 9) }, 612 { "k", tZONE, HOUR( 10) }, 613 { "l", tZONE, HOUR( 11) }, 614 { "m", tZONE, HOUR( 12) }, 615 { "n", tZONE, HOUR(- 1) }, 616 { "o", tZONE, HOUR(- 2) }, 617 { "p", tZONE, HOUR(- 3) }, 618 { "q", tZONE, HOUR(- 4) }, 619 { "r", tZONE, HOUR(- 5) }, 620 { "s", tZONE, HOUR(- 6) }, 621 { "t", tZONE, HOUR(- 7) }, 622 { "u", tZONE, HOUR(- 8) }, 623 { "v", tZONE, HOUR(- 9) }, 624 { "w", tZONE, HOUR(-10) }, 625 { "x", tZONE, HOUR(-11) }, 626 { "y", tZONE, HOUR(-12) }, 627 { "z", tZONE, HOUR( 0) }, 628 { NULL, 0, 0 } 629 }; 630 631 static const TABLE TimeNames[] = { 632 { "midnight", tTIME, 0 }, 633 { "mn", tTIME, 0 }, 634 { "noon", tTIME, 12 }, 635 { "midday", tTIME, 12 }, 636 { NULL, 0, 0 } 637 }; 638 639 640 641 /* ARGSUSED */ 642 static int 643 yyerror(struct dateinfo *param, const char **inp, const char *s __unused) 644 { 645 return 0; 646 } 647 648 /* 649 * Save a relative value, if it fits 650 */ 651 static void 652 RelVal(struct dateinfo *param, time_t v, int type) 653 { 654 int i; 655 656 if ((i = param->yyHaveRel) >= MAXREL) 657 return; 658 param->yyRel[i].yyRelMonth = type; 659 param->yyRel[i].yyRelVal = v; 660 } 661 662 /* 663 * Adjust year from a value that might be abbreviated, to a full value. 664 * e.g. convert 70 to 1970. 665 * Input Year is either: 666 * - A negative number, which means to use its absolute value (why?) 667 * - A number from 0 to 99, which means a year from 1900 to 1999, or 668 * - The actual year (>=100). 669 * Returns the full year. 670 */ 671 static time_t 672 AdjustYear(time_t Year) 673 { 674 /* XXX Y2K */ 675 if (Year < 0) 676 Year = -Year; 677 if (Year < 70) 678 Year += 2000; 679 else if (Year < 100) 680 Year += 1900; 681 return Year; 682 } 683 684 static time_t 685 Convert( 686 time_t Month, /* month of year [1-12] */ 687 time_t Day, /* day of month [1-31] */ 688 time_t Year, /* year, not abbreviated in any way */ 689 time_t Hours, /* Hour of day [0-24] */ 690 time_t Minutes, /* Minute of hour [0-59] */ 691 time_t Seconds, /* Second of minute [0-60] */ 692 time_t Timezone, /* Timezone as minutes east of UTC, 693 * or USE_LOCAL_TIME special case */ 694 MERIDIAN Meridian, /* Hours are am/pm/24 hour clock */ 695 DSTMODE DSTmode /* DST on/off/maybe */ 696 ) 697 { 698 struct tm tm = {.tm_sec = 0}; 699 struct tm otm; 700 time_t result; 701 702 tm.tm_sec = Seconds; 703 tm.tm_min = Minutes; 704 tm.tm_hour = ((Hours == 12 && Meridian != MER24) ? 0 : Hours) + 705 (Meridian == MERpm ? 12 : 0); 706 707 tm.tm_mday = Day; 708 tm.tm_mon = Month - 1; 709 tm.tm_year = Year - 1900; 710 if (Timezone == USE_LOCAL_TIME) { 711 switch (DSTmode) { 712 case DSTon: tm.tm_isdst = 1; break; 713 case DSToff: tm.tm_isdst = 0; break; 714 default: tm.tm_isdst = -1; break; 715 } 716 otm = tm; 717 result = mktime(&tm); 718 } else { 719 /* We rely on mktime_z(NULL, ...) working in UTC */ 720 tm.tm_isdst = 0; /* hence cannot be summer time */ 721 otm = tm; 722 errno = 0; 723 result = mktime_z(NULL, &tm); 724 if (result != -1 || errno == 0) { 725 result += Timezone * 60; 726 if (DSTmode == DSTon) /* if specified sumer time */ 727 result -= 3600; /* UTC is 1 hour earlier XXX */ 728 } 729 } 730 731 #if PARSEDATE_DEBUG 732 fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd" 733 " mer=%d DST=%d)", 734 __func__, 735 (intmax_t)Month, (intmax_t)Day, (intmax_t)Year, 736 (intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds, 737 (intmax_t)Timezone, (int)Meridian, (int)DSTmode); 738 fprintf(stderr, " -> %jd", (intmax_t)result); 739 fprintf(stderr, " %s", ctime(&result)); 740 #endif 741 742 #define TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld) 743 if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) || 744 TM_NE(hour) || TM_NE(min) || TM_NE(sec)) { 745 /* mktime() "corrected" our tm, so it must have been invalid */ 746 result = -1; 747 errno = EAGAIN; 748 } 749 #undef TM_NE 750 751 return result; 752 } 753 754 755 static time_t 756 DSTcorrect( 757 time_t Start, 758 time_t Future 759 ) 760 { 761 time_t StartDay; 762 time_t FutureDay; 763 struct tm tm; 764 765 if (localtime_r(&Start, &tm) == NULL) 766 return -1; 767 StartDay = (tm.tm_hour + 1) % 24; 768 769 if (localtime_r(&Future, &tm) == NULL) 770 return -1; 771 FutureDay = (tm.tm_hour + 1) % 24; 772 773 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; 774 } 775 776 777 static time_t 778 RelativeDate( 779 time_t Start, 780 time_t DayOrdinal, 781 time_t DayNumber 782 ) 783 { 784 struct tm tm; 785 time_t now; 786 787 now = Start; 788 if (localtime_r(&now, &tm) == NULL) 789 return -1; 790 now += SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7); 791 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); 792 return DSTcorrect(Start, now); 793 } 794 795 796 static time_t 797 RelativeMonth( 798 time_t Start, 799 time_t RelMonth, 800 time_t Timezone 801 ) 802 { 803 struct tm tm; 804 time_t Month; 805 time_t Then; 806 int Day; 807 808 if (RelMonth == 0) 809 return 0; 810 /* 811 * It doesn't matter what timezone we use to do this computation, 812 * as long as we use the same one to reassemble the time that we 813 * used to disassemble it. So always use localtime and mktime. In 814 * particular, don't use Convert() to reassemble, because it will 815 * not only reassemble with the wrong timezone but it will also 816 * fail if we do e.g. three months from March 31 yielding July 1. 817 */ 818 (void)Timezone; 819 820 if (localtime_r(&Start, &tm) == NULL) 821 return -1; 822 823 Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth; 824 tm.tm_year = (Month / 12) - 1900; 825 tm.tm_mon = Month % 12; 826 if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] + 827 ((tm.tm_mon==1) ? isleap(tm.tm_year) : 0))) 828 tm.tm_mday = Day; 829 errno = 0; 830 Then = mktime(&tm); 831 if (Then == -1 && errno != 0) 832 return -1; 833 return DSTcorrect(Start, Then); 834 } 835 836 837 static int 838 LookupWord(YYSTYPE *yylval, char *buff) 839 { 840 register char *p; 841 register char *q; 842 register const TABLE *tp; 843 int i; 844 int abbrev; 845 846 /* Make it lowercase. */ 847 for (p = buff; *p; p++) 848 if (isupper((unsigned char)*p)) 849 *p = tolower((unsigned char)*p); 850 851 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) { 852 yylval->Meridian = MERam; 853 return tMERIDIAN; 854 } 855 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) { 856 yylval->Meridian = MERpm; 857 return tMERIDIAN; 858 } 859 860 /* See if we have an abbreviation for a month. */ 861 if (strlen(buff) == 3) 862 abbrev = 1; 863 else if (strlen(buff) == 4 && buff[3] == '.') { 864 abbrev = 1; 865 buff[3] = '\0'; 866 } 867 else 868 abbrev = 0; 869 870 for (tp = MonthDayTable; tp->name; tp++) { 871 if (abbrev) { 872 if (strncmp(buff, tp->name, 3) == 0) { 873 yylval->Number = tp->value; 874 return tp->type; 875 } 876 } 877 else if (strcmp(buff, tp->name) == 0) { 878 yylval->Number = tp->value; 879 return tp->type; 880 } 881 } 882 883 for (tp = TimezoneTable; tp->name; tp++) 884 if (strcmp(buff, tp->name) == 0) { 885 yylval->Number = tp->value; 886 return tp->type; 887 } 888 889 if (strcmp(buff, "dst") == 0) 890 return tDST; 891 892 for (tp = TimeNames; tp->name; tp++) 893 if (strcmp(buff, tp->name) == 0) { 894 yylval->Number = tp->value; 895 return tp->type; 896 } 897 898 for (tp = UnitsTable; tp->name; tp++) 899 if (strcmp(buff, tp->name) == 0) { 900 yylval->Number = tp->value; 901 return tp->type; 902 } 903 904 /* Strip off any plural and try the units table again. */ 905 i = strlen(buff) - 1; 906 if (buff[i] == 's') { 907 buff[i] = '\0'; 908 for (tp = UnitsTable; tp->name; tp++) 909 if (strcmp(buff, tp->name) == 0) { 910 yylval->Number = tp->value; 911 return tp->type; 912 } 913 buff[i] = 's'; /* Put back for "this" in OtherTable. */ 914 } 915 916 for (tp = OtherTable; tp->name; tp++) 917 if (strcmp(buff, tp->name) == 0) { 918 yylval->Number = tp->value; 919 return tp->type; 920 } 921 922 /* Military timezones. */ 923 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) { 924 for (tp = MilitaryTable; tp->name; tp++) 925 if (strcmp(buff, tp->name) == 0) { 926 yylval->Number = tp->value; 927 return tp->type; 928 } 929 } 930 931 /* Drop out any periods and try the timezone table again. */ 932 for (i = 0, p = q = buff; *q; q++) 933 if (*q != '.') 934 *p++ = *q; 935 else 936 i++; 937 *p = '\0'; 938 if (i) 939 for (tp = TimezoneTable; tp->name; tp++) 940 if (strcmp(buff, tp->name) == 0) { 941 yylval->Number = tp->value; 942 return tp->type; 943 } 944 945 return tID; 946 } 947 948 949 static int 950 yylex(YYSTYPE *yylval, const char **yyInput) 951 { 952 register char c; 953 register char *p; 954 char buff[20]; 955 int Count; 956 int sign; 957 const char *inp = *yyInput; 958 959 for ( ; ; ) { 960 while (isspace((unsigned char)*inp)) 961 inp++; 962 963 if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') { 964 if (c == '-' || c == '+') { 965 sign = c == '-' ? -1 : 1; 966 if (!isdigit((unsigned char)*++inp)) 967 /* skip the '-' sign */ 968 continue; 969 } 970 else 971 sign = 0; 972 for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); ) 973 yylval->Number = 10 * yylval->Number + c - '0'; 974 if (sign < 0) 975 yylval->Number = -yylval->Number; 976 *yyInput = --inp; 977 return sign ? tSNUMBER : tUNUMBER; 978 } 979 if (isalpha((unsigned char)c)) { 980 for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; ) 981 if (p < &buff[sizeof buff - 1]) 982 *p++ = c; 983 *p = '\0'; 984 *yyInput = --inp; 985 return LookupWord(yylval, buff); 986 } 987 if (c == '@') { 988 *yyInput = ++inp; 989 return AT_SIGN; 990 } 991 if (c != '(') { 992 *yyInput = ++inp; 993 return c; 994 } 995 Count = 0; 996 do { 997 c = *inp++; 998 if (c == '\0') 999 return c; 1000 if (c == '(') 1001 Count++; 1002 else if (c == ')') 1003 Count--; 1004 } while (Count > 0); 1005 } 1006 } 1007 1008 #define TM_YEAR_ORIGIN 1900 1009 1010 time_t 1011 parsedate(const char *p, const time_t *now, const int *zone) 1012 { 1013 struct tm local, *tm; 1014 time_t nowt; 1015 int zonet; 1016 time_t Start; 1017 time_t tod, rm; 1018 struct dateinfo param; 1019 int saved_errno; 1020 int i; 1021 1022 saved_errno = errno; 1023 errno = 0; 1024 1025 if (now == NULL) { 1026 now = &nowt; 1027 (void)time(&nowt); 1028 } 1029 if (zone == NULL) { 1030 zone = &zonet; 1031 zonet = USE_LOCAL_TIME; 1032 if ((tm = localtime_r(now, &local)) == NULL) 1033 return -1; 1034 } else { 1035 /* 1036 * Should use the specified zone, not localtime. 1037 * Fake it using gmtime and arithmetic. 1038 * This is good enough because we use only the year/month/day, 1039 * not other fields of struct tm. 1040 */ 1041 time_t fake = *now + (*zone * 60); 1042 if ((tm = gmtime_r(&fake, &local)) == NULL) 1043 return -1; 1044 } 1045 param.yyYear = tm->tm_year + 1900; 1046 param.yyMonth = tm->tm_mon + 1; 1047 param.yyDay = tm->tm_mday; 1048 param.yyTimezone = *zone; 1049 param.yyDSTmode = DSTmaybe; 1050 param.yyHour = 0; 1051 param.yyMinutes = 0; 1052 param.yySeconds = 0; 1053 param.yyMeridian = MER24; 1054 param.yyHaveDate = 0; 1055 param.yyHaveFullYear = 0; 1056 param.yyHaveDay = 0; 1057 param.yyHaveRel = 0; 1058 param.yyHaveTime = 0; 1059 param.yyHaveZone = 0; 1060 1061 if (yyparse(¶m, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 || 1062 param.yyHaveDate > 1 || param.yyHaveDay > 1) { 1063 errno = EINVAL; 1064 return -1; 1065 } 1066 1067 if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) { 1068 if (! param.yyHaveFullYear) { 1069 param.yyYear = AdjustYear(param.yyYear); 1070 param.yyHaveFullYear = 1; 1071 } 1072 errno = 0; 1073 Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour, 1074 param.yyMinutes, param.yySeconds, param.yyTimezone, 1075 param.yyMeridian, param.yyDSTmode); 1076 if (Start == -1 && errno != 0) 1077 return -1; 1078 } 1079 else { 1080 Start = *now; 1081 if (!param.yyHaveRel) 1082 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; 1083 } 1084 1085 if (param.yyHaveRel > MAXREL) { 1086 errno = EINVAL; 1087 return -1; 1088 } 1089 for (i = 0; i < param.yyHaveRel; i++) { 1090 if (param.yyRel[i].yyRelMonth) { 1091 errno = 0; 1092 rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone); 1093 if (rm == -1 && errno != 0) 1094 return -1; 1095 Start += rm; 1096 } else 1097 Start += param.yyRel[i].yyRelVal; 1098 } 1099 1100 if (param.yyHaveDay && !param.yyHaveDate) { 1101 errno = 0; 1102 tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber); 1103 if (tod == -1 && errno != 0) 1104 return -1; 1105 Start += tod; 1106 } 1107 1108 errno = saved_errno; 1109 return Start; 1110 } 1111 1112 1113 #if defined(TEST) 1114 1115 /* ARGSUSED */ 1116 int 1117 main(int ac, char *av[]) 1118 { 1119 char buff[128]; 1120 time_t d; 1121 1122 (void)printf("Enter date, or blank line to exit.\n\t> "); 1123 (void)fflush(stdout); 1124 while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') { 1125 errno = 0; 1126 d = parsedate(buff, NULL, NULL); 1127 if (d == -1 && errno != 0) 1128 (void)printf("Bad format - couldn't convert: %s\n", 1129 strerror(errno)); 1130 else 1131 (void)printf("%jd\t%s", (intmax_t)d, ctime(&d)); 1132 (void)printf("\t> "); 1133 (void)fflush(stdout); 1134 } 1135 exit(0); 1136 /* NOTREACHED */ 1137 } 1138 #endif /* defined(TEST) */ 1139