%{
/* Parse a string into an internal time stamp.
- Copyright 1999 Free Software Foundation, Inc.
+ Copyright (C) 1999, 2000, 2002, 2003 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#ifdef HAVE_CONFIG_H
# include <config.h>
-# ifdef HAVE_ALLOCA_H
-# include <alloca.h>
-# endif
#endif
+#include <alloca.h>
+
/* Since the code of getdate.y is not included in the Emacs executable
itself, there is no need to #define static in this file. Even if
the code were included in the Emacs executable, it probably
- Its arg may be any int or unsigned int; it need not be an unsigned char.
- It's guaranteed to evaluate its argument exactly once.
- It's typically faster.
- Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
- only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless
- it's important to use the locale's definition of `digit' even when the
- host does not conform to Posix. */
+ POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
+ ISDIGIT_LOCALE unless it's important to use the locale's definition
+ of `digit' even when the host does not conform to POSIX. */
#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
#if STDC_HEADERS || HAVE_STRING_H
# include <string.h>
#endif
-#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
# define __attribute__(x)
#endif
#endif
#define EPOCH_YEAR 1970
-#define TM_YEAR_ORIGIN 1900
+#define TM_YEAR_BASE 1900
#define HOUR(x) ((x) * 60)
+/* An integer value, and the number of digits in its textual
+ representation. */
+typedef struct
+{
+ int value;
+ int digits;
+} textint;
+
/* An entry in the lexical lookup table. */
typedef struct
{
enum { MERam, MERpm, MER24 };
/* Information passed to and from the parser. */
-struct parser_control
-{
+typedef struct
+{
/* The input string remaining to be parsed. */
const char *input;
int meridian;
/* Gregorian year, month, day, hour, minutes, and seconds. */
- int year;
+ textint year;
int month;
int day;
int hour;
/* Table of local time zone abbrevations, terminated by a null entry. */
table local_time_zone_table[3];
-};
+} parser_control;
-#define PC (* (struct parser_control *) parm)
+#define PC (* (parser_control *) parm)
#define YYLEX_PARAM parm
#define YYPARSE_PARAM parm
-#define YYSTYPE int
static int yyerror ();
static int yylex ();
/* This grammar has 13 shift/reduce conflicts. */
%expect 13
-%token tAGO tDAY tDAY_UNIT tDAYZONE tDST tHOUR_UNIT tID
-%token tLOCAL_ZONE tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
-%token tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
+%union
+{
+ int intval;
+ textint textintval;
+}
+
+%token tAGO tDST
+
+%token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
+%token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
+
+%token <textintval> tSNUMBER tUNUMBER
+
+%type <intval> o_merid
%%
time:
tUNUMBER tMERIDIAN
{
- PC.hour = $1;
+ PC.hour = $1.value;
PC.minutes = 0;
PC.seconds = 0;
PC.meridian = $2;
}
| tUNUMBER ':' tUNUMBER o_merid
{
- PC.hour = $1;
- PC.minutes = $3;
+ PC.hour = $1.value;
+ PC.minutes = $3.value;
PC.seconds = 0;
PC.meridian = $4;
}
| tUNUMBER ':' tUNUMBER tSNUMBER
{
- PC.hour = $1;
- PC.minutes = $3;
+ PC.hour = $1.value;
+ PC.minutes = $3.value;
PC.meridian = MER24;
PC.zones_seen++;
- PC.time_zone = $4 % 100 + ($4 / 100) * 60;
+ PC.time_zone = $4.value % 100 + ($4.value / 100) * 60;
}
| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid
{
- PC.hour = $1;
- PC.minutes = $3;
- PC.seconds = $5;
+ PC.hour = $1.value;
+ PC.minutes = $3.value;
+ PC.seconds = $5.value;
PC.meridian = $6;
}
| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER
{
- PC.hour = $1;
- PC.minutes = $3;
- PC.seconds = $5;
+ PC.hour = $1.value;
+ PC.minutes = $3.value;
+ PC.seconds = $5.value;
PC.meridian = MER24;
PC.zones_seen++;
- PC.time_zone = $6 % 100 + ($6 / 100) * 60;
+ PC.time_zone = $6.value % 100 + ($6.value / 100) * 60;
}
;
}
| tUNUMBER tDAY
{
- PC.day_ordinal = $1;
+ PC.day_ordinal = $1.value;
PC.day_number = $2;
}
;
date:
tUNUMBER '/' tUNUMBER
{
- PC.month = $1;
- PC.day = $3;
+ PC.month = $1.value;
+ PC.day = $3.value;
}
| tUNUMBER '/' tUNUMBER '/' tUNUMBER
{
- /* Interpret as YYYY/MM/DD if 1000 <= $1, otherwise as MM/DD/YY.
+ /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ otherwise as MM/DD/YY.
The goal in recognizing YYYY/MM/DD is solely to support legacy
machine-generated dates like those in an RCS log listing. If
you want portability, use the ISO 8601 format. */
- if (1000 <= $1)
+ if (4 <= $1.digits)
{
PC.year = $1;
- PC.month = $3;
- PC.day = $5;
+ PC.month = $3.value;
+ PC.day = $5.value;
}
else
{
- PC.month = $1;
- PC.day = $3;
+ PC.month = $1.value;
+ PC.day = $3.value;
PC.year = $5;
}
}
{
/* ISO 8601 format. YYYY-MM-DD. */
PC.year = $1;
- PC.month = -$2;
- PC.day = -$3;
+ PC.month = -$2.value;
+ PC.day = -$3.value;
}
| tUNUMBER tMONTH tSNUMBER
{
/* e.g. 17-JUN-1992. */
- PC.day = $1;
+ PC.day = $1.value;
PC.month = $2;
- PC.year = -$3;
+ PC.year.value = -$3.value;
+ PC.year.digits = $3.digits;
}
| tMONTH tUNUMBER
{
PC.month = $1;
- PC.day = $2;
+ PC.day = $2.value;
}
| tMONTH tUNUMBER ',' tUNUMBER
{
PC.month = $1;
- PC.day = $2;
+ PC.day = $2.value;
PC.year = $4;
}
| tUNUMBER tMONTH
{
+ PC.day = $1.value;
PC.month = $2;
- PC.day = $1;
}
| tUNUMBER tMONTH tUNUMBER
{
+ PC.day = $1.value;
PC.month = $2;
- PC.day = $1;
PC.year = $3;
}
;
relunit:
tUNUMBER tYEAR_UNIT
- { PC.rel_year += $1 * $2; }
+ { PC.rel_year += $1.value * $2; }
| tSNUMBER tYEAR_UNIT
- { PC.rel_year += $1 * $2; }
+ { PC.rel_year += $1.value * $2; }
| tYEAR_UNIT
{ PC.rel_year += $1; }
| tUNUMBER tMONTH_UNIT
- { PC.rel_month += $1 * $2; }
+ { PC.rel_month += $1.value * $2; }
| tSNUMBER tMONTH_UNIT
- { PC.rel_month += $1 * $2; }
+ { PC.rel_month += $1.value * $2; }
| tMONTH_UNIT
{ PC.rel_month += $1; }
| tUNUMBER tDAY_UNIT
- { PC.rel_day += $1 * $2; }
+ { PC.rel_day += $1.value * $2; }
| tSNUMBER tDAY_UNIT
- { PC.rel_day += $1 * $2; }
+ { PC.rel_day += $1.value * $2; }
| tDAY_UNIT
{ PC.rel_day += $1; }
| tUNUMBER tHOUR_UNIT
- { PC.rel_hour += $1 * $2; }
+ { PC.rel_hour += $1.value * $2; }
| tSNUMBER tHOUR_UNIT
- { PC.rel_hour += $1 * $2; }
+ { PC.rel_hour += $1.value * $2; }
| tHOUR_UNIT
{ PC.rel_hour += $1; }
| tUNUMBER tMINUTE_UNIT
- { PC.rel_minutes += $1 * $2; }
+ { PC.rel_minutes += $1.value * $2; }
| tSNUMBER tMINUTE_UNIT
- { PC.rel_minutes += $1 * $2; }
+ { PC.rel_minutes += $1.value * $2; }
| tMINUTE_UNIT
{ PC.rel_minutes += $1; }
| tUNUMBER tSEC_UNIT
- { PC.rel_seconds += $1 * $2; }
+ { PC.rel_seconds += $1.value * $2; }
| tSNUMBER tSEC_UNIT
- { PC.rel_seconds += $1 * $2; }
+ { PC.rel_seconds += $1.value * $2; }
| tSEC_UNIT
{ PC.rel_seconds += $1; }
;
number:
tUNUMBER
{
- if (PC.dates_seen && ! PC.rels_seen && (PC.times_seen || 100 <= $1))
+ if (PC.dates_seen
+ && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits))
PC.year = $1;
else
{
- if (10000 < $1)
+ if (4 < $1.digits)
{
PC.dates_seen++;
- PC.day = $1 % 100;
- PC.month = ($1 / 100) % 100;
- PC.year = $1 / 10000;
+ PC.day = $1.value % 100;
+ PC.month = ($1.value / 100) % 100;
+ PC.year.value = $1.value / 10000;
+ PC.year.digits = $1.digits - 4;
}
else
{
PC.times_seen++;
- if ($1 < 100)
+ if ($1.digits <= 2)
{
- PC.hour = $1;
+ PC.hour = $1.value;
PC.minutes = 0;
}
else
{
- PC.hour = $1 / 100;
- PC.minutes = $1 % 100;
+ PC.hour = $1.value / 100;
+ PC.minutes = $1.value % 100;
}
PC.seconds = 0;
PC.meridian = MER24;
may define-away `const'. We want the prototype for get_date to have
the same signature as the function definition. */
#include "getdate.h"
+#include "unlocked-io.h"
#ifndef gmtime
struct tm *gmtime ();
}
static int
-to_year (int year)
+to_year (textint textyear)
{
+ int year = textyear.value;
+
if (year < 0)
year = -year;
/* XPG4 suggests that years 00-68 map to 2000-2068, and
years 69-99 map to 1969-1999. */
- if (year < 69)
- year += 2000;
- else if (year < 100)
- year += 1900;
+ if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
return year;
}
static table const *
-lookup_zone (struct parser_control const *pc, char const *name)
+lookup_zone (parser_control const *pc, char const *name)
{
table const *tp;
return 0;
}
-/* Yield A - B, measured in seconds. */
+#if ! HAVE_TM_GMTOFF
+/* Yield the difference between *A and *B,
+ measured in seconds, ignoring leap seconds.
+ The body of this function is taken directly from the GNU C Library;
+ see src/strftime.c. */
static int
-difftm (struct tm *a, struct tm *b)
+tm_diff (struct tm const *a, struct tm const *b)
{
- int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
- int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
- int days = (
- /* difference in day of year */
- a->tm_yday - b->tm_yday
- /* + intervening leap days */
- + ((ay >> 2) - (by >> 2))
- - (ay / 100 - by / 100)
- + ((ay / 100 >> 2) - (by / 100 >> 2))
- /* + difference in years * 365 */
- + (int) (ay - by) * 365
- );
+ /* Compute intervening leap days correctly even if year is negative.
+ Take care to avoid int overflow in leap day calculations,
+ but it's OK to assume that A and B are close to each other. */
+ int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
+ int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = a100 >> 2;
+ int b400 = b100 >> 2;
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+ int years = a->tm_year - b->tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (a->tm_yday - b->tm_yday));
return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ (a->tm_min - b->tm_min))
+ (a->tm_sec - b->tm_sec));
}
+#endif /* ! HAVE_TM_GMTOFF */
static table const *
-lookup_word (struct parser_control const *pc, char *word)
+lookup_word (parser_control const *pc, char *word)
{
char *p;
char *q;
}
static int
-yylex (YYSTYPE *lvalp, struct parser_control *pc)
+yylex (YYSTYPE *lvalp, parser_control *pc)
{
unsigned char c;
int count;
if (ISDIGIT (c) || c == '-' || c == '+')
{
+ char const *p;
int sign;
+ int value;
if (c == '-' || c == '+')
{
sign = c == '-' ? -1 : 1;
- if (! ISDIGIT (*++pc->input))
+ c = *++pc->input;
+ if (! ISDIGIT (c))
/* skip the '-' sign */
continue;
}
else
sign = 0;
- for (*lvalp = 0; ISDIGIT (c = *pc->input++);)
- *lvalp = 10 * *lvalp + (c - '0');
- pc->input--;
- if (sign < 0)
- *lvalp = - *lvalp;
+ p = pc->input;
+ value = 0;
+ do
+ {
+ value = 10 * value + c - '0';
+ c = *++p;
+ }
+ while (ISDIGIT (c));
+ lvalp->textintval.value = sign < 0 ? -value : value;
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
return sign ? tSNUMBER : tUNUMBER;
}
*p = '\0';
tp = lookup_word (pc, buff);
if (! tp)
- return tID;
- *lvalp = tp->value;
+ return '?';
+ lvalp->intval = tp->value;
return tp->type;
}
return 0;
}
-/* ?? */
+/* Parse a date/time string P. Return the corresponding time_t value,
+ or (time_t) -1 if there is an error. P can be an incomplete or
+ relative time specification; if so, use *NOW as the basis for the
+ returned time. */
time_t
get_date (const char *p, const time_t *now)
{
struct tm *tmp = localtime (&Start);
struct tm tm;
struct tm tm0;
- struct parser_control pc;
+ parser_control pc;
if (! tmp)
return -1;
pc.input = p;
- pc.year = tmp->tm_year + TM_YEAR_ORIGIN;
+ pc.year.value = tmp->tm_year + TM_YEAR_BASE;
+ pc.year.digits = 4;
pc.month = tmp->tm_mon + 1;
pc.day = tmp->tm_mday;
pc.hour = tmp->tm_hour;
pc.local_zones_seen = 0;
pc.zones_seen = 0;
-#if HAVE_TM_ZONE
+#if HAVE_STRUCT_TM_TM_ZONE
pc.local_time_zone_table[0].name = tmp->tm_zone;
pc.local_time_zone_table[0].type = tLOCAL_ZONE;
pc.local_time_zone_table[0].value = tmp->tm_isdst;
|| (pc.local_zones_seen && 1 < pc.local_isdst))
return -1;
- tm.tm_year = to_year (pc.year) - TM_YEAR_ORIGIN + pc.rel_year;
+ tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
tm.tm_mon = pc.month - 1 + pc.rel_month;
tm.tm_mday = pc.day + pc.rel_day;
if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
{
tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
}
- tm.tm_hour += pc.rel_hour;
- tm.tm_min += pc.rel_minutes;
- tm.tm_sec += pc.rel_seconds;
/* Let mktime deduce tm_isdst if we have an absolute time stamp,
or if the relative time stamp mentions days, months, or years. */
- if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day | pc.rel_month | pc.rel_year)
+ if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
+ | pc.rel_month | pc.rel_year)
tm.tm_isdst = -1;
/* But if the input explicitly specifies local time with or without
if (pc.zones_seen)
{
tm = tm0;
- if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_ORIGIN)
+ if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
{
tm.tm_mday++;
pc.time_zone += 24 * 60;
{
tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
+ 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
+ tm.tm_isdst = -1;
Start = mktime (&tm);
if (Start == (time_t) -1)
return Start;
if (pc.zones_seen)
{
- int delta;
+ int delta = pc.time_zone * 60;
+#ifdef HAVE_TM_GMTOFF
+ delta -= tm.tm_gmtoff;
+#else
struct tm *gmt = gmtime (&Start);
if (! gmt)
return -1;
- delta = pc.time_zone * 60 + difftm (gmt, &tm);
- if ((Start - delta < Start) != (delta < 0))
+ delta -= tm_diff (&tm, gmt);
+#endif
+ if ((Start < Start - delta) != (delta < 0))
return -1; /* time_t overflow */
Start -= delta;
}
+ /* Add relative hours, minutes, and seconds. Ignore leap seconds;
+ i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
+ leap second. Typically this is not what the user wants, but it's
+ too hard to do it the other way, because the time zone indicator
+ must be applied before relative times, and if mktime is applied
+ again the time zone will be lost. */
+ {
+ time_t t0 = Start;
+ long d1 = 60 * 60 * (long) pc.rel_hour;
+ time_t t1 = t0 + d1;
+ long d2 = 60 * (long) pc.rel_minutes;
+ time_t t2 = t1 + d2;
+ int d3 = pc.rel_seconds;
+ time_t t3 = t2 + d3;
+ if ((d1 / (60 * 60) ^ pc.rel_hour)
+ | (d2 / 60 ^ pc.rel_minutes)
+ | ((t0 + d1 < t0) ^ (d1 < 0))
+ | ((t1 + d2 < t1) ^ (d2 < 0))
+ | ((t2 + d3 < t2) ^ (d3 < 0)))
+ return -1;
+ Start = t3;
+ }
+
return Start;
}