1%{ 2/* Parse a string into an internal time stamp. 3 Copyright (C) 1999, 2000, 2002 Free Software Foundation, Inc. 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2, or (at your option) 8 any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, see <http://www.gnu.org/licenses/>. */ 17 18/* Originally written by Steven M. Bellovin <smb@research.att.com> while 19 at the University of North Carolina at Chapel Hill. Later tweaked by 20 a couple of people on Usenet. Completely overhauled by Rich $alz 21 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990. 22 23 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do 24 the right thing about local DST. Unlike previous versions, this 25 version is reentrant. */ 26 27#ifdef HAVE_CONFIG_H 28# include <config.h> 29# ifdef HAVE_ALLOCA_H 30# include <alloca.h> 31# endif 32#endif 33 34/* Since the code of getdate.y is not included in the Emacs executable 35 itself, there is no need to #define static in this file. Even if 36 the code were included in the Emacs executable, it probably 37 wouldn't do any harm to #undef it here; this will only cause 38 problems if we try to write to a static variable, which I don't 39 think this code needs to do. */ 40#ifdef emacs 41# undef static 42#endif 43 44#include <ctype.h> 45#include <string.h> 46 47#if HAVE_STDLIB_H 48# include <stdlib.h> /* for `free'; used by Bison 1.27 */ 49#endif 50 51#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII) 52# define IN_CTYPE_DOMAIN(c) 1 53#else 54# define IN_CTYPE_DOMAIN(c) isascii (c) 55#endif 56 57#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c)) 58#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) 59#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) 60#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) 61 62/* ISDIGIT differs from ISDIGIT_LOCALE, as follows: 63 - Its arg may be any int or unsigned int; it need not be an unsigned char. 64 - It's guaranteed to evaluate its argument exactly once. 65 - It's typically faster. 66 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to 67 ISDIGIT_LOCALE unless it's important to use the locale's definition 68 of `digit' even when the host does not conform to POSIX. */ 69#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) 70 71#if STDC_HEADERS || HAVE_STRING_H 72# include <string.h> 73#endif 74 75#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__ 76# define __attribute__(x) 77#endif 78 79#ifndef ATTRIBUTE_UNUSED 80# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) 81#endif 82 83#define EPOCH_YEAR 1970 84#define TM_YEAR_BASE 1900 85 86#define HOUR(x) ((x) * 60) 87 88/* An integer value, and the number of digits in its textual 89 representation. */ 90typedef struct 91{ 92 int value; 93 int digits; 94} textint; 95 96/* An entry in the lexical lookup table. */ 97typedef struct 98{ 99 char const *name; 100 int type; 101 int value; 102} table; 103 104/* Meridian: am, pm, or 24-hour style. */ 105enum { MERam, MERpm, MER24 }; 106 107/* Information passed to and from the parser. */ 108typedef struct 109{ 110 /* The input string remaining to be parsed. */ 111 const char *input; 112 113 /* N, if this is the Nth Tuesday. */ 114 int day_ordinal; 115 116 /* Day of week; Sunday is 0. */ 117 int day_number; 118 119 /* tm_isdst flag for the local zone. */ 120 int local_isdst; 121 122 /* Time zone, in minutes east of UTC. */ 123 int time_zone; 124 125 /* Style used for time. */ 126 int meridian; 127 128 /* Gregorian year, month, day, hour, minutes, and seconds. */ 129 textint year; 130 int month; 131 int day; 132 int hour; 133 int minutes; 134 int seconds; 135 136 /* Relative year, month, day, hour, minutes, and seconds. */ 137 int rel_year; 138 int rel_month; 139 int rel_day; 140 int rel_hour; 141 int rel_minutes; 142 int rel_seconds; 143 144 /* Counts of nonterminals of various flavors parsed so far. */ 145 int dates_seen; 146 int days_seen; 147 int local_zones_seen; 148 int rels_seen; 149 int times_seen; 150 int zones_seen; 151 152 /* Table of local time zone abbrevations, terminated by a null entry. */ 153 table local_time_zone_table[3]; 154} parser_control; 155 156#define PC (* (parser_control *) parm) 157#define YYLEX_PARAM parm 158#define YYPARSE_PARAM parm 159 160%} 161 162/* We want a reentrant parser. */ 163%pure_parser 164 165/* This grammar has 13 shift/reduce conflicts. */ 166%expect 13 167 168%union 169{ 170 int intval; 171 textint textintval; 172} 173 174%{ 175 176static int yyerror(const char *); 177static int yylex(YYSTYPE *, parser_control *); 178 179%} 180 181%token tAGO tDST 182 183%token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN 184%token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE 185 186%token <textintval> tSNUMBER tUNUMBER 187 188%type <intval> o_merid 189 190%% 191 192spec: 193 /* empty */ 194 | spec item 195 ; 196 197item: 198 time 199 { PC.times_seen++; } 200 | local_zone 201 { PC.local_zones_seen++; } 202 | zone 203 { PC.zones_seen++; } 204 | date 205 { PC.dates_seen++; } 206 | day 207 { PC.days_seen++; } 208 | rel 209 { PC.rels_seen++; } 210 | number 211 ; 212 213time: 214 tUNUMBER tMERIDIAN 215 { 216 PC.hour = $1.value; 217 PC.minutes = 0; 218 PC.seconds = 0; 219 PC.meridian = $2; 220 } 221 | tUNUMBER ':' tUNUMBER o_merid 222 { 223 PC.hour = $1.value; 224 PC.minutes = $3.value; 225 PC.seconds = 0; 226 PC.meridian = $4; 227 } 228 | tUNUMBER ':' tUNUMBER tSNUMBER 229 { 230 PC.hour = $1.value; 231 PC.minutes = $3.value; 232 PC.meridian = MER24; 233 PC.zones_seen++; 234 PC.time_zone = $4.value % 100 + ($4.value / 100) * 60; 235 } 236 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid 237 { 238 PC.hour = $1.value; 239 PC.minutes = $3.value; 240 PC.seconds = $5.value; 241 PC.meridian = $6; 242 } 243 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER 244 { 245 PC.hour = $1.value; 246 PC.minutes = $3.value; 247 PC.seconds = $5.value; 248 PC.meridian = MER24; 249 PC.zones_seen++; 250 PC.time_zone = $6.value % 100 + ($6.value / 100) * 60; 251 } 252 ; 253 254local_zone: 255 tLOCAL_ZONE 256 { PC.local_isdst = $1; } 257 | tLOCAL_ZONE tDST 258 { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; } 259 ; 260 261zone: 262 tZONE 263 { PC.time_zone = $1; } 264 | tDAYZONE 265 { PC.time_zone = $1 + 60; } 266 | tZONE tDST 267 { PC.time_zone = $1 + 60; } 268 ; 269 270day: 271 tDAY 272 { 273 PC.day_ordinal = 1; 274 PC.day_number = $1; 275 } 276 | tDAY ',' 277 { 278 PC.day_ordinal = 1; 279 PC.day_number = $1; 280 } 281 | tUNUMBER tDAY 282 { 283 PC.day_ordinal = $1.value; 284 PC.day_number = $2; 285 } 286 ; 287 288date: 289 tUNUMBER '/' tUNUMBER 290 { 291 PC.month = $1.value; 292 PC.day = $3.value; 293 } 294 | tUNUMBER '/' tUNUMBER '/' tUNUMBER 295 { 296 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits, 297 otherwise as MM/DD/YY. 298 The goal in recognizing YYYY/MM/DD is solely to support legacy 299 machine-generated dates like those in an RCS log listing. If 300 you want portability, use the ISO 8601 format. */ 301 if (4 <= $1.digits) 302 { 303 PC.year = $1; 304 PC.month = $3.value; 305 PC.day = $5.value; 306 } 307 else 308 { 309 PC.month = $1.value; 310 PC.day = $3.value; 311 PC.year = $5; 312 } 313 } 314 | tUNUMBER tSNUMBER tSNUMBER 315 { 316 /* ISO 8601 format. YYYY-MM-DD. */ 317 PC.year = $1; 318 PC.month = -$2.value; 319 PC.day = -$3.value; 320 } 321 | tUNUMBER tMONTH tSNUMBER 322 { 323 /* e.g. 17-JUN-1992. */ 324 PC.day = $1.value; 325 PC.month = $2; 326 PC.year.value = -$3.value; 327 PC.year.digits = $3.digits; 328 } 329 | tMONTH tUNUMBER 330 { 331 PC.month = $1; 332 PC.day = $2.value; 333 } 334 | tMONTH tUNUMBER ',' tUNUMBER 335 { 336 PC.month = $1; 337 PC.day = $2.value; 338 PC.year = $4; 339 } 340 | tUNUMBER tMONTH 341 { 342 PC.day = $1.value; 343 PC.month = $2; 344 } 345 | tUNUMBER tMONTH tUNUMBER 346 { 347 PC.day = $1.value; 348 PC.month = $2; 349 PC.year = $3; 350 } 351 ; 352 353rel: 354 relunit tAGO 355 { 356 PC.rel_seconds = -PC.rel_seconds; 357 PC.rel_minutes = -PC.rel_minutes; 358 PC.rel_hour = -PC.rel_hour; 359 PC.rel_day = -PC.rel_day; 360 PC.rel_month = -PC.rel_month; 361 PC.rel_year = -PC.rel_year; 362 } 363 | relunit 364 ; 365 366relunit: 367 tUNUMBER tYEAR_UNIT 368 { PC.rel_year += $1.value * $2; } 369 | tSNUMBER tYEAR_UNIT 370 { PC.rel_year += $1.value * $2; } 371 | tYEAR_UNIT 372 { PC.rel_year += $1; } 373 | tUNUMBER tMONTH_UNIT 374 { PC.rel_month += $1.value * $2; } 375 | tSNUMBER tMONTH_UNIT 376 { PC.rel_month += $1.value * $2; } 377 | tMONTH_UNIT 378 { PC.rel_month += $1; } 379 | tUNUMBER tDAY_UNIT 380 { PC.rel_day += $1.value * $2; } 381 | tSNUMBER tDAY_UNIT 382 { PC.rel_day += $1.value * $2; } 383 | tDAY_UNIT 384 { PC.rel_day += $1; } 385 | tUNUMBER tHOUR_UNIT 386 { PC.rel_hour += $1.value * $2; } 387 | tSNUMBER tHOUR_UNIT 388 { PC.rel_hour += $1.value * $2; } 389 | tHOUR_UNIT 390 { PC.rel_hour += $1; } 391 | tUNUMBER tMINUTE_UNIT 392 { PC.rel_minutes += $1.value * $2; } 393 | tSNUMBER tMINUTE_UNIT 394 { PC.rel_minutes += $1.value * $2; } 395 | tMINUTE_UNIT 396 { PC.rel_minutes += $1; } 397 | tUNUMBER tSEC_UNIT 398 { PC.rel_seconds += $1.value * $2; } 399 | tSNUMBER tSEC_UNIT 400 { PC.rel_seconds += $1.value * $2; } 401 | tSEC_UNIT 402 { PC.rel_seconds += $1; } 403 ; 404 405number: 406 tUNUMBER 407 { 408 if (PC.dates_seen 409 && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits)) 410 PC.year = $1; 411 else 412 { 413 if (4 < $1.digits) 414 { 415 PC.dates_seen++; 416 PC.day = $1.value % 100; 417 PC.month = ($1.value / 100) % 100; 418 PC.year.value = $1.value / 10000; 419 PC.year.digits = $1.digits - 4; 420 } 421 else 422 { 423 PC.times_seen++; 424 if ($1.digits <= 2) 425 { 426 PC.hour = $1.value; 427 PC.minutes = 0; 428 } 429 else 430 { 431 PC.hour = $1.value / 100; 432 PC.minutes = $1.value % 100; 433 } 434 PC.seconds = 0; 435 PC.meridian = MER24; 436 } 437 } 438 } 439 ; 440 441o_merid: 442 /* empty */ 443 { $$ = MER24; } 444 | tMERIDIAN 445 { $$ = $1; } 446 ; 447 448%% 449 450/* Include this file down here because bison inserts code above which 451 may define-away `const'. We want the prototype for get_date to have 452 the same signature as the function definition. */ 453#include "modules/getdate.h" 454 455#ifndef gmtime 456struct tm *gmtime (const time_t *); 457#endif 458#ifndef localtime 459struct tm *localtime (const time_t *); 460#endif 461#ifndef mktime 462time_t mktime (struct tm *); 463#endif 464 465static table const meridian_table[] = 466{ 467 { "AM", tMERIDIAN, MERam }, 468 { "A.M.", tMERIDIAN, MERam }, 469 { "PM", tMERIDIAN, MERpm }, 470 { "P.M.", tMERIDIAN, MERpm }, 471 { 0, 0, 0 } 472}; 473 474static table const dst_table[] = 475{ 476 { "DST", tDST, 0 } 477}; 478 479static table const month_and_day_table[] = 480{ 481 { "JANUARY", tMONTH, 1 }, 482 { "FEBRUARY", tMONTH, 2 }, 483 { "MARCH", tMONTH, 3 }, 484 { "APRIL", tMONTH, 4 }, 485 { "MAY", tMONTH, 5 }, 486 { "JUNE", tMONTH, 6 }, 487 { "JULY", tMONTH, 7 }, 488 { "AUGUST", tMONTH, 8 }, 489 { "SEPTEMBER",tMONTH, 9 }, 490 { "SEPT", tMONTH, 9 }, 491 { "OCTOBER", tMONTH, 10 }, 492 { "NOVEMBER", tMONTH, 11 }, 493 { "DECEMBER", tMONTH, 12 }, 494 { "SUNDAY", tDAY, 0 }, 495 { "MONDAY", tDAY, 1 }, 496 { "TUESDAY", tDAY, 2 }, 497 { "TUES", tDAY, 2 }, 498 { "WEDNESDAY",tDAY, 3 }, 499 { "WEDNES", tDAY, 3 }, 500 { "THURSDAY", tDAY, 4 }, 501 { "THUR", tDAY, 4 }, 502 { "THURS", tDAY, 4 }, 503 { "FRIDAY", tDAY, 5 }, 504 { "SATURDAY", tDAY, 6 }, 505 { 0, 0, 0 } 506}; 507 508static table const time_units_table[] = 509{ 510 { "YEAR", tYEAR_UNIT, 1 }, 511 { "MONTH", tMONTH_UNIT, 1 }, 512 { "FORTNIGHT",tDAY_UNIT, 14 }, 513 { "WEEK", tDAY_UNIT, 7 }, 514 { "DAY", tDAY_UNIT, 1 }, 515 { "HOUR", tHOUR_UNIT, 1 }, 516 { "MINUTE", tMINUTE_UNIT, 1 }, 517 { "MIN", tMINUTE_UNIT, 1 }, 518 { "SECOND", tSEC_UNIT, 1 }, 519 { "SEC", tSEC_UNIT, 1 }, 520 { 0, 0, 0 } 521}; 522 523/* Assorted relative-time words. */ 524static table const relative_time_table[] = 525{ 526 { "TOMORROW", tMINUTE_UNIT, 24 * 60 }, 527 { "YESTERDAY",tMINUTE_UNIT, - (24 * 60) }, 528 { "TODAY", tMINUTE_UNIT, 0 }, 529 { "NOW", tMINUTE_UNIT, 0 }, 530 { "LAST", tUNUMBER, -1 }, 531 { "THIS", tUNUMBER, 0 }, 532 { "NEXT", tUNUMBER, 1 }, 533 { "FIRST", tUNUMBER, 1 }, 534/*{ "SECOND", tUNUMBER, 2 }, */ 535 { "THIRD", tUNUMBER, 3 }, 536 { "FOURTH", tUNUMBER, 4 }, 537 { "FIFTH", tUNUMBER, 5 }, 538 { "SIXTH", tUNUMBER, 6 }, 539 { "SEVENTH", tUNUMBER, 7 }, 540 { "EIGHTH", tUNUMBER, 8 }, 541 { "NINTH", tUNUMBER, 9 }, 542 { "TENTH", tUNUMBER, 10 }, 543 { "ELEVENTH", tUNUMBER, 11 }, 544 { "TWELFTH", tUNUMBER, 12 }, 545 { "AGO", tAGO, 1 }, 546 { 0, 0, 0 } 547}; 548 549/* The time zone table. This table is necessarily incomplete, as time 550 zone abbreviations are ambiguous; e.g. Australians interpret "EST" 551 as Eastern time in Australia, not as US Eastern Standard Time. 552 You cannot rely on getdate to handle arbitrary time zone 553 abbreviations; use numeric abbreviations like `-0500' instead. */ 554static table const time_zone_table[] = 555{ 556 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */ 557 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */ 558 { "UTC", tZONE, HOUR ( 0) }, 559 { "WET", tZONE, HOUR ( 0) }, /* Western European */ 560 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */ 561 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */ 562 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */ 563 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */ 564 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */ 565 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */ 566 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */ 567 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */ 568 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */ 569 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */ 570 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */ 571 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */ 572 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */ 573 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */ 574 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */ 575 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */ 576 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */ 577 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */ 578 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */ 579 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */ 580 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */ 581 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */ 582 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */ 583 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */ 584 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */ 585 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */ 586 { "CET", tZONE, HOUR ( 1) }, /* Central European */ 587 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */ 588 { "MET", tZONE, HOUR ( 1) }, /* Middle European */ 589 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */ 590 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */ 591 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */ 592 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */ 593 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */ 594 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */ 595 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */ 596 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */ 597 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */ 598 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */ 599 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */ 600 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */ 601 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */ 602 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */ 603 { "GST", tZONE, HOUR (10) }, /* Guam Standard */ 604 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */ 605 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */ 606 { 0, 0, 0 } 607}; 608 609/* Military time zone table. */ 610static table const military_table[] = 611{ 612 { "A", tZONE, -HOUR ( 1) }, 613 { "B", tZONE, -HOUR ( 2) }, 614 { "C", tZONE, -HOUR ( 3) }, 615 { "D", tZONE, -HOUR ( 4) }, 616 { "E", tZONE, -HOUR ( 5) }, 617 { "F", tZONE, -HOUR ( 6) }, 618 { "G", tZONE, -HOUR ( 7) }, 619 { "H", tZONE, -HOUR ( 8) }, 620 { "I", tZONE, -HOUR ( 9) }, 621 { "K", tZONE, -HOUR (10) }, 622 { "L", tZONE, -HOUR (11) }, 623 { "M", tZONE, -HOUR (12) }, 624 { "N", tZONE, HOUR ( 1) }, 625 { "O", tZONE, HOUR ( 2) }, 626 { "P", tZONE, HOUR ( 3) }, 627 { "Q", tZONE, HOUR ( 4) }, 628 { "R", tZONE, HOUR ( 5) }, 629 { "S", tZONE, HOUR ( 6) }, 630 { "T", tZONE, HOUR ( 7) }, 631 { "U", tZONE, HOUR ( 8) }, 632 { "V", tZONE, HOUR ( 9) }, 633 { "W", tZONE, HOUR (10) }, 634 { "X", tZONE, HOUR (11) }, 635 { "Y", tZONE, HOUR (12) }, 636 { "Z", tZONE, HOUR ( 0) }, 637 { 0, 0, 0 } 638}; 639 640 641 642static int 643to_hour (int hours, int meridian) 644{ 645 switch (meridian) 646 { 647 case MER24: 648 return 0 <= hours && hours < 24 ? hours : -1; 649 case MERam: 650 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1; 651 case MERpm: 652 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1; 653 default: 654 abort (); 655 } 656 /* NOTREACHED */ 657 return 0; 658} 659 660static int 661to_year (textint textyear) 662{ 663 int year = textyear.value; 664 665 if (year < 0) 666 year = -year; 667 668 /* XPG4 suggests that years 00-68 map to 2000-2068, and 669 years 69-99 map to 1969-1999. */ 670 if (textyear.digits == 2) 671 year += year < 69 ? 2000 : 1900; 672 673 return year; 674} 675 676static table const * 677lookup_zone (parser_control const *pc, char const *name) 678{ 679 table const *tp; 680 681 /* Try local zone abbreviations first; they're more likely to be right. */ 682 for (tp = pc->local_time_zone_table; tp->name; tp++) 683 if (strcmp (name, tp->name) == 0) 684 return tp; 685 686 for (tp = time_zone_table; tp->name; tp++) 687 if (strcmp (name, tp->name) == 0) 688 return tp; 689 690 return 0; 691} 692 693#if ! HAVE_TM_GMTOFF 694/* Yield the difference between *A and *B, 695 measured in seconds, ignoring leap seconds. 696 The body of this function is taken directly from the GNU C Library; 697 see src/strftime.c. */ 698static int 699tm_diff (struct tm const *a, struct tm const *b) 700{ 701 /* Compute intervening leap days correctly even if year is negative. 702 Take care to avoid int overflow in leap day calculations, 703 but it's OK to assume that A and B are close to each other. */ 704 int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3); 705 int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3); 706 int a100 = a4 / 25 - (a4 % 25 < 0); 707 int b100 = b4 / 25 - (b4 % 25 < 0); 708 int a400 = a100 >> 2; 709 int b400 = b100 >> 2; 710 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); 711 int years = a->tm_year - b->tm_year; 712 int days = (365 * years + intervening_leap_days 713 + (a->tm_yday - b->tm_yday)); 714 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour)) 715 + (a->tm_min - b->tm_min)) 716 + (a->tm_sec - b->tm_sec)); 717} 718#endif /* ! HAVE_TM_GMTOFF */ 719 720static table const * 721lookup_word (parser_control const *pc, char *word) 722{ 723 char *p; 724 char *q; 725 size_t wordlen; 726 table const *tp; 727 int i; 728 int abbrev; 729 730 /* Make it uppercase. */ 731 for (p = word; *p; p++) 732 if (ISLOWER ((unsigned char) *p)) 733 *p = toupper ((unsigned char) *p); 734 735 for (tp = meridian_table; tp->name; tp++) 736 if (strcmp (word, tp->name) == 0) 737 return tp; 738 739 /* See if we have an abbreviation for a month. */ 740 wordlen = strlen (word); 741 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.'); 742 743 for (tp = month_and_day_table; tp->name; tp++) 744 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0) 745 return tp; 746 747 if ((tp = lookup_zone (pc, word))) 748 return tp; 749 750 if (strcmp (word, dst_table[0].name) == 0) 751 return dst_table; 752 753 for (tp = time_units_table; tp->name; tp++) 754 if (strcmp (word, tp->name) == 0) 755 return tp; 756 757 /* Strip off any plural and try the units table again. */ 758 if (word[wordlen - 1] == 'S') 759 { 760 word[wordlen - 1] = '\0'; 761 for (tp = time_units_table; tp->name; tp++) 762 if (strcmp (word, tp->name) == 0) 763 return tp; 764 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */ 765 } 766 767 for (tp = relative_time_table; tp->name; tp++) 768 if (strcmp (word, tp->name) == 0) 769 return tp; 770 771 /* Military time zones. */ 772 if (wordlen == 1) 773 for (tp = military_table; tp->name; tp++) 774 if (word[0] == tp->name[0]) 775 return tp; 776 777 /* Drop out any periods and try the time zone table again. */ 778 for (i = 0, p = q = word; (*p = *q); q++) 779 if (*q == '.') 780 i = 1; 781 else 782 p++; 783 if (i && (tp = lookup_zone (pc, word))) 784 return tp; 785 786 return 0; 787} 788 789static int 790yylex (YYSTYPE *lvalp, parser_control *pc) 791{ 792 unsigned char c; 793 int count; 794 795 for (;;) 796 { 797 while (c = *pc->input, ISSPACE (c)) 798 pc->input++; 799 800 if (ISDIGIT (c) || c == '-' || c == '+') 801 { 802 char const *p; 803 int sign; 804 int value; 805 if (c == '-' || c == '+') 806 { 807 sign = c == '-' ? -1 : 1; 808 c = *++pc->input; 809 if (! ISDIGIT (c)) 810 /* skip the '-' sign */ 811 continue; 812 } 813 else 814 sign = 0; 815 p = pc->input; 816 value = 0; 817 do 818 { 819 value = 10 * value + c - '0'; 820 c = *++p; 821 } 822 while (ISDIGIT (c)); 823 lvalp->textintval.value = sign < 0 ? -value : value; 824 lvalp->textintval.digits = p - pc->input; 825 pc->input = p; 826 return sign ? tSNUMBER : tUNUMBER; 827 } 828 829 if (ISALPHA (c)) 830 { 831 char buff[20]; 832 char *p = buff; 833 table const *tp; 834 835 do 836 { 837 if (p < buff + sizeof buff - 1) 838 *p++ = c; 839 c = *++pc->input; 840 } 841 while (ISALPHA (c) || c == '.'); 842 843 *p = '\0'; 844 tp = lookup_word (pc, buff); 845 if (! tp) 846 return '?'; 847 lvalp->intval = tp->value; 848 return tp->type; 849 } 850 851 if (c != '(') 852 return *pc->input++; 853 count = 0; 854 do 855 { 856 c = *pc->input++; 857 if (c == '\0') 858 return c; 859 if (c == '(') 860 count++; 861 else if (c == ')') 862 count--; 863 } 864 while (count > 0); 865 } 866} 867 868/* Do nothing if the parser reports an error. */ 869static int 870yyerror (const char *s ATTRIBUTE_UNUSED) 871{ 872 return 0; 873} 874 875/* Parse a date/time string P. Return the corresponding time_t value, 876 or (time_t) -1 if there is an error. P can be an incomplete or 877 relative time specification; if so, use *NOW as the basis for the 878 returned time. */ 879time_t 880get_date (const char *p, const time_t *now) 881{ 882 time_t Start = now ? *now : time (0); 883 struct tm *tmp = localtime (&Start); 884 struct tm tm; 885 struct tm tm0; 886 parser_control pc; 887 888 if (! tmp) 889 return -1; 890 891 pc.input = p; 892 pc.year.value = tmp->tm_year + TM_YEAR_BASE; 893 pc.year.digits = 4; 894 pc.month = tmp->tm_mon + 1; 895 pc.day = tmp->tm_mday; 896 pc.hour = tmp->tm_hour; 897 pc.minutes = tmp->tm_min; 898 pc.seconds = tmp->tm_sec; 899 tm.tm_isdst = tmp->tm_isdst; 900 901 pc.meridian = MER24; 902 pc.rel_seconds = 0; 903 pc.rel_minutes = 0; 904 pc.rel_hour = 0; 905 pc.rel_day = 0; 906 pc.rel_month = 0; 907 pc.rel_year = 0; 908 pc.dates_seen = 0; 909 pc.days_seen = 0; 910 pc.rels_seen = 0; 911 pc.times_seen = 0; 912 pc.local_zones_seen = 0; 913 pc.zones_seen = 0; 914 915#if HAVE_STRUCT_TM_TM_ZONE 916 pc.local_time_zone_table[0].name = tmp->tm_zone; 917 pc.local_time_zone_table[0].type = tLOCAL_ZONE; 918 pc.local_time_zone_table[0].value = tmp->tm_isdst; 919 pc.local_time_zone_table[1].name = 0; 920 921 /* Probe the names used in the next three calendar quarters, looking 922 for a tm_isdst different from the one we already have. */ 923 { 924 int quarter; 925 for (quarter = 1; quarter <= 3; quarter++) 926 { 927 time_t probe = Start + quarter * (90 * 24 * 60 * 60); 928 struct tm *probe_tm = localtime (&probe); 929 if (probe_tm && probe_tm->tm_zone 930 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value) 931 { 932 { 933 pc.local_time_zone_table[1].name = probe_tm->tm_zone; 934 pc.local_time_zone_table[1].type = tLOCAL_ZONE; 935 pc.local_time_zone_table[1].value = probe_tm->tm_isdst; 936 pc.local_time_zone_table[2].name = 0; 937 } 938 break; 939 } 940 } 941 } 942#else 943#if HAVE_TZNAME 944 { 945# ifndef tzname 946 extern char *tzname[]; 947# endif 948 int i; 949 for (i = 0; i < 2; i++) 950 { 951 pc.local_time_zone_table[i].name = tzname[i]; 952 pc.local_time_zone_table[i].type = tLOCAL_ZONE; 953 pc.local_time_zone_table[i].value = i; 954 } 955 pc.local_time_zone_table[i].name = 0; 956 } 957#else 958 pc.local_time_zone_table[0].name = 0; 959#endif 960#endif 961 962 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name 963 && ! strcmp (pc.local_time_zone_table[0].name, 964 pc.local_time_zone_table[1].name)) 965 { 966 /* This locale uses the same abbrevation for standard and 967 daylight times. So if we see that abbreviation, we don't 968 know whether it's daylight time. */ 969 pc.local_time_zone_table[0].value = -1; 970 pc.local_time_zone_table[1].name = 0; 971 } 972 973 if (yyparse (&pc) != 0 974 || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen 975 || 1 < (pc.local_zones_seen + pc.zones_seen) 976 || (pc.local_zones_seen && 1 < pc.local_isdst)) 977 return -1; 978 979 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year; 980 tm.tm_mon = pc.month - 1 + pc.rel_month; 981 tm.tm_mday = pc.day + pc.rel_day; 982 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen)) 983 { 984 tm.tm_hour = to_hour (pc.hour, pc.meridian); 985 if (tm.tm_hour < 0) 986 return -1; 987 tm.tm_min = pc.minutes; 988 tm.tm_sec = pc.seconds; 989 } 990 else 991 { 992 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 993 } 994 995 /* Let mktime deduce tm_isdst if we have an absolute time stamp, 996 or if the relative time stamp mentions days, months, or years. */ 997 if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day 998 | pc.rel_month | pc.rel_year) 999 tm.tm_isdst = -1; 1000 1001 /* But if the input explicitly specifies local time with or without 1002 DST, give mktime that information. */ 1003 if (pc.local_zones_seen) 1004 tm.tm_isdst = pc.local_isdst; 1005 1006 tm0 = tm; 1007 1008 Start = mktime (&tm); 1009 1010 if (Start == (time_t) -1) 1011 { 1012 1013 /* Guard against falsely reporting errors near the time_t boundaries 1014 when parsing times in other time zones. For example, if the min 1015 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead 1016 of UTC, then the min localtime value is 1970-01-01 08:00:00; if 1017 we apply mktime to 1970-01-01 00:00:00 we will get an error, so 1018 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time 1019 zone by 24 hours to compensate. This algorithm assumes that 1020 there is no DST transition within a day of the time_t boundaries. */ 1021 if (pc.zones_seen) 1022 { 1023 tm = tm0; 1024 if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE) 1025 { 1026 tm.tm_mday++; 1027 pc.time_zone += 24 * 60; 1028 } 1029 else 1030 { 1031 tm.tm_mday--; 1032 pc.time_zone -= 24 * 60; 1033 } 1034 Start = mktime (&tm); 1035 } 1036 1037 if (Start == (time_t) -1) 1038 return Start; 1039 } 1040 1041 if (pc.days_seen && ! pc.dates_seen) 1042 { 1043 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 1044 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal))); 1045 tm.tm_isdst = -1; 1046 Start = mktime (&tm); 1047 if (Start == (time_t) -1) 1048 return Start; 1049 } 1050 1051 if (pc.zones_seen) 1052 { 1053 int delta = pc.time_zone * 60; 1054#ifdef HAVE_TM_GMTOFF 1055 delta -= tm.tm_gmtoff; 1056#else 1057 struct tm *gmt = gmtime (&Start); 1058 if (! gmt) 1059 return -1; 1060 delta -= tm_diff (&tm, gmt); 1061#endif 1062 if ((Start < Start - delta) != (delta < 0)) 1063 return -1; /* time_t overflow */ 1064 Start -= delta; 1065 } 1066 1067 /* Add relative hours, minutes, and seconds. Ignore leap seconds; 1068 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a 1069 leap second. Typically this is not what the user wants, but it's 1070 too hard to do it the other way, because the time zone indicator 1071 must be applied before relative times, and if mktime is applied 1072 again the time zone will be lost. */ 1073 { 1074 time_t t0 = Start; 1075 long d1 = 60 * 60 * (long) pc.rel_hour; 1076 time_t t1 = t0 + d1; 1077 long d2 = 60 * (long) pc.rel_minutes; 1078 time_t t2 = t1 + d2; 1079 int d3 = pc.rel_seconds; 1080 time_t t3 = t2 + d3; 1081 if ((d1 / (60 * 60) ^ pc.rel_hour) 1082 | (d2 / 60 ^ pc.rel_minutes) 1083 | ((t0 + d1 < t0) ^ (d1 < 0)) 1084 | ((t1 + d2 < t1) ^ (d2 < 0)) 1085 | ((t2 + d3 < t2) ^ (d3 < 0))) 1086 return -1; 1087 Start = t3; 1088 } 1089 1090 return Start; 1091} 1092 1093#if TEST 1094 1095#include <stdio.h> 1096 1097int 1098main (int ac, char **av) 1099{ 1100 char buff[BUFSIZ]; 1101 time_t d; 1102 1103 printf ("Enter date, or blank line to exit.\n\t> "); 1104 fflush (stdout); 1105 1106 buff[BUFSIZ - 1] = 0; 1107 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0]) 1108 { 1109 d = get_date (buff, 0); 1110 if (d == (time_t) -1) 1111 printf ("Bad format - couldn't convert.\n"); 1112 else 1113 printf ("%s", ctime (&d)); 1114 printf ("\t> "); 1115 fflush (stdout); 1116 } 1117 return 0; 1118} 1119#endif /* defined TEST */ 1120