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