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.29 2016/06/26 07:09:24 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 { NULL, 0, 0 } 591 }; 592 593 594 595 /* ARGSUSED */ 596 static int 597 yyerror(struct dateinfo *param, const char **inp, const char *s __unused) 598 { 599 return 0; 600 } 601 602 /* 603 * Save a relative value, if it fits 604 */ 605 static void 606 RelVal(struct dateinfo *param, time_t v, int type) 607 { 608 int i; 609 610 if ((i = param->yyHaveRel) >= MAXREL) 611 return; 612 param->yyRel[i].yyRelMonth = type; 613 param->yyRel[i].yyRelVal = v; 614 } 615 616 617 /* Adjust year from a value that might be abbreviated, to a full value. 618 * e.g. convert 70 to 1970. 619 * Input Year is either: 620 * - A negative number, which means to use its absolute value (why?) 621 * - A number from 0 to 99, which means a year from 1900 to 1999, or 622 * - The actual year (>=100). 623 * Returns the full year. */ 624 static time_t 625 AdjustYear(time_t Year) 626 { 627 /* XXX Y2K */ 628 if (Year < 0) 629 Year = -Year; 630 if (Year < 70) 631 Year += 2000; 632 else if (Year < 100) 633 Year += 1900; 634 return Year; 635 } 636 637 static time_t 638 Convert( 639 time_t Month, /* month of year [1-12] */ 640 time_t Day, /* day of month [1-31] */ 641 time_t Year, /* year, not abbreviated in any way */ 642 time_t Hours, /* Hour of day [0-24] */ 643 time_t Minutes, /* Minute of hour [0-59] */ 644 time_t Seconds, /* Second of minute [0-60] */ 645 time_t Timezone, /* Timezone as minutes east of UTC, 646 * or USE_LOCAL_TIME special case */ 647 MERIDIAN Meridian, /* Hours are am/pm/24 hour clock */ 648 DSTMODE DSTmode /* DST on/off/maybe */ 649 ) 650 { 651 struct tm tm = {.tm_sec = 0}; 652 struct tm otm; 653 time_t result; 654 655 tm.tm_sec = Seconds; 656 tm.tm_min = Minutes; 657 tm.tm_hour = Hours + (Meridian == MERpm ? 12 : 0); 658 tm.tm_mday = Day; 659 tm.tm_mon = Month - 1; 660 tm.tm_year = Year - 1900; 661 if (Timezone == USE_LOCAL_TIME) { 662 switch (DSTmode) { 663 case DSTon: tm.tm_isdst = 1; break; 664 case DSToff: tm.tm_isdst = 0; break; 665 default: tm.tm_isdst = -1; break; 666 } 667 otm = tm; 668 result = mktime(&tm); 669 } else { 670 /* We rely on mktime_z(NULL, ...) working in UTC */ 671 tm.tm_isdst = 0; /* hence cannot be summer time */ 672 otm = tm; 673 errno = 0; 674 result = mktime_z(NULL, &tm); 675 if (result != -1 || errno == 0) { 676 result += Timezone * 60; 677 if (DSTmode == DSTon) /* if specified sumer time */ 678 result -= 3600; /* UTC is 1 hour earlier XXX */ 679 } 680 } 681 682 #if PARSEDATE_DEBUG 683 fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd" 684 " mer=%d DST=%d)", 685 __func__, 686 (intmax_t)Month, (intmax_t)Day, (intmax_t)Year, 687 (intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds, 688 (intmax_t)Timezone, (int)Meridian, (int)DSTmode); 689 fprintf(stderr, " -> %jd", (intmax_t)result); 690 fprintf(stderr, " %s", ctime(&result)); 691 #endif 692 693 #define TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld) 694 if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) || 695 TM_NE(hour) || TM_NE(min) || TM_NE(sec)) { 696 /* mktime() "corrected" our tm, so it must have been invalid */ 697 result = -1; 698 errno = EAGAIN; 699 } 700 #undef TM_NE 701 702 return result; 703 } 704 705 706 static time_t 707 DSTcorrect( 708 time_t Start, 709 time_t Future 710 ) 711 { 712 time_t StartDay; 713 time_t FutureDay; 714 struct tm tm; 715 716 if (localtime_r(&Start, &tm) == NULL) 717 return -1; 718 StartDay = (tm.tm_hour + 1) % 24; 719 720 if (localtime_r(&Future, &tm) == NULL) 721 return -1; 722 FutureDay = (tm.tm_hour + 1) % 24; 723 724 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; 725 } 726 727 728 static time_t 729 RelativeDate( 730 time_t Start, 731 time_t DayOrdinal, 732 time_t DayNumber 733 ) 734 { 735 struct tm tm; 736 time_t now; 737 738 now = Start; 739 if (localtime_r(&now, &tm) == NULL) 740 return -1; 741 now += SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7); 742 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); 743 return DSTcorrect(Start, now); 744 } 745 746 747 static time_t 748 RelativeMonth( 749 time_t Start, 750 time_t RelMonth, 751 time_t Timezone 752 ) 753 { 754 struct tm tm; 755 time_t Month; 756 time_t Then; 757 int Day; 758 759 if (RelMonth == 0) 760 return 0; 761 /* 762 * It doesn't matter what timezone we use to do this computation, 763 * as long as we use the same one to reassemble the time that we 764 * used to disassemble it. So always use localtime and mktime. In 765 * particular, don't use Convert() to reassemble, because it will 766 * not only reassemble with the wrong timezone but it will also 767 * fail if we do e.g. three months from March 31 yielding July 1. 768 */ 769 (void)Timezone; 770 771 if (localtime_r(&Start, &tm) == NULL) 772 return -1; 773 774 Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth; 775 tm.tm_year = (Month / 12) - 1900; 776 tm.tm_mon = Month % 12; 777 if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] + 778 ((tm.tm_mon==1) ? isleap(tm.tm_year) : 0))) 779 tm.tm_mday = Day; 780 errno = 0; 781 Then = mktime(&tm); 782 if (Then == -1 && errno != 0) 783 return -1; 784 return DSTcorrect(Start, Then); 785 } 786 787 788 static int 789 LookupWord(YYSTYPE *yylval, char *buff) 790 { 791 register char *p; 792 register char *q; 793 register const TABLE *tp; 794 int i; 795 int abbrev; 796 797 /* Make it lowercase. */ 798 for (p = buff; *p; p++) 799 if (isupper((unsigned char)*p)) 800 *p = tolower((unsigned char)*p); 801 802 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) { 803 yylval->Meridian = MERam; 804 return tMERIDIAN; 805 } 806 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) { 807 yylval->Meridian = MERpm; 808 return tMERIDIAN; 809 } 810 811 /* See if we have an abbreviation for a month. */ 812 if (strlen(buff) == 3) 813 abbrev = 1; 814 else if (strlen(buff) == 4 && buff[3] == '.') { 815 abbrev = 1; 816 buff[3] = '\0'; 817 } 818 else 819 abbrev = 0; 820 821 for (tp = MonthDayTable; tp->name; tp++) { 822 if (abbrev) { 823 if (strncmp(buff, tp->name, 3) == 0) { 824 yylval->Number = tp->value; 825 return tp->type; 826 } 827 } 828 else if (strcmp(buff, tp->name) == 0) { 829 yylval->Number = tp->value; 830 return tp->type; 831 } 832 } 833 834 for (tp = TimezoneTable; tp->name; tp++) 835 if (strcmp(buff, tp->name) == 0) { 836 yylval->Number = tp->value; 837 return tp->type; 838 } 839 840 if (strcmp(buff, "dst") == 0) 841 return tDST; 842 843 for (tp = TimeNames; tp->name; tp++) 844 if (strcmp(buff, tp->name) == 0) { 845 yylval->Number = tp->value; 846 return tp->type; 847 } 848 849 for (tp = UnitsTable; tp->name; tp++) 850 if (strcmp(buff, tp->name) == 0) { 851 yylval->Number = tp->value; 852 return tp->type; 853 } 854 855 /* Strip off any plural and try the units table again. */ 856 i = strlen(buff) - 1; 857 if (buff[i] == 's') { 858 buff[i] = '\0'; 859 for (tp = UnitsTable; tp->name; tp++) 860 if (strcmp(buff, tp->name) == 0) { 861 yylval->Number = tp->value; 862 return tp->type; 863 } 864 buff[i] = 's'; /* Put back for "this" in OtherTable. */ 865 } 866 867 for (tp = OtherTable; tp->name; tp++) 868 if (strcmp(buff, tp->name) == 0) { 869 yylval->Number = tp->value; 870 return tp->type; 871 } 872 873 /* Military timezones. */ 874 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) { 875 for (tp = MilitaryTable; tp->name; tp++) 876 if (strcmp(buff, tp->name) == 0) { 877 yylval->Number = tp->value; 878 return tp->type; 879 } 880 } 881 882 /* Drop out any periods and try the timezone table again. */ 883 for (i = 0, p = q = buff; *q; q++) 884 if (*q != '.') 885 *p++ = *q; 886 else 887 i++; 888 *p = '\0'; 889 if (i) 890 for (tp = TimezoneTable; tp->name; tp++) 891 if (strcmp(buff, tp->name) == 0) { 892 yylval->Number = tp->value; 893 return tp->type; 894 } 895 896 return tID; 897 } 898 899 900 static int 901 yylex(YYSTYPE *yylval, const char **yyInput) 902 { 903 register char c; 904 register char *p; 905 char buff[20]; 906 int Count; 907 int sign; 908 const char *inp = *yyInput; 909 910 for ( ; ; ) { 911 while (isspace((unsigned char)*inp)) 912 inp++; 913 914 if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') { 915 if (c == '-' || c == '+') { 916 sign = c == '-' ? -1 : 1; 917 if (!isdigit((unsigned char)*++inp)) 918 /* skip the '-' sign */ 919 continue; 920 } 921 else 922 sign = 0; 923 for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); ) 924 yylval->Number = 10 * yylval->Number + c - '0'; 925 if (sign < 0) 926 yylval->Number = -yylval->Number; 927 *yyInput = --inp; 928 return sign ? tSNUMBER : tUNUMBER; 929 } 930 if (isalpha((unsigned char)c)) { 931 for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; ) 932 if (p < &buff[sizeof buff - 1]) 933 *p++ = c; 934 *p = '\0'; 935 *yyInput = --inp; 936 return LookupWord(yylval, buff); 937 } 938 if (c == '@') { 939 *yyInput = ++inp; 940 return AT_SIGN; 941 } 942 if (c != '(') { 943 *yyInput = ++inp; 944 return c; 945 } 946 Count = 0; 947 do { 948 c = *inp++; 949 if (c == '\0') 950 return c; 951 if (c == '(') 952 Count++; 953 else if (c == ')') 954 Count--; 955 } while (Count > 0); 956 } 957 } 958 959 #define TM_YEAR_ORIGIN 1900 960 961 time_t 962 parsedate(const char *p, const time_t *now, const int *zone) 963 { 964 struct tm local, *tm; 965 time_t nowt; 966 int zonet; 967 time_t Start; 968 time_t tod, rm; 969 struct dateinfo param; 970 int saved_errno; 971 int i; 972 973 saved_errno = errno; 974 errno = 0; 975 976 if (now == NULL) { 977 now = &nowt; 978 (void)time(&nowt); 979 } 980 if (zone == NULL) { 981 zone = &zonet; 982 zonet = USE_LOCAL_TIME; 983 if ((tm = localtime_r(now, &local)) == NULL) 984 return -1; 985 } else { 986 /* 987 * Should use the specified zone, not localtime. 988 * Fake it using gmtime and arithmetic. 989 * This is good enough because we use only the year/month/day, 990 * not other fields of struct tm. 991 */ 992 time_t fake = *now + (*zone * 60); 993 if ((tm = gmtime_r(&fake, &local)) == NULL) 994 return -1; 995 } 996 param.yyYear = tm->tm_year + 1900; 997 param.yyMonth = tm->tm_mon + 1; 998 param.yyDay = tm->tm_mday; 999 param.yyTimezone = *zone; 1000 param.yyDSTmode = DSTmaybe; 1001 param.yyHour = 0; 1002 param.yyMinutes = 0; 1003 param.yySeconds = 0; 1004 param.yyMeridian = MER24; 1005 param.yyHaveDate = 0; 1006 param.yyHaveFullYear = 0; 1007 param.yyHaveDay = 0; 1008 param.yyHaveRel = 0; 1009 param.yyHaveTime = 0; 1010 param.yyHaveZone = 0; 1011 1012 if (yyparse(¶m, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 || 1013 param.yyHaveDate > 1 || param.yyHaveDay > 1) { 1014 errno = EINVAL; 1015 return -1; 1016 } 1017 1018 if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) { 1019 if (! param.yyHaveFullYear) { 1020 param.yyYear = AdjustYear(param.yyYear); 1021 param.yyHaveFullYear = 1; 1022 } 1023 errno = 0; 1024 Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour, 1025 param.yyMinutes, param.yySeconds, param.yyTimezone, 1026 param.yyMeridian, param.yyDSTmode); 1027 if (Start == -1 && errno != 0) 1028 return -1; 1029 } 1030 else { 1031 Start = *now; 1032 if (!param.yyHaveRel) 1033 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; 1034 } 1035 1036 if (param.yyHaveRel > MAXREL) { 1037 errno = EINVAL; 1038 return -1; 1039 } 1040 for (i = 0; i < param.yyHaveRel; i++) { 1041 if (param.yyRel[i].yyRelMonth) { 1042 errno = 0; 1043 rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone); 1044 if (rm == -1 && errno != 0) 1045 return -1; 1046 Start += rm; 1047 } else 1048 Start += param.yyRel[i].yyRelVal; 1049 } 1050 1051 if (param.yyHaveDay && !param.yyHaveDate) { 1052 errno = 0; 1053 tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber); 1054 if (tod == -1 && errno != 0) 1055 return -1; 1056 Start += tod; 1057 } 1058 1059 errno = saved_errno; 1060 return Start; 1061 } 1062 1063 1064 #if defined(TEST) 1065 1066 /* ARGSUSED */ 1067 int 1068 main(int ac, char *av[]) 1069 { 1070 char buff[128]; 1071 time_t d; 1072 1073 (void)printf("Enter date, or blank line to exit.\n\t> "); 1074 (void)fflush(stdout); 1075 while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') { 1076 errno = 0; 1077 d = parsedate(buff, NULL, NULL); 1078 if (d == -1 && errno != 0) 1079 (void)printf("Bad format - couldn't convert: %s\n", 1080 strerror(errno)); 1081 else 1082 (void)printf("%jd\t%s", (intmax_t)d, ctime(&d)); 1083 (void)printf("\t> "); 1084 (void)fflush(stdout); 1085 } 1086 exit(0); 1087 /* NOTREACHED */ 1088 } 1089 #endif /* defined(TEST) */ 1090