X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fgetdate.y;h=9d948dc1285844196e89017d00f043ba4a28d286;hb=ec354b13bec21d8510dd60e1e129cc194809248b;hp=658da69fcda14200134889cea2bc5619f3be0625;hpb=e38e9590fbbec00cf4da4d82a426235f09769e7a;p=gnulib.git diff --git a/lib/getdate.y b/lib/getdate.y index 658da69fc..9d948dc12 100644 --- a/lib/getdate.y +++ b/lib/getdate.y @@ -1,6 +1,6 @@ %{ /* Parse a string into an internal time stamp. - Copyright 1999 Free Software Foundation, Inc. + Copyright (C) 1999, 2000, 2002, 2003, 2004 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 @@ -22,16 +22,25 @@ 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 + the right thing about local DST, and in February 2004 to support + nanosecond-resolution time stamps. Unlike previous versions, this version is reentrant. */ +/* FIXME: Check for arithmetic overflow in all cases, not just + some of them. + + FIXME: The current code uses 'int' to count seconds; it should use + something like 'intmax_t' to support time stamps that don't fit in + 32 bits. */ + #ifdef HAVE_CONFIG_H # include -# ifdef HAVE_ALLOCA_H -# include -# endif #endif +#include "getdate.h" + +#include + /* 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 @@ -43,10 +52,8 @@ #endif #include - -#if HAVE_STDLIB_H -# include /* for `free'; used by Bison 1.27 */ -#endif +#include +#include /* for `free'; used by Bison 1.27 */ #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII) # define IN_CTYPE_DOMAIN(c) 1 @@ -63,15 +70,14 @@ - 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. */ -#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) - -#if STDC_HEADERS || HAVE_STRING_H -# include -#endif + 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 int) (c) - '0' <= 9) + +#include + +#include "unlocked-io.h" #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__ # define __attribute__(x) @@ -82,10 +88,18 @@ #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 +{ + long int value; + size_t digits; +} textint; + /* An entry in the lexical lookup table. */ typedef struct { @@ -97,14 +111,16 @@ typedef struct /* Meridian: am, pm, or 24-hour style. */ enum { MERam, MERpm, MER24 }; +enum { BILLION = 1000000000, LOG10_BILLION = 9 }; + /* 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,43 +129,44 @@ 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; + /* 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. */ + long int rel_year; + long int rel_month; + long int rel_day; + long int rel_hour; + long int rel_minutes; + long int rel_seconds; + long int rel_ns; /* 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; + bool timespec_seen; + size_t dates_seen; + size_t days_seen; + size_t local_zones_seen; + size_t rels_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]; -}; +} 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 (); @@ -162,15 +179,42 @@ 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 +{ + long int intval; + textint textintval; + struct timespec timespec; +} + +%token tAGO tDST + +%token tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN +%token tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE + +%token tSNUMBER tUNUMBER +%token tSDECIMAL_NUMBER tUDECIMAL_NUMBER + +%type o_merid +%type seconds signed_seconds unsigned_seconds %% spec: + timespec + | items + ; + +timespec: + '@' seconds + { + PC.seconds = $2; + PC.timespec_seen = true; + } + ; + +items: /* empty */ - | spec item + | items item ; item: @@ -192,41 +236,45 @@ item: time: tUNUMBER tMERIDIAN { - PC.hour = $1; + PC.hour = $1.value; PC.minutes = 0; - PC.seconds = 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.hour = $1.value; + PC.minutes = $3.value; + PC.seconds.tv_sec = 0; + PC.seconds.tv_nsec = 0; PC.meridian = $4; } | tUNUMBER ':' tUNUMBER tSNUMBER { - PC.hour = $1; - PC.minutes = $3; + 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 = $4 % 100 + ($4 / 100) * 60; + PC.time_zone = $4.value % 100 + ($4.value / 100) * 60; } - | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid { - PC.hour = $1; - PC.minutes = $3; + PC.hour = $1.value; + PC.minutes = $3.value; PC.seconds = $5; PC.meridian = $6; } - | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER { - PC.hour = $1; - PC.minutes = $3; + PC.hour = $1.value; + PC.minutes = $3.value; PC.seconds = $5; PC.meridian = MER24; PC.zones_seen++; - PC.time_zone = $6 % 100 + ($6 / 100) * 60; + PC.time_zone = $6.value % 100 + ($6.value / 100) * 60; } ; @@ -259,7 +307,7 @@ day: } | tUNUMBER tDAY { - PC.day_ordinal = $1; + PC.day_ordinal = $1.value; PC.day_number = $2; } ; @@ -267,25 +315,26 @@ day: 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; } } @@ -293,36 +342,45 @@ date: { /* 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 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.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; } ; @@ -330,6 +388,7 @@ date: rel: relunit tAGO { + PC.rel_ns = -PC.rel_ns; PC.rel_seconds = -PC.rel_seconds; PC.rel_minutes = -PC.rel_minutes; PC.rel_hour = -PC.rel_hour; @@ -342,71 +401,92 @@ rel: 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; } + | tSDECIMAL_NUMBER tSEC_UNIT + { PC.rel_seconds += $1.tv_sec * $2; PC.rel_ns += $1.tv_nsec * $2; } + | tUDECIMAL_NUMBER tSEC_UNIT + { PC.rel_seconds += $1.tv_sec * $2; PC.rel_ns += $1.tv_nsec * $2; } | tSEC_UNIT { PC.rel_seconds += $1; } ; +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)) + 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.seconds.tv_sec = 0; + PC.seconds.tv_nsec = 0; PC.meridian = MER24; } } @@ -422,21 +502,6 @@ 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 }, @@ -498,10 +563,10 @@ static table const time_units_table[] = /* 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 }, + { "TOMORROW", tDAY_UNIT, 1 }, + { "YESTERDAY",tDAY_UNIT, -1 }, + { "TODAY", tDAY_UNIT, 0 }, + { "NOW", tDAY_UNIT, 0 }, { "LAST", tUNUMBER, -1 }, { "THIS", tUNUMBER, 0 }, { "NEXT", tUNUMBER, 1 }, @@ -615,7 +680,7 @@ static table const military_table[] = static int -to_hour (int hours, int meridian) +to_hour (long int hours, int meridian) { switch (meridian) { @@ -631,24 +696,24 @@ to_hour (int hours, int meridian) /* 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; @@ -664,41 +729,50 @@ lookup_zone (struct parser_control const *pc, char const *name) return 0; } -/* 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 = (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); + 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; + if (ISLOWER (ch)) + *p = toupper (ch); + } for (tp = meridian_table; tp->name; tp++) if (strcmp (word, tp->name) == 0) @@ -743,22 +817,22 @@ 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; } static int -yylex (YYSTYPE *lvalp, struct parser_control *pc) +yylex (YYSTYPE *lvalp, parser_control *pc) { unsigned char c; - int count; + size_t count; for (;;) { @@ -767,22 +841,113 @@ yylex (YYSTYPE *lvalp, struct parser_control *pc) 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 + { + 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)) @@ -802,8 +967,8 @@ yylex (YYSTYPE *lvalp, struct parser_control *pc) *p = '\0'; tp = lookup_word (pc, buff); if (! tp) - return tID; - *lvalp = tp->value; + return '?'; + lvalp->intval = tp->value; return tp->type; } @@ -820,7 +985,7 @@ yylex (YYSTYPE *lvalp, struct parser_control *pc) else if (c == ')') count--; } - while (count > 0); + while (count != 0); } } @@ -831,38 +996,56 @@ yyerror (char *s ATTRIBUTE_UNUSED) 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) +/* 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; + + if (! now) + { + if (gettime (&gettime_buffer) != 0) + return false; + now = &gettime_buffer; + } + Start = now->tv_sec; + Start_ns = now->tv_nsec; + + tmp = localtime (&now->tv_sec); if (! tmp) - return -1; + return false; 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 = 4; 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_ns = 0; 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.timespec_seen = false; pc.dates_seen = 0; pc.days_seen = 0; pc.rels_seen = 0; @@ -870,7 +1053,7 @@ get_date (const char *p, const time_t *now) 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; @@ -883,7 +1066,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) { @@ -928,34 +1111,41 @@ get_date (const char *p, const time_t *now) pc.local_time_zone_table[1].name = 0; } - if (yyparse (&pc) != 0 - || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen + if (yyparse (&pc) != 0) + return false; + + if (pc.timespec_seen) + { + *result = pc.seconds; + return true; + } + + if (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; + return false; - 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 = to_hour (pc.hour, pc.meridian); if (tm.tm_hour < 0) - return -1; + return false; tm.tm_min = pc.minutes; - tm.tm_sec = pc.seconds; + tm.tm_sec = pc.seconds.tv_sec; } else { tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + pc.seconds.tv_nsec = 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 @@ -971,17 +1161,17 @@ get_date (const char *p, const time_t *now) { /* 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. */ + 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) { 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; @@ -995,31 +1185,69 @@ get_date (const char *p, const time_t *now) } if (Start == (time_t) -1) - return Start; + return false; } 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) - return Start; + return false; } if (pc.zones_seen) { - int delta; - struct tm *gmt = gmtime (&Start); + 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) - return -1; - delta = pc.time_zone * 60 + difftm (gmt, &tm); - if ((Start - delta < Start) != (delta < 0)) - return -1; /* time_t overflow */ - Start -= delta; + return false; + delta -= tm_diff (&tm, gmt); +#endif + t1 = Start - delta; + if ((Start < t1) != (delta < 0)) + return false; /* time_t overflow */ + Start = t1; } - return Start; + /* 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. */ + { + 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))) + return false; + + result->tv_sec = t4; + result->tv_nsec = normalized_ns; + return true; + } } #if TEST @@ -1030,7 +1258,6 @@ int main (int ac, char **av) { char buff[BUFSIZ]; - time_t d; printf ("Enter date, or blank line to exit.\n\t> "); fflush (stdout); @@ -1038,11 +1265,22 @@ main (int ac, char **av) 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); }