Blame lib/parse-datetime.y

Packit 8f70b4
%{
Packit 8f70b4
/* Parse a string into an internal timestamp.
Packit 8f70b4
Packit 8f70b4
   Copyright (C) 1999-2000, 2002-2018 Free Software Foundation, Inc.
Packit 8f70b4
Packit 8f70b4
   This program is free software: you can redistribute it and/or modify
Packit 8f70b4
   it under the terms of the GNU General Public License as published by
Packit 8f70b4
   the Free Software Foundation; either version 3 of the License, or
Packit 8f70b4
   (at your option) any later version.
Packit 8f70b4
Packit 8f70b4
   This program is distributed in the hope that it will be useful,
Packit 8f70b4
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8f70b4
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8f70b4
   GNU General Public License for more details.
Packit 8f70b4
Packit 8f70b4
   You should have received a copy of the GNU General Public License
Packit 8f70b4
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
Packit 8f70b4
Packit 8f70b4
/* Originally written by Steven M. Bellovin <smb@research.att.com> while
Packit 8f70b4
   at the University of North Carolina at Chapel Hill.  Later tweaked by
Packit 8f70b4
   a couple of people on Usenet.  Completely overhauled by Rich $alz
Packit 8f70b4
   <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
Packit 8f70b4
Packit 8f70b4
   Modified by Assaf Gordon <assafgordon@gmail.com> in 2016 to add
Packit 8f70b4
   debug output.
Packit 8f70b4
Packit 8f70b4
   Modified by Paul Eggert <eggert@twinsun.com> in 1999 to do the
Packit 8f70b4
   right thing about local DST.  Also modified by Paul Eggert
Packit 8f70b4
   <eggert@cs.ucla.edu> in 2004 to support nanosecond-resolution
Packit 8f70b4
   timestamps, in 2004 to support TZ strings in dates, and in 2017 to
Packit 8f70b4
   check for integer overflow and to support longer-than-'long'
Packit 8f70b4
   'time_t' and 'tv_nsec'.  */
Packit 8f70b4
Packit 8f70b4
#include <config.h>
Packit 8f70b4
Packit 8f70b4
#include "parse-datetime.h"
Packit 8f70b4
Packit 8f70b4
#include "intprops.h"
Packit 8f70b4
#include "timespec.h"
Packit 8f70b4
#include "verify.h"
Packit 8f70b4
#include "strftime.h"
Packit 8f70b4
Packit 8f70b4
/* There's no need to extend the stack, so there's no need to involve
Packit 8f70b4
   alloca.  */
Packit 8f70b4
#define YYSTACK_USE_ALLOCA 0
Packit 8f70b4
Packit 8f70b4
/* Tell Bison how much stack space is needed.  20 should be plenty for
Packit 8f70b4
   this grammar, which is not right recursive.  Beware setting it too
Packit 8f70b4
   high, since that might cause problems on machines whose
Packit 8f70b4
   implementations have lame stack-overflow checking.  */
Packit 8f70b4
#define YYMAXDEPTH 20
Packit 8f70b4
#define YYINITDEPTH YYMAXDEPTH
Packit 8f70b4
Packit 8f70b4
/* Since the code of parse-datetime.y is not included in the Emacs executable
Packit 8f70b4
   itself, there is no need to #define static in this file.  Even if
Packit 8f70b4
   the code were included in the Emacs executable, it probably
Packit 8f70b4
   wouldn't do any harm to #undef it here; this will only cause
Packit 8f70b4
   problems if we try to write to a static variable, which I don't
Packit 8f70b4
   think this code needs to do.  */
Packit 8f70b4
#ifdef emacs
Packit 8f70b4
# undef static
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
#include <inttypes.h>
Packit 8f70b4
#include <c-ctype.h>
Packit 8f70b4
#include <limits.h>
Packit 8f70b4
#include <stdarg.h>
Packit 8f70b4
#include <stdio.h>
Packit 8f70b4
#include <stdlib.h>
Packit 8f70b4
#include <string.h>
Packit 8f70b4
Packit 8f70b4
#include "gettext.h"
Packit 8f70b4
Packit 8f70b4
#define _(str) gettext (str)
Packit 8f70b4
Packit 8f70b4
/* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
Packit 8f70b4
   use _STDLIB_H_ as witness.  Map the latter to the one bison uses.  */
Packit 8f70b4
/* FIXME: this is temporary.  Remove when we have a mechanism to ensure
Packit 8f70b4
   that the version we're using is fixed, too.  */
Packit 8f70b4
#ifdef _STDLIB_H_
Packit 8f70b4
# undef _STDLIB_H
Packit 8f70b4
# define _STDLIB_H 1
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
/* The __attribute__ feature is available in gcc versions 2.5 and later.
Packit 8f70b4
   The __-protected variants of the attributes 'format' and 'printf' are
Packit 8f70b4
   accepted by gcc versions 2.6.4 (effectively 2.7) and later.
Packit 8f70b4
   Enable _GL_ATTRIBUTE_FORMAT only if these are supported too, because
Packit 8f70b4
   gnulib and libintl do '#define printf __printf__' when they override
Packit 8f70b4
   the 'printf' function.  */
Packit 8f70b4
#if 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
Packit 8f70b4
# define _GL_ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
Packit 8f70b4
#else
Packit 8f70b4
# define _GL_ATTRIBUTE_FORMAT(spec) /* empty */
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
/* Shift A right by B bits portably, by dividing A by 2**B and
Packit 8f70b4
   truncating towards minus infinity.  A and B should be free of side
Packit 8f70b4
   effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
Packit 8f70b4
   INT_BITS is the number of useful bits in an int.  GNU code can
Packit 8f70b4
   assume that INT_BITS is at least 32.
Packit 8f70b4
Packit 8f70b4
   ISO C99 says that A >> B is implementation-defined if A < 0.  Some
Packit 8f70b4
   implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
Packit 8f70b4
   right in the usual way when A < 0, so SHR falls back on division if
Packit 8f70b4
   ordinary A >> B doesn't seem to be the usual signed shift.  */
Packit 8f70b4
#define SHR(a, b)       \
Packit 8f70b4
  (-1 >> 1 == -1        \
Packit 8f70b4
   ? (a) >> (b)         \
Packit 8f70b4
   : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
Packit 8f70b4
Packit 8f70b4
#define HOUR(x) (60 * 60 * (x))
Packit 8f70b4
Packit 8f70b4
#define STREQ(a, b) (strcmp (a, b) == 0)
Packit 8f70b4
Packit 8f70b4
/* Verify that time_t is an integer as POSIX requires, and that every
Packit 8f70b4
   time_t value fits in intmax_t.  Please file a bug report if these
Packit 8f70b4
   assumptions are false on your platform.  */
Packit 8f70b4
verify (TYPE_IS_INTEGER (time_t));
Packit 8f70b4
verify (!TYPE_SIGNED (time_t) || INTMAX_MIN <= TYPE_MINIMUM (time_t));
Packit 8f70b4
verify (TYPE_MAXIMUM (time_t) <= INTMAX_MAX);
Packit 8f70b4
Packit 8f70b4
/* True if N is out of range for time_t.  */
Packit 8f70b4
static bool
Packit 8f70b4
time_overflow (intmax_t n)
Packit 8f70b4
{
Packit 8f70b4
  return ! ((TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= n : 0 <= n)
Packit 8f70b4
            && n <= TYPE_MAXIMUM (time_t));
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Convert a possibly-signed character to an unsigned character.  This is
Packit 8f70b4
   a bit safer than casting to unsigned char, since it catches some type
Packit 8f70b4
   errors that the cast doesn't.  */
Packit 8f70b4
static unsigned char to_uchar (char ch) { return ch; }
Packit 8f70b4
Packit 8f70b4
static void _GL_ATTRIBUTE_FORMAT ((__printf__, 1, 2))
Packit 8f70b4
dbg_printf (char const *msg, ...)
Packit 8f70b4
{
Packit 8f70b4
  va_list args;
Packit 8f70b4
  /* TODO: use gnulib's 'program_name' instead?  */
Packit 8f70b4
  fputs ("date: ", stderr);
Packit 8f70b4
Packit 8f70b4
  va_start (args, msg);
Packit 8f70b4
  vfprintf (stderr, msg, args);
Packit 8f70b4
  va_end (args);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
Packit 8f70b4
/* An integer value, and the number of digits in its textual
Packit 8f70b4
   representation.  */
Packit 8f70b4
typedef struct
Packit 8f70b4
{
Packit 8f70b4
  bool negative;
Packit 8f70b4
  intmax_t value;
Packit 8f70b4
  ptrdiff_t digits;
Packit 8f70b4
} textint;
Packit 8f70b4
Packit 8f70b4
/* An entry in the lexical lookup table.  */
Packit 8f70b4
typedef struct
Packit 8f70b4
{
Packit 8f70b4
  char const *name;
Packit 8f70b4
  int type;
Packit 8f70b4
  int value;
Packit 8f70b4
} table;
Packit 8f70b4
Packit 8f70b4
/* Meridian: am, pm, or 24-hour style.  */
Packit 8f70b4
enum { MERam, MERpm, MER24 };
Packit 8f70b4
Packit 8f70b4
/* A reasonable upper bound for the buffer used in debug output.  */
Packit 8f70b4
enum { DBGBUFSIZE = 100 };
Packit 8f70b4
Packit 8f70b4
enum { BILLION = 1000000000, LOG10_BILLION = 9 };
Packit 8f70b4
Packit 8f70b4
/* Relative times.  */
Packit 8f70b4
typedef struct
Packit 8f70b4
{
Packit 8f70b4
  /* Relative year, month, day, hour, minutes, seconds, and nanoseconds.  */
Packit 8f70b4
  intmax_t year;
Packit 8f70b4
  intmax_t month;
Packit 8f70b4
  intmax_t day;
Packit 8f70b4
  intmax_t hour;
Packit 8f70b4
  intmax_t minutes;
Packit 8f70b4
  intmax_t seconds;
Packit 8f70b4
  int ns;
Packit 8f70b4
} relative_time;
Packit 8f70b4
Packit 8f70b4
#if HAVE_COMPOUND_LITERALS
Packit 8f70b4
# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
Packit 8f70b4
#else
Packit 8f70b4
static relative_time const RELATIVE_TIME_0;
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
/* Information passed to and from the parser.  */
Packit 8f70b4
typedef struct
Packit 8f70b4
{
Packit 8f70b4
  /* The input string remaining to be parsed.  */
Packit 8f70b4
  const char *input;
Packit 8f70b4
Packit 8f70b4
  /* N, if this is the Nth Tuesday.  */
Packit 8f70b4
  intmax_t day_ordinal;
Packit 8f70b4
Packit 8f70b4
  /* Day of week; Sunday is 0.  */
Packit 8f70b4
  int day_number;
Packit 8f70b4
Packit 8f70b4
  /* tm_isdst flag for the local zone.  */
Packit 8f70b4
  int local_isdst;
Packit 8f70b4
Packit 8f70b4
  /* Time zone, in seconds east of UT.  */
Packit 8f70b4
  int time_zone;
Packit 8f70b4
Packit 8f70b4
  /* Style used for time.  */
Packit 8f70b4
  int meridian;
Packit 8f70b4
Packit 8f70b4
  /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds.  */
Packit 8f70b4
  textint year;
Packit 8f70b4
  intmax_t month;
Packit 8f70b4
  intmax_t day;
Packit 8f70b4
  intmax_t hour;
Packit 8f70b4
  intmax_t minutes;
Packit 8f70b4
  struct timespec seconds; /* includes nanoseconds */
Packit 8f70b4
Packit 8f70b4
  /* Relative year, month, day, hour, minutes, seconds, and nanoseconds.  */
Packit 8f70b4
  relative_time rel;
Packit 8f70b4
Packit 8f70b4
  /* Presence or counts of nonterminals of various flavors parsed so far.  */
Packit 8f70b4
  bool timespec_seen;
Packit 8f70b4
  bool rels_seen;
Packit 8f70b4
  ptrdiff_t dates_seen;
Packit 8f70b4
  ptrdiff_t days_seen;
Packit 8f70b4
  ptrdiff_t local_zones_seen;
Packit 8f70b4
  ptrdiff_t dsts_seen;
Packit 8f70b4
  ptrdiff_t times_seen;
Packit 8f70b4
  ptrdiff_t zones_seen;
Packit 8f70b4
  bool year_seen;
Packit 8f70b4
Packit 8f70b4
  /* Print debugging output to stderr.  */
Packit 8f70b4
  bool parse_datetime_debug;
Packit 8f70b4
Packit 8f70b4
  /* Which of the 'seen' parts have been printed when debugging.  */
Packit 8f70b4
  bool debug_dates_seen;
Packit 8f70b4
  bool debug_days_seen;
Packit 8f70b4
  bool debug_local_zones_seen;
Packit 8f70b4
  bool debug_times_seen;
Packit 8f70b4
  bool debug_zones_seen;
Packit 8f70b4
  bool debug_year_seen;
Packit 8f70b4
Packit 8f70b4
  /* The user specified explicit ordinal day value.  */
Packit 8f70b4
  bool debug_ordinal_day_seen;
Packit 8f70b4
Packit 8f70b4
  /* Table of local time zone abbreviations, terminated by a null entry.  */
Packit 8f70b4
  table local_time_zone_table[3];
Packit 8f70b4
} parser_control;
Packit 8f70b4
Packit 8f70b4
union YYSTYPE;
Packit 8f70b4
static int yylex (union YYSTYPE *, parser_control *);
Packit 8f70b4
static int yyerror (parser_control const *, char const *);
Packit 8f70b4
static bool time_zone_hhmm (parser_control *, textint, intmax_t);
Packit 8f70b4
Packit 8f70b4
/* Extract into *PC any date and time info from a string of digits
Packit 8f70b4
   of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
Packit 8f70b4
   YYYY, ...).  */
Packit 8f70b4
static void
Packit 8f70b4
digits_to_date_time (parser_control *pc, textint text_int)
Packit 8f70b4
{
Packit 8f70b4
  if (pc->dates_seen && ! pc->year.digits
Packit 8f70b4
      && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
Packit 8f70b4
    {
Packit 8f70b4
      pc->year_seen = true;
Packit 8f70b4
      pc->year = text_int;
Packit 8f70b4
    }
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      if (4 < text_int.digits)
Packit 8f70b4
        {
Packit 8f70b4
          pc->dates_seen++;
Packit 8f70b4
          pc->day = text_int.value % 100;
Packit 8f70b4
          pc->month = (text_int.value / 100) % 100;
Packit 8f70b4
          pc->year.value = text_int.value / 10000;
Packit 8f70b4
          pc->year.digits = text_int.digits - 4;
Packit 8f70b4
        }
Packit 8f70b4
      else
Packit 8f70b4
        {
Packit 8f70b4
          pc->times_seen++;
Packit 8f70b4
          if (text_int.digits <= 2)
Packit 8f70b4
            {
Packit 8f70b4
              pc->hour = text_int.value;
Packit 8f70b4
              pc->minutes = 0;
Packit 8f70b4
            }
Packit 8f70b4
          else
Packit 8f70b4
            {
Packit 8f70b4
              pc->hour = text_int.value / 100;
Packit 8f70b4
              pc->minutes = text_int.value % 100;
Packit 8f70b4
            }
Packit 8f70b4
          pc->seconds.tv_sec = 0;
Packit 8f70b4
          pc->seconds.tv_nsec = 0;
Packit 8f70b4
          pc->meridian = MER24;
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1).  Return true
Packit 8f70b4
   if successful, false if an overflow occurred.  */
Packit 8f70b4
static bool
Packit 8f70b4
apply_relative_time (parser_control *pc, relative_time rel, int factor)
Packit 8f70b4
{
Packit 8f70b4
  if (factor < 0
Packit 8f70b4
      ? (INT_SUBTRACT_WRAPV (pc->rel.ns, rel.ns, &pc->rel.ns)
Packit 8f70b4
         | INT_SUBTRACT_WRAPV (pc->rel.seconds, rel.seconds, &pc->rel.seconds)
Packit 8f70b4
         | INT_SUBTRACT_WRAPV (pc->rel.minutes, rel.minutes, &pc->rel.minutes)
Packit 8f70b4
         | INT_SUBTRACT_WRAPV (pc->rel.hour, rel.hour, &pc->rel.hour)
Packit 8f70b4
         | INT_SUBTRACT_WRAPV (pc->rel.day, rel.day, &pc->rel.day)
Packit 8f70b4
         | INT_SUBTRACT_WRAPV (pc->rel.month, rel.month, &pc->rel.month)
Packit 8f70b4
         | INT_SUBTRACT_WRAPV (pc->rel.year, rel.year, &pc->rel.year))
Packit 8f70b4
      : (INT_ADD_WRAPV (pc->rel.ns, rel.ns, &pc->rel.ns)
Packit 8f70b4
         | INT_ADD_WRAPV (pc->rel.seconds, rel.seconds, &pc->rel.seconds)
Packit 8f70b4
         | INT_ADD_WRAPV (pc->rel.minutes, rel.minutes, &pc->rel.minutes)
Packit 8f70b4
         | INT_ADD_WRAPV (pc->rel.hour, rel.hour, &pc->rel.hour)
Packit 8f70b4
         | INT_ADD_WRAPV (pc->rel.day, rel.day, &pc->rel.day)
Packit 8f70b4
         | INT_ADD_WRAPV (pc->rel.month, rel.month, &pc->rel.month)
Packit 8f70b4
         | INT_ADD_WRAPV (pc->rel.year, rel.year, &pc->rel.year)))
Packit 8f70b4
    return false;
Packit 8f70b4
  pc->rels_seen = true;
Packit 8f70b4
  return true;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments.  */
Packit 8f70b4
static void
Packit 8f70b4
set_hhmmss (parser_control *pc, intmax_t hour, intmax_t minutes,
Packit 8f70b4
            time_t sec, int nsec)
Packit 8f70b4
{
Packit 8f70b4
  pc->hour = hour;
Packit 8f70b4
  pc->minutes = minutes;
Packit 8f70b4
  pc->seconds.tv_sec = sec;
Packit 8f70b4
  pc->seconds.tv_nsec = nsec;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Return a textual representation of the day ordinal/number values
Packit 8f70b4
   in the parser_control struct (e.g., "last wed", "this tues", "thu").  */
Packit 8f70b4
static const char *
Packit 8f70b4
str_days (parser_control *pc, char *buffer, int n)
Packit 8f70b4
{
Packit 8f70b4
  /* TODO: use relative_time_table for reverse lookup.  */
Packit 8f70b4
  static char const ordinal_values[][11] = {
Packit 8f70b4
     "last",
Packit 8f70b4
     "this",
Packit 8f70b4
     "next/first",
Packit 8f70b4
     "(SECOND)", /* SECOND is commented out in relative_time_table.  */
Packit 8f70b4
     "third",
Packit 8f70b4
     "fourth",
Packit 8f70b4
     "fifth",
Packit 8f70b4
     "sixth",
Packit 8f70b4
     "seventh",
Packit 8f70b4
     "eight",
Packit 8f70b4
     "ninth",
Packit 8f70b4
     "tenth",
Packit 8f70b4
     "eleventh",
Packit 8f70b4
     "twelfth"
Packit 8f70b4
  };
Packit 8f70b4
Packit 8f70b4
  static char const days_values[][4] = {
Packit 8f70b4
     "Sun",
Packit 8f70b4
     "Mon",
Packit 8f70b4
     "Tue",
Packit 8f70b4
     "Wed",
Packit 8f70b4
     "Thu",
Packit 8f70b4
     "Fri",
Packit 8f70b4
     "Sat"
Packit 8f70b4
  };
Packit 8f70b4
Packit 8f70b4
  int len;
Packit 8f70b4
Packit 8f70b4
  /* Don't add an ordinal prefix if the user didn't specify it
Packit 8f70b4
     (e.g., "this wed" vs "wed").  */
Packit 8f70b4
  if (pc->debug_ordinal_day_seen)
Packit 8f70b4
    {
Packit 8f70b4
      /* Use word description if possible (e.g., -1 = last, 3 = third).  */
Packit 8f70b4
      len = (-1 <= pc->day_ordinal && pc->day_ordinal <= 12
Packit 8f70b4
             ? snprintf (buffer, n, "%s", ordinal_values[pc->day_ordinal + 1])
Packit 8f70b4
             : snprintf (buffer, n, "%"PRIdMAX, pc->day_ordinal));
Packit 8f70b4
    }
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      buffer[0] = '\0';
Packit 8f70b4
      len = 0;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  /* Add the day name */
Packit 8f70b4
  if (0 <= pc->day_number && pc->day_number <= 6 && 0 <= len && len < n)
Packit 8f70b4
    snprintf (buffer + len, n - len, &" %s"[len == 0],
Packit 8f70b4
              days_values[pc->day_number]);
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      /* invalid day_number value - should never happen */
Packit 8f70b4
    }
Packit 8f70b4
  return buffer;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Convert a time zone to its string representation.  */
Packit 8f70b4
Packit 8f70b4
enum { TIME_ZONE_BUFSIZE = INT_STRLEN_BOUND (intmax_t) + sizeof ":MM:SS" } ;
Packit 8f70b4
Packit 8f70b4
static char const *
Packit 8f70b4
time_zone_str (int time_zone, char time_zone_buf[TIME_ZONE_BUFSIZE])
Packit 8f70b4
{
Packit 8f70b4
  char *p = time_zone_buf;
Packit 8f70b4
  char sign = time_zone < 0 ? '-' : '+';
Packit 8f70b4
  int hour = abs (time_zone / (60 * 60));
Packit 8f70b4
  p += sprintf (time_zone_buf, "%c%02d", sign, hour);
Packit 8f70b4
  int offset_from_hour = abs (time_zone % (60 * 60));
Packit 8f70b4
  if (offset_from_hour != 0)
Packit 8f70b4
    {
Packit 8f70b4
      int mm = offset_from_hour / 60;
Packit 8f70b4
      int ss = offset_from_hour % 60;
Packit 8f70b4
      *p++ = ':';
Packit 8f70b4
      *p++ = '0' + mm / 10;
Packit 8f70b4
      *p++ = '0' + mm % 10;
Packit 8f70b4
      if (ss)
Packit 8f70b4
        {
Packit 8f70b4
          *p++ = ':';
Packit 8f70b4
          *p++ = '0' + ss / 10;
Packit 8f70b4
          *p++ = '0' + ss % 10;
Packit 8f70b4
        }
Packit 8f70b4
      *p = '\0';
Packit 8f70b4
    }
Packit 8f70b4
  return time_zone_buf;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* debugging: print the current time in the parser_control structure.
Packit 8f70b4
   The parser will increment "*_seen" members for those which were parsed.
Packit 8f70b4
   This function will print only newly seen parts.  */
Packit 8f70b4
static void
Packit 8f70b4
debug_print_current_time (char const *item, parser_control *pc)
Packit 8f70b4
{
Packit 8f70b4
  bool space = false;
Packit 8f70b4
Packit 8f70b4
  if (!pc->parse_datetime_debug)
Packit 8f70b4
    return;
Packit 8f70b4
Packit 8f70b4
  /* no newline, more items printed below */
Packit 8f70b4
  dbg_printf (_("parsed %s part: "), item);
Packit 8f70b4
Packit 8f70b4
  if (pc->dates_seen && !pc->debug_dates_seen)
Packit 8f70b4
    {
Packit 8f70b4
      /*TODO: use pc->year.negative?  */
Packit 8f70b4
      fprintf (stderr, "(Y-M-D) %04"PRIdMAX"-%02"PRIdMAX"-%02"PRIdMAX,
Packit 8f70b4
              pc->year.value, pc->month, pc->day);
Packit 8f70b4
      pc->debug_dates_seen = true;
Packit 8f70b4
      space = true;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (pc->year_seen != pc->debug_year_seen)
Packit 8f70b4
    {
Packit 8f70b4
      if (space)
Packit 8f70b4
        fputc (' ', stderr);
Packit 8f70b4
      fprintf (stderr, _("year: %04"PRIdMAX), pc->year.value);
Packit 8f70b4
Packit 8f70b4
      pc->debug_year_seen = pc->year_seen;
Packit 8f70b4
      space = true;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (pc->times_seen && !pc->debug_times_seen)
Packit 8f70b4
    {
Packit 8f70b4
      intmax_t sec = pc->seconds.tv_sec;
Packit 8f70b4
      fprintf (stderr, &" %02"PRIdMAX":%02"PRIdMAX":%02"PRIdMAX[!space],
Packit 8f70b4
               pc->hour, pc->minutes, sec);
Packit 8f70b4
      if (pc->seconds.tv_nsec != 0)
Packit 8f70b4
        {
Packit 8f70b4
          int nsec = pc->seconds.tv_nsec;
Packit 8f70b4
          fprintf (stderr, ".%09d", nsec);
Packit 8f70b4
        }
Packit 8f70b4
      if (pc->meridian == MERpm)
Packit 8f70b4
        fputs ("pm", stderr);
Packit 8f70b4
Packit 8f70b4
      pc->debug_times_seen = true;
Packit 8f70b4
      space = true;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (pc->days_seen && !pc->debug_days_seen)
Packit 8f70b4
    {
Packit 8f70b4
      if (space)
Packit 8f70b4
        fputc (' ', stderr);
Packit 8f70b4
      char tmp[DBGBUFSIZE];
Packit 8f70b4
      fprintf (stderr, _("%s (day ordinal=%"PRIdMAX" number=%d)"),
Packit 8f70b4
               str_days (pc, tmp, sizeof tmp),
Packit 8f70b4
               pc->day_ordinal, pc->day_number);
Packit 8f70b4
      pc->debug_days_seen = true;
Packit 8f70b4
      space = true;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  /* local zone strings only change the DST settings,
Packit 8f70b4
     not the timezone value.  If seen, inform about the DST.  */
Packit 8f70b4
  if (pc->local_zones_seen && !pc->debug_local_zones_seen)
Packit 8f70b4
    {
Packit 8f70b4
      fprintf (stderr, &" isdst=%d%s"[!space],
Packit 8f70b4
               pc->local_isdst, pc->dsts_seen ? " DST" : "");
Packit 8f70b4
      pc->debug_local_zones_seen = true;
Packit 8f70b4
      space = true;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (pc->zones_seen && !pc->debug_zones_seen)
Packit 8f70b4
    {
Packit 8f70b4
      char time_zone_buf[TIME_ZONE_BUFSIZE];
Packit 8f70b4
      fprintf (stderr, &" UTC%s"[!space],
Packit 8f70b4
               time_zone_str (pc->time_zone, time_zone_buf));
Packit 8f70b4
      pc->debug_zones_seen = true;
Packit 8f70b4
      space = true;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (pc->timespec_seen)
Packit 8f70b4
    {
Packit 8f70b4
      intmax_t sec = pc->seconds.tv_sec;
Packit 8f70b4
      if (space)
Packit 8f70b4
        fputc (' ', stderr);
Packit 8f70b4
      fprintf (stderr, _("number of seconds: %"PRIdMAX), sec);
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  fputc ('\n', stderr);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Debugging: print the current relative values.  */
Packit 8f70b4
Packit 8f70b4
static bool
Packit 8f70b4
print_rel_part (bool space, intmax_t val, char const *name)
Packit 8f70b4
{
Packit 8f70b4
  if (val == 0)
Packit 8f70b4
    return space;
Packit 8f70b4
  fprintf (stderr, &" %+"PRIdMAX" %s"[!space], val, name);
Packit 8f70b4
  return true;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static void
Packit 8f70b4
debug_print_relative_time (char const *item, parser_control const *pc)
Packit 8f70b4
{
Packit 8f70b4
  bool space = false;
Packit 8f70b4
Packit 8f70b4
  if (!pc->parse_datetime_debug)
Packit 8f70b4
    return;
Packit 8f70b4
Packit 8f70b4
  /* no newline, more items printed below */
Packit 8f70b4
  dbg_printf (_("parsed %s part: "), item);
Packit 8f70b4
Packit 8f70b4
  if (pc->rel.year == 0 && pc->rel.month == 0 && pc->rel.day == 0
Packit 8f70b4
      && pc->rel.hour == 0 && pc->rel.minutes == 0 && pc->rel.seconds == 0
Packit 8f70b4
      && pc->rel.ns == 0)
Packit 8f70b4
    {
Packit 8f70b4
      /* Special case: relative time of this/today/now */
Packit 8f70b4
      fputs (_("today/this/now\n"), stderr);
Packit 8f70b4
      return;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  space = print_rel_part (space, pc->rel.year, "year(s)");
Packit 8f70b4
  space = print_rel_part (space, pc->rel.month, "month(s)");
Packit 8f70b4
  space = print_rel_part (space, pc->rel.day, "day(s)");
Packit 8f70b4
  space = print_rel_part (space, pc->rel.hour, "hour(s)");
Packit 8f70b4
  space = print_rel_part (space, pc->rel.minutes, "minutes");
Packit 8f70b4
  space = print_rel_part (space, pc->rel.seconds, "seconds");
Packit 8f70b4
  print_rel_part (space, pc->rel.ns, "nanoseconds");
Packit 8f70b4
Packit 8f70b4
  fputc ('\n', stderr);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
Packit 8f70b4
Packit 8f70b4
%}
Packit 8f70b4
Packit 8f70b4
/* We want a reentrant parser, even if the TZ manipulation and the calls to
Packit 8f70b4
   localtime and gmtime are not reentrant.  */
Packit 8f70b4
%pure-parser
Packit 8f70b4
%parse-param { parser_control *pc }
Packit 8f70b4
%lex-param { parser_control *pc }
Packit 8f70b4
Packit 8f70b4
/* This grammar has 31 shift/reduce conflicts.  */
Packit 8f70b4
%expect 31
Packit 8f70b4
Packit 8f70b4
%union
Packit 8f70b4
{
Packit 8f70b4
  intmax_t intval;
Packit 8f70b4
  textint textintval;
Packit 8f70b4
  struct timespec timespec;
Packit 8f70b4
  relative_time rel;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
%token <intval> tAGO
Packit 8f70b4
%token tDST
Packit 8f70b4
Packit 8f70b4
%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
Packit 8f70b4
%token <intval> tDAY_UNIT tDAY_SHIFT
Packit 8f70b4
Packit 8f70b4
%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
Packit 8f70b4
%token <intval> tMONTH tORDINAL tZONE
Packit 8f70b4
Packit 8f70b4
%token <textintval> tSNUMBER tUNUMBER
Packit 8f70b4
%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
Packit 8f70b4
Packit 8f70b4
%type <intval> o_colon_minutes
Packit 8f70b4
%type <timespec> seconds signed_seconds unsigned_seconds
Packit 8f70b4
Packit 8f70b4
%type <rel> relunit relunit_snumber dayshift
Packit 8f70b4
Packit 8f70b4
%%
Packit 8f70b4
Packit 8f70b4
spec:
Packit 8f70b4
    timespec
Packit 8f70b4
  | items
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
timespec:
Packit 8f70b4
    '@' seconds
Packit 8f70b4
      {
Packit 8f70b4
        pc->seconds = $2;
Packit 8f70b4
        pc->timespec_seen = true;
Packit 8f70b4
        debug_print_current_time (_("number of seconds"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
items:
Packit 8f70b4
    /* empty */
Packit 8f70b4
  | items item
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
item:
Packit 8f70b4
    datetime
Packit 8f70b4
      {
Packit 8f70b4
        pc->times_seen++; pc->dates_seen++;
Packit 8f70b4
        debug_print_current_time (_("datetime"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | time
Packit 8f70b4
      {
Packit 8f70b4
        pc->times_seen++;
Packit 8f70b4
        debug_print_current_time (_("time"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | local_zone
Packit 8f70b4
      {
Packit 8f70b4
        pc->local_zones_seen++;
Packit 8f70b4
        debug_print_current_time (_("local_zone"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | zone
Packit 8f70b4
      {
Packit 8f70b4
        pc->zones_seen++;
Packit 8f70b4
        debug_print_current_time (_("zone"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | date
Packit 8f70b4
      {
Packit 8f70b4
        pc->dates_seen++;
Packit 8f70b4
        debug_print_current_time (_("date"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | day
Packit 8f70b4
      {
Packit 8f70b4
        pc->days_seen++;
Packit 8f70b4
        debug_print_current_time (_("day"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | rel
Packit 8f70b4
      {
Packit 8f70b4
        debug_print_relative_time (_("relative"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | number
Packit 8f70b4
      {
Packit 8f70b4
        debug_print_current_time (_("number"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | hybrid
Packit 8f70b4
      {
Packit 8f70b4
        debug_print_relative_time (_("hybrid"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
datetime:
Packit 8f70b4
    iso_8601_datetime
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
iso_8601_datetime:
Packit 8f70b4
    iso_8601_date 'T' iso_8601_time
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
time:
Packit 8f70b4
    tUNUMBER tMERIDIAN
Packit 8f70b4
      {
Packit 8f70b4
        set_hhmmss (pc, $1.value, 0, 0, 0);
Packit 8f70b4
        pc->meridian = $2;
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER ':' tUNUMBER tMERIDIAN
Packit 8f70b4
      {
Packit 8f70b4
        set_hhmmss (pc, $1.value, $3.value, 0, 0);
Packit 8f70b4
        pc->meridian = $4;
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
Packit 8f70b4
      {
Packit 8f70b4
        set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
Packit 8f70b4
        pc->meridian = $6;
Packit 8f70b4
      }
Packit 8f70b4
  | iso_8601_time
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
iso_8601_time:
Packit 8f70b4
    tUNUMBER zone_offset
Packit 8f70b4
      {
Packit 8f70b4
        set_hhmmss (pc, $1.value, 0, 0, 0);
Packit 8f70b4
        pc->meridian = MER24;
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER ':' tUNUMBER o_zone_offset
Packit 8f70b4
      {
Packit 8f70b4
        set_hhmmss (pc, $1.value, $3.value, 0, 0);
Packit 8f70b4
        pc->meridian = MER24;
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset
Packit 8f70b4
      {
Packit 8f70b4
        set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
Packit 8f70b4
        pc->meridian = MER24;
Packit 8f70b4
      }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
o_zone_offset:
Packit 8f70b4
  /* empty */
Packit 8f70b4
  | zone_offset
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
zone_offset:
Packit 8f70b4
    tSNUMBER o_colon_minutes
Packit 8f70b4
      {
Packit 8f70b4
        pc->zones_seen++;
Packit 8f70b4
        if (! time_zone_hhmm (pc, $1, $2)) YYABORT;
Packit 8f70b4
      }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
/* Local zone strings affect only the DST setting, and take effect
Packit 8f70b4
   only if the current TZ setting is relevant.
Packit 8f70b4
Packit 8f70b4
   Example 1:
Packit 8f70b4
   'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
Packit 8f70b4
        TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
Packit 8f70b4
Packit 8f70b4
   Example 2:
Packit 8f70b4
   'EEST' is parsed as tDAYZONE:
Packit 8f70b4
        TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
Packit 8f70b4
Packit 8f70b4
   This is implemented by probing the next three calendar quarters
Packit 8f70b4
   of the effective timezone and looking for DST changes -
Packit 8f70b4
   if found, the timezone name (EEST) is inserted into
Packit 8f70b4
   the lexical lookup table with type tLOCAL_ZONE.
Packit 8f70b4
   (Search for 'quarter' comment in  'parse_datetime2'.)
Packit 8f70b4
*/
Packit 8f70b4
local_zone:
Packit 8f70b4
    tLOCAL_ZONE
Packit 8f70b4
      { pc->local_isdst = $1; }
Packit 8f70b4
  | tLOCAL_ZONE tDST
Packit 8f70b4
      {
Packit 8f70b4
        pc->local_isdst = 1;
Packit 8f70b4
        pc->dsts_seen++;
Packit 8f70b4
      }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
/* Note 'T' is a special case, as it is used as the separator in ISO
Packit 8f70b4
   8601 date and time of day representation.  */
Packit 8f70b4
zone:
Packit 8f70b4
    tZONE
Packit 8f70b4
      { pc->time_zone = $1; }
Packit 8f70b4
  | 'T'
Packit 8f70b4
      { pc->time_zone = HOUR (7); }
Packit 8f70b4
  | tZONE relunit_snumber
Packit 8f70b4
      { pc->time_zone = $1;
Packit 8f70b4
        if (! apply_relative_time (pc, $2, 1)) YYABORT;
Packit 8f70b4
        debug_print_relative_time (_("relative"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | 'T' relunit_snumber
Packit 8f70b4
      { pc->time_zone = HOUR (7);
Packit 8f70b4
        if (! apply_relative_time (pc, $2, 1)) YYABORT;
Packit 8f70b4
        debug_print_relative_time (_("relative"), pc);
Packit 8f70b4
      }
Packit 8f70b4
  | tZONE tSNUMBER o_colon_minutes
Packit 8f70b4
      { if (! time_zone_hhmm (pc, $2, $3)) YYABORT;
Packit 8f70b4
        if (INT_ADD_WRAPV (pc->time_zone, $1, &pc->time_zone)) YYABORT; }
Packit 8f70b4
  | tDAYZONE
Packit 8f70b4
      { pc->time_zone = $1 + 60 * 60; }
Packit 8f70b4
  | tZONE tDST
Packit 8f70b4
      { pc->time_zone = $1 + 60 * 60; }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
day:
Packit 8f70b4
    tDAY
Packit 8f70b4
      {
Packit 8f70b4
        pc->day_ordinal = 0;
Packit 8f70b4
        pc->day_number = $1;
Packit 8f70b4
      }
Packit 8f70b4
  | tDAY ','
Packit 8f70b4
      {
Packit 8f70b4
        pc->day_ordinal = 0;
Packit 8f70b4
        pc->day_number = $1;
Packit 8f70b4
      }
Packit 8f70b4
  | tORDINAL tDAY
Packit 8f70b4
      {
Packit 8f70b4
        pc->day_ordinal = $1;
Packit 8f70b4
        pc->day_number = $2;
Packit 8f70b4
        pc->debug_ordinal_day_seen = true;
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER tDAY
Packit 8f70b4
      {
Packit 8f70b4
        pc->day_ordinal = $1.value;
Packit 8f70b4
        pc->day_number = $2;
Packit 8f70b4
        pc->debug_ordinal_day_seen = true;
Packit 8f70b4
      }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
date:
Packit 8f70b4
    tUNUMBER '/' tUNUMBER
Packit 8f70b4
      {
Packit 8f70b4
        pc->month = $1.value;
Packit 8f70b4
        pc->day = $3.value;
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER '/' tUNUMBER '/' tUNUMBER
Packit 8f70b4
      {
Packit 8f70b4
        /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
Packit 8f70b4
           otherwise as MM/DD/YY.
Packit 8f70b4
           The goal in recognizing YYYY/MM/DD is solely to support legacy
Packit 8f70b4
           machine-generated dates like those in an RCS log listing.  If
Packit 8f70b4
           you want portability, use the ISO 8601 format.  */
Packit 8f70b4
        if (4 <= $1.digits)
Packit 8f70b4
          {
Packit 8f70b4
            if (pc->parse_datetime_debug)
Packit 8f70b4
              {
Packit 8f70b4
                intmax_t digits = $1.digits;
Packit 8f70b4
                dbg_printf (_("warning: value %"PRIdMAX" has %"PRIdMAX" digits. "
Packit 8f70b4
                              "Assuming YYYY/MM/DD\n"),
Packit 8f70b4
                            $1.value, digits);
Packit 8f70b4
              }
Packit 8f70b4
Packit 8f70b4
            pc->year = $1;
Packit 8f70b4
            pc->month = $3.value;
Packit 8f70b4
            pc->day = $5.value;
Packit 8f70b4
          }
Packit 8f70b4
        else
Packit 8f70b4
          {
Packit 8f70b4
            if (pc->parse_datetime_debug)
Packit 8f70b4
              dbg_printf (_("warning: value %"PRIdMAX" has less than 4 digits. "
Packit 8f70b4
                            "Assuming MM/DD/YY[YY]\n"),
Packit 8f70b4
                          $1.value);
Packit 8f70b4
Packit 8f70b4
            pc->month = $1.value;
Packit 8f70b4
            pc->day = $3.value;
Packit 8f70b4
            pc->year = $5;
Packit 8f70b4
          }
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER tMONTH tSNUMBER
Packit 8f70b4
      {
Packit 8f70b4
        /* E.g., 17-JUN-1992.  */
Packit 8f70b4
        pc->day = $1.value;
Packit 8f70b4
        pc->month = $2;
Packit 8f70b4
        if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->year.value)) YYABORT;
Packit 8f70b4
        pc->year.digits = $3.digits;
Packit 8f70b4
      }
Packit 8f70b4
  | tMONTH tSNUMBER tSNUMBER
Packit 8f70b4
      {
Packit 8f70b4
        /* E.g., JUN-17-1992.  */
Packit 8f70b4
        pc->month = $1;
Packit 8f70b4
        if (INT_SUBTRACT_WRAPV (0, $2.value, &pc->day)) YYABORT;
Packit 8f70b4
        if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->year.value)) YYABORT;
Packit 8f70b4
        pc->year.digits = $3.digits;
Packit 8f70b4
      }
Packit 8f70b4
  | tMONTH tUNUMBER
Packit 8f70b4
      {
Packit 8f70b4
        pc->month = $1;
Packit 8f70b4
        pc->day = $2.value;
Packit 8f70b4
      }
Packit 8f70b4
  | tMONTH tUNUMBER ',' tUNUMBER
Packit 8f70b4
      {
Packit 8f70b4
        pc->month = $1;
Packit 8f70b4
        pc->day = $2.value;
Packit 8f70b4
        pc->year = $4;
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER tMONTH
Packit 8f70b4
      {
Packit 8f70b4
        pc->day = $1.value;
Packit 8f70b4
        pc->month = $2;
Packit 8f70b4
      }
Packit 8f70b4
  | tUNUMBER tMONTH tUNUMBER
Packit 8f70b4
      {
Packit 8f70b4
        pc->day = $1.value;
Packit 8f70b4
        pc->month = $2;
Packit 8f70b4
        pc->year = $3;
Packit 8f70b4
      }
Packit 8f70b4
  | iso_8601_date
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
iso_8601_date:
Packit 8f70b4
    tUNUMBER tSNUMBER tSNUMBER
Packit 8f70b4
      {
Packit 8f70b4
        /* ISO 8601 format.  YYYY-MM-DD.  */
Packit 8f70b4
        pc->year = $1;
Packit 8f70b4
        if (INT_SUBTRACT_WRAPV (0, $2.value, &pc->month)) YYABORT;
Packit 8f70b4
        if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->day)) YYABORT;
Packit 8f70b4
      }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
rel:
Packit 8f70b4
    relunit tAGO
Packit 8f70b4
      { if (! apply_relative_time (pc, $1, $2)) YYABORT; }
Packit 8f70b4
  | relunit
Packit 8f70b4
      { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
Packit 8f70b4
  | dayshift
Packit 8f70b4
      { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
relunit:
Packit 8f70b4
    tORDINAL tYEAR_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.year = $1; }
Packit 8f70b4
  | tUNUMBER tYEAR_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
Packit 8f70b4
  | tYEAR_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.year = 1; }
Packit 8f70b4
  | tORDINAL tMONTH_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.month = $1; }
Packit 8f70b4
  | tUNUMBER tMONTH_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
Packit 8f70b4
  | tMONTH_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.month = 1; }
Packit 8f70b4
  | tORDINAL tDAY_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0;
Packit 8f70b4
        if (INT_MULTIPLY_WRAPV ($1, $2, &$$.day)) YYABORT; }
Packit 8f70b4
  | tUNUMBER tDAY_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0;
Packit 8f70b4
        if (INT_MULTIPLY_WRAPV ($1.value, $2, &$$.day)) YYABORT; }
Packit 8f70b4
  | tDAY_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.day = $1; }
Packit 8f70b4
  | tORDINAL tHOUR_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.hour = $1; }
Packit 8f70b4
  | tUNUMBER tHOUR_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
Packit 8f70b4
  | tHOUR_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.hour = 1; }
Packit 8f70b4
  | tORDINAL tMINUTE_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
Packit 8f70b4
  | tUNUMBER tMINUTE_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
Packit 8f70b4
  | tMINUTE_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
Packit 8f70b4
  | tORDINAL tSEC_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
Packit 8f70b4
  | tUNUMBER tSEC_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
Packit 8f70b4
  | tSDECIMAL_NUMBER tSEC_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
Packit 8f70b4
  | tUDECIMAL_NUMBER tSEC_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
Packit 8f70b4
  | tSEC_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
Packit 8f70b4
  | relunit_snumber
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
relunit_snumber:
Packit 8f70b4
    tSNUMBER tYEAR_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
Packit 8f70b4
  | tSNUMBER tMONTH_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
Packit 8f70b4
  | tSNUMBER tDAY_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0;
Packit 8f70b4
        if (INT_MULTIPLY_WRAPV ($1.value, $2, &$$.day)) YYABORT; }
Packit 8f70b4
  | tSNUMBER tHOUR_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
Packit 8f70b4
  | tSNUMBER tMINUTE_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
Packit 8f70b4
  | tSNUMBER tSEC_UNIT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
dayshift:
Packit 8f70b4
    tDAY_SHIFT
Packit 8f70b4
      { $$ = RELATIVE_TIME_0; $$.day = $1; }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
seconds: signed_seconds | unsigned_seconds;
Packit 8f70b4
Packit 8f70b4
signed_seconds:
Packit 8f70b4
    tSDECIMAL_NUMBER
Packit 8f70b4
  | tSNUMBER
Packit 8f70b4
      { if (time_overflow ($1.value)) YYABORT;
Packit 8f70b4
        $$.tv_sec = $1.value; $$.tv_nsec = 0; }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
unsigned_seconds:
Packit 8f70b4
    tUDECIMAL_NUMBER
Packit 8f70b4
  | tUNUMBER
Packit 8f70b4
      { if (time_overflow ($1.value)) YYABORT;
Packit 8f70b4
        $$.tv_sec = $1.value; $$.tv_nsec = 0; }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
number:
Packit 8f70b4
    tUNUMBER
Packit 8f70b4
      { digits_to_date_time (pc, $1); }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
hybrid:
Packit 8f70b4
    tUNUMBER relunit_snumber
Packit 8f70b4
      {
Packit 8f70b4
        /* Hybrid all-digit and relative offset, so that we accept e.g.,
Packit 8f70b4
           "YYYYMMDD +N days" as well as "YYYYMMDD N days".  */
Packit 8f70b4
        digits_to_date_time (pc, $1);
Packit 8f70b4
        if (! apply_relative_time (pc, $2, 1)) YYABORT;
Packit 8f70b4
      }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
o_colon_minutes:
Packit 8f70b4
    /* empty */
Packit 8f70b4
      { $$ = -1; }
Packit 8f70b4
  | ':' tUNUMBER
Packit 8f70b4
      { $$ = $2.value; }
Packit 8f70b4
  ;
Packit 8f70b4
Packit 8f70b4
%%
Packit 8f70b4
Packit 8f70b4
static table const meridian_table[] =
Packit 8f70b4
{
Packit 8f70b4
  { "AM",   tMERIDIAN, MERam },
Packit 8f70b4
  { "A.M.", tMERIDIAN, MERam },
Packit 8f70b4
  { "PM",   tMERIDIAN, MERpm },
Packit 8f70b4
  { "P.M.", tMERIDIAN, MERpm },
Packit 8f70b4
  { NULL, 0, 0 }
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
static table const dst_table[] =
Packit 8f70b4
{
Packit 8f70b4
  { "DST", tDST, 0 }
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
static table const month_and_day_table[] =
Packit 8f70b4
{
Packit 8f70b4
  { "JANUARY",  tMONTH,  1 },
Packit 8f70b4
  { "FEBRUARY", tMONTH,  2 },
Packit 8f70b4
  { "MARCH",    tMONTH,  3 },
Packit 8f70b4
  { "APRIL",    tMONTH,  4 },
Packit 8f70b4
  { "MAY",      tMONTH,  5 },
Packit 8f70b4
  { "JUNE",     tMONTH,  6 },
Packit 8f70b4
  { "JULY",     tMONTH,  7 },
Packit 8f70b4
  { "AUGUST",   tMONTH,  8 },
Packit 8f70b4
  { "SEPTEMBER",tMONTH,  9 },
Packit 8f70b4
  { "SEPT",     tMONTH,  9 },
Packit 8f70b4
  { "OCTOBER",  tMONTH, 10 },
Packit 8f70b4
  { "NOVEMBER", tMONTH, 11 },
Packit 8f70b4
  { "DECEMBER", tMONTH, 12 },
Packit 8f70b4
  { "SUNDAY",   tDAY,    0 },
Packit 8f70b4
  { "MONDAY",   tDAY,    1 },
Packit 8f70b4
  { "TUESDAY",  tDAY,    2 },
Packit 8f70b4
  { "TUES",     tDAY,    2 },
Packit 8f70b4
  { "WEDNESDAY",tDAY,    3 },
Packit 8f70b4
  { "WEDNES",   tDAY,    3 },
Packit 8f70b4
  { "THURSDAY", tDAY,    4 },
Packit 8f70b4
  { "THUR",     tDAY,    4 },
Packit 8f70b4
  { "THURS",    tDAY,    4 },
Packit 8f70b4
  { "FRIDAY",   tDAY,    5 },
Packit 8f70b4
  { "SATURDAY", tDAY,    6 },
Packit 8f70b4
  { NULL, 0, 0 }
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
static table const time_units_table[] =
Packit 8f70b4
{
Packit 8f70b4
  { "YEAR",     tYEAR_UNIT,      1 },
Packit 8f70b4
  { "MONTH",    tMONTH_UNIT,     1 },
Packit 8f70b4
  { "FORTNIGHT",tDAY_UNIT,      14 },
Packit 8f70b4
  { "WEEK",     tDAY_UNIT,       7 },
Packit 8f70b4
  { "DAY",      tDAY_UNIT,       1 },
Packit 8f70b4
  { "HOUR",     tHOUR_UNIT,      1 },
Packit 8f70b4
  { "MINUTE",   tMINUTE_UNIT,    1 },
Packit 8f70b4
  { "MIN",      tMINUTE_UNIT,    1 },
Packit 8f70b4
  { "SECOND",   tSEC_UNIT,       1 },
Packit 8f70b4
  { "SEC",      tSEC_UNIT,       1 },
Packit 8f70b4
  { NULL, 0, 0 }
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
/* Assorted relative-time words.  */
Packit 8f70b4
static table const relative_time_table[] =
Packit 8f70b4
{
Packit 8f70b4
  { "TOMORROW", tDAY_SHIFT,      1 },
Packit 8f70b4
  { "YESTERDAY",tDAY_SHIFT,     -1 },
Packit 8f70b4
  { "TODAY",    tDAY_SHIFT,      0 },
Packit 8f70b4
  { "NOW",      tDAY_SHIFT,      0 },
Packit 8f70b4
  { "LAST",     tORDINAL,       -1 },
Packit 8f70b4
  { "THIS",     tORDINAL,        0 },
Packit 8f70b4
  { "NEXT",     tORDINAL,        1 },
Packit 8f70b4
  { "FIRST",    tORDINAL,        1 },
Packit 8f70b4
/*{ "SECOND",   tORDINAL,        2 }, */
Packit 8f70b4
  { "THIRD",    tORDINAL,        3 },
Packit 8f70b4
  { "FOURTH",   tORDINAL,        4 },
Packit 8f70b4
  { "FIFTH",    tORDINAL,        5 },
Packit 8f70b4
  { "SIXTH",    tORDINAL,        6 },
Packit 8f70b4
  { "SEVENTH",  tORDINAL,        7 },
Packit 8f70b4
  { "EIGHTH",   tORDINAL,        8 },
Packit 8f70b4
  { "NINTH",    tORDINAL,        9 },
Packit 8f70b4
  { "TENTH",    tORDINAL,       10 },
Packit 8f70b4
  { "ELEVENTH", tORDINAL,       11 },
Packit 8f70b4
  { "TWELFTH",  tORDINAL,       12 },
Packit 8f70b4
  { "AGO",      tAGO,           -1 },
Packit 8f70b4
  { "HENCE",    tAGO,            1 },
Packit 8f70b4
  { NULL, 0, 0 }
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
/* The universal time zone table.  These labels can be used even for
Packit 8f70b4
   timestamps that would not otherwise be valid, e.g., GMT timestamps
Packit 8f70b4
   oin London during summer.  */
Packit 8f70b4
static table const universal_time_zone_table[] =
Packit 8f70b4
{
Packit 8f70b4
  { "GMT",      tZONE,     HOUR ( 0) }, /* Greenwich Mean */
Packit 8f70b4
  { "UT",       tZONE,     HOUR ( 0) }, /* Universal (Coordinated) */
Packit 8f70b4
  { "UTC",      tZONE,     HOUR ( 0) },
Packit 8f70b4
  { NULL, 0, 0 }
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
/* The time zone table.  This table is necessarily incomplete, as time
Packit 8f70b4
   zone abbreviations are ambiguous; e.g., Australians interpret "EST"
Packit 8f70b4
   as Eastern time in Australia, not as US Eastern Standard Time.
Packit 8f70b4
   You cannot rely on parse_datetime to handle arbitrary time zone
Packit 8f70b4
   abbreviations; use numeric abbreviations like "-0500" instead.  */
Packit 8f70b4
static table const time_zone_table[] =
Packit 8f70b4
{
Packit 8f70b4
  { "WET",      tZONE,     HOUR ( 0) }, /* Western European */
Packit 8f70b4
  { "WEST",     tDAYZONE,  HOUR ( 0) }, /* Western European Summer */
Packit 8f70b4
  { "BST",      tDAYZONE,  HOUR ( 0) }, /* British Summer */
Packit 8f70b4
  { "ART",      tZONE,    -HOUR ( 3) }, /* Argentina */
Packit 8f70b4
  { "BRT",      tZONE,    -HOUR ( 3) }, /* Brazil */
Packit 8f70b4
  { "BRST",     tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
Packit 8f70b4
  { "NST",      tZONE,   -(HOUR ( 3) + 30 * 60) }, /* Newfoundland Standard */
Packit 8f70b4
  { "NDT",      tDAYZONE,-(HOUR ( 3) + 30 * 60) }, /* Newfoundland Daylight */
Packit 8f70b4
  { "AST",      tZONE,    -HOUR ( 4) }, /* Atlantic Standard */
Packit 8f70b4
  { "ADT",      tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
Packit 8f70b4
  { "CLT",      tZONE,    -HOUR ( 4) }, /* Chile */
Packit 8f70b4
  { "CLST",     tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
Packit 8f70b4
  { "EST",      tZONE,    -HOUR ( 5) }, /* Eastern Standard */
Packit 8f70b4
  { "EDT",      tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
Packit 8f70b4
  { "CST",      tZONE,    -HOUR ( 6) }, /* Central Standard */
Packit 8f70b4
  { "CDT",      tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
Packit 8f70b4
  { "MST",      tZONE,    -HOUR ( 7) }, /* Mountain Standard */
Packit 8f70b4
  { "MDT",      tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
Packit 8f70b4
  { "PST",      tZONE,    -HOUR ( 8) }, /* Pacific Standard */
Packit 8f70b4
  { "PDT",      tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
Packit 8f70b4
  { "AKST",     tZONE,    -HOUR ( 9) }, /* Alaska Standard */
Packit 8f70b4
  { "AKDT",     tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
Packit 8f70b4
  { "HST",      tZONE,    -HOUR (10) }, /* Hawaii Standard */
Packit 8f70b4
  { "HAST",     tZONE,    -HOUR (10) }, /* Hawaii-Aleutian Standard */
Packit 8f70b4
  { "HADT",     tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
Packit 8f70b4
  { "SST",      tZONE,    -HOUR (12) }, /* Samoa Standard */
Packit 8f70b4
  { "WAT",      tZONE,     HOUR ( 1) }, /* West Africa */
Packit 8f70b4
  { "CET",      tZONE,     HOUR ( 1) }, /* Central European */
Packit 8f70b4
  { "CEST",     tDAYZONE,  HOUR ( 1) }, /* Central European Summer */
Packit 8f70b4
  { "MET",      tZONE,     HOUR ( 1) }, /* Middle European */
Packit 8f70b4
  { "MEZ",      tZONE,     HOUR ( 1) }, /* Middle European */
Packit 8f70b4
  { "MEST",     tDAYZONE,  HOUR ( 1) }, /* Middle European Summer */
Packit 8f70b4
  { "MESZ",     tDAYZONE,  HOUR ( 1) }, /* Middle European Summer */
Packit 8f70b4
  { "EET",      tZONE,     HOUR ( 2) }, /* Eastern European */
Packit 8f70b4
  { "EEST",     tDAYZONE,  HOUR ( 2) }, /* Eastern European Summer */
Packit 8f70b4
  { "CAT",      tZONE,     HOUR ( 2) }, /* Central Africa */
Packit 8f70b4
  { "SAST",     tZONE,     HOUR ( 2) }, /* South Africa Standard */
Packit 8f70b4
  { "EAT",      tZONE,     HOUR ( 3) }, /* East Africa */
Packit 8f70b4
  { "MSK",      tZONE,     HOUR ( 3) }, /* Moscow */
Packit 8f70b4
  { "MSD",      tDAYZONE,  HOUR ( 3) }, /* Moscow Daylight */
Packit 8f70b4
  { "IST",      tZONE,    (HOUR ( 5) + 30 * 60) }, /* India Standard */
Packit 8f70b4
  { "SGT",      tZONE,     HOUR ( 8) }, /* Singapore */
Packit 8f70b4
  { "KST",      tZONE,     HOUR ( 9) }, /* Korea Standard */
Packit 8f70b4
  { "JST",      tZONE,     HOUR ( 9) }, /* Japan Standard */
Packit 8f70b4
  { "GST",      tZONE,     HOUR (10) }, /* Guam Standard */
Packit 8f70b4
  { "NZST",     tZONE,     HOUR (12) }, /* New Zealand Standard */
Packit 8f70b4
  { "NZDT",     tDAYZONE,  HOUR (12) }, /* New Zealand Daylight */
Packit 8f70b4
  { NULL, 0, 0 }
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
/* Military time zone table.
Packit 8f70b4
Packit 8f70b4
   Note 'T' is a special case, as it is used as the separator in ISO
Packit 8f70b4
   8601 date and time of day representation.  */
Packit 8f70b4
static table const military_table[] =
Packit 8f70b4
{
Packit 8f70b4
  { "A", tZONE, -HOUR ( 1) },
Packit 8f70b4
  { "B", tZONE, -HOUR ( 2) },
Packit 8f70b4
  { "C", tZONE, -HOUR ( 3) },
Packit 8f70b4
  { "D", tZONE, -HOUR ( 4) },
Packit 8f70b4
  { "E", tZONE, -HOUR ( 5) },
Packit 8f70b4
  { "F", tZONE, -HOUR ( 6) },
Packit 8f70b4
  { "G", tZONE, -HOUR ( 7) },
Packit 8f70b4
  { "H", tZONE, -HOUR ( 8) },
Packit 8f70b4
  { "I", tZONE, -HOUR ( 9) },
Packit 8f70b4
  { "K", tZONE, -HOUR (10) },
Packit 8f70b4
  { "L", tZONE, -HOUR (11) },
Packit 8f70b4
  { "M", tZONE, -HOUR (12) },
Packit 8f70b4
  { "N", tZONE,  HOUR ( 1) },
Packit 8f70b4
  { "O", tZONE,  HOUR ( 2) },
Packit 8f70b4
  { "P", tZONE,  HOUR ( 3) },
Packit 8f70b4
  { "Q", tZONE,  HOUR ( 4) },
Packit 8f70b4
  { "R", tZONE,  HOUR ( 5) },
Packit 8f70b4
  { "S", tZONE,  HOUR ( 6) },
Packit 8f70b4
  { "T", 'T',    0 },
Packit 8f70b4
  { "U", tZONE,  HOUR ( 8) },
Packit 8f70b4
  { "V", tZONE,  HOUR ( 9) },
Packit 8f70b4
  { "W", tZONE,  HOUR (10) },
Packit 8f70b4
  { "X", tZONE,  HOUR (11) },
Packit 8f70b4
  { "Y", tZONE,  HOUR (12) },
Packit 8f70b4
  { "Z", tZONE,  HOUR ( 0) },
Packit 8f70b4
  { NULL, 0, 0 }
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4

Packit 8f70b4
Packit 8f70b4
/* Convert a time zone expressed as HH:MM into an integer count of
Packit 8f70b4
   seconds.  If MM is negative, then S is of the form HHMM and needs
Packit 8f70b4
   to be picked apart; otherwise, S is of the form HH.  As specified in
Packit 8f70b4
   http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
Packit 8f70b4
   only valid TZ range, and consider first two digits as hours, if no
Packit 8f70b4
   minutes specified.  Return true if successful.  */
Packit 8f70b4
Packit 8f70b4
static bool
Packit 8f70b4
time_zone_hhmm (parser_control *pc, textint s, intmax_t mm)
Packit 8f70b4
{
Packit 8f70b4
  intmax_t n_minutes;
Packit 8f70b4
  bool overflow = false;
Packit 8f70b4
Packit 8f70b4
  /* If the length of S is 1 or 2 and no minutes are specified,
Packit 8f70b4
     interpret it as a number of hours.  */
Packit 8f70b4
  if (s.digits <= 2 && mm < 0)
Packit 8f70b4
    s.value *= 100;
Packit 8f70b4
Packit 8f70b4
  if (mm < 0)
Packit 8f70b4
    n_minutes = (s.value / 100) * 60 + s.value % 100;
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      overflow |= INT_MULTIPLY_WRAPV (s.value, 60, &n_minutes);
Packit 8f70b4
      overflow |= (s.negative
Packit 8f70b4
                   ? INT_SUBTRACT_WRAPV (n_minutes, mm, &n_minutes)
Packit 8f70b4
                   : INT_ADD_WRAPV (n_minutes, mm, &n_minutes));
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (overflow || ! (-24 * 60 <= n_minutes && n_minutes <= 24 * 60))
Packit 8f70b4
    return false;
Packit 8f70b4
  pc->time_zone = n_minutes * 60;
Packit 8f70b4
  return true;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static int
Packit 8f70b4
to_hour (intmax_t hours, int meridian)
Packit 8f70b4
{
Packit 8f70b4
  switch (meridian)
Packit 8f70b4
    {
Packit 8f70b4
    default: /* Pacify GCC.  */
Packit 8f70b4
    case MER24:
Packit 8f70b4
      return 0 <= hours && hours < 24 ? hours : -1;
Packit 8f70b4
    case MERam:
Packit 8f70b4
      return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
Packit 8f70b4
    case MERpm:
Packit 8f70b4
      return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
Packit 8f70b4
    }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
enum { TM_YEAR_BASE = 1900 };
Packit 8f70b4
enum { TM_YEAR_BUFSIZE = INT_BUFSIZE_BOUND (int) + 1 };
Packit 8f70b4
Packit 8f70b4
/* Convert TM_YEAR, a year minus 1900, to a string that is numerically
Packit 8f70b4
   correct even if subtracting 1900 would overflow.  */
Packit 8f70b4
Packit 8f70b4
static char const *
Packit 8f70b4
tm_year_str (int tm_year, char buf[TM_YEAR_BUFSIZE])
Packit 8f70b4
{
Packit 8f70b4
  verify (TM_YEAR_BASE % 100 == 0);
Packit 8f70b4
  sprintf (buf, &"-%02d%02d"[-TM_YEAR_BASE <= tm_year],
Packit 8f70b4
           abs (tm_year / 100 + TM_YEAR_BASE / 100),
Packit 8f70b4
           abs (tm_year % 100));
Packit 8f70b4
  return buf;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Convert a text year number to a year minus 1900, working correctly
Packit 8f70b4
   even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1.  */
Packit 8f70b4
Packit 8f70b4
static bool
Packit 8f70b4
to_tm_year (textint textyear, bool debug, int *tm_year)
Packit 8f70b4
{
Packit 8f70b4
  intmax_t year = textyear.value;
Packit 8f70b4
Packit 8f70b4
  /* XPG4 suggests that years 00-68 map to 2000-2068, and
Packit 8f70b4
     years 69-99 map to 1969-1999.  */
Packit 8f70b4
  if (0 <= year && textyear.digits == 2)
Packit 8f70b4
    {
Packit 8f70b4
      year += year < 69 ? 2000 : 1900;
Packit 8f70b4
      if (debug)
Packit 8f70b4
        dbg_printf (_("warning: adjusting year value %"PRIdMAX
Packit 8f70b4
                      " to %"PRIdMAX"\n"),
Packit 8f70b4
                    textyear.value, year);
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (year < 0
Packit 8f70b4
      ? INT_SUBTRACT_WRAPV (-TM_YEAR_BASE, year, tm_year)
Packit 8f70b4
      : INT_SUBTRACT_WRAPV (year, TM_YEAR_BASE, tm_year))
Packit 8f70b4
    {
Packit 8f70b4
      if (debug)
Packit 8f70b4
        dbg_printf (_("error: out-of-range year %"PRIdMAX"\n"), year);
Packit 8f70b4
      return false;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  return true;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static table const * _GL_ATTRIBUTE_PURE
Packit 8f70b4
lookup_zone (parser_control const *pc, char const *name)
Packit 8f70b4
{
Packit 8f70b4
  table const *tp;
Packit 8f70b4
Packit 8f70b4
  for (tp = universal_time_zone_table; tp->name; tp++)
Packit 8f70b4
    if (strcmp (name, tp->name) == 0)
Packit 8f70b4
      return tp;
Packit 8f70b4
Packit 8f70b4
  /* Try local zone abbreviations before those in time_zone_table, as
Packit 8f70b4
     the local ones are more likely to be right.  */
Packit 8f70b4
  for (tp = pc->local_time_zone_table; tp->name; tp++)
Packit 8f70b4
    if (strcmp (name, tp->name) == 0)
Packit 8f70b4
      return tp;
Packit 8f70b4
Packit 8f70b4
  for (tp = time_zone_table; tp->name; tp++)
Packit 8f70b4
    if (strcmp (name, tp->name) == 0)
Packit 8f70b4
      return tp;
Packit 8f70b4
Packit 8f70b4
  return NULL;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
#if ! HAVE_TM_GMTOFF
Packit 8f70b4
/* Yield the difference between *A and *B,
Packit 8f70b4
   measured in seconds, ignoring leap seconds.
Packit 8f70b4
   The body of this function is taken directly from the GNU C Library;
Packit 8f70b4
   see strftime.c.  */
Packit 8f70b4
static int
Packit 8f70b4
tm_diff (const struct tm *a, const struct tm *b)
Packit 8f70b4
{
Packit 8f70b4
  /* Compute intervening leap days correctly even if year is negative.
Packit 8f70b4
     Take care to avoid int overflow in leap day calculations,
Packit 8f70b4
     but it's OK to assume that A and B are close to each other.  */
Packit 8f70b4
  int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
Packit 8f70b4
  int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
Packit 8f70b4
  int a100 = a4 / 25 - (a4 % 25 < 0);
Packit 8f70b4
  int b100 = b4 / 25 - (b4 % 25 < 0);
Packit 8f70b4
  int a400 = SHR (a100, 2);
Packit 8f70b4
  int b400 = SHR (b100, 2);
Packit 8f70b4
  int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
Packit 8f70b4
  int years = a->tm_year - b->tm_year;
Packit 8f70b4
  int days = (365 * years + intervening_leap_days
Packit 8f70b4
              + (a->tm_yday - b->tm_yday));
Packit 8f70b4
  return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
Packit 8f70b4
                + (a->tm_min - b->tm_min))
Packit 8f70b4
          + (a->tm_sec - b->tm_sec));
Packit 8f70b4
}
Packit 8f70b4
#endif /* ! HAVE_TM_GMTOFF */
Packit 8f70b4
Packit 8f70b4
static table const *
Packit 8f70b4
lookup_word (parser_control const *pc, char *word)
Packit 8f70b4
{
Packit 8f70b4
  char *p;
Packit 8f70b4
  char *q;
Packit 8f70b4
  ptrdiff_t wordlen;
Packit 8f70b4
  table const *tp;
Packit 8f70b4
  bool period_found;
Packit 8f70b4
  bool abbrev;
Packit 8f70b4
Packit 8f70b4
  /* Make it uppercase.  */
Packit 8f70b4
  for (p = word; *p; p++)
Packit 8f70b4
    *p = c_toupper (to_uchar (*p));
Packit 8f70b4
Packit 8f70b4
  for (tp = meridian_table; tp->name; tp++)
Packit 8f70b4
    if (strcmp (word, tp->name) == 0)
Packit 8f70b4
      return tp;
Packit 8f70b4
Packit 8f70b4
  /* See if we have an abbreviation for a month.  */
Packit 8f70b4
  wordlen = strlen (word);
Packit 8f70b4
  abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
Packit 8f70b4
Packit 8f70b4
  for (tp = month_and_day_table; tp->name; tp++)
Packit 8f70b4
    if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
Packit 8f70b4
      return tp;
Packit 8f70b4
Packit 8f70b4
  if ((tp = lookup_zone (pc, word)))
Packit 8f70b4
    return tp;
Packit 8f70b4
Packit 8f70b4
  if (strcmp (word, dst_table[0].name) == 0)
Packit 8f70b4
    return dst_table;
Packit 8f70b4
Packit 8f70b4
  for (tp = time_units_table; tp->name; tp++)
Packit 8f70b4
    if (strcmp (word, tp->name) == 0)
Packit 8f70b4
      return tp;
Packit 8f70b4
Packit 8f70b4
  /* Strip off any plural and try the units table again.  */
Packit 8f70b4
  if (word[wordlen - 1] == 'S')
Packit 8f70b4
    {
Packit 8f70b4
      word[wordlen - 1] = '\0';
Packit 8f70b4
      for (tp = time_units_table; tp->name; tp++)
Packit 8f70b4
        if (strcmp (word, tp->name) == 0)
Packit 8f70b4
          return tp;
Packit 8f70b4
      word[wordlen - 1] = 'S';  /* For "this" in relative_time_table.  */
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  for (tp = relative_time_table; tp->name; tp++)
Packit 8f70b4
    if (strcmp (word, tp->name) == 0)
Packit 8f70b4
      return tp;
Packit 8f70b4
Packit 8f70b4
  /* Military time zones.  */
Packit 8f70b4
  if (wordlen == 1)
Packit 8f70b4
    for (tp = military_table; tp->name; tp++)
Packit 8f70b4
      if (word[0] == tp->name[0])
Packit 8f70b4
        return tp;
Packit 8f70b4
Packit 8f70b4
  /* Drop out any periods and try the time zone table again.  */
Packit 8f70b4
  for (period_found = false, p = q = word; (*p = *q); q++)
Packit 8f70b4
    if (*q == '.')
Packit 8f70b4
      period_found = true;
Packit 8f70b4
    else
Packit 8f70b4
      p++;
Packit 8f70b4
  if (period_found && (tp = lookup_zone (pc, word)))
Packit 8f70b4
    return tp;
Packit 8f70b4
Packit 8f70b4
  return NULL;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static int
Packit 8f70b4
yylex (union YYSTYPE *lvalp, parser_control *pc)
Packit 8f70b4
{
Packit 8f70b4
  unsigned char c;
Packit 8f70b4
Packit 8f70b4
  for (;;)
Packit 8f70b4
    {
Packit 8f70b4
      while (c = *pc->input, c_isspace (c))
Packit 8f70b4
        pc->input++;
Packit 8f70b4
Packit 8f70b4
      if (c_isdigit (c) || c == '-' || c == '+')
Packit 8f70b4
        {
Packit 8f70b4
          char const *p;
Packit 8f70b4
          int sign;
Packit 8f70b4
          intmax_t value = 0;
Packit 8f70b4
          if (c == '-' || c == '+')
Packit 8f70b4
            {
Packit 8f70b4
              sign = c == '-' ? -1 : 1;
Packit 8f70b4
              while (c = *++pc->input, c_isspace (c))
Packit 8f70b4
                continue;
Packit 8f70b4
              if (! c_isdigit (c))
Packit 8f70b4
                /* skip the '-' sign */
Packit 8f70b4
                continue;
Packit 8f70b4
            }
Packit 8f70b4
          else
Packit 8f70b4
            sign = 0;
Packit 8f70b4
          p = pc->input;
Packit 8f70b4
Packit 8f70b4
          do
Packit 8f70b4
            {
Packit 8f70b4
              if (INT_MULTIPLY_WRAPV (value, 10, &value))
Packit 8f70b4
                return '?';
Packit 8f70b4
              if (INT_ADD_WRAPV (value, sign < 0 ? '0' - c : c - '0', &value))
Packit 8f70b4
                return '?';
Packit 8f70b4
              c = *++p;
Packit 8f70b4
            }
Packit 8f70b4
          while (c_isdigit (c));
Packit 8f70b4
Packit 8f70b4
          if ((c == '.' || c == ',') && c_isdigit (p[1]))
Packit 8f70b4
            {
Packit 8f70b4
              time_t s;
Packit 8f70b4
              int ns;
Packit 8f70b4
              int digits;
Packit 8f70b4
Packit 8f70b4
              if (time_overflow (value))
Packit 8f70b4
                return '?';
Packit 8f70b4
              s = value;
Packit 8f70b4
Packit 8f70b4
              /* Accumulate fraction, to ns precision.  */
Packit 8f70b4
              p++;
Packit 8f70b4
              ns = *p++ - '0';
Packit 8f70b4
              for (digits = 2; digits <= LOG10_BILLION; digits++)
Packit 8f70b4
                {
Packit 8f70b4
                  ns *= 10;
Packit 8f70b4
                  if (c_isdigit (*p))
Packit 8f70b4
                    ns += *p++ - '0';
Packit 8f70b4
                }
Packit 8f70b4
Packit 8f70b4
              /* Skip excess digits, truncating toward -Infinity.  */
Packit 8f70b4
              if (sign < 0)
Packit 8f70b4
                for (; c_isdigit (*p); p++)
Packit 8f70b4
                  if (*p != '0')
Packit 8f70b4
                    {
Packit 8f70b4
                      ns++;
Packit 8f70b4
                      break;
Packit 8f70b4
                    }
Packit 8f70b4
              while (c_isdigit (*p))
Packit 8f70b4
                p++;
Packit 8f70b4
Packit 8f70b4
              /* Adjust to the timespec convention, which is that
Packit 8f70b4
                 tv_nsec is always a positive offset even if tv_sec is
Packit 8f70b4
                 negative.  */
Packit 8f70b4
              if (sign < 0 && ns)
Packit 8f70b4
                {
Packit 8f70b4
                  if (s == TYPE_MINIMUM (time_t))
Packit 8f70b4
                    return '?';
Packit 8f70b4
                  s--;
Packit 8f70b4
                  ns = BILLION - ns;
Packit 8f70b4
                }
Packit 8f70b4
Packit 8f70b4
              lvalp->timespec.tv_sec = s;
Packit 8f70b4
              lvalp->timespec.tv_nsec = ns;
Packit 8f70b4
              pc->input = p;
Packit 8f70b4
              return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
Packit 8f70b4
            }
Packit 8f70b4
          else
Packit 8f70b4
            {
Packit 8f70b4
              lvalp->textintval.negative = sign < 0;
Packit 8f70b4
              lvalp->textintval.value = value;
Packit 8f70b4
              lvalp->textintval.digits = p - pc->input;
Packit 8f70b4
              pc->input = p;
Packit 8f70b4
              return sign ? tSNUMBER : tUNUMBER;
Packit 8f70b4
            }
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      if (c_isalpha (c))
Packit 8f70b4
        {
Packit 8f70b4
          char buff[20];
Packit 8f70b4
          char *p = buff;
Packit 8f70b4
          table const *tp;
Packit 8f70b4
Packit 8f70b4
          do
Packit 8f70b4
            {
Packit 8f70b4
              if (p < buff + sizeof buff - 1)
Packit 8f70b4
                *p++ = c;
Packit 8f70b4
              c = *++pc->input;
Packit 8f70b4
            }
Packit 8f70b4
          while (c_isalpha (c) || c == '.');
Packit 8f70b4
Packit 8f70b4
          *p = '\0';
Packit 8f70b4
          tp = lookup_word (pc, buff);
Packit 8f70b4
          if (! tp)
Packit 8f70b4
            {
Packit 8f70b4
              if (pc->parse_datetime_debug)
Packit 8f70b4
                dbg_printf (_("error: unknown word '%s'\n"), buff);
Packit 8f70b4
              return '?';
Packit 8f70b4
            }
Packit 8f70b4
          lvalp->intval = tp->value;
Packit 8f70b4
          return tp->type;
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      if (c != '(')
Packit 8f70b4
        return to_uchar (*pc->input++);
Packit 8f70b4
Packit 8f70b4
      ptrdiff_t count = 0;
Packit 8f70b4
      do
Packit 8f70b4
        {
Packit 8f70b4
          c = *pc->input++;
Packit 8f70b4
          if (c == '\0')
Packit 8f70b4
            return c;
Packit 8f70b4
          if (c == '(')
Packit 8f70b4
            count++;
Packit 8f70b4
          else if (c == ')')
Packit 8f70b4
            count--;
Packit 8f70b4
        }
Packit 8f70b4
      while (count != 0);
Packit 8f70b4
    }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Do nothing if the parser reports an error.  */
Packit 8f70b4
static int
Packit 8f70b4
yyerror (parser_control const *pc _GL_UNUSED,
Packit 8f70b4
         char const *s _GL_UNUSED)
Packit 8f70b4
{
Packit 8f70b4
  return 0;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* In timezone TZ, if *TM0 is the old and *TM1 is the new value of a
Packit 8f70b4
   struct tm after passing it to mktime_z, return true if it's OK that
Packit 8f70b4
   mktime_z returned T.  It's not OK if *TM0 has out-of-range
Packit 8f70b4
   members.  */
Packit 8f70b4
Packit 8f70b4
static bool
Packit 8f70b4
mktime_ok (timezone_t tz, struct tm const *tm0, struct tm const *tm1, time_t t)
Packit 8f70b4
{
Packit 8f70b4
  struct tm ltm;
Packit 8f70b4
  if (t == (time_t) -1)
Packit 8f70b4
    {
Packit 8f70b4
      /* Guard against falsely reporting an error when parsing a
Packit 8f70b4
         timestamp that happens to equal (time_t) -1, on a host that
Packit 8f70b4
         supports such a timestamp.  */
Packit 8f70b4
      tm1 = localtime_rz (tz, &t, <m;;
Packit 8f70b4
      if (!tm1)
Packit 8f70b4
        return false;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  return ! ((tm0->tm_sec ^ tm1->tm_sec)
Packit 8f70b4
            | (tm0->tm_min ^ tm1->tm_min)
Packit 8f70b4
            | (tm0->tm_hour ^ tm1->tm_hour)
Packit 8f70b4
            | (tm0->tm_mday ^ tm1->tm_mday)
Packit 8f70b4
            | (tm0->tm_mon ^ tm1->tm_mon)
Packit 8f70b4
            | (tm0->tm_year ^ tm1->tm_year));
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Debugging: format a 'struct tm' into a buffer, taking the parser's
Packit 8f70b4
   timezone information into account (if pc != NULL).  */
Packit 8f70b4
static char const *
Packit 8f70b4
debug_strfdatetime (struct tm const *tm, parser_control const *pc,
Packit 8f70b4
                    char *buf, int n)
Packit 8f70b4
{
Packit 8f70b4
  /* TODO:
Packit 8f70b4
     1. find an optimal way to print date string in a clear and unambiguous
Packit 8f70b4
        format.  Currently, always add '(Y-M-D)' prefix.
Packit 8f70b4
        Consider '2016y01m10d'  or 'year(2016) month(01) day(10)'.
Packit 8f70b4
Packit 8f70b4
        If the user needs debug printing, it means he/she already having
Packit 8f70b4
        issues with the parsing - better to avoid formats that could
Packit 8f70b4
        be mis-interpreted (e.g., just YYYY-MM-DD).
Packit 8f70b4
Packit 8f70b4
     2. Can strftime be used instead?
Packit 8f70b4
        depends if it is portable and can print invalid dates on all systems.
Packit 8f70b4
Packit 8f70b4
     3. Print timezone information ?
Packit 8f70b4
Packit 8f70b4
     4. Print DST information ?
Packit 8f70b4
Packit 8f70b4
     5. Print nanosecond information ?
Packit 8f70b4
Packit 8f70b4
     NOTE:
Packit 8f70b4
     Printed date/time values might not be valid, e.g., '2016-02-31'
Packit 8f70b4
     or '2016-19-2016' .  These are the values as parsed from the user
Packit 8f70b4
     string, before validation.
Packit 8f70b4
  */
Packit 8f70b4
  int m = nstrftime (buf, n, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm, 0, 0);
Packit 8f70b4
Packit 8f70b4
  /* If parser_control information was provided (for timezone),
Packit 8f70b4
     and there's enough space in the buffer, add timezone info.  */
Packit 8f70b4
  if (pc && m < n && pc->zones_seen)
Packit 8f70b4
    {
Packit 8f70b4
      int tz = pc->time_zone;
Packit 8f70b4
Packit 8f70b4
      /* Account for DST if tLOCAL_ZONE was seen.  */
Packit 8f70b4
      if (pc->local_zones_seen && !pc->zones_seen && 0 < pc->local_isdst)
Packit 8f70b4
        tz += 60 * 60;
Packit 8f70b4
Packit 8f70b4
      char time_zone_buf[TIME_ZONE_BUFSIZE];
Packit 8f70b4
      snprintf (&buf[m], n - m, " TZ=%s", time_zone_str (tz, time_zone_buf));
Packit 8f70b4
    }
Packit 8f70b4
  return buf;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static char const *
Packit 8f70b4
debug_strfdate (struct tm const *tm, char *buf, int n)
Packit 8f70b4
{
Packit 8f70b4
  char tm_year_buf[TM_YEAR_BUFSIZE];
Packit 8f70b4
  snprintf (buf, n, "(Y-M-D) %s-%02d-%02d",
Packit 8f70b4
            tm_year_str (tm->tm_year, tm_year_buf),
Packit 8f70b4
            tm->tm_mon + 1, tm->tm_mday);
Packit 8f70b4
  return buf;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static char const *
Packit 8f70b4
debug_strftime (struct tm const *tm, char *buf, int n)
Packit 8f70b4
{
Packit 8f70b4
  snprintf (buf, n, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
Packit 8f70b4
  return buf;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* If mktime_ok failed, display the failed time values,
Packit 8f70b4
   and provide possible hints.  Example output:
Packit 8f70b4
Packit 8f70b4
    date: error: invalid date/time value:
Packit 8f70b4
    date:     user provided time: '(Y-M-D) 2006-04-02 02:45:00'
Packit 8f70b4
    date:        normalized time: '(Y-M-D) 2006-04-02 03:45:00'
Packit 8f70b4
    date:                                             __
Packit 8f70b4
    date:      possible reasons:
Packit 8f70b4
    date:        non-existing due to daylight-saving time;
Packit 8f70b4
    date:        numeric values overflow;
Packit 8f70b4
    date:        missing timezone;
Packit 8f70b4
 */
Packit 8f70b4
static void
Packit 8f70b4
debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
Packit 8f70b4
                     parser_control const *pc, bool time_zone_seen)
Packit 8f70b4
{
Packit 8f70b4
  /* TODO: handle t == -1 (as in 'mktime_ok').  */
Packit 8f70b4
  char tmp[DBGBUFSIZE];
Packit 8f70b4
  int i;
Packit 8f70b4
  const bool eq_sec   = (tm0->tm_sec  == tm1->tm_sec);
Packit 8f70b4
  const bool eq_min   = (tm0->tm_min  == tm1->tm_min);
Packit 8f70b4
  const bool eq_hour  = (tm0->tm_hour == tm1->tm_hour);
Packit 8f70b4
  const bool eq_mday  = (tm0->tm_mday == tm1->tm_mday);
Packit 8f70b4
  const bool eq_month = (tm0->tm_mon  == tm1->tm_mon);
Packit 8f70b4
  const bool eq_year  = (tm0->tm_year == tm1->tm_year);
Packit 8f70b4
Packit 8f70b4
  const bool dst_shift = eq_sec && eq_min && !eq_hour
Packit 8f70b4
                         && eq_mday && eq_month && eq_year;
Packit 8f70b4
Packit 8f70b4
  if (!pc->parse_datetime_debug)
Packit 8f70b4
    return;
Packit 8f70b4
Packit 8f70b4
  dbg_printf (_("error: invalid date/time value:\n"));
Packit 8f70b4
  dbg_printf (_("    user provided time: '%s'\n"),
Packit 8f70b4
              debug_strfdatetime (tm0, pc, tmp, sizeof tmp));
Packit 8f70b4
  dbg_printf (_("       normalized time: '%s'\n"),
Packit 8f70b4
              debug_strfdatetime (tm1, pc, tmp, sizeof tmp));
Packit 8f70b4
  /* The format must be aligned with debug_strfdatetime and the two
Packit 8f70b4
     DEBUG statements above.  This string is not translated.  */
Packit 8f70b4
  i = snprintf (tmp, sizeof tmp,
Packit 8f70b4
                "                                 %4s %2s %2s %2s %2s %2s",
Packit 8f70b4
                eq_year ? "" : "----",
Packit 8f70b4
                eq_month ? "" : "--",
Packit 8f70b4
                eq_mday ? "" : "--",
Packit 8f70b4
                eq_hour ? "" : "--",
Packit 8f70b4
                eq_min ? "" : "--",
Packit 8f70b4
                eq_sec ? "" : "--");
Packit 8f70b4
  /* Trim trailing whitespace.  */
Packit 8f70b4
  if (0 <= i)
Packit 8f70b4
    {
Packit 8f70b4
      if (sizeof tmp - 1 < i)
Packit 8f70b4
        i = sizeof tmp - 1;
Packit 8f70b4
      while (0 < i && tmp[i - 1] == ' ')
Packit 8f70b4
        --i;
Packit 8f70b4
      tmp[i] = '\0';
Packit 8f70b4
    }
Packit 8f70b4
  dbg_printf ("%s\n", tmp);
Packit 8f70b4
Packit 8f70b4
  dbg_printf (_("     possible reasons:\n"));
Packit 8f70b4
  if (dst_shift)
Packit 8f70b4
    dbg_printf (_("       non-existing due to daylight-saving time;\n"));
Packit 8f70b4
  if (!eq_mday && !eq_month)
Packit 8f70b4
    dbg_printf (_("       invalid day/month combination;\n"));
Packit 8f70b4
  dbg_printf (_("       numeric values overflow;\n"));
Packit 8f70b4
  dbg_printf ("       %s\n", (time_zone_seen ? _("incorrect timezone")
Packit 8f70b4
                              : _("missing timezone")));
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* The original interface: run with debug=false and the default timezone.   */
Packit 8f70b4
bool
Packit 8f70b4
parse_datetime (struct timespec *result, char const *p,
Packit 8f70b4
                struct timespec const *now)
Packit 8f70b4
{
Packit 8f70b4
  char const *tzstring = getenv ("TZ");
Packit 8f70b4
  timezone_t tz = tzalloc (tzstring);
Packit 8f70b4
  if (!tz)
Packit 8f70b4
    return false;
Packit 8f70b4
  bool ok = parse_datetime2 (result, p, now, 0, tz, tzstring);
Packit 8f70b4
  tzfree (tz);
Packit 8f70b4
  return ok;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Parse a date/time string, storing the resulting time value into *RESULT.
Packit 8f70b4
   The string itself is pointed to by P.  Return true if successful.
Packit 8f70b4
   P can be an incomplete or relative time specification; if so, use
Packit 8f70b4
   *NOW as the basis for the returned time.  Default to timezone
Packit 8f70b4
   TZDEFAULT, which corresponds to tzalloc (TZSTRING).  */
Packit 8f70b4
bool
Packit 8f70b4
parse_datetime2 (struct timespec *result, char const *p,
Packit 8f70b4
                 struct timespec const *now, unsigned int flags,
Packit 8f70b4
                 timezone_t tzdefault, char const *tzstring)
Packit 8f70b4
{
Packit 8f70b4
  struct tm tm;
Packit 8f70b4
  struct tm tm0;
Packit 8f70b4
  char time_zone_buf[TIME_ZONE_BUFSIZE];
Packit 8f70b4
  char dbg_tm[DBGBUFSIZE];
Packit 8f70b4
  bool ok = false;
Packit 8f70b4
  char const *input_sentinel = p + strlen (p);
Packit 8f70b4
  char *tz1alloc = NULL;
Packit 8f70b4
Packit 8f70b4
  /* A reasonable upper bound for the size of ordinary TZ strings.
Packit 8f70b4
     Use heap allocation if TZ's length exceeds this.  */
Packit 8f70b4
  enum { TZBUFSIZE = 100 };
Packit 8f70b4
  char tz1buf[TZBUFSIZE];
Packit 8f70b4
Packit 8f70b4
  struct timespec gettime_buffer;
Packit 8f70b4
  if (! now)
Packit 8f70b4
    {
Packit 8f70b4
      gettime (&gettime_buffer);
Packit 8f70b4
      now = &gettime_buffer;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  time_t Start = now->tv_sec;
Packit 8f70b4
  int Start_ns = now->tv_nsec;
Packit 8f70b4
Packit 8f70b4
  unsigned char c;
Packit 8f70b4
  while (c = *p, c_isspace (c))
Packit 8f70b4
    p++;
Packit 8f70b4
Packit 8f70b4
  timezone_t tz = tzdefault;
Packit 8f70b4
Packit 8f70b4
  /* Store a local copy prior to first "goto".  Without this, a prior use
Packit 8f70b4
     below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
Packit 8f70b4
     to-temporary, which would trigger a -Wjump-misses-init warning.  */
Packit 8f70b4
  const relative_time rel_time_0 = RELATIVE_TIME_0;
Packit 8f70b4
Packit 8f70b4
  if (strncmp (p, "TZ=\"", 4) == 0)
Packit 8f70b4
    {
Packit 8f70b4
      char const *tzbase = p + 4;
Packit 8f70b4
      ptrdiff_t tzsize = 1;
Packit 8f70b4
      char const *s;
Packit 8f70b4
Packit 8f70b4
      for (s = tzbase; *s; s++, tzsize++)
Packit 8f70b4
        if (*s == '\\')
Packit 8f70b4
          {
Packit 8f70b4
            s++;
Packit 8f70b4
            if (! (*s == '\\' || *s == '"'))
Packit 8f70b4
              break;
Packit 8f70b4
          }
Packit 8f70b4
        else if (*s == '"')
Packit 8f70b4
          {
Packit 8f70b4
            timezone_t tz1;
Packit 8f70b4
            char *tz1string = tz1buf;
Packit 8f70b4
            char *z;
Packit 8f70b4
            if (TZBUFSIZE < tzsize)
Packit 8f70b4
              {
Packit 8f70b4
                tz1alloc = malloc (tzsize);
Packit 8f70b4
                if (!tz1alloc)
Packit 8f70b4
                  goto fail;
Packit 8f70b4
                tz1string = tz1alloc;
Packit 8f70b4
              }
Packit 8f70b4
            z = tz1string;
Packit 8f70b4
            for (s = tzbase; *s != '"'; s++)
Packit 8f70b4
              *z++ = *(s += *s == '\\');
Packit 8f70b4
            *z = '\0';
Packit 8f70b4
            tz1 = tzalloc (tz1string);
Packit 8f70b4
            if (!tz1)
Packit 8f70b4
              goto fail;
Packit 8f70b4
            tz = tz1;
Packit 8f70b4
            tzstring = tz1string;
Packit 8f70b4
Packit 8f70b4
            p = s + 1;
Packit 8f70b4
            while (c = *p, c_isspace (c))
Packit 8f70b4
              p++;
Packit 8f70b4
Packit 8f70b4
            break;
Packit 8f70b4
          }
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  struct tm tmp;
Packit 8f70b4
  if (! localtime_rz (tz, &now->tv_sec, &tmp))
Packit 8f70b4
    goto fail;
Packit 8f70b4
Packit 8f70b4
  /* As documented, be careful to treat the empty string just like
Packit 8f70b4
     a date string of "0".  Without this, an empty string would be
Packit 8f70b4
     declared invalid when parsed during a DST transition.  */
Packit 8f70b4
  if (*p == '\0')
Packit 8f70b4
    p = "0";
Packit 8f70b4
Packit 8f70b4
  parser_control pc;
Packit 8f70b4
  pc.input = p;
Packit 8f70b4
  pc.parse_datetime_debug = (flags & PARSE_DATETIME_DEBUG) != 0;
Packit 8f70b4
  if (INT_ADD_WRAPV (tmp.tm_year, TM_YEAR_BASE, &pc.year.value))
Packit 8f70b4
    {
Packit 8f70b4
      if (pc.parse_datetime_debug)
Packit 8f70b4
        dbg_printf (_("error: initial year out of range\n"));
Packit 8f70b4
      goto fail;
Packit 8f70b4
    }
Packit 8f70b4
  pc.year.digits = 0;
Packit 8f70b4
  pc.month = tmp.tm_mon + 1;
Packit 8f70b4
  pc.day = tmp.tm_mday;
Packit 8f70b4
  pc.hour = tmp.tm_hour;
Packit 8f70b4
  pc.minutes = tmp.tm_min;
Packit 8f70b4
  pc.seconds.tv_sec = tmp.tm_sec;
Packit 8f70b4
  pc.seconds.tv_nsec = Start_ns;
Packit 8f70b4
  tm.tm_isdst = tmp.tm_isdst;
Packit 8f70b4
Packit 8f70b4
  pc.meridian = MER24;
Packit 8f70b4
  pc.rel = rel_time_0;
Packit 8f70b4
  pc.timespec_seen = false;
Packit 8f70b4
  pc.rels_seen = false;
Packit 8f70b4
  pc.dates_seen = 0;
Packit 8f70b4
  pc.days_seen = 0;
Packit 8f70b4
  pc.times_seen = 0;
Packit 8f70b4
  pc.local_zones_seen = 0;
Packit 8f70b4
  pc.dsts_seen = 0;
Packit 8f70b4
  pc.zones_seen = 0;
Packit 8f70b4
  pc.year_seen = false;
Packit 8f70b4
  pc.debug_dates_seen = false;
Packit 8f70b4
  pc.debug_days_seen = false;
Packit 8f70b4
  pc.debug_times_seen = false;
Packit 8f70b4
  pc.debug_local_zones_seen = false;
Packit 8f70b4
  pc.debug_zones_seen = false;
Packit 8f70b4
  pc.debug_year_seen = false;
Packit 8f70b4
  pc.debug_ordinal_day_seen = false;
Packit 8f70b4
Packit 8f70b4
#if HAVE_STRUCT_TM_TM_ZONE
Packit 8f70b4
  pc.local_time_zone_table[0].name = tmp.tm_zone;
Packit 8f70b4
  pc.local_time_zone_table[0].type = tLOCAL_ZONE;
Packit 8f70b4
  pc.local_time_zone_table[0].value = tmp.tm_isdst;
Packit 8f70b4
  pc.local_time_zone_table[1].name = NULL;
Packit 8f70b4
Packit 8f70b4
  /* Probe the names used in the next three calendar quarters, looking
Packit 8f70b4
     for a tm_isdst different from the one we already have.  */
Packit 8f70b4
  {
Packit 8f70b4
    int quarter;
Packit 8f70b4
    for (quarter = 1; quarter <= 3; quarter++)
Packit 8f70b4
      {
Packit 8f70b4
        intmax_t iprobe;
Packit 8f70b4
        if (INT_ADD_WRAPV (Start, quarter * (90 * 24 * 60 * 60), &iprobe)
Packit 8f70b4
            || time_overflow (iprobe))
Packit 8f70b4
          break;
Packit 8f70b4
        time_t probe = iprobe;
Packit 8f70b4
        struct tm probe_tm;
Packit 8f70b4
        if (localtime_rz (tz, &probe, &probe_tm) && probe_tm.tm_zone
Packit 8f70b4
            && probe_tm.tm_isdst != pc.local_time_zone_table[0].value)
Packit 8f70b4
          {
Packit 8f70b4
              {
Packit 8f70b4
                pc.local_time_zone_table[1].name = probe_tm.tm_zone;
Packit 8f70b4
                pc.local_time_zone_table[1].type = tLOCAL_ZONE;
Packit 8f70b4
                pc.local_time_zone_table[1].value = probe_tm.tm_isdst;
Packit 8f70b4
                pc.local_time_zone_table[2].name = NULL;
Packit 8f70b4
              }
Packit 8f70b4
            break;
Packit 8f70b4
          }
Packit 8f70b4
      }
Packit 8f70b4
  }
Packit 8f70b4
#else
Packit 8f70b4
#if HAVE_TZNAME
Packit 8f70b4
  {
Packit 8f70b4
# if !HAVE_DECL_TZNAME
Packit 8f70b4
    extern char *tzname[];
Packit 8f70b4
# endif
Packit 8f70b4
    int i;
Packit 8f70b4
    for (i = 0; i < 2; i++)
Packit 8f70b4
      {
Packit 8f70b4
        pc.local_time_zone_table[i].name = tzname[i];
Packit 8f70b4
        pc.local_time_zone_table[i].type = tLOCAL_ZONE;
Packit 8f70b4
        pc.local_time_zone_table[i].value = i;
Packit 8f70b4
      }
Packit 8f70b4
    pc.local_time_zone_table[i].name = NULL;
Packit 8f70b4
  }
Packit 8f70b4
#else
Packit 8f70b4
  pc.local_time_zone_table[0].name = NULL;
Packit 8f70b4
#endif
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
  if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
Packit 8f70b4
      && ! strcmp (pc.local_time_zone_table[0].name,
Packit 8f70b4
                   pc.local_time_zone_table[1].name))
Packit 8f70b4
    {
Packit 8f70b4
      /* This locale uses the same abbreviation for standard and
Packit 8f70b4
         daylight times.  So if we see that abbreviation, we don't
Packit 8f70b4
         know whether it's daylight time.  */
Packit 8f70b4
      pc.local_time_zone_table[0].value = -1;
Packit 8f70b4
      pc.local_time_zone_table[1].name = NULL;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (yyparse (&pc) != 0)
Packit 8f70b4
    {
Packit 8f70b4
      if (pc.parse_datetime_debug)
Packit 8f70b4
        dbg_printf ((input_sentinel <= pc.input
Packit 8f70b4
                     ? _("error: parsing failed\n")
Packit 8f70b4
                     : _("error: parsing failed, stopped at '%s'\n")),
Packit 8f70b4
                    pc.input);
Packit 8f70b4
      goto fail;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
Packit 8f70b4
  /* Determine effective timezone source.  */
Packit 8f70b4
Packit 8f70b4
  if (pc.parse_datetime_debug)
Packit 8f70b4
    {
Packit 8f70b4
      dbg_printf (_("input timezone: "));
Packit 8f70b4
Packit 8f70b4
      if (pc.timespec_seen)
Packit 8f70b4
        fprintf (stderr, _("'@timespec' - always UTC"));
Packit 8f70b4
      else if (pc.zones_seen)
Packit 8f70b4
        fprintf (stderr, _("parsed date/time string"));
Packit 8f70b4
      else if (tzstring)
Packit 8f70b4
        {
Packit 8f70b4
          if (tz != tzdefault)
Packit 8f70b4
            fprintf (stderr, _("TZ=\"%s\" in date string"), tzstring);
Packit 8f70b4
          else if (STREQ (tzstring, "UTC0"))
Packit 8f70b4
            {
Packit 8f70b4
              /* Special case: 'date -u' sets TZ="UTC0".  */
Packit 8f70b4
              fprintf (stderr, _("TZ=\"UTC0\" environment value or -u"));
Packit 8f70b4
            }
Packit 8f70b4
          else
Packit 8f70b4
            fprintf (stderr, _("TZ=\"%s\" environment value"), tzstring);
Packit 8f70b4
        }
Packit 8f70b4
      else
Packit 8f70b4
        fprintf (stderr, _("system default"));
Packit 8f70b4
Packit 8f70b4
      /* Account for DST changes if tLOCAL_ZONE was seen.
Packit 8f70b4
         local timezone only changes DST and is relative to the
Packit 8f70b4
         default timezone.*/
Packit 8f70b4
      if (pc.local_zones_seen && !pc.zones_seen && 0 < pc.local_isdst)
Packit 8f70b4
        fprintf (stderr, ", dst");
Packit 8f70b4
Packit 8f70b4
      if (pc.zones_seen)
Packit 8f70b4
        fprintf (stderr, " (%s)", time_zone_str (pc.time_zone, time_zone_buf));
Packit 8f70b4
Packit 8f70b4
      fputc ('\n', stderr);
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (pc.timespec_seen)
Packit 8f70b4
    *result = pc.seconds;
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
Packit 8f70b4
               | (pc.local_zones_seen + pc.zones_seen)))
Packit 8f70b4
        {
Packit 8f70b4
          if (pc.parse_datetime_debug)
Packit 8f70b4
            {
Packit 8f70b4
              if (pc.times_seen > 1)
Packit 8f70b4
                dbg_printf ("error: seen multiple time parts\n");
Packit 8f70b4
              if (pc.dates_seen > 1)
Packit 8f70b4
                dbg_printf ("error: seen multiple date parts\n");
Packit 8f70b4
              if (pc.days_seen > 1)
Packit 8f70b4
                dbg_printf ("error: seen multiple days parts\n");
Packit 8f70b4
              if (pc.dsts_seen > 1)
Packit 8f70b4
                dbg_printf ("error: seen multiple daylight-saving parts\n");
Packit 8f70b4
              if ((pc.local_zones_seen + pc.zones_seen) > 1)
Packit 8f70b4
                dbg_printf ("error: seen multiple time-zone parts\n");
Packit 8f70b4
            }
Packit 8f70b4
          goto fail;
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      if (! to_tm_year (pc.year, pc.parse_datetime_debug, &tm.tm_year)
Packit 8f70b4
          || INT_ADD_WRAPV (pc.month, -1, &tm.tm_mon)
Packit 8f70b4
          || INT_ADD_WRAPV (pc.day, 0, &tm.tm_mday))
Packit 8f70b4
        {
Packit 8f70b4
          if (pc.parse_datetime_debug)
Packit 8f70b4
            dbg_printf (_("error: year, month, or day overflow\n"));
Packit 8f70b4
          goto fail;
Packit 8f70b4
        }
Packit 8f70b4
      if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
Packit 8f70b4
        {
Packit 8f70b4
          tm.tm_hour = to_hour (pc.hour, pc.meridian);
Packit 8f70b4
          if (tm.tm_hour < 0)
Packit 8f70b4
            {
Packit 8f70b4
              char const *mrd = (pc.meridian == MERam ? "am"
Packit 8f70b4
                                 : pc.meridian == MERpm ?"pm" : "");
Packit 8f70b4
              if (pc.parse_datetime_debug)
Packit 8f70b4
                dbg_printf (_("error: invalid hour %"PRIdMAX"%s\n"),
Packit 8f70b4
                            pc.hour, mrd);
Packit 8f70b4
              goto fail;
Packit 8f70b4
            }
Packit 8f70b4
          tm.tm_min = pc.minutes;
Packit 8f70b4
          tm.tm_sec = pc.seconds.tv_sec;
Packit 8f70b4
          if (pc.parse_datetime_debug)
Packit 8f70b4
            dbg_printf ((pc.times_seen
Packit 8f70b4
                         ? _("using specified time as starting value: '%s'\n")
Packit 8f70b4
                         : _("using current time as starting value: '%s'\n")),
Packit 8f70b4
                        debug_strftime (&tm, dbg_tm, sizeof dbg_tm));
Packit 8f70b4
        }
Packit 8f70b4
      else
Packit 8f70b4
        {
Packit 8f70b4
          tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
Packit 8f70b4
          pc.seconds.tv_nsec = 0;
Packit 8f70b4
          if (pc.parse_datetime_debug)
Packit 8f70b4
            dbg_printf ("warning: using midnight as starting time: 00:00:00\n");
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      /* Let mktime deduce tm_isdst if we have an absolute timestamp.  */
Packit 8f70b4
      if (pc.dates_seen | pc.days_seen | pc.times_seen)
Packit 8f70b4
        tm.tm_isdst = -1;
Packit 8f70b4
Packit 8f70b4
      /* But if the input explicitly specifies local time with or without
Packit 8f70b4
         DST, give mktime that information.  */
Packit 8f70b4
      if (pc.local_zones_seen)
Packit 8f70b4
        tm.tm_isdst = pc.local_isdst;
Packit 8f70b4
Packit 8f70b4
      tm0.tm_sec = tm.tm_sec;
Packit 8f70b4
      tm0.tm_min = tm.tm_min;
Packit 8f70b4
      tm0.tm_hour = tm.tm_hour;
Packit 8f70b4
      tm0.tm_mday = tm.tm_mday;
Packit 8f70b4
      tm0.tm_mon = tm.tm_mon;
Packit 8f70b4
      tm0.tm_year = tm.tm_year;
Packit 8f70b4
      tm0.tm_isdst = tm.tm_isdst;
Packit 8f70b4
Packit 8f70b4
      Start = mktime_z (tz, &tm;;
Packit 8f70b4
Packit 8f70b4
      if (! mktime_ok (tz, &tm0, &tm, Start))
Packit 8f70b4
        {
Packit 8f70b4
          bool repaired = false;
Packit 8f70b4
          bool time_zone_seen = pc.zones_seen != 0;
Packit 8f70b4
          if (time_zone_seen)
Packit 8f70b4
            {
Packit 8f70b4
              /* Guard against falsely reporting errors near the time_t
Packit 8f70b4
                 boundaries when parsing times in other time zones.  For
Packit 8f70b4
                 example, suppose the input string "1969-12-31 23:00:00 -0100",
Packit 8f70b4
                 the current time zone is 8 hours ahead of UTC, and the min
Packit 8f70b4
                 time_t value is 1970-01-01 00:00:00 UTC.  Then the min
Packit 8f70b4
                 localtime value is 1970-01-01 08:00:00, and mktime will
Packit 8f70b4
                 therefore fail on 1969-12-31 23:00:00.  To work around the
Packit 8f70b4
                 problem, set the time zone to 1 hour behind UTC temporarily
Packit 8f70b4
                 by setting TZ="XXX1:00" and try mktime again.  */
Packit 8f70b4
Packit 8f70b4
              char tz2buf[sizeof "XXX" - 1 + TIME_ZONE_BUFSIZE];
Packit 8f70b4
              tz2buf[0] = tz2buf[1] = tz2buf[2] = 'X';
Packit 8f70b4
              time_zone_str (pc.time_zone, &tz2buf[3]);
Packit 8f70b4
              timezone_t tz2 = tzalloc (tz2buf);
Packit 8f70b4
              if (!tz2)
Packit 8f70b4
                {
Packit 8f70b4
                  if (pc.parse_datetime_debug)
Packit 8f70b4
                    dbg_printf (_("error: tzalloc (\"%s\") failed\n"), tz2buf);
Packit 8f70b4
                  goto fail;
Packit 8f70b4
                }
Packit 8f70b4
              tm.tm_sec = tm0.tm_sec;
Packit 8f70b4
              tm.tm_min = tm0.tm_min;
Packit 8f70b4
              tm.tm_hour = tm0.tm_hour;
Packit 8f70b4
              tm.tm_mday = tm0.tm_mday;
Packit 8f70b4
              tm.tm_mon = tm0.tm_mon;
Packit 8f70b4
              tm.tm_year = tm0.tm_year;
Packit 8f70b4
              tm.tm_isdst = tm0.tm_isdst;
Packit 8f70b4
              Start = mktime_z (tz2, &tm;;
Packit 8f70b4
              repaired = mktime_ok (tz2, &tm0, &tm, Start);
Packit 8f70b4
              tzfree (tz2);
Packit 8f70b4
            }
Packit 8f70b4
Packit 8f70b4
          if (! repaired)
Packit 8f70b4
            {
Packit 8f70b4
              debug_mktime_not_ok (&tm0, &tm, &pc, time_zone_seen);
Packit 8f70b4
              goto fail;
Packit 8f70b4
            }
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      char dbg_ord[DBGBUFSIZE];
Packit 8f70b4
Packit 8f70b4
      if (pc.days_seen && ! pc.dates_seen)
Packit 8f70b4
        {
Packit 8f70b4
          intmax_t dayincr;
Packit 8f70b4
          if (INT_MULTIPLY_WRAPV ((pc.day_ordinal
Packit 8f70b4
                                   - (0 < pc.day_ordinal
Packit 8f70b4
                                      && tm.tm_wday != pc.day_number)),
Packit 8f70b4
                                  7, &dayincr)
Packit 8f70b4
              || INT_ADD_WRAPV ((pc.day_number - tm.tm_wday + 7) % 7,
Packit 8f70b4
                                dayincr, &dayincr)
Packit 8f70b4
              || INT_ADD_WRAPV (dayincr, tm.tm_mday, &tm.tm_mday))
Packit 8f70b4
            Start = -1;
Packit 8f70b4
          else
Packit 8f70b4
            {
Packit 8f70b4
              tm.tm_isdst = -1;
Packit 8f70b4
              Start = mktime_z (tz, &tm;;
Packit 8f70b4
            }
Packit 8f70b4
Packit 8f70b4
          if (Start == (time_t) -1)
Packit 8f70b4
            {
Packit 8f70b4
              if (pc.parse_datetime_debug)
Packit 8f70b4
                dbg_printf (_("error: day '%s' "
Packit 8f70b4
                              "(day ordinal=%"PRIdMAX" number=%d) "
Packit 8f70b4
                              "resulted in an invalid date: '%s'\n"),
Packit 8f70b4
                            str_days (&pc, dbg_ord, sizeof dbg_ord),
Packit 8f70b4
                            pc.day_ordinal, pc.day_number,
Packit 8f70b4
                            debug_strfdatetime (&tm, &pc, dbg_tm,
Packit 8f70b4
                                                sizeof dbg_tm));
Packit 8f70b4
              goto fail;
Packit 8f70b4
            }
Packit 8f70b4
Packit 8f70b4
          if (pc.parse_datetime_debug)
Packit 8f70b4
            dbg_printf (_("new start date: '%s' is '%s'\n"),
Packit 8f70b4
                        str_days (&pc, dbg_ord, sizeof dbg_ord),
Packit 8f70b4
                        debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
Packit 8f70b4
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      if (pc.parse_datetime_debug)
Packit 8f70b4
        {
Packit 8f70b4
          if (!pc.dates_seen && !pc.days_seen)
Packit 8f70b4
            dbg_printf (_("using current date as starting value: '%s'\n"),
Packit 8f70b4
                        debug_strfdate (&tm, dbg_tm, sizeof dbg_tm));
Packit 8f70b4
Packit 8f70b4
          if (pc.days_seen && pc.dates_seen)
Packit 8f70b4
            dbg_printf (_("warning: day (%s) ignored when explicit dates "
Packit 8f70b4
                          "are given\n"),
Packit 8f70b4
                        str_days (&pc, dbg_ord, sizeof dbg_ord));
Packit 8f70b4
Packit 8f70b4
          dbg_printf (_("starting date/time: '%s'\n"),
Packit 8f70b4
                      debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      /* Add relative date.  */
Packit 8f70b4
      if (pc.rel.year | pc.rel.month | pc.rel.day)
Packit 8f70b4
        {
Packit 8f70b4
          if (pc.parse_datetime_debug)
Packit 8f70b4
            {
Packit 8f70b4
              if ((pc.rel.year != 0 || pc.rel.month != 0) && tm.tm_mday != 15)
Packit 8f70b4
                dbg_printf (_("warning: when adding relative months/years, "
Packit 8f70b4
                              "it is recommended to specify the 15th of the "
Packit 8f70b4
                              "months\n"));
Packit 8f70b4
Packit 8f70b4
              if (pc.rel.day != 0 && tm.tm_hour != 12)
Packit 8f70b4
                dbg_printf (_("warning: when adding relative days, "
Packit 8f70b4
                              "it is recommended to specify noon\n"));
Packit 8f70b4
            }
Packit 8f70b4
Packit 8f70b4
          int year, month, day;
Packit 8f70b4
          if (INT_ADD_WRAPV (tm.tm_year, pc.rel.year, &year)
Packit 8f70b4
              || INT_ADD_WRAPV (tm.tm_mon, pc.rel.month, &month)
Packit 8f70b4
              || INT_ADD_WRAPV (tm.tm_mday, pc.rel.day, &day))
Packit 8f70b4
            {
Packit 8f70b4
              if (pc.parse_datetime_debug)
Packit 8f70b4
                dbg_printf (_("error: %s:%d\n"), __FILE__, __LINE__);
Packit 8f70b4
              goto fail;
Packit 8f70b4
            }
Packit 8f70b4
          tm.tm_year = year;
Packit 8f70b4
          tm.tm_mon = month;
Packit 8f70b4
          tm.tm_mday = day;
Packit 8f70b4
          tm.tm_hour = tm0.tm_hour;
Packit 8f70b4
          tm.tm_min = tm0.tm_min;
Packit 8f70b4
          tm.tm_sec = tm0.tm_sec;
Packit 8f70b4
          tm.tm_isdst = tm0.tm_isdst;
Packit 8f70b4
          Start = mktime_z (tz, &tm;;
Packit 8f70b4
          if (Start == (time_t) -1)
Packit 8f70b4
            {
Packit 8f70b4
              if (pc.parse_datetime_debug)
Packit 8f70b4
                dbg_printf (_("error: adding relative date resulted "
Packit 8f70b4
                              "in an invalid date: '%s'\n"),
Packit 8f70b4
                            debug_strfdatetime (&tm, &pc, dbg_tm,
Packit 8f70b4
                                                sizeof dbg_tm));
Packit 8f70b4
              goto fail;
Packit 8f70b4
            }
Packit 8f70b4
Packit 8f70b4
          if (pc.parse_datetime_debug)
Packit 8f70b4
            {
Packit 8f70b4
              dbg_printf (_("after date adjustment "
Packit 8f70b4
                            "(%+"PRIdMAX" years, %+"PRIdMAX" months, "
Packit 8f70b4
                            "%+"PRIdMAX" days),\n"),
Packit 8f70b4
                          pc.rel.year, pc.rel.month, pc.rel.day);
Packit 8f70b4
              dbg_printf (_("    new date/time = '%s'\n"),
Packit 8f70b4
                          debug_strfdatetime (&tm, &pc, dbg_tm,
Packit 8f70b4
                                              sizeof dbg_tm));
Packit 8f70b4
Packit 8f70b4
              /* Warn about crossing DST due to time adjustment.
Packit 8f70b4
                 Example: https://bugs.gnu.org/8357
Packit 8f70b4
                 env TZ=Europe/Helsinki \
Packit 8f70b4
                   date --debug \
Packit 8f70b4
                        -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
Packit 8f70b4
Packit 8f70b4
                 This case is different than DST changes due to time adjustment,
Packit 8f70b4
                 i.e., "1 day ago" vs "24 hours ago" are calculated in different
Packit 8f70b4
                 places.
Packit 8f70b4
Packit 8f70b4
                 'tm0.tm_isdst' contains the DST of the input date,
Packit 8f70b4
                 'tm.tm_isdst' is the normalized result after calling
Packit 8f70b4
                 mktime (&tm).
Packit 8f70b4
              */
Packit 8f70b4
              if (tm0.tm_isdst != -1 && tm.tm_isdst != tm0.tm_isdst)
Packit 8f70b4
                dbg_printf (_("warning: daylight saving time changed after "
Packit 8f70b4
                              "date adjustment\n"));
Packit 8f70b4
Packit 8f70b4
              /* Warn if the user did not ask to adjust days but mday changed,
Packit 8f70b4
                 or
Packit 8f70b4
                 user did not ask to adjust months/days but the month changed.
Packit 8f70b4
Packit 8f70b4
                 Example for first case:
Packit 8f70b4
                 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
Packit 8f70b4
                 User asked to adjust month, but the day changed from 31 to 01.
Packit 8f70b4
Packit 8f70b4
                 Example for second case:
Packit 8f70b4
                 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
Packit 8f70b4
                 User asked to adjust year, but the month changed from 02 to 03.
Packit 8f70b4
              */
Packit 8f70b4
              if (pc.rel.day == 0
Packit 8f70b4
                  && (tm.tm_mday != day
Packit 8f70b4
                      || (pc.rel.month == 0 && tm.tm_mon != month)))
Packit 8f70b4
                {
Packit 8f70b4
                  dbg_printf (_("warning: month/year adjustment resulted in "
Packit 8f70b4
                                "shifted dates:\n"));
Packit 8f70b4
                  char tm_year_buf[TM_YEAR_BUFSIZE];
Packit 8f70b4
                  dbg_printf (_("     adjusted Y M D: %s %02d %02d\n"),
Packit 8f70b4
                              tm_year_str (year, tm_year_buf), month + 1, day);
Packit 8f70b4
                  dbg_printf (_("   normalized Y M D: %s %02d %02d\n"),
Packit 8f70b4
                              tm_year_str (tm.tm_year, tm_year_buf),
Packit 8f70b4
                              tm.tm_mon + 1, tm.tm_mday);
Packit 8f70b4
                }
Packit 8f70b4
            }
Packit 8f70b4
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      /* The only "output" of this if-block is an updated Start value,
Packit 8f70b4
         so this block must follow others that clobber Start.  */
Packit 8f70b4
      if (pc.zones_seen)
Packit 8f70b4
        {
Packit 8f70b4
          intmax_t delta = pc.time_zone, t1;
Packit 8f70b4
          bool overflow = false;
Packit 8f70b4
#ifdef HAVE_TM_GMTOFF
Packit 8f70b4
          long int utcoff = tm.tm_gmtoff;
Packit 8f70b4
#else
Packit 8f70b4
          time_t t = Start;
Packit 8f70b4
          struct tm gmt;
Packit 8f70b4
          int utcoff = (gmtime_r (&t, &gmt)
Packit 8f70b4
                        ? tm_diff (&tm, &gmt)
Packit 8f70b4
                        : (overflow = true, 0));
Packit 8f70b4
#endif
Packit 8f70b4
          overflow |= INT_SUBTRACT_WRAPV (delta, utcoff, &delta);
Packit 8f70b4
          overflow |= INT_SUBTRACT_WRAPV (Start, delta, &t1;;
Packit 8f70b4
          if (overflow || time_overflow (t1))
Packit 8f70b4
            {
Packit 8f70b4
              if (pc.parse_datetime_debug)
Packit 8f70b4
                dbg_printf (_("error: timezone %d caused time_t overflow\n"),
Packit 8f70b4
                            pc.time_zone);
Packit 8f70b4
              goto fail;
Packit 8f70b4
            }
Packit 8f70b4
          Start = t1;
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      if (pc.parse_datetime_debug)
Packit 8f70b4
        {
Packit 8f70b4
          intmax_t Starti = Start;
Packit 8f70b4
          dbg_printf (_("'%s' = %"PRIdMAX" epoch-seconds\n"),
Packit 8f70b4
                      debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm),
Packit 8f70b4
                      Starti);
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
Packit 8f70b4
      /* Add relative hours, minutes, and seconds.  On hosts that support
Packit 8f70b4
         leap seconds, ignore the possibility of leap seconds; e.g.,
Packit 8f70b4
         "+ 10 minutes" adds 600 seconds, even if one of them is a
Packit 8f70b4
         leap second.  Typically this is not what the user wants, but it's
Packit 8f70b4
         too hard to do it the other way, because the time zone indicator
Packit 8f70b4
         must be applied before relative times, and if mktime is applied
Packit 8f70b4
         again the time zone will be lost.  */
Packit 8f70b4
      {
Packit 8f70b4
        intmax_t orig_ns = pc.seconds.tv_nsec;
Packit 8f70b4
        intmax_t sum_ns = orig_ns + pc.rel.ns;
Packit 8f70b4
        int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
Packit 8f70b4
        int d4 = (sum_ns - normalized_ns) / BILLION;
Packit 8f70b4
        intmax_t d1, t1, d2, t2, t3, t4;
Packit 8f70b4
        if (INT_MULTIPLY_WRAPV (pc.rel.hour, 60 * 60, &d1)
Packit 8f70b4
            || INT_ADD_WRAPV (Start, d1, &t1)
Packit 8f70b4
            || INT_MULTIPLY_WRAPV (pc.rel.minutes, 60, &d2)
Packit 8f70b4
            || INT_ADD_WRAPV (t1, d2, &t2)
Packit 8f70b4
            || INT_ADD_WRAPV (t2, pc.rel.seconds, &t3)
Packit 8f70b4
            || INT_ADD_WRAPV (t3, d4, &t4)
Packit 8f70b4
            || time_overflow (t4))
Packit 8f70b4
          {
Packit 8f70b4
            if (pc.parse_datetime_debug)
Packit 8f70b4
              dbg_printf (_("error: adding relative time caused an "
Packit 8f70b4
                            "overflow\n"));
Packit 8f70b4
            goto fail;
Packit 8f70b4
          }
Packit 8f70b4
Packit 8f70b4
        result->tv_sec = t4;
Packit 8f70b4
        result->tv_nsec = normalized_ns;
Packit 8f70b4
Packit 8f70b4
        if (pc.parse_datetime_debug
Packit 8f70b4
            && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns))
Packit 8f70b4
          {
Packit 8f70b4
            dbg_printf (_("after time adjustment (%+"PRIdMAX" hours, "
Packit 8f70b4
                          "%+"PRIdMAX" minutes, "
Packit 8f70b4
                          "%+"PRIdMAX" seconds, %+d ns),\n"),
Packit 8f70b4
                        pc.rel.hour, pc.rel.minutes, pc.rel.seconds,
Packit 8f70b4
                        pc.rel.ns);
Packit 8f70b4
            dbg_printf (_("    new time = %"PRIdMAX" epoch-seconds\n"), t4);
Packit 8f70b4
Packit 8f70b4
            /* Warn about crossing DST due to time adjustment.
Packit 8f70b4
               Example: https://bugs.gnu.org/8357
Packit 8f70b4
               env TZ=Europe/Helsinki           \
Packit 8f70b4
               date --debug                                             \
Packit 8f70b4
               -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
Packit 8f70b4
Packit 8f70b4
               This case is different than DST changes due to days adjustment,
Packit 8f70b4
               i.e., "1 day ago" vs "24 hours ago" are calculated in different
Packit 8f70b4
               places.
Packit 8f70b4
Packit 8f70b4
               'tm.tm_isdst' contains the date after date adjustment.  */
Packit 8f70b4
            struct tm lmt;
Packit 8f70b4
            if (tm.tm_isdst != -1 && localtime_rz (tz, &result->tv_sec, &lmt)
Packit 8f70b4
                && tm.tm_isdst != lmt.tm_isdst)
Packit 8f70b4
              dbg_printf (_("warning: daylight saving time changed after "
Packit 8f70b4
                            "time adjustment\n"));
Packit 8f70b4
          }
Packit 8f70b4
      }
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (pc.parse_datetime_debug)
Packit 8f70b4
    {
Packit 8f70b4
      /* Special case: using 'date -u' simply set TZ=UTC0 */
Packit 8f70b4
      if (! tzstring)
Packit 8f70b4
        dbg_printf (_("timezone: system default\n"));
Packit 8f70b4
      else if (STREQ (tzstring, "UTC0"))
Packit 8f70b4
        dbg_printf (_("timezone: Universal Time\n"));
Packit 8f70b4
      else
Packit 8f70b4
        dbg_printf (_("timezone: TZ=\"%s\" environment value\n"), tzstring);
Packit 8f70b4
Packit 8f70b4
      intmax_t sec = result->tv_sec;
Packit 8f70b4
      int nsec = result->tv_nsec;
Packit 8f70b4
      dbg_printf (_("final: %"PRIdMAX".%09d (epoch-seconds)\n"),
Packit 8f70b4
                  sec, nsec);
Packit 8f70b4
Packit 8f70b4
      struct tm gmt, lmt;
Packit 8f70b4
      bool got_utc = !!gmtime_r (&result->tv_sec, &gmt);
Packit 8f70b4
      if (got_utc)
Packit 8f70b4
        dbg_printf (_("final: %s (UTC)\n"),
Packit 8f70b4
                    debug_strfdatetime (&gmt, NULL,
Packit 8f70b4
                                        dbg_tm, sizeof dbg_tm));
Packit 8f70b4
      if (localtime_rz (tz, &result->tv_sec, &lmt))
Packit 8f70b4
        {
Packit 8f70b4
#ifdef HAVE_TM_GMTOFF
Packit 8f70b4
          bool got_utcoff = true;
Packit 8f70b4
          long int utcoff = lmt.tm_gmtoff;
Packit 8f70b4
#else
Packit 8f70b4
          bool got_utcoff = got_utc;
Packit 8f70b4
          int utcoff;
Packit 8f70b4
          if (got_utcoff)
Packit 8f70b4
            utcoff = tm_diff (&lmt, &gmt);
Packit 8f70b4
#endif
Packit 8f70b4
          if (got_utcoff)
Packit 8f70b4
            dbg_printf (_("final: %s (UTC%s)\n"),
Packit 8f70b4
                        debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm),
Packit 8f70b4
                        time_zone_str (utcoff, time_zone_buf));
Packit 8f70b4
          else
Packit 8f70b4
            dbg_printf (_("final: %s (unknown time zone offset)\n"),
Packit 8f70b4
                        debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm));
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  ok = true;
Packit 8f70b4
Packit 8f70b4
 fail:
Packit 8f70b4
  if (tz != tzdefault)
Packit 8f70b4
    tzfree (tz);
Packit 8f70b4
  free (tz1alloc);
Packit 8f70b4
  return ok;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
#if TEST
Packit 8f70b4
Packit 8f70b4
int
Packit 8f70b4
main (int ac, char **av)
Packit 8f70b4
{
Packit 8f70b4
  char buff[BUFSIZ];
Packit 8f70b4
Packit 8f70b4
  printf ("Enter date, or blank line to exit.\n\t> ");
Packit 8f70b4
  fflush (stdout);
Packit 8f70b4
Packit 8f70b4
  buff[BUFSIZ - 1] = '\0';
Packit 8f70b4
  while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
Packit 8f70b4
    {
Packit 8f70b4
      struct timespec d;
Packit 8f70b4
      struct tm const *tm;
Packit 8f70b4
      if (! parse_datetime (&d, buff, NULL))
Packit 8f70b4
        printf ("Bad format - couldn't convert.\n");
Packit 8f70b4
      else if (! (tm = localtime (&d.tv_sec)))
Packit 8f70b4
        {
Packit 8f70b4
          intmax_t sec = d.tv_sec;
Packit 8f70b4
          printf ("localtime (%"PRIdMAX") failed\n", sec);
Packit 8f70b4
        }
Packit 8f70b4
      else
Packit 8f70b4
        {
Packit 8f70b4
          int ns = d.tv_nsec;
Packit 8f70b4
          char tm_year_buf[TM_YEAR_BUFSIZE];
Packit 8f70b4
          printf ("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
Packit 8f70b4
                  tm_year_str (tm->tm_year, tm_year_buf),
Packit 8f70b4
                  tm->tm_mon + 1, tm->tm_mday,
Packit 8f70b4
                  tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
Packit 8f70b4
        }
Packit 8f70b4
      printf ("\t> ");
Packit 8f70b4
      fflush (stdout);
Packit 8f70b4
    }
Packit 8f70b4
  return 0;
Packit 8f70b4
}
Packit 8f70b4
#endif /* TEST */