* General date syntax:: Common rules.
* Calendar date items:: 19 Dec 1994.
* Time of day items:: 9:20pm.
-* Time zone items:: @sc{est}, @sc{pdt}, @sc{gmt}, ...
+* Time zone items:: @sc{est}, @sc{pdt}, @sc{gmt}.
* Day of week items:: Monday and others.
* Relative items in date strings:: next tuesday, 2 years ago.
* Pure numbers in date strings:: 19931219, 1440.
* Seconds since the Epoch:: @@1078100502.
+* Specifying time zone rules:: TZ="America/New_York", TZ="UTC0".
* Authors of get_date:: Bellovin, Eggert, Salz, Berets, et al.
@end menu
@itemize @bullet
@item calendar date items
-@item time of the day items
+@item time of day items
@item time zone items
@item day of the week items
@item relative items
@cindex language, in dates
@cindex time zone item
-The output of @command{date} is not always acceptable as a date string,
+The output of the @command{date} command
+is not always acceptable as a date string,
not only because of the language problem, but also because there is no
standard meaning for time zone items like @samp{IST}. When using
@command{date} to generate a date string intended to be parsed later,
20:02-0500 # In @sc{est} (U.S. Eastern Standard Time).
@end example
-More generally, the time of the day may be given as
+More generally, the time of day may be given as
@samp{@var{hour}:@var{minute}:@var{second}}, where @var{hour} is
a number between 0 and 23, @var{minute} is a number between 0 and
59, and @var{second} is a number between 0 and 59 possibly followed by
forces interpretation of the time relative to
Coordinated Universal Time (@sc{utc}), overriding any previous
specification for the time zone or the local time zone. The @var{minute}
-part of the time of the day may not be elided when a time zone correction
+part of the time of day may not be elided when a time zone correction
is used. This is the best way to specify a time zone correction by
fractional parts of an hour.
unambiguous numeric time zone corrections like @samp{-0500}, as
described in the previous section.
+If neither a time zone item nor a time zone correction is supplied,
+time stamps are interpreted using the rules of the default time zone
+(@pxref{Specifying time zone rules}).
+
@node Day of week items
@section Day of week items
date strings like @samp{this thursday}.
When a relative item causes the resulting date to cross a boundary
-where the clocks were adjusted, typically for daylight-saving time,
+where the clocks were adjusted, typically for daylight saving time,
the resulting date and time are adjusted accordingly.
The fuzz in units can cause problems with relative items. For
If the decimal number is of the form @var{hh}@var{mm} and no other time
of day item appears before it in the date string, then @var{hh} is read
as the hour of the day and @var{mm} as the minute of the hour, for the
-specified time of the day. @var{mm} can also be omitted.
+specified time of day. @var{mm} can also be omitted.
If both a calendar date and a time of day appear to the left of a number
in the date string, but no relative item, then the number overrides the
stamp as a count of seconds. The number can contain an internal
decimal point (either @samp{.} or @samp{,}); any excess precision not
supported by the internal representation is truncated toward minus
-infinity.
+infinity. Such a number cannot be combined with any other date
+item, as it specifies a complete time stamp.
@cindex beginning of time, for @acronym{POSIX}
@cindex epoch, for @acronym{POSIX}
@sc{utc}, and there is no way to represent the intervening leap second
1998-12-31 23:59:60 @sc{utc}.
+@node Specifying time zone rules
+@section Specifying time zone rules
+
+@vindex TZ
+Normally, dates are interpreted using the rules of the current time
+zone, which in turn are specified by the @env{TZ} environment
+variable, or by a system default if @env{TZ} is not set. To specify a
+different set of default time zone rules that apply just to one date,
+start the date with a string of the form @samp{TZ="@var{rule}"}. The
+two quote characters (@samp{"}) must be present in the date, and any
+quotes or backslashes within @var{rule} must be escaped by a
+backslash.
+
+For example, with the @acronym{GNU} @command{date} command you can
+answer the question ``What time is it in New York when a Paris clock
+shows 6:30am on October 31, 2004?'' by using a date beginning with
+@samp{TZ="Europe/Paris"} as shown in the following shell transcript:
+
+@example
+$ export TZ="America/New_York"
+$ date --date='TZ="Europe/Paris" 2004-10-31 06:30'
+Sun Oct 31 01:30:00 EDT 2004
+@end example
+
+In this example, the @option{--date} operand begins with its own
+@env{TZ} setting, so the rest of that operand is processed according
+to @samp{Europe/Paris} rules, treating the string @samp{2004-10-31
+06:30} as if it were in Paris. However, since the output of the
+@command{date} command is processed according to the overall time zone
+rules, it uses New York time. (Paris was normally six hours ahead of
+New York in 2004, but this example refers to a brief Halloween period
+when the gap was five hours.)
+
+A @env{TZ} value is a rule that typically names a location in the
+@uref{http://www.twinsun.com/tz/tz-link.htm, @samp{tz} database}.
+A recent catalog of location names appears in the
+@uref{http://twiki.org/cgi-bin/xtra/tzdate, TWiki Date and Time
+Gateway}. A few non-@acronym{GNU} hosts require a colon before a
+location name in a @env{TZ} setting, e.g.,
+@samp{TZ=":America/New_York"}.
+
+The @samp{tz} database includes a wide variety of locations ranging
+from @samp{Arctic/Longyearbyen} to @samp{Antarctica/South_Pole}, but
+if you are at sea and have your own private time zone, or if you are
+using a non-@acronym{GNU} host that does not support the @samp{tz}
+database, you may need to use a @acronym{POSIX} rule instead. Simple
+@acronym{POSIX} rules like @samp{UTC0} specify a time zone without
+daylight saving time; other rules can specify simple daylight saving
+regimes. @xref{TZ Variable,, Specifying the Time Zone with @code{TZ},
+libc, The GNU C Library}.
+
@node Authors of get_date
@section Authors of @code{get_date}
<rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
- the right thing about local DST, and in February 2004 to support
- nanosecond-resolution time stamps. Unlike previous versions, this
- version is reentrant. */
+ the right thing about local DST. Also modified by Paul Eggert
+ <eggert@cs.ucla.edu> in February 2004 to support
+ nanosecond-resolution time stamps, and in October 2004 to support
+ TZ strings in dates. */
/* 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. */
+ some of them. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#include <ctype.h>
#include <limits.h>
-#include <stdlib.h> /* for `free'; used by Bison 1.27 */
+#include <stdlib.h>
+#include <string.h>
+
+#include "setenv.h"
+#include "xalloc.h"
#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
# define IN_CTYPE_DOMAIN(c) 1
#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:
+/* ISDIGIT differs from isdigit, 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.
- It's typically faster.
POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
- ISDIGIT_LOCALE unless it's important to use the locale's definition
+ 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)
-#include <string.h>
-
-#if USE_UNLOCKED_IO
-# include "unlocked-io.h"
-#endif
-
#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
# define __attribute__(x)
#endif
table local_time_zone_table[3];
} parser_control;
-#define PC (* (parser_control *) parm)
-#define YYLEX_PARAM parm
-#define YYPARSE_PARAM parm
-
-static int yyerror ();
-static int yylex ();
+union YYSTYPE;
+static int yylex (union YYSTYPE *, parser_control *);
+static int yyerror (parser_control *, char *);
%}
-/* 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 13 shift/reduce conflicts. */
%expect 13
timespec:
'@' seconds
{
- PC.seconds = $2;
- PC.timespec_seen = true;
+ pc->seconds = $2;
+ pc->timespec_seen = true;
}
;
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++; }
| number
;
time:
tUNUMBER tMERIDIAN
{
- PC.hour = $1.value;
- PC.minutes = 0;
- PC.seconds.tv_sec = 0;
- PC.seconds.tv_nsec = 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.value;
- PC.minutes = $3.value;
- PC.seconds.tv_sec = 0;
- PC.seconds.tv_nsec = 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
{
- 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.value % 100 + ($4.value / 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 = $4.value % 100 + ($4.value / 100) * 60;
}
| tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
{
- PC.hour = $1.value;
- PC.minutes = $3.value;
- PC.seconds = $5;
- PC.meridian = $6;
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds = $5;
+ pc->meridian = $6;
}
| tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
{
- PC.hour = $1.value;
- PC.minutes = $3.value;
- PC.seconds = $5;
- PC.meridian = MER24;
- PC.zones_seen++;
- PC.time_zone = $6.value % 100 + ($6.value / 100) * 60;
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds = $5;
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
}
;
local_zone:
tLOCAL_ZONE
- { PC.local_isdst = $1; }
+ { pc->local_isdst = $1; }
| tLOCAL_ZONE tDST
- { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; }
+ { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
;
zone:
tZONE
- { PC.time_zone = $1; }
+ { pc->time_zone = $1; }
| 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;
}
| tUNUMBER tDAY
{
- PC.day_ordinal = $1.value;
- PC.day_number = $2;
+ pc->day_ordinal = $1.value;
+ pc->day_number = $2;
}
;
date:
tUNUMBER '/' tUNUMBER
{
- PC.month = $1.value;
- PC.day = $3.value;
+ pc->month = $1.value;
+ pc->day = $3.value;
}
| tUNUMBER '/' tUNUMBER '/' tUNUMBER
{
you want portability, use the ISO 8601 format. */
if (4 <= $1.digits)
{
- PC.year = $1;
- PC.month = $3.value;
- PC.day = $5.value;
+ pc->year = $1;
+ pc->month = $3.value;
+ pc->day = $5.value;
}
else
{
- PC.month = $1.value;
- PC.day = $3.value;
- 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.value;
- PC.day = -$3.value;
+ pc->year = $1;
+ pc->month = -$2.value;
+ pc->day = -$3.value;
}
| tUNUMBER tMONTH tSNUMBER
{
/* e.g. 17-JUN-1992. */
- PC.day = $1.value;
- PC.month = $2;
- PC.year.value = -$3.value;
- PC.year.digits = $3.digits;
+ 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;
+ 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.value;
+ pc->month = $1;
+ pc->day = $2.value;
}
| tMONTH tUNUMBER ',' tUNUMBER
{
- PC.month = $1;
- PC.day = $2.value;
- PC.year = $4;
+ pc->month = $1;
+ pc->day = $2.value;
+ pc->year = $4;
}
| tUNUMBER tMONTH
{
- PC.day = $1.value;
- PC.month = $2;
+ pc->day = $1.value;
+ pc->month = $2;
}
| tUNUMBER tMONTH tUNUMBER
{
- PC.day = $1.value;
- PC.month = $2;
- PC.year = $3;
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year = $3;
}
;
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;
- PC.rel_day = -PC.rel_day;
- PC.rel_month = -PC.rel_month;
- PC.rel_year = -PC.rel_year;
+ pc->rel_ns = -pc->rel_ns;
+ 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;
}
| relunit
;
relunit:
tUNUMBER tYEAR_UNIT
- { PC.rel_year += $1.value * $2; }
+ { pc->rel_year += $1.value * $2; }
| tSNUMBER tYEAR_UNIT
- { PC.rel_year += $1.value * $2; }
+ { pc->rel_year += $1.value * $2; }
| tYEAR_UNIT
- { PC.rel_year += $1; }
+ { pc->rel_year += $1; }
| tUNUMBER tMONTH_UNIT
- { PC.rel_month += $1.value * $2; }
+ { pc->rel_month += $1.value * $2; }
| tSNUMBER tMONTH_UNIT
- { PC.rel_month += $1.value * $2; }
+ { pc->rel_month += $1.value * $2; }
| tMONTH_UNIT
- { PC.rel_month += $1; }
+ { pc->rel_month += $1; }
| tUNUMBER tDAY_UNIT
- { PC.rel_day += $1.value * $2; }
+ { pc->rel_day += $1.value * $2; }
| tSNUMBER tDAY_UNIT
- { PC.rel_day += $1.value * $2; }
+ { pc->rel_day += $1.value * $2; }
| tDAY_UNIT
- { PC.rel_day += $1; }
+ { pc->rel_day += $1; }
| tUNUMBER tHOUR_UNIT
- { PC.rel_hour += $1.value * $2; }
+ { pc->rel_hour += $1.value * $2; }
| tSNUMBER tHOUR_UNIT
- { PC.rel_hour += $1.value * $2; }
+ { pc->rel_hour += $1.value * $2; }
| tHOUR_UNIT
- { PC.rel_hour += $1; }
+ { pc->rel_hour += $1; }
| tUNUMBER tMINUTE_UNIT
- { PC.rel_minutes += $1.value * $2; }
+ { pc->rel_minutes += $1.value * $2; }
| tSNUMBER tMINUTE_UNIT
- { PC.rel_minutes += $1.value * $2; }
+ { pc->rel_minutes += $1.value * $2; }
| tMINUTE_UNIT
- { PC.rel_minutes += $1; }
+ { pc->rel_minutes += $1; }
| tUNUMBER tSEC_UNIT
- { PC.rel_seconds += $1.value * $2; }
+ { pc->rel_seconds += $1.value * $2; }
| tSNUMBER tSEC_UNIT
- { PC.rel_seconds += $1.value * $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; }
+ { 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; }
+ { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
| tSEC_UNIT
- { PC.rel_seconds += $1; }
+ { pc->rel_seconds += $1; }
;
seconds: signed_seconds | unsigned_seconds;
number:
tUNUMBER
{
- if (PC.dates_seen
- && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits))
- PC.year = $1;
+ if (pc->dates_seen
+ && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
+ pc->year = $1;
else
{
if (4 < $1.digits)
{
- 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;
+ 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++;
+ pc->times_seen++;
if ($1.digits <= 2)
{
- PC.hour = $1.value;
- PC.minutes = 0;
+ pc->hour = $1.value;
+ pc->minutes = 0;
}
else
{
- PC.hour = $1.value / 100;
- PC.minutes = $1.value % 100;
+ pc->hour = $1.value / 100;
+ pc->minutes = $1.value % 100;
}
- PC.seconds.tv_sec = 0;
- PC.seconds.tv_nsec = 0;
- PC.meridian = MER24;
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
}
}
}
{ "A.M.", tMERIDIAN, MERam },
{ "PM", tMERIDIAN, MERpm },
{ "P.M.", tMERIDIAN, MERpm },
- { 0, 0, 0 }
+ { NULL, 0, 0 }
};
static table const dst_table[] =
{ "THURS", tDAY, 4 },
{ "FRIDAY", tDAY, 5 },
{ "SATURDAY", tDAY, 6 },
- { 0, 0, 0 }
+ { NULL, 0, 0 }
};
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. */
{ "ELEVENTH", tUNUMBER, 11 },
{ "TWELFTH", tUNUMBER, 12 },
{ "AGO", tAGO, 1 },
- { 0, 0, 0 }
+ { NULL, 0, 0 }
};
/* The time zone table. This table is necessarily incomplete, as time
{ "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. */
{ "X", tZONE, HOUR (11) },
{ "Y", tZONE, HOUR (12) },
{ "Z", tZONE, HOUR ( 0) },
- { 0, 0, 0 }
+ { NULL, 0, 0 }
};
\f
{
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 long int
if (strcmp (name, tp->name) == 0)
return tp;
- return 0;
+ return NULL;
}
#if ! HAVE_TM_GMTOFF
if (period_found && (tp = lookup_zone (pc, word)))
return tp;
- return 0;
+ return NULL;
}
static int
/* Do nothing if the parser reports an error. */
static int
-yyerror (char *s ATTRIBUTE_UNUSED)
+yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
{
return 0;
}
+/* 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
struct tm tm0;
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)
{
if (! tmp)
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.value = tmp->tm_year;
pc.year.value += TM_YEAR_BASE;
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. */
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;
}
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
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)
- return false;
+ goto fail;
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 false;
-
- 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 false;
- tm.tm_min = pc.minutes;
- tm.tm_sec = pc.seconds.tv_sec;
- }
+ *result = pc.seconds;
else
{
- tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
- pc.seconds.tv_nsec = 0;
- }
-
- /* 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 || 1 < pc.dates_seen || 1 < pc.days_seen
+ || 1 < (pc.local_zones_seen + pc.zones_seen)
+ || (pc.local_zones_seen && 1 < pc.local_isdst))
+ 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_BASE)
- {
- 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;
}
- Start = mktime (&tm);
}
- if (Start == (time_t) -1)
- 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 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)
+ goto fail;
+ }
- if (pc.zones_seen)
- {
- long int delta = pc.time_zone * 60;
- time_t t1;
+ if (pc.zones_seen)
+ {
+ long int delta = pc.time_zone * 60;
+ time_t t1;
#ifdef HAVE_TM_GMTOFF
- delta -= tm.tm_gmtoff;
+ delta -= tm.tm_gmtoff;
#else
- time_t t = Start;
- struct tm const *gmt = gmtime (&t);
- if (! gmt)
- return false;
- delta -= tm_diff (&tm, gmt);
+ 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))
- return false; /* time_t overflow */
- Start = t1;
+ t1 = Start - delta;
+ if ((Start < t1) != (delta < 0))
+ goto fail; /* time_t overflow */
+ Start = t1;
+ }
+
+ /* 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;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1)
+ goto fail;
+ }
+
+ /* 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;
+ }
}
- /* 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;
- }
+ 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
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])
{
struct timespec d;
}
return 0;
}
-#endif /* defined TEST */
+#endif /* TEST */