X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fgetdate.y;h=15073107bc926f908a271cde8878288e5750c88f;hb=738e912a0f97a43c2b195bcec6fe34512208e81f;hp=e67af871ee5892b438de92401be0ace623ed08ae;hpb=8a62dabaf9162e3f1cdd6dabb5929da052308769;p=gnulib.git diff --git a/lib/getdate.y b/lib/getdate.y index e67af871e..15073107b 100644 --- a/lib/getdate.y +++ b/lib/getdate.y @@ -1,6 +1,8 @@ %{ /* Parse a string into an internal time stamp. - Copyright 1999 Free Software Foundation, Inc. + + Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006 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 @@ -14,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Originally written by Steven M. Bellovin while at the University of North Carolina at Chapel Hill. Later tweaked by @@ -22,15 +24,28 @@ and Jim Berets in August, 1990. Modified by Paul Eggert in August 1999 to do - the right thing about local DST. Unlike previous versions, this - version is reentrant. */ + the right thing about local DST. Also modified by Paul Eggert + in February 2004 to support + nanosecond-resolution time stamps, and in October 2004 to support + TZ strings in dates. */ -#ifdef HAVE_CONFIG_H -# include -# ifdef HAVE_ALLOCA_H -# include -# endif -#endif +/* FIXME: Check for arithmetic overflow in all cases, not just + some of them. */ + +#include + +#include "getdate.h" + +/* There's no need to extend the stack, so there's no need to involve + alloca. */ +#define YYSTACK_USE_ALLOCA 0 + +/* Tell Bison how much stack space is needed. 20 should be plenty for + this grammar, which is not right recursive. Beware setting it too + high, since that might cause problems on machines whose + implementations have lame stack-overflow checking. */ +#define YYMAXDEPTH 20 +#define YYINITDEPTH YYMAXDEPTH /* 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 @@ -43,49 +58,63 @@ #endif #include +#include +#include +#include +#include -#if HAVE_STDLIB_H -# include /* for `free'; used by Bison 1.27 */ -#endif +#include "setenv.h" +#include "xalloc.h" -#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII) -# define IN_CTYPE_DOMAIN(c) 1 -#else -# define IN_CTYPE_DOMAIN(c) isascii (c) -#endif -#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c)) -#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) -#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) -#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) - -/* ISDIGIT differs from ISDIGIT_LOCALE, as follows: - - 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. +/* ISDIGIT differs from isdigit, as follows: + - Its arg may be any int or unsigned int; it need not be an unsigned char + or EOF. - 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. */ -#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) - -#if STDC_HEADERS || HAVE_STRING_H -# include -#endif - -#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7) -# define __attribute__(x) + POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to + isdigit 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 int) (c) - '0' <= 9) + +#ifndef __attribute__ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__ +# define __attribute__(x) +# endif #endif #ifndef ATTRIBUTE_UNUSED # define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) #endif +/* Shift A right by B bits portably, by dividing A by 2**B and + truncating towards minus infinity. A and B should be free of side + effects, and B should be in the range 0 <= B <= INT_BITS - 2, where + INT_BITS is the number of useful bits in an int. GNU code can + assume that INT_BITS is at least 32. + + ISO C99 says that A >> B is implementation-defined if A < 0. Some + implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift + right in the usual way when A < 0, so SHR falls back on division if + ordinary A >> B doesn't seem to be the usual signed shift. */ +#define SHR(a, b) \ + (-1 >> 1 == -1 \ + ? (a) >> (b) \ + : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0)) + #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 +{ + bool negative; + long int value; + size_t digits; +} textint; + /* An entry in the lexical lookup table. */ typedef struct { @@ -97,14 +126,35 @@ typedef struct /* Meridian: am, pm, or 24-hour style. */ enum { MERam, MERpm, MER24 }; +enum { BILLION = 1000000000, LOG10_BILLION = 9 }; + +/* Relative times. */ +typedef struct +{ + /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */ + long int year; + long int month; + long int day; + long int hour; + long int minutes; + long int seconds; + long int ns; +} relative_time; + +#if HAVE_COMPOUND_LITERALS +# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 }) +#else +static relative_time const RELATIVE_TIME_0; +#endif + /* Information passed to and from the parser. */ -struct parser_control +typedef struct { /* The input string remaining to be parsed. */ const char *input; /* N, if this is the Nth Tuesday. */ - int day_ordinal; + long int day_ordinal; /* Day of week; Sunday is 0. */ int day_number; @@ -113,306 +163,427 @@ struct parser_control int local_isdst; /* Time zone, in minutes east of UTC. */ - int time_zone; + long int time_zone; /* Style used for time. */ int meridian; - /* Gregorian year, month, day, hour, minutes, and seconds. */ - int year; - int month; - int day; - int hour; - int minutes; - int seconds; - - /* Relative year, month, day, hour, minutes, and seconds. */ - int rel_year; - int rel_month; - int rel_day; - int rel_hour; - int rel_minutes; - int rel_seconds; - - /* Counts of nonterminals of various flavors parsed so far. */ - int dates_seen; - int days_seen; - int local_zones_seen; - int rels_seen; - int times_seen; - int zones_seen; + /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */ + textint year; + long int month; + long int day; + long int hour; + long int minutes; + struct timespec seconds; /* includes nanoseconds */ + + /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */ + relative_time rel; + + /* Presence or counts of nonterminals of various flavors parsed so far. */ + bool timespec_seen; + bool rels_seen; + size_t dates_seen; + size_t days_seen; + size_t local_zones_seen; + size_t dsts_seen; + size_t times_seen; + size_t zones_seen; /* Table of local time zone abbrevations, terminated by a null entry. */ table local_time_zone_table[3]; -}; - -#define PC (* (struct parser_control *) parm) -#define YYLEX_PARAM parm -#define YYPARSE_PARAM parm -#define YYSTYPE int +} parser_control; -static int yyerror (); -static int yylex (); +union YYSTYPE; +static int yylex (union YYSTYPE *, parser_control *); +static int yyerror (parser_control const *, char const *); +static long int time_zone_hhmm (textint, long int); %} -/* We want a reentrant parser. */ -%pure_parser +/* We want a reentrant parser, even if the TZ manipulation and the calls to + localtime and gmtime are not reentrant. */ +%pure-parser +%parse-param { parser_control *pc } +%lex-param { parser_control *pc } + +/* This grammar has 20 shift/reduce conflicts. */ +%expect 20 + +%union +{ + long int intval; + textint textintval; + struct timespec timespec; + relative_time rel; +} + +%token tAGO tDST -/* This grammar has 13 shift/reduce conflicts. */ -%expect 13 +%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT +%token tDAY_UNIT -%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 +%token tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN +%token tMONTH tORDINAL tZONE + +%token tSNUMBER tUNUMBER +%token tSDECIMAL_NUMBER tUDECIMAL_NUMBER + +%type o_colon_minutes o_merid +%type seconds signed_seconds unsigned_seconds + +%type relunit relunit_snumber %% spec: + timespec + | items + ; + +timespec: + '@' seconds + { + pc->seconds = $2; + pc->timespec_seen = true; + } + ; + +items: /* empty */ - | spec item + | items item ; item: time - { PC.times_seen++; } + { pc->times_seen++; } | local_zone - { PC.local_zones_seen++; } + { pc->local_zones_seen++; } | zone - { PC.zones_seen++; } + { pc->zones_seen++; } | date - { PC.dates_seen++; } + { pc->dates_seen++; } | day - { PC.days_seen++; } + { pc->days_seen++; } | rel - { PC.rels_seen++; } + { pc->rels_seen = true; } | number ; time: tUNUMBER tMERIDIAN { - PC.hour = $1; - PC.minutes = 0; - PC.seconds = 0; - PC.meridian = $2; + pc->hour = $1.value; + pc->minutes = 0; + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = $2; } | tUNUMBER ':' tUNUMBER o_merid { - PC.hour = $1; - PC.minutes = $3; - PC.seconds = 0; - PC.meridian = $4; + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = $4; } - | tUNUMBER ':' tUNUMBER tSNUMBER + | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes { - PC.hour = $1; - PC.minutes = $3; - PC.meridian = MER24; - PC.zones_seen++; - PC.time_zone = $4 % 100 + ($4 / 100) * 60; + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = MER24; + pc->zones_seen++; + pc->time_zone = time_zone_hhmm ($4, $5); } - | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid { - PC.hour = $1; - PC.minutes = $3; - PC.seconds = $5; - PC.meridian = $6; + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds = $5; + pc->meridian = $6; } - | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes { - PC.hour = $1; - PC.minutes = $3; - PC.seconds = $5; - PC.meridian = MER24; - PC.zones_seen++; - PC.time_zone = $6 % 100 + ($6 / 100) * 60; + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds = $5; + pc->meridian = MER24; + pc->zones_seen++; + pc->time_zone = time_zone_hhmm ($6, $7); } ; local_zone: tLOCAL_ZONE - { PC.local_isdst = $1; } + { + pc->local_isdst = $1; + pc->dsts_seen += (0 < $1); + } | tLOCAL_ZONE tDST - { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; } + { + pc->local_isdst = 1; + pc->dsts_seen += (0 < $1) + 1; + } ; zone: tZONE - { PC.time_zone = $1; } + { pc->time_zone = $1; } + | tZONE relunit_snumber + { pc->time_zone = $1; + pc->rel.ns += $2.ns; + pc->rel.seconds += $2.seconds; + pc->rel.minutes += $2.minutes; + pc->rel.hour += $2.hour; + pc->rel.day += $2.day; + pc->rel.month += $2.month; + pc->rel.year += $2.year; + pc->rels_seen = true; } + | tZONE tSNUMBER o_colon_minutes + { pc->time_zone = $1 + time_zone_hhmm ($2, $3); } | tDAYZONE - { PC.time_zone = $1 + 60; } + { pc->time_zone = $1 + 60; } | tZONE tDST - { PC.time_zone = $1 + 60; } + { pc->time_zone = $1 + 60; } ; day: tDAY { - PC.day_ordinal = 1; - PC.day_number = $1; + pc->day_ordinal = 1; + pc->day_number = $1; } | tDAY ',' { - PC.day_ordinal = 1; - PC.day_number = $1; + pc->day_ordinal = 1; + pc->day_number = $1; + } + | tORDINAL tDAY + { + pc->day_ordinal = $1; + pc->day_number = $2; } | tUNUMBER tDAY { - PC.day_ordinal = $1; - PC.day_number = $2; + 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->year = $1; + pc->month = $3.value; + pc->day = $5.value; } else { - PC.month = $1; - PC.day = $3; - PC.year = $5; + pc->month = $1.value; + pc->day = $3.value; + pc->year = $5; } } | tUNUMBER tSNUMBER tSNUMBER { /* ISO 8601 format. YYYY-MM-DD. */ - PC.year = $1; - PC.month = -$2; - PC.day = -$3; + pc->year = $1; + pc->month = -$2.value; + pc->day = -$3.value; } | tUNUMBER tMONTH tSNUMBER { /* e.g. 17-JUN-1992. */ - PC.day = $1; - PC.month = $2; - PC.year = -$3; + pc->day = $1.value; + pc->month = $2; + pc->year.value = -$3.value; + pc->year.digits = $3.digits; + } + | tMONTH tSNUMBER tSNUMBER + { + /* e.g. JUN-17-1992. */ + pc->month = $1; + pc->day = -$2.value; + pc->year.value = -$3.value; + pc->year.digits = $3.digits; } | tMONTH tUNUMBER { - PC.month = $1; - PC.day = $2; + pc->month = $1; + pc->day = $2.value; } | tMONTH tUNUMBER ',' tUNUMBER { - PC.month = $1; - PC.day = $2; - PC.year = $4; + pc->month = $1; + pc->day = $2.value; + pc->year = $4; } | tUNUMBER tMONTH { - PC.month = $2; - PC.day = $1; + pc->day = $1.value; + pc->month = $2; } | tUNUMBER tMONTH tUNUMBER { - PC.month = $2; - PC.day = $1; - PC.year = $3; + pc->day = $1.value; + pc->month = $2; + pc->year = $3; } ; rel: relunit tAGO { - PC.rel_seconds = -PC.rel_seconds; - PC.rel_minutes = -PC.rel_minutes; - PC.rel_hour = -PC.rel_hour; - PC.rel_day = -PC.rel_day; - PC.rel_month = -PC.rel_month; - PC.rel_year = -PC.rel_year; + pc->rel.ns -= $1.ns; + pc->rel.seconds -= $1.seconds; + pc->rel.minutes -= $1.minutes; + pc->rel.hour -= $1.hour; + pc->rel.day -= $1.day; + pc->rel.month -= $1.month; + pc->rel.year -= $1.year; } | relunit + { + pc->rel.ns += $1.ns; + pc->rel.seconds += $1.seconds; + pc->rel.minutes += $1.minutes; + pc->rel.hour += $1.hour; + pc->rel.day += $1.day; + pc->rel.month += $1.month; + pc->rel.year += $1.year; + } ; relunit: - tUNUMBER tYEAR_UNIT - { PC.rel_year += $1 * $2; } - | tSNUMBER tYEAR_UNIT - { PC.rel_year += $1 * $2; } + tORDINAL tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1; } + | tUNUMBER tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1.value; } | tYEAR_UNIT - { PC.rel_year += $1; } + { $$ = RELATIVE_TIME_0; $$.year = 1; } + | tORDINAL tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = $1; } | tUNUMBER tMONTH_UNIT - { PC.rel_month += $1 * $2; } - | tSNUMBER tMONTH_UNIT - { PC.rel_month += $1 * $2; } + { $$ = RELATIVE_TIME_0; $$.month = $1.value; } | tMONTH_UNIT - { PC.rel_month += $1; } + { $$ = RELATIVE_TIME_0; $$.month = 1; } + | tORDINAL tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; } | tUNUMBER tDAY_UNIT - { PC.rel_day += $1 * $2; } - | tSNUMBER tDAY_UNIT - { PC.rel_day += $1 * $2; } + { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; } | tDAY_UNIT - { PC.rel_day += $1; } + { $$ = RELATIVE_TIME_0; $$.day = $1; } + | tORDINAL tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = $1; } | tUNUMBER tHOUR_UNIT - { PC.rel_hour += $1 * $2; } - | tSNUMBER tHOUR_UNIT - { PC.rel_hour += $1 * $2; } + { $$ = RELATIVE_TIME_0; $$.hour = $1.value; } | tHOUR_UNIT - { PC.rel_hour += $1; } + { $$ = RELATIVE_TIME_0; $$.hour = 1; } + | tORDINAL tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = $1; } | tUNUMBER tMINUTE_UNIT - { PC.rel_minutes += $1 * $2; } - | tSNUMBER tMINUTE_UNIT - { PC.rel_minutes += $1 * $2; } + { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; } | tMINUTE_UNIT - { PC.rel_minutes += $1; } + { $$ = RELATIVE_TIME_0; $$.minutes = 1; } + | tORDINAL tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1; } | tUNUMBER tSEC_UNIT - { PC.rel_seconds += $1 * $2; } - | tSNUMBER tSEC_UNIT - { PC.rel_seconds += $1 * $2; } + { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; } + | tSDECIMAL_NUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; } + | tUDECIMAL_NUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; } | tSEC_UNIT - { PC.rel_seconds += $1; } + { $$ = RELATIVE_TIME_0; $$.seconds = 1; } + | relunit_snumber + ; + +relunit_snumber: + tSNUMBER tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1.value; } + | tSNUMBER tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = $1.value; } + | tSNUMBER tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; } + | tSNUMBER tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = $1.value; } + | tSNUMBER tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; } + | tSNUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; } + ; + +seconds: signed_seconds | unsigned_seconds; + +signed_seconds: + tSDECIMAL_NUMBER + | tSNUMBER + { $$.tv_sec = $1.value; $$.tv_nsec = 0; } + ; + +unsigned_seconds: + tUDECIMAL_NUMBER + | tUNUMBER + { $$.tv_sec = $1.value; $$.tv_nsec = 0; } ; number: tUNUMBER { - if (PC.dates_seen && ! PC.rels_seen && (PC.times_seen || 100 <= $1)) - PC.year = $1; + if (pc->dates_seen && ! pc->year.digits + && ! 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->dates_seen++; + 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) + pc->times_seen++; + if ($1.digits <= 2) { - PC.hour = $1; - PC.minutes = 0; + 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; + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = MER24; } } } ; +o_colon_minutes: + /* empty */ + { $$ = -1; } + | ':' tUNUMBER + { $$ = $2.value; } + ; + o_merid: /* empty */ { $$ = MER24; } @@ -422,28 +593,13 @@ o_merid: %% -/* Include this file down here because bison inserts code above which - may define-away `const'. We want the prototype for get_date to have - the same signature as the function definition. */ -#include "getdate.h" - -#ifndef gmtime -struct tm *gmtime (); -#endif -#ifndef localtime -struct tm *localtime (); -#endif -#ifndef mktime -time_t mktime (); -#endif - static table const meridian_table[] = { { "AM", tMERIDIAN, MERam }, { "A.M.", tMERIDIAN, MERam }, { "PM", tMERIDIAN, MERpm }, { "P.M.", tMERIDIAN, MERpm }, - { 0, 0, 0 } + { NULL, 0, 0 } }; static table const dst_table[] = @@ -477,7 +633,7 @@ static table const month_and_day_table[] = { "THURS", tDAY, 4 }, { "FRIDAY", tDAY, 5 }, { "SATURDAY", tDAY, 6 }, - { 0, 0, 0 } + { NULL, 0, 0 } }; static table const time_units_table[] = @@ -492,33 +648,44 @@ static table const time_units_table[] = { "MIN", tMINUTE_UNIT, 1 }, { "SECOND", tSEC_UNIT, 1 }, { "SEC", tSEC_UNIT, 1 }, - { 0, 0, 0 } + { NULL, 0, 0 } }; /* Assorted relative-time words. */ static table const relative_time_table[] = { - { "TOMORROW", tMINUTE_UNIT, 24 * 60 }, - { "YESTERDAY",tMINUTE_UNIT, - (24 * 60) }, - { "TODAY", tMINUTE_UNIT, 0 }, - { "NOW", tMINUTE_UNIT, 0 }, - { "LAST", tUNUMBER, -1 }, - { "THIS", tUNUMBER, 0 }, - { "NEXT", tUNUMBER, 1 }, - { "FIRST", tUNUMBER, 1 }, -/*{ "SECOND", tUNUMBER, 2 }, */ - { "THIRD", tUNUMBER, 3 }, - { "FOURTH", tUNUMBER, 4 }, - { "FIFTH", tUNUMBER, 5 }, - { "SIXTH", tUNUMBER, 6 }, - { "SEVENTH", tUNUMBER, 7 }, - { "EIGHTH", tUNUMBER, 8 }, - { "NINTH", tUNUMBER, 9 }, - { "TENTH", tUNUMBER, 10 }, - { "ELEVENTH", tUNUMBER, 11 }, - { "TWELFTH", tUNUMBER, 12 }, + { "TOMORROW", tDAY_UNIT, 1 }, + { "YESTERDAY",tDAY_UNIT, -1 }, + { "TODAY", tDAY_UNIT, 0 }, + { "NOW", tDAY_UNIT, 0 }, + { "LAST", tORDINAL, -1 }, + { "THIS", tORDINAL, 0 }, + { "NEXT", tORDINAL, 1 }, + { "FIRST", tORDINAL, 1 }, +/*{ "SECOND", tORDINAL, 2 }, */ + { "THIRD", tORDINAL, 3 }, + { "FOURTH", tORDINAL, 4 }, + { "FIFTH", tORDINAL, 5 }, + { "SIXTH", tORDINAL, 6 }, + { "SEVENTH", tORDINAL, 7 }, + { "EIGHTH", tORDINAL, 8 }, + { "NINTH", tORDINAL, 9 }, + { "TENTH", tORDINAL, 10 }, + { "ELEVENTH", tORDINAL, 11 }, + { "TWELFTH", tORDINAL, 12 }, { "AGO", tAGO, 1 }, - { 0, 0, 0 } + { NULL, 0, 0 } +}; + +/* The universal time zone table. These labels can be used even for + time stamps that would not otherwise be valid, e.g., GMT time + stamps in London during summer. */ +static table const universal_time_zone_table[] = +{ + { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */ + { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */ + { "UTC", tZONE, HOUR ( 0) }, + { NULL, 0, 0 } }; /* The time zone table. This table is necessarily incomplete, as time @@ -528,9 +695,6 @@ static table const relative_time_table[] = abbreviations; use numeric abbreviations like `-0500' instead. */ static table const time_zone_table[] = { - { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */ - { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */ - { "UTC", tZONE, HOUR ( 0) }, { "WET", tZONE, HOUR ( 0) }, /* Western European */ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */ @@ -578,7 +742,7 @@ static table const time_zone_table[] = { "GST", tZONE, HOUR (10) }, /* Guam Standard */ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */ - { 0, 0, 0 } + { NULL, 0, 0 } }; /* Military time zone table. */ @@ -609,50 +773,66 @@ static table const military_table[] = { "X", tZONE, HOUR (11) }, { "Y", tZONE, HOUR (12) }, { "Z", tZONE, HOUR ( 0) }, - { 0, 0, 0 } + { NULL, 0, 0 } }; +/* Convert a time zone expressed as HH:MM into an integer count of + minutes. If MM is negative, then S is of the form HHMM and needs + to be picked apart; otherwise, S is of the form HH. */ + +static long int +time_zone_hhmm (textint s, long int mm) +{ + if (mm < 0) + return (s.value / 100) * 60 + s.value % 100; + else + return s.value * 60 + (s.negative ? -mm : mm); +} + static int -to_hour (int hours, int meridian) +to_hour (long int hours, int meridian) { switch (meridian) { + default: /* Pacify GCC. */ case MER24: return 0 <= hours && hours < 24 ? hours : -1; case MERam: return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1; case MERpm: return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1; - default: - abort (); } - /* NOTREACHED */ } -static int -to_year (int year) +static long int +to_year (textint textyear) { + long 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; + else 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; - /* Try local zone abbreviations first; they're more likely to be right. */ + for (tp = universal_time_zone_table; tp->name; tp++) + if (strcmp (name, tp->name) == 0) + return tp; + + /* Try local zone abbreviations before those in time_zone_table, as + the local ones are more likely to be right. */ for (tp = pc->local_time_zone_table; tp->name; tp++) if (strcmp (name, tp->name) == 0) return tp; @@ -661,44 +841,52 @@ lookup_zone (struct parser_control const *pc, char const *name) if (strcmp (name, tp->name) == 0) return tp; - return 0; + return NULL; } -/* Yield A - B, measured in seconds. */ -static int -difftm (struct tm *a, struct tm *b) +#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 long int +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. */ + int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3); + int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3); + int a100 = a4 / 25 - (a4 % 25 < 0); + int b100 = b4 / 25 - (b4 % 25 < 0); + int a400 = SHR (a100, 2); + int b400 = SHR (b100, 2); + int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); + long int ayear = a->tm_year; + long int years = ayear - b->tm_year; + long 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; size_t wordlen; table const *tp; - int i; - int abbrev; + bool period_found; + bool abbrev; /* Make it uppercase. */ for (p = word; *p; p++) - if (ISLOWER ((unsigned char) *p)) - *p = toupper ((unsigned char) *p); + { + unsigned char ch = *p; + *p = toupper (ch); + } for (tp = meridian_table; tp->name; tp++) if (strcmp (word, tp->name) == 0) @@ -743,49 +931,141 @@ lookup_word (struct parser_control const *pc, char *word) return tp; /* Drop out any periods and try the time zone table again. */ - for (i = 0, p = q = word; (*p = *q); q++) + for (period_found = false, p = q = word; (*p = *q); q++) if (*q == '.') - i = 1; + period_found = true; else p++; - if (i && (tp = lookup_zone (pc, word))) + if (period_found && (tp = lookup_zone (pc, word))) return tp; - return 0; + return NULL; } static int -yylex (YYSTYPE *lvalp, struct parser_control *pc) +yylex (YYSTYPE *lvalp, parser_control *pc) { unsigned char c; - int count; + size_t count; for (;;) { - while (c = *pc->input, ISSPACE (c)) + while (c = *pc->input, isspace (c)) pc->input++; if (ISDIGIT (c) || c == '-' || c == '+') { + char const *p; int sign; + unsigned long int value; if (c == '-' || c == '+') { sign = c == '-' ? -1 : 1; - if (! ISDIGIT (*++pc->input)) + while (c = *++pc->input, isspace (c)) + continue; + 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; - return sign ? tSNUMBER : tUNUMBER; + p = pc->input; + for (value = 0; ; value *= 10) + { + unsigned long int value1 = value + (c - '0'); + if (value1 < value) + return '?'; + value = value1; + c = *++p; + if (! ISDIGIT (c)) + break; + if (ULONG_MAX / 10 < value) + return '?'; + } + if ((c == '.' || c == ',') && ISDIGIT (p[1])) + { + time_t s; + int ns; + int digits; + unsigned long int value1; + + /* Check for overflow when converting value to time_t. */ + if (sign < 0) + { + s = - value; + if (0 < s) + return '?'; + value1 = -s; + } + else + { + s = value; + if (s < 0) + return '?'; + value1 = s; + } + if (value != value1) + return '?'; + + /* Accumulate fraction, to ns precision. */ + p++; + ns = *p++ - '0'; + for (digits = 2; digits <= LOG10_BILLION; digits++) + { + ns *= 10; + if (ISDIGIT (*p)) + ns += *p++ - '0'; + } + + /* Skip excess digits, truncating toward -Infinity. */ + if (sign < 0) + for (; ISDIGIT (*p); p++) + if (*p != '0') + { + ns++; + break; + } + while (ISDIGIT (*p)) + p++; + + /* Adjust to the timespec convention, which is that + tv_nsec is always a positive offset even if tv_sec is + negative. */ + if (sign < 0 && ns) + { + s--; + if (! (s < 0)) + return '?'; + ns = BILLION - ns; + } + + lvalp->timespec.tv_sec = s; + lvalp->timespec.tv_nsec = ns; + pc->input = p; + return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER; + } + else + { + lvalp->textintval.negative = sign < 0; + if (sign < 0) + { + lvalp->textintval.value = - value; + if (0 < lvalp->textintval.value) + return '?'; + } + else + { + lvalp->textintval.value = value; + if (lvalp->textintval.value < 0) + return '?'; + } + lvalp->textintval.digits = p - pc->input; + pc->input = p; + return sign ? tSNUMBER : tUNUMBER; + } } - if (ISALPHA (c)) + if (isalpha (c)) { char buff[20]; char *p = buff; @@ -797,13 +1077,13 @@ yylex (YYSTYPE *lvalp, struct parser_control *pc) *p++ = c; c = *++pc->input; } - while (ISALPHA (c) || c == '.'); + while (isalpha (c) || c == '.'); *p = '\0'; tp = lookup_word (pc, buff); if (! tp) - return tID; - *lvalp = tp->value; + return '?'; + lvalp->intval = tp->value; return tp->type; } @@ -820,58 +1100,162 @@ yylex (YYSTYPE *lvalp, struct parser_control *pc) else if (c == ')') count--; } - while (count > 0); + while (count != 0); } } /* Do nothing if the parser reports an error. */ static int -yyerror (char *s ATTRIBUTE_UNUSED) +yyerror (parser_control const *pc ATTRIBUTE_UNUSED, + char const *s ATTRIBUTE_UNUSED) { return 0; } -/* ?? */ -time_t -get_date (const char *p, const time_t *now) +/* If *TM0 is the old and *TM1 is the new value of a struct tm after + passing it to mktime, return true if it's OK that mktime returned T. + It's not OK if *TM0 has out-of-range members. */ + +static bool +mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t) +{ + if (t == (time_t) -1) + { + /* Guard against falsely reporting an error when parsing a time + stamp that happens to equal (time_t) -1, on a host that + supports such a time stamp. */ + tm1 = localtime (&t); + if (!tm1) + return false; + } + + return ! ((tm0->tm_sec ^ tm1->tm_sec) + | (tm0->tm_min ^ tm1->tm_min) + | (tm0->tm_hour ^ tm1->tm_hour) + | (tm0->tm_mday ^ tm1->tm_mday) + | (tm0->tm_mon ^ tm1->tm_mon) + | (tm0->tm_year ^ tm1->tm_year)); +} + +/* A reasonable upper bound for the size of ordinary TZ strings. + Use heap allocation if TZ's length exceeds this. */ +enum { TZBUFSIZE = 100 }; + +/* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated + otherwise. */ +static char * +get_tz (char tzbuf[TZBUFSIZE]) +{ + char *tz = getenv ("TZ"); + if (tz) + { + size_t tzsize = strlen (tz) + 1; + tz = (tzsize <= TZBUFSIZE + ? memcpy (tzbuf, tz, tzsize) + : xmemdup (tz, tzsize)); + } + return tz; +} + +/* Parse a date/time string, storing the resulting time value into *RESULT. + The string itself is pointed to by P. Return true if successful. + P can be an incomplete or relative time specification; if so, use + *NOW as the basis for the returned time. */ +bool +get_date (struct timespec *result, char const *p, struct timespec const *now) { - time_t Start = now ? *now : time (0); - struct tm *tmp = localtime (&Start); + time_t Start; + long int Start_ns; + struct tm const *tmp; struct tm tm; struct tm tm0; - struct parser_control pc; + parser_control pc; + struct timespec gettime_buffer; + unsigned char c; + bool tz_was_altered = false; + char *tz0 = NULL; + char tz0buf[TZBUFSIZE]; + bool ok = true; + + if (! now) + { + gettime (&gettime_buffer); + now = &gettime_buffer; + } + Start = now->tv_sec; + Start_ns = now->tv_nsec; + + tmp = localtime (&now->tv_sec); if (! tmp) - return -1; + return false; + + while (c = *p, isspace (c)) + p++; + + if (strncmp (p, "TZ=\"", 4) == 0) + { + char const *tzbase = p + 4; + size_t tzsize = 1; + char const *s; + + for (s = tzbase; *s; s++, tzsize++) + if (*s == '\\') + { + s++; + if (! (*s == '\\' || *s == '"')) + break; + } + else if (*s == '"') + { + char *z; + char *tz1; + char tz1buf[TZBUFSIZE]; + bool large_tz = TZBUFSIZE < tzsize; + bool setenv_ok; + tz0 = get_tz (tz0buf); + z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf; + for (s = tzbase; *s != '"'; s++) + *z++ = *(s += *s == '\\'); + *z = '\0'; + setenv_ok = setenv ("TZ", tz1, 1) == 0; + if (large_tz) + free (tz1); + if (!setenv_ok) + goto fail; + tz_was_altered = true; + p = s + 1; + } + } pc.input = p; - pc.year = tmp->tm_year + TM_YEAR_ORIGIN; + pc.year.value = tmp->tm_year; + pc.year.value += TM_YEAR_BASE; + pc.year.digits = 0; pc.month = tmp->tm_mon + 1; pc.day = tmp->tm_mday; pc.hour = tmp->tm_hour; pc.minutes = tmp->tm_min; - pc.seconds = tmp->tm_sec; + pc.seconds.tv_sec = tmp->tm_sec; + pc.seconds.tv_nsec = Start_ns; tm.tm_isdst = tmp->tm_isdst; pc.meridian = MER24; - pc.rel_seconds = 0; - pc.rel_minutes = 0; - pc.rel_hour = 0; - pc.rel_day = 0; - pc.rel_month = 0; - pc.rel_year = 0; + pc.rel = RELATIVE_TIME_0; + pc.timespec_seen = false; + pc.rels_seen = false; pc.dates_seen = 0; pc.days_seen = 0; - pc.rels_seen = 0; pc.times_seen = 0; pc.local_zones_seen = 0; + pc.dsts_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_time_zone_table[1].name = 0; + pc.local_time_zone_table[1].name = NULL; /* Probe the names used in the next three calendar quarters, looking for a tm_isdst different from the one we already have. */ @@ -880,7 +1264,7 @@ get_date (const char *p, const time_t *now) for (quarter = 1; quarter <= 3; quarter++) { time_t probe = Start + quarter * (90 * 24 * 60 * 60); - struct tm *probe_tm = localtime (&probe); + struct tm const *probe_tm = localtime (&probe); if (probe_tm && probe_tm->tm_zone && probe_tm->tm_isdst != pc.local_time_zone_table[0].value) { @@ -888,7 +1272,7 @@ get_date (const char *p, const time_t *now) pc.local_time_zone_table[1].name = probe_tm->tm_zone; pc.local_time_zone_table[1].type = tLOCAL_ZONE; pc.local_time_zone_table[1].value = probe_tm->tm_isdst; - pc.local_time_zone_table[2].name = 0; + pc.local_time_zone_table[2].name = NULL; } break; } @@ -907,10 +1291,10 @@ get_date (const char *p, const time_t *now) pc.local_time_zone_table[i].type = tLOCAL_ZONE; pc.local_time_zone_table[i].value = i; } - pc.local_time_zone_table[i].name = 0; + pc.local_time_zone_table[i].name = NULL; } #else - pc.local_time_zone_table[0].name = 0; + pc.local_time_zone_table[0].name = NULL; #endif #endif @@ -922,127 +1306,214 @@ get_date (const char *p, const time_t *now) daylight times. So if we see that abbreviation, we don't know whether it's daylight time. */ pc.local_time_zone_table[0].value = -1; - pc.local_time_zone_table[1].name = 0; + pc.local_time_zone_table[1].name = NULL; } - if (yyparse (&pc) != 0 - || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen - || 1 < (pc.local_zones_seen + pc.zones_seen) - || (pc.local_zones_seen && 1 < pc.local_isdst)) - return -1; + if (yyparse (&pc) != 0) + goto fail; - tm.tm_year = to_year (pc.year) - TM_YEAR_ORIGIN + 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 = to_hour (pc.hour, pc.meridian); - if (tm.tm_hour < 0) - return -1; - tm.tm_min = pc.minutes; - tm.tm_sec = pc.seconds; - } + if (pc.timespec_seen) + *result = pc.seconds; else { - 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) - tm.tm_isdst = -1; + if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen + | (pc.local_zones_seen + pc.zones_seen))) + goto fail; + + tm.tm_year = to_year (pc.year) - TM_YEAR_BASE; + tm.tm_mon = pc.month - 1; + tm.tm_mday = pc.day; + if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen)) + { + tm.tm_hour = to_hour (pc.hour, pc.meridian); + if (tm.tm_hour < 0) + goto fail; + tm.tm_min = pc.minutes; + tm.tm_sec = pc.seconds.tv_sec; + } + else + { + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + pc.seconds.tv_nsec = 0; + } - /* But if the input explicitly specifies local time with or without - DST, give mktime that information. */ - if (pc.local_zones_seen) - tm.tm_isdst = pc.local_isdst; + /* Let mktime deduce tm_isdst if we have an absolute time stamp. */ + if (pc.dates_seen | pc.days_seen | pc.times_seen) + tm.tm_isdst = -1; - tm0 = tm; + /* But if the input explicitly specifies local time with or without + DST, give mktime that information. */ + if (pc.local_zones_seen) + tm.tm_isdst = pc.local_isdst; - Start = mktime (&tm); + tm0 = tm; - if (Start == (time_t) -1) - { + Start = mktime (&tm); - /* Guard against falsely reporting errors near the time_t boundaries - when parsing times in other time zones. For example, if the min - time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead - of UTC, then the min localtime value is 1970-01-01 08:00:00; if - we apply mktime to 1970-01-01 00:00:00 we will get an error, so - we apply mktime to 1970-01-02 08:00:00 instead and adjust the time - zone by 24 hours to compensate. This algorithm assumes that - there is no DST transition within a day of the time_t boundaries. */ - if (pc.zones_seen) + if (! mktime_ok (&tm0, &tm, Start)) { - tm = tm0; - if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_ORIGIN) - { - tm.tm_mday++; - pc.time_zone += 24 * 60; - } + if (! pc.zones_seen) + goto fail; else { - tm.tm_mday--; - pc.time_zone -= 24 * 60; + /* Guard against falsely reporting errors near the time_t + boundaries when parsing times in other time zones. For + example, suppose the input string "1969-12-31 23:00:00 -0100", + the current time zone is 8 hours ahead of UTC, and the min + time_t value is 1970-01-01 00:00:00 UTC. Then the min + localtime value is 1970-01-01 08:00:00, and mktime will + therefore fail on 1969-12-31 23:00:00. To work around the + problem, set the time zone to 1 hour behind UTC temporarily + by setting TZ="XXX1:00" and try mktime again. */ + + long int time_zone = pc.time_zone; + long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone; + long int abs_time_zone_hour = abs_time_zone / 60; + int abs_time_zone_min = abs_time_zone % 60; + char tz1buf[sizeof "XXX+0:00" + + sizeof pc.time_zone * CHAR_BIT / 3]; + if (!tz_was_altered) + tz0 = get_tz (tz0buf); + sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0), + abs_time_zone_hour, abs_time_zone_min); + if (setenv ("TZ", tz1buf, 1) != 0) + goto fail; + tz_was_altered = true; + tm = tm0; + Start = mktime (&tm); + if (! mktime_ok (&tm0, &tm, Start)) + goto fail; } + } + + if (pc.days_seen && ! pc.dates_seen) + { + 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) + goto fail; } - if (Start == (time_t) -1) - return Start; - } + if (pc.zones_seen) + { + long int delta = pc.time_zone * 60; + time_t t1; +#ifdef HAVE_TM_GMTOFF + delta -= tm.tm_gmtoff; +#else + time_t t = Start; + struct tm const *gmt = gmtime (&t); + if (! gmt) + goto fail; + delta -= tm_diff (&tm, gmt); +#endif + t1 = Start - delta; + if ((Start < t1) != (delta < 0)) + goto fail; /* time_t overflow */ + Start = t1; + } - if (pc.days_seen && ! pc.dates_seen) - { - tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 - + 7 * (pc.day_ordinal - (0 < pc.day_ordinal))); - Start = mktime (&tm); - if (Start == (time_t) -1) - return Start; - } + /* Add relative date. */ + if (pc.rel.year | pc.rel.month | pc.rel.day) + { + int year = tm.tm_year + pc.rel.year; + int month = tm.tm_mon + pc.rel.month; + int day = tm.tm_mday + pc.rel.day; + if (((year < tm.tm_year) ^ (pc.rel.year < 0)) + | ((month < tm.tm_mon) ^ (pc.rel.month < 0)) + | ((day < tm.tm_mday) ^ (pc.rel.day < 0))) + goto fail; + tm.tm_year = year; + tm.tm_mon = month; + tm.tm_mday = day; + tm.tm_hour = tm0.tm_hour; + tm.tm_min = tm0.tm_min; + tm.tm_sec = tm0.tm_sec; + tm.tm_isdst = tm0.tm_isdst; + Start = mktime (&tm); + if (Start == (time_t) -1) + goto fail; + } - if (pc.zones_seen) - { - int delta; - struct tm *gmt = gmtime (&Start); - if (! gmt) - return -1; - delta = pc.time_zone * 60 + difftm (gmt, &tm); - if ((Start - delta < Start) != (delta < 0)) - return -1; /* time_t overflow */ - Start -= delta; + /* Add relative hours, minutes, and seconds. On hosts that support + leap seconds, ignore the possibility of leap seconds; e.g., + "+ 10 minutes" adds 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. */ + { + long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns; + long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION; + time_t t0 = Start; + long int d1 = 60 * 60 * pc.rel.hour; + time_t t1 = t0 + d1; + long int d2 = 60 * pc.rel.minutes; + time_t t2 = t1 + d2; + long int d3 = pc.rel.seconds; + time_t t3 = t2 + d3; + long int d4 = (sum_ns - normalized_ns) / BILLION; + time_t t4 = t3 + d4; + + if ((d1 / (60 * 60) ^ pc.rel.hour) + | (d2 / 60 ^ pc.rel.minutes) + | ((t1 < t0) ^ (d1 < 0)) + | ((t2 < t1) ^ (d2 < 0)) + | ((t3 < t2) ^ (d3 < 0)) + | ((t4 < t3) ^ (d4 < 0))) + goto fail; + + result->tv_sec = t4; + result->tv_nsec = normalized_ns; + } } - return Start; + goto done; + + fail: + ok = false; + done: + if (tz_was_altered) + ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0; + if (tz0 != tz0buf) + free (tz0); + return ok; } #if TEST -#include - int main (int ac, char **av) { char buff[BUFSIZ]; - time_t d; printf ("Enter date, or blank line to exit.\n\t> "); fflush (stdout); - buff[BUFSIZ - 1] = 0; + buff[BUFSIZ - 1] = '\0'; while (fgets (buff, BUFSIZ - 1, stdin) && buff[0]) { - d = get_date (buff, 0); - if (d == (time_t) -1) + struct timespec d; + struct tm const *tm; + if (! get_date (&d, buff, NULL)) printf ("Bad format - couldn't convert.\n"); + else if (! (tm = localtime (&d.tv_sec))) + { + long int sec = d.tv_sec; + printf ("localtime (%ld) failed\n", sec); + } else - printf ("%s", ctime (&d)); + { + int ns = d.tv_nsec; + printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n", + tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, ns); + } printf ("\t> "); fflush (stdout); } return 0; } -#endif /* defined TEST */ +#endif /* TEST */