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