root/source3/modules/getdate.y

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. to_hour
  2. to_year
  3. lookup_zone
  4. tm_diff
  5. lookup_word
  6. yylex
  7. yyerror
  8. get_date
  9. main

   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.  */
  90 typedef struct
  91 {
  92   int value;
  93   int digits;
  94 } textint;
  95 
  96 /* An entry in the lexical lookup table.  */
  97 typedef struct
  98 {
  99   char const *name;
 100   int type;
 101   int value;
 102 } table;
 103 
 104 /* Meridian: am, pm, or 24-hour style.  */
 105 enum { MERam, MERpm, MER24 };
 106 
 107 /* Information passed to and from the parser.  */
 108 typedef 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 
 176 static int yyerror(const char *);
 177 static 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 
 192 spec:
 193     /* empty */
 194   | spec item
 195   ;
 196 
 197 item:
 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 
 213 time:
 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 
 254 local_zone:
 255     tLOCAL_ZONE
 256       { PC.local_isdst = $1; }
 257   | tLOCAL_ZONE tDST
 258       { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; }
 259   ;
 260 
 261 zone:
 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 
 270 day:
 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 
 288 date:
 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 
 353 rel:
 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 
 366 relunit:
 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 
 405 number:
 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 
 441 o_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
 456 struct tm *gmtime (const time_t *);
 457 #endif
 458 #ifndef localtime
 459 struct tm *localtime (const time_t *);
 460 #endif
 461 #ifndef mktime
 462 time_t mktime (struct tm *);
 463 #endif
 464 
 465 static 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 
 474 static table const dst_table[] =
 475 {
 476   { "DST", tDST, 0 }
 477 };
 478 
 479 static 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 
 508 static 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. */
 524 static 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.  */
 554 static 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. */
 610 static 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 
 642 static int
 643 to_hour (int hours, int meridian)
     /* [<][>][^][v][top][bottom][index][help] */
 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 
 660 static int
 661 to_year (textint textyear)
     /* [<][>][^][v][top][bottom][index][help] */
 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 
 676 static table const *
 677 lookup_zone (parser_control const *pc, char const *name)
     /* [<][>][^][v][top][bottom][index][help] */
 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.  */
 698 static int
 699 tm_diff (struct tm const *a, struct tm const *b)
     /* [<][>][^][v][top][bottom][index][help] */
 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 
 720 static table const *
 721 lookup_word (parser_control const *pc, char *word)
     /* [<][>][^][v][top][bottom][index][help] */
 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 
 789 static int
 790 yylex (YYSTYPE *lvalp, parser_control *pc)
     /* [<][>][^][v][top][bottom][index][help] */
 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.  */
 869 static int
 870 yyerror (const char *s ATTRIBUTE_UNUSED)
     /* [<][>][^][v][top][bottom][index][help] */
 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.  */
 879 time_t
 880 get_date (const char *p, const time_t *now)
     /* [<][>][^][v][top][bottom][index][help] */
 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 
1097 int
1098 main (int ac, char **av)
     /* [<][>][^][v][top][bottom][index][help] */
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 */

/* [<][>][^][v][top][bottom][index][help] */