* NEWS: Reword the deprecation notice.
* modules/get_date: Rename to modules/parse-datetime.
* modules/get_date-tests: Rename to modules/parse-datetime-tests.
* m4/get_date.m4: Rename to m4/parse-datetime.m4.
* lib/get_date.y: Rename to lib/parse-datetime.y.
* tests/test-get_date.c: Rename to tests/test-parse-datetime.c.
* doc/get_date.texi: Rename to doc/parse-datetime.texi.
* doc/getdate.texi: Provide fallback wrapper.
* lib/getdate.h: Move guts, and wrap...
* lib/parse-datetime.h: ...new file.
* lib/parse-datetime.y (get_date): Rename...
(parse_datetime): ...to this.
* m4/parse-datetime.m4 (gl_GET_DATE): Rename...
(gl_PARSE_DATETIME): ...to this.
* doc/posix-functions/getdate.texi (get_date): Provide fallback
documentation.
* modules/getdate (Files): Provide fallback docs and header.
(Notice, Depends-on): Update references.
* tests/test-parse-datetime.c: Likewise.
* DEPENDENCIES: Likewise.
* MODULES.html.sh (Date and time <time.h>): Likewise.
* doc/parse-datetime.texi (Date input formats)
(Authors of parse_datetime): Likewise.
* modules/parse-datetime (Files, configure.ac, Makefile.am)
(Include): Likewise.
* modules/parse-datetime-tests (Files, Makefile.am): Likewise.
* gnulib-tool: Likewise.
* m4/bison.m4 (gl_BISON): Likewise.
Suggested by Bruno Haible.
Signed-off-by: Eric Blake <eblake@redhat.com>
2010-10-05 Eric Blake <eblake@redhat.com>
+ parse-datetime: better name than get_date
+ * NEWS: Reword the deprecation notice.
+ * modules/get_date: Rename to modules/parse-datetime.
+ * modules/get_date-tests: Rename to modules/parse-datetime-tests.
+ * m4/get_date.m4: Rename to m4/parse-datetime.m4.
+ * lib/get_date.y: Rename to lib/parse-datetime.y.
+ * tests/test-get_date.c: Rename to tests/test-parse-datetime.c.
+ * doc/get_date.texi: Rename to doc/parse-datetime.texi.
+ * doc/getdate.texi: Provide fallback wrapper.
+ * lib/getdate.h: Move guts, and wrap...
+ * lib/parse-datetime.h: ...new file.
+ * lib/parse-datetime.y (get_date): Rename...
+ (parse_datetime): ...to this.
+ * m4/parse-datetime.m4 (gl_GET_DATE): Rename...
+ (gl_PARSE_DATETIME): ...to this.
+ * doc/posix-functions/getdate.texi (get_date): Provide fallback
+ documentation.
+ * modules/getdate (Files): Provide fallback docs and header.
+ (Notice, Depends-on): Update references.
+ * tests/test-parse-datetime.c: Likewise.
+ * DEPENDENCIES: Likewise.
+ * MODULES.html.sh (Date and time <time.h>): Likewise.
+ * doc/parse-datetime.texi (Date input formats)
+ (Authors of parse_datetime): Likewise.
+ * modules/parse-datetime (Files, configure.ac, Makefile.am)
+ (Include): Likewise.
+ * modules/parse-datetime-tests (Files, Makefile.am): Likewise.
+ * gnulib-tool: Likewise.
+ * m4/bison.m4 (gl_BISON): Likewise.
+ Suggested by Bruno Haible.
+
bootstrap: fix Solaris regression
* build-aux/bootstrap (check_versions): Solaris tr still needs []
around ranges.
* Bison 2.0 or newer.
+ Recommended.
- Needed if you use the 'get_date' module.
+ Needed if you use the 'parse-datetime' module.
+ Homepage:
http://www.gnu.org/software/bison/
+ Download:
func_echo "$element"
func_begin_table
- func_module get_date
+ func_module parse-datetime
func_module timegm
func_module tzset
func_end_table
Date Modules Changes
-2010-09-30 getdate This module is deprecated. Please use get_date
- for get_date(), or help us write getdate-posix
- for getdate(). Also, doc/getdate.texi has been
- renamed to doc/get_date.texi.
+2010-10-05 getdate This module is deprecated. Please use the new
+ parse-datetime module for the replacement
+ function parse_datetime(), or help us write
+ getdate-posix for getdate(). Also, the header
+ "getdate.h" has been renamed "parse-datetime.h",
+ and doc/getdate.texi to doc/parse-datetime.texi.
2010-09-29 sys_wait This module no longer provides the waitpid()
function. If you need this function, you now need
+++ /dev/null
-@c GNU date syntax documentation
-
-@c Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
-@c 2004, 2005, 2006, 2009, 2010 Free Software Foundation, Inc.
-
-@c Permission is granted to copy, distribute and/or modify this document
-@c under the terms of the GNU Free Documentation License, Version 1.3 or
-@c any later version published by the Free Software Foundation; with no
-@c Invariant Sections, with no Front-Cover Texts, and with no Back-Cover
-@c Texts. A copy of the license is included in the ``GNU Free
-@c Documentation License'' file as part of this distribution.
-
-@node Date input formats
-@chapter Date input formats
-
-@cindex date input formats
-@findex get_date
-
-First, a quote:
-
-@quotation
-Our units of temporal measurement, from seconds on up to months, are so
-complicated, asymmetrical and disjunctive so as to make coherent mental
-reckoning in time all but impossible. Indeed, had some tyrannical god
-contrived to enslave our minds to time, to make it all but impossible
-for us to escape subjection to sodden routines and unpleasant surprises,
-he could hardly have done better than handing down our present system.
-It is like a set of trapezoidal building blocks, with no vertical or
-horizontal surfaces, like a language in which the simplest thought
-demands ornate constructions, useless particles and lengthy
-circumlocutions. Unlike the more successful patterns of language and
-science, which enable us to face experience boldly or at least
-level-headedly, our system of temporal calculation silently and
-persistently encourages our terror of time.
-
-@dots{} It is as though architects had to measure length in feet, width
-in meters and height in ells; as though basic instruction manuals
-demanded a knowledge of five different languages. It is no wonder then
-that we often look into our own immediate past or future, last Tuesday
-or a week from Sunday, with feelings of helpless confusion. @dots{}
-
---- Robert Grudin, @cite{Time and the Art of Living}.
-@end quotation
-
-This section describes the textual date representations that @sc{gnu}
-programs accept. These are the strings you, as a user, can supply as
-arguments to the various programs. The C interface (via the
-@code{get_date} function) is not described here.
-
-@menu
-* 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}.
-* 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
-
-
-@node General date syntax
-@section General date syntax
-
-@cindex general date syntax
-
-@cindex items in date strings
-A @dfn{date} is a string, possibly empty, containing many items
-separated by whitespace. The whitespace may be omitted when no
-ambiguity arises. The empty string means the beginning of today (i.e.,
-midnight). Order of the items is immaterial. A date string may contain
-many flavors of items:
-
-@itemize @bullet
-@item calendar date items
-@item time of day items
-@item time zone items
-@item day of the week items
-@item relative items
-@item pure numbers.
-@end itemize
-
-@noindent We describe each of these item types in turn, below.
-
-@cindex numbers, written-out
-@cindex ordinal numbers
-@findex first @r{in date strings}
-@findex next @r{in date strings}
-@findex last @r{in date strings}
-A few ordinal numbers may be written out in words in some contexts. This is
-most useful for specifying day of the week items or relative items (see
-below). Among the most commonly used ordinal numbers, the word
-@samp{last} stands for @math{-1}, @samp{this} stands for 0, and
-@samp{first} and @samp{next} both stand for 1. Because the word
-@samp{second} stands for the unit of time there is no way to write the
-ordinal number 2, but for convenience @samp{third} stands for 3,
-@samp{fourth} for 4, @samp{fifth} for 5,
-@samp{sixth} for 6, @samp{seventh} for 7, @samp{eighth} for 8,
-@samp{ninth} for 9, @samp{tenth} for 10, @samp{eleventh} for 11 and
-@samp{twelfth} for 12.
-
-@cindex months, written-out
-When a month is written this way, it is still considered to be written
-numerically, instead of being ``spelled in full''; this changes the
-allowed strings.
-
-@cindex language, in dates
-In the current implementation, only English is supported for words and
-abbreviations like @samp{AM}, @samp{DST}, @samp{EST}, @samp{first},
-@samp{January}, @samp{Sunday}, @samp{tomorrow}, and @samp{year}.
-
-@cindex language, in dates
-@cindex time zone item
-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,
-specify a date format that is independent of language and that does not
-use time zone items other than @samp{UTC} and @samp{Z}. Here are some
-ways to do this:
-
-@example
-$ LC_ALL=C TZ=UTC0 date
-Mon Mar 1 00:21:42 UTC 2004
-$ TZ=UTC0 date +'%Y-%m-%d %H:%M:%SZ'
-2004-03-01 00:21:42Z
-$ date --iso-8601=ns | tr T ' ' # --iso-8601 is a GNU extension.
-2004-02-29 16:21:42,692722128-0800
-$ date --rfc-2822 # a GNU extension
-Sun, 29 Feb 2004 16:21:42 -0800
-$ date +'%Y-%m-%d %H:%M:%S %z' # %z is a GNU extension.
-2004-02-29 16:21:42 -0800
-$ date +'@@%s.%N' # %s and %N are GNU extensions.
-@@1078100502.692722128
-@end example
-
-@cindex case, ignored in dates
-@cindex comments, in dates
-Alphabetic case is completely ignored in dates. Comments may be introduced
-between round parentheses, as long as included parentheses are properly
-nested. Hyphens not followed by a digit are currently ignored. Leading
-zeros on numbers are ignored.
-
-Invalid dates like @samp{2005-02-29} or times like @samp{24:00} are
-rejected. In the typical case of a host that does not support leap
-seconds, a time like @samp{23:59:60} is rejected even if it
-corresponds to a valid leap second.
-
-
-@node Calendar date items
-@section Calendar date items
-
-@cindex calendar date item
-
-A @dfn{calendar date item} specifies a day of the year. It is
-specified differently, depending on whether the month is specified
-numerically or literally. All these strings specify the same calendar date:
-
-@example
-1972-09-24 # @sc{iso} 8601.
-72-9-24 # Assume 19xx for 69 through 99,
- # 20xx for 00 through 68.
-72-09-24 # Leading zeros are ignored.
-9/24/72 # Common U.S. writing.
-24 September 1972
-24 Sept 72 # September has a special abbreviation.
-24 Sep 72 # Three-letter abbreviations always allowed.
-Sep 24, 1972
-24-sep-72
-24sep72
-@end example
-
-The year can also be omitted. In this case, the last specified year is
-used, or the current year if none. For example:
-
-@example
-9/24
-sep 24
-@end example
-
-Here are the rules.
-
-@cindex @sc{iso} 8601 date format
-@cindex date format, @sc{iso} 8601
-For numeric months, the @sc{iso} 8601 format
-@samp{@var{year}-@var{month}-@var{day}} is allowed, where @var{year} is
-any positive number, @var{month} is a number between 01 and 12, and
-@var{day} is a number between 01 and 31. A leading zero must be present
-if a number is less than ten. If @var{year} is 68 or smaller, then 2000
-is added to it; otherwise, if @var{year} is less than 100,
-then 1900 is added to it. The construct
-@samp{@var{month}/@var{day}/@var{year}}, popular in the United States,
-is accepted. Also @samp{@var{month}/@var{day}}, omitting the year.
-
-@cindex month names in date strings
-@cindex abbreviations for months
-Literal months may be spelled out in full: @samp{January},
-@samp{February}, @samp{March}, @samp{April}, @samp{May}, @samp{June},
-@samp{July}, @samp{August}, @samp{September}, @samp{October},
-@samp{November} or @samp{December}. Literal months may be abbreviated
-to their first three letters, possibly followed by an abbreviating dot.
-It is also permitted to write @samp{Sept} instead of @samp{September}.
-
-When months are written literally, the calendar date may be given as any
-of the following:
-
-@example
-@var{day} @var{month} @var{year}
-@var{day} @var{month}
-@var{month} @var{day} @var{year}
-@var{day}-@var{month}-@var{year}
-@end example
-
-Or, omitting the year:
-
-@example
-@var{month} @var{day}
-@end example
-
-
-@node Time of day items
-@section Time of day items
-
-@cindex time of day item
-
-A @dfn{time of day item} in date strings specifies the time on a given
-day. Here are some examples, all of which represent the same time:
-
-@example
-20:02:00.000000
-20:02
-8:02pm
-20:02-0500 # In @sc{est} (U.S. Eastern Standard Time).
-@end example
-
-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
-@samp{.} or @samp{,} and a fraction containing one or more digits.
-Alternatively,
-@samp{:@var{second}} can be omitted, in which case it is taken to
-be zero. On the rare hosts that support leap seconds, @var{second}
-may be 60.
-
-@findex am @r{in date strings}
-@findex pm @r{in date strings}
-@findex midnight @r{in date strings}
-@findex noon @r{in date strings}
-If the time is followed by @samp{am} or @samp{pm} (or @samp{a.m.}
-or @samp{p.m.}), @var{hour} is restricted to run from 1 to 12, and
-@samp{:@var{minute}} may be omitted (taken to be zero). @samp{am}
-indicates the first half of the day, @samp{pm} indicates the second
-half of the day. In this notation, 12 is the predecessor of 1:
-midnight is @samp{12am} while noon is @samp{12pm}.
-(This is the zero-oriented interpretation of @samp{12am} and @samp{12pm},
-as opposed to the old tradition derived from Latin
-which uses @samp{12m} for noon and @samp{12pm} for midnight.)
-
-@cindex time zone correction
-@cindex minutes, time zone correction by
-The time may alternatively be followed by a time zone correction,
-expressed as @samp{@var{s}@var{hh}@var{mm}}, where @var{s} is @samp{+}
-or @samp{-}, @var{hh} is a number of zone hours and @var{mm} is a number
-of zone minutes.
-The zone minutes term, @var{mm}, may be omitted, in which case
-the one- or two-digit correction is interpreted as a number of hours.
-You can also separate @var{hh} from @var{mm} with a colon.
-When a time zone correction is given this way, it
-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. For example,
-@samp{+0530} and @samp{+05:30} both stand for the time zone 5.5 hours
-ahead of @sc{utc} (e.g., India).
-This is the best way to
-specify a time zone correction by fractional parts of an hour.
-The maximum zone correction is 24 hours.
-
-Either @samp{am}/@samp{pm} or a time zone correction may be specified,
-but not both.
-
-
-@node Time zone items
-@section Time zone items
-
-@cindex time zone item
-
-A @dfn{time zone item} specifies an international time zone, indicated
-by a small set of letters, e.g., @samp{UTC} or @samp{Z}
-for Coordinated Universal
-Time. Any included periods are ignored. By following a
-non-daylight-saving time zone by the string @samp{DST} in a separate
-word (that is, separated by some white space), the corresponding
-daylight saving time zone may be specified.
-Alternatively, a non-daylight-saving time zone can be followed by a
-time zone correction, to add the two values. This is normally done
-only for @samp{UTC}; for example, @samp{UTC+05:30} is equivalent to
-@samp{+05:30}.
-
-Time zone items other than @samp{UTC} and @samp{Z}
-are obsolescent and are not recommended, because they
-are ambiguous; for example, @samp{EST} has a different meaning in
-Australia than in the United States. Instead, it's better to use
-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
-
-@cindex day of week item
-
-The explicit mention of a day of the week will forward the date
-(only if necessary) to reach that day of the week in the future.
-
-Days of the week may be spelled out in full: @samp{Sunday},
-@samp{Monday}, @samp{Tuesday}, @samp{Wednesday}, @samp{Thursday},
-@samp{Friday} or @samp{Saturday}. Days may be abbreviated to their
-first three letters, optionally followed by a period. The special
-abbreviations @samp{Tues} for @samp{Tuesday}, @samp{Wednes} for
-@samp{Wednesday} and @samp{Thur} or @samp{Thurs} for @samp{Thursday} are
-also allowed.
-
-@findex next @var{day}
-@findex last @var{day}
-A number may precede a day of the week item to move forward
-supplementary weeks. It is best used in expression like @samp{third
-monday}. In this context, @samp{last @var{day}} or @samp{next
-@var{day}} is also acceptable; they move one week before or after
-the day that @var{day} by itself would represent.
-
-A comma following a day of the week item is ignored.
-
-
-@node Relative items in date strings
-@section Relative items in date strings
-
-@cindex relative items in date strings
-@cindex displacement of dates
-
-@dfn{Relative items} adjust a date (or the current date if none) forward
-or backward. The effects of relative items accumulate. Here are some
-examples:
-
-@example
-1 year
-1 year ago
-3 years
-2 days
-@end example
-
-@findex year @r{in date strings}
-@findex month @r{in date strings}
-@findex fortnight @r{in date strings}
-@findex week @r{in date strings}
-@findex day @r{in date strings}
-@findex hour @r{in date strings}
-@findex minute @r{in date strings}
-The unit of time displacement may be selected by the string @samp{year}
-or @samp{month} for moving by whole years or months. These are fuzzy
-units, as years and months are not all of equal duration. More precise
-units are @samp{fortnight} which is worth 14 days, @samp{week} worth 7
-days, @samp{day} worth 24 hours, @samp{hour} worth 60 minutes,
-@samp{minute} or @samp{min} worth 60 seconds, and @samp{second} or
-@samp{sec} worth one second. An @samp{s} suffix on these units is
-accepted and ignored.
-
-@findex ago @r{in date strings}
-The unit of time may be preceded by a multiplier, given as an optionally
-signed number. Unsigned numbers are taken as positively signed. No
-number at all implies 1 for a multiplier. Following a relative item by
-the string @samp{ago} is equivalent to preceding the unit by a
-multiplier with value @math{-1}.
-
-@findex day @r{in date strings}
-@findex tomorrow @r{in date strings}
-@findex yesterday @r{in date strings}
-The string @samp{tomorrow} is worth one day in the future (equivalent
-to @samp{day}), the string @samp{yesterday} is worth
-one day in the past (equivalent to @samp{day ago}).
-
-@findex now @r{in date strings}
-@findex today @r{in date strings}
-@findex this @r{in date strings}
-The strings @samp{now} or @samp{today} are relative items corresponding
-to zero-valued time displacement, these strings come from the fact
-a zero-valued time displacement represents the current time when not
-otherwise changed by previous items. They may be used to stress other
-items, like in @samp{12:00 today}. The string @samp{this} also has
-the meaning of a zero-valued time displacement, but is preferred in
-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,
-the resulting date and time are adjusted accordingly.
-
-The fuzz in units can cause problems with relative items. For
-example, @samp{2003-07-31 -1 month} might evaluate to 2003-07-01,
-because 2003-06-31 is an invalid date. To determine the previous
-month more reliably, you can ask for the month before the 15th of the
-current month. For example:
-
-@example
-$ date -R
-Thu, 31 Jul 2003 13:02:39 -0700
-$ date --date='-1 month' +'Last month was %B?'
-Last month was July?
-$ date --date="$(date +%Y-%m-15) -1 month" +'Last month was %B!'
-Last month was June!
-@end example
-
-Also, take care when manipulating dates around clock changes such as
-daylight saving leaps. In a few cases these have added or subtracted
-as much as 24 hours from the clock, so it is often wise to adopt
-universal time by setting the @env{TZ} environment variable to
-@samp{UTC0} before embarking on calendrical calculations.
-
-@node Pure numbers in date strings
-@section Pure numbers in date strings
-
-@cindex pure numbers in date strings
-
-The precise interpretation of a pure decimal number depends
-on the context in the date string.
-
-If the decimal number is of the form @var{yyyy}@var{mm}@var{dd} and no
-other calendar date item (@pxref{Calendar date items}) appears before it
-in the date string, then @var{yyyy} is read as the year, @var{mm} as the
-month number and @var{dd} as the day of the month, for the specified
-calendar date.
-
-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 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
-year.
-
-
-@node Seconds since the Epoch
-@section Seconds since the Epoch
-
-If you precede a number with @samp{@@}, it represents an internal time
-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. 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}
-Internally, computer times are represented as a count of seconds since
-an epoch---a well-defined point of time. On @acronym{GNU} and
-@acronym{POSIX} systems, the epoch is 1970-01-01 00:00:00 @sc{utc}, so
-@samp{@@0} represents this time, @samp{@@1} represents 1970-01-01
-00:00:01 @sc{utc}, and so forth. @acronym{GNU} and most other
-@acronym{POSIX}-compliant systems support such times as an extension
-to @acronym{POSIX}, using negative counts, so that @samp{@@-1}
-represents 1969-12-31 23:59:59 @sc{utc}.
-
-Traditional Unix systems count seconds with 32-bit two's-complement
-integers and can represent times from 1901-12-13 20:45:52 through
-2038-01-19 03:14:07 @sc{utc}. More modern systems use 64-bit counts
-of seconds with nanosecond subcounts, and can represent all the times
-in the known lifetime of the universe to a resolution of 1 nanosecond.
-
-On most hosts, these counts ignore the presence of leap seconds.
-For example, on most hosts @samp{@@915148799} represents 1998-12-31
-23:59:59 @sc{utc}, @samp{@@915148800} represents 1999-01-01 00:00:00
-@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}
-
-@cindex authors of @code{get_date}
-
-@cindex Bellovin, Steven M.
-@cindex Salz, Rich
-@cindex Berets, Jim
-@cindex MacKenzie, David
-@cindex Meyering, Jim
-@cindex Eggert, Paul
-@code{get_date} was originally implemented by Steven M. Bellovin
-(@email{smb@@research.att.com}) while at the University of North Carolina
-at Chapel Hill. The code was later tweaked by a couple of people on
-Usenet, then completely overhauled by Rich $alz (@email{rsalz@@bbn.com})
-and Jim Berets (@email{jberets@@bbn.com}) in August, 1990. Various
-revisions for the @sc{gnu} system were made by David MacKenzie, Jim Meyering,
-Paul Eggert and others.
-
-@cindex Pinard, F.
-@cindex Berry, K.
-This chapter was originally produced by Fran@,{c}ois Pinard
-(@email{pinard@@iro.umontreal.ca}) from the @file{get_date.y} source code,
-and then edited by K.@: Berry (@email{kb@@cs.umb.edu}).
--- /dev/null
+@include parse-datetime.texi
--- /dev/null
+@c GNU date syntax documentation
+
+@c Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
+@c 2004, 2005, 2006, 2009, 2010 Free Software Foundation, Inc.
+
+@c Permission is granted to copy, distribute and/or modify this document
+@c under the terms of the GNU Free Documentation License, Version 1.3 or
+@c any later version published by the Free Software Foundation; with no
+@c Invariant Sections, with no Front-Cover Texts, and with no Back-Cover
+@c Texts. A copy of the license is included in the ``GNU Free
+@c Documentation License'' file as part of this distribution.
+
+@node Date input formats
+@chapter Date input formats
+
+@cindex date input formats
+@findex parse_datetime
+
+First, a quote:
+
+@quotation
+Our units of temporal measurement, from seconds on up to months, are so
+complicated, asymmetrical and disjunctive so as to make coherent mental
+reckoning in time all but impossible. Indeed, had some tyrannical god
+contrived to enslave our minds to time, to make it all but impossible
+for us to escape subjection to sodden routines and unpleasant surprises,
+he could hardly have done better than handing down our present system.
+It is like a set of trapezoidal building blocks, with no vertical or
+horizontal surfaces, like a language in which the simplest thought
+demands ornate constructions, useless particles and lengthy
+circumlocutions. Unlike the more successful patterns of language and
+science, which enable us to face experience boldly or at least
+level-headedly, our system of temporal calculation silently and
+persistently encourages our terror of time.
+
+@dots{} It is as though architects had to measure length in feet, width
+in meters and height in ells; as though basic instruction manuals
+demanded a knowledge of five different languages. It is no wonder then
+that we often look into our own immediate past or future, last Tuesday
+or a week from Sunday, with feelings of helpless confusion. @dots{}
+
+--- Robert Grudin, @cite{Time and the Art of Living}.
+@end quotation
+
+This section describes the textual date representations that @sc{gnu}
+programs accept. These are the strings you, as a user, can supply as
+arguments to the various programs. The C interface (via the
+@code{parse_datetime} function) is not described here.
+
+@menu
+* 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}.
+* 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 parse_datetime:: Bellovin, Eggert, Salz, Berets, et al.
+@end menu
+
+
+@node General date syntax
+@section General date syntax
+
+@cindex general date syntax
+
+@cindex items in date strings
+A @dfn{date} is a string, possibly empty, containing many items
+separated by whitespace. The whitespace may be omitted when no
+ambiguity arises. The empty string means the beginning of today (i.e.,
+midnight). Order of the items is immaterial. A date string may contain
+many flavors of items:
+
+@itemize @bullet
+@item calendar date items
+@item time of day items
+@item time zone items
+@item day of the week items
+@item relative items
+@item pure numbers.
+@end itemize
+
+@noindent We describe each of these item types in turn, below.
+
+@cindex numbers, written-out
+@cindex ordinal numbers
+@findex first @r{in date strings}
+@findex next @r{in date strings}
+@findex last @r{in date strings}
+A few ordinal numbers may be written out in words in some contexts. This is
+most useful for specifying day of the week items or relative items (see
+below). Among the most commonly used ordinal numbers, the word
+@samp{last} stands for @math{-1}, @samp{this} stands for 0, and
+@samp{first} and @samp{next} both stand for 1. Because the word
+@samp{second} stands for the unit of time there is no way to write the
+ordinal number 2, but for convenience @samp{third} stands for 3,
+@samp{fourth} for 4, @samp{fifth} for 5,
+@samp{sixth} for 6, @samp{seventh} for 7, @samp{eighth} for 8,
+@samp{ninth} for 9, @samp{tenth} for 10, @samp{eleventh} for 11 and
+@samp{twelfth} for 12.
+
+@cindex months, written-out
+When a month is written this way, it is still considered to be written
+numerically, instead of being ``spelled in full''; this changes the
+allowed strings.
+
+@cindex language, in dates
+In the current implementation, only English is supported for words and
+abbreviations like @samp{AM}, @samp{DST}, @samp{EST}, @samp{first},
+@samp{January}, @samp{Sunday}, @samp{tomorrow}, and @samp{year}.
+
+@cindex language, in dates
+@cindex time zone item
+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,
+specify a date format that is independent of language and that does not
+use time zone items other than @samp{UTC} and @samp{Z}. Here are some
+ways to do this:
+
+@example
+$ LC_ALL=C TZ=UTC0 date
+Mon Mar 1 00:21:42 UTC 2004
+$ TZ=UTC0 date +'%Y-%m-%d %H:%M:%SZ'
+2004-03-01 00:21:42Z
+$ date --iso-8601=ns | tr T ' ' # --iso-8601 is a GNU extension.
+2004-02-29 16:21:42,692722128-0800
+$ date --rfc-2822 # a GNU extension
+Sun, 29 Feb 2004 16:21:42 -0800
+$ date +'%Y-%m-%d %H:%M:%S %z' # %z is a GNU extension.
+2004-02-29 16:21:42 -0800
+$ date +'@@%s.%N' # %s and %N are GNU extensions.
+@@1078100502.692722128
+@end example
+
+@cindex case, ignored in dates
+@cindex comments, in dates
+Alphabetic case is completely ignored in dates. Comments may be introduced
+between round parentheses, as long as included parentheses are properly
+nested. Hyphens not followed by a digit are currently ignored. Leading
+zeros on numbers are ignored.
+
+Invalid dates like @samp{2005-02-29} or times like @samp{24:00} are
+rejected. In the typical case of a host that does not support leap
+seconds, a time like @samp{23:59:60} is rejected even if it
+corresponds to a valid leap second.
+
+
+@node Calendar date items
+@section Calendar date items
+
+@cindex calendar date item
+
+A @dfn{calendar date item} specifies a day of the year. It is
+specified differently, depending on whether the month is specified
+numerically or literally. All these strings specify the same calendar date:
+
+@example
+1972-09-24 # @sc{iso} 8601.
+72-9-24 # Assume 19xx for 69 through 99,
+ # 20xx for 00 through 68.
+72-09-24 # Leading zeros are ignored.
+9/24/72 # Common U.S. writing.
+24 September 1972
+24 Sept 72 # September has a special abbreviation.
+24 Sep 72 # Three-letter abbreviations always allowed.
+Sep 24, 1972
+24-sep-72
+24sep72
+@end example
+
+The year can also be omitted. In this case, the last specified year is
+used, or the current year if none. For example:
+
+@example
+9/24
+sep 24
+@end example
+
+Here are the rules.
+
+@cindex @sc{iso} 8601 date format
+@cindex date format, @sc{iso} 8601
+For numeric months, the @sc{iso} 8601 format
+@samp{@var{year}-@var{month}-@var{day}} is allowed, where @var{year} is
+any positive number, @var{month} is a number between 01 and 12, and
+@var{day} is a number between 01 and 31. A leading zero must be present
+if a number is less than ten. If @var{year} is 68 or smaller, then 2000
+is added to it; otherwise, if @var{year} is less than 100,
+then 1900 is added to it. The construct
+@samp{@var{month}/@var{day}/@var{year}}, popular in the United States,
+is accepted. Also @samp{@var{month}/@var{day}}, omitting the year.
+
+@cindex month names in date strings
+@cindex abbreviations for months
+Literal months may be spelled out in full: @samp{January},
+@samp{February}, @samp{March}, @samp{April}, @samp{May}, @samp{June},
+@samp{July}, @samp{August}, @samp{September}, @samp{October},
+@samp{November} or @samp{December}. Literal months may be abbreviated
+to their first three letters, possibly followed by an abbreviating dot.
+It is also permitted to write @samp{Sept} instead of @samp{September}.
+
+When months are written literally, the calendar date may be given as any
+of the following:
+
+@example
+@var{day} @var{month} @var{year}
+@var{day} @var{month}
+@var{month} @var{day} @var{year}
+@var{day}-@var{month}-@var{year}
+@end example
+
+Or, omitting the year:
+
+@example
+@var{month} @var{day}
+@end example
+
+
+@node Time of day items
+@section Time of day items
+
+@cindex time of day item
+
+A @dfn{time of day item} in date strings specifies the time on a given
+day. Here are some examples, all of which represent the same time:
+
+@example
+20:02:00.000000
+20:02
+8:02pm
+20:02-0500 # In @sc{est} (U.S. Eastern Standard Time).
+@end example
+
+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
+@samp{.} or @samp{,} and a fraction containing one or more digits.
+Alternatively,
+@samp{:@var{second}} can be omitted, in which case it is taken to
+be zero. On the rare hosts that support leap seconds, @var{second}
+may be 60.
+
+@findex am @r{in date strings}
+@findex pm @r{in date strings}
+@findex midnight @r{in date strings}
+@findex noon @r{in date strings}
+If the time is followed by @samp{am} or @samp{pm} (or @samp{a.m.}
+or @samp{p.m.}), @var{hour} is restricted to run from 1 to 12, and
+@samp{:@var{minute}} may be omitted (taken to be zero). @samp{am}
+indicates the first half of the day, @samp{pm} indicates the second
+half of the day. In this notation, 12 is the predecessor of 1:
+midnight is @samp{12am} while noon is @samp{12pm}.
+(This is the zero-oriented interpretation of @samp{12am} and @samp{12pm},
+as opposed to the old tradition derived from Latin
+which uses @samp{12m} for noon and @samp{12pm} for midnight.)
+
+@cindex time zone correction
+@cindex minutes, time zone correction by
+The time may alternatively be followed by a time zone correction,
+expressed as @samp{@var{s}@var{hh}@var{mm}}, where @var{s} is @samp{+}
+or @samp{-}, @var{hh} is a number of zone hours and @var{mm} is a number
+of zone minutes.
+The zone minutes term, @var{mm}, may be omitted, in which case
+the one- or two-digit correction is interpreted as a number of hours.
+You can also separate @var{hh} from @var{mm} with a colon.
+When a time zone correction is given this way, it
+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. For example,
+@samp{+0530} and @samp{+05:30} both stand for the time zone 5.5 hours
+ahead of @sc{utc} (e.g., India).
+This is the best way to
+specify a time zone correction by fractional parts of an hour.
+The maximum zone correction is 24 hours.
+
+Either @samp{am}/@samp{pm} or a time zone correction may be specified,
+but not both.
+
+
+@node Time zone items
+@section Time zone items
+
+@cindex time zone item
+
+A @dfn{time zone item} specifies an international time zone, indicated
+by a small set of letters, e.g., @samp{UTC} or @samp{Z}
+for Coordinated Universal
+Time. Any included periods are ignored. By following a
+non-daylight-saving time zone by the string @samp{DST} in a separate
+word (that is, separated by some white space), the corresponding
+daylight saving time zone may be specified.
+Alternatively, a non-daylight-saving time zone can be followed by a
+time zone correction, to add the two values. This is normally done
+only for @samp{UTC}; for example, @samp{UTC+05:30} is equivalent to
+@samp{+05:30}.
+
+Time zone items other than @samp{UTC} and @samp{Z}
+are obsolescent and are not recommended, because they
+are ambiguous; for example, @samp{EST} has a different meaning in
+Australia than in the United States. Instead, it's better to use
+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
+
+@cindex day of week item
+
+The explicit mention of a day of the week will forward the date
+(only if necessary) to reach that day of the week in the future.
+
+Days of the week may be spelled out in full: @samp{Sunday},
+@samp{Monday}, @samp{Tuesday}, @samp{Wednesday}, @samp{Thursday},
+@samp{Friday} or @samp{Saturday}. Days may be abbreviated to their
+first three letters, optionally followed by a period. The special
+abbreviations @samp{Tues} for @samp{Tuesday}, @samp{Wednes} for
+@samp{Wednesday} and @samp{Thur} or @samp{Thurs} for @samp{Thursday} are
+also allowed.
+
+@findex next @var{day}
+@findex last @var{day}
+A number may precede a day of the week item to move forward
+supplementary weeks. It is best used in expression like @samp{third
+monday}. In this context, @samp{last @var{day}} or @samp{next
+@var{day}} is also acceptable; they move one week before or after
+the day that @var{day} by itself would represent.
+
+A comma following a day of the week item is ignored.
+
+
+@node Relative items in date strings
+@section Relative items in date strings
+
+@cindex relative items in date strings
+@cindex displacement of dates
+
+@dfn{Relative items} adjust a date (or the current date if none) forward
+or backward. The effects of relative items accumulate. Here are some
+examples:
+
+@example
+1 year
+1 year ago
+3 years
+2 days
+@end example
+
+@findex year @r{in date strings}
+@findex month @r{in date strings}
+@findex fortnight @r{in date strings}
+@findex week @r{in date strings}
+@findex day @r{in date strings}
+@findex hour @r{in date strings}
+@findex minute @r{in date strings}
+The unit of time displacement may be selected by the string @samp{year}
+or @samp{month} for moving by whole years or months. These are fuzzy
+units, as years and months are not all of equal duration. More precise
+units are @samp{fortnight} which is worth 14 days, @samp{week} worth 7
+days, @samp{day} worth 24 hours, @samp{hour} worth 60 minutes,
+@samp{minute} or @samp{min} worth 60 seconds, and @samp{second} or
+@samp{sec} worth one second. An @samp{s} suffix on these units is
+accepted and ignored.
+
+@findex ago @r{in date strings}
+The unit of time may be preceded by a multiplier, given as an optionally
+signed number. Unsigned numbers are taken as positively signed. No
+number at all implies 1 for a multiplier. Following a relative item by
+the string @samp{ago} is equivalent to preceding the unit by a
+multiplier with value @math{-1}.
+
+@findex day @r{in date strings}
+@findex tomorrow @r{in date strings}
+@findex yesterday @r{in date strings}
+The string @samp{tomorrow} is worth one day in the future (equivalent
+to @samp{day}), the string @samp{yesterday} is worth
+one day in the past (equivalent to @samp{day ago}).
+
+@findex now @r{in date strings}
+@findex today @r{in date strings}
+@findex this @r{in date strings}
+The strings @samp{now} or @samp{today} are relative items corresponding
+to zero-valued time displacement, these strings come from the fact
+a zero-valued time displacement represents the current time when not
+otherwise changed by previous items. They may be used to stress other
+items, like in @samp{12:00 today}. The string @samp{this} also has
+the meaning of a zero-valued time displacement, but is preferred in
+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,
+the resulting date and time are adjusted accordingly.
+
+The fuzz in units can cause problems with relative items. For
+example, @samp{2003-07-31 -1 month} might evaluate to 2003-07-01,
+because 2003-06-31 is an invalid date. To determine the previous
+month more reliably, you can ask for the month before the 15th of the
+current month. For example:
+
+@example
+$ date -R
+Thu, 31 Jul 2003 13:02:39 -0700
+$ date --date='-1 month' +'Last month was %B?'
+Last month was July?
+$ date --date="$(date +%Y-%m-15) -1 month" +'Last month was %B!'
+Last month was June!
+@end example
+
+Also, take care when manipulating dates around clock changes such as
+daylight saving leaps. In a few cases these have added or subtracted
+as much as 24 hours from the clock, so it is often wise to adopt
+universal time by setting the @env{TZ} environment variable to
+@samp{UTC0} before embarking on calendrical calculations.
+
+@node Pure numbers in date strings
+@section Pure numbers in date strings
+
+@cindex pure numbers in date strings
+
+The precise interpretation of a pure decimal number depends
+on the context in the date string.
+
+If the decimal number is of the form @var{yyyy}@var{mm}@var{dd} and no
+other calendar date item (@pxref{Calendar date items}) appears before it
+in the date string, then @var{yyyy} is read as the year, @var{mm} as the
+month number and @var{dd} as the day of the month, for the specified
+calendar date.
+
+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 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
+year.
+
+
+@node Seconds since the Epoch
+@section Seconds since the Epoch
+
+If you precede a number with @samp{@@}, it represents an internal time
+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. 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}
+Internally, computer times are represented as a count of seconds since
+an epoch---a well-defined point of time. On @acronym{GNU} and
+@acronym{POSIX} systems, the epoch is 1970-01-01 00:00:00 @sc{utc}, so
+@samp{@@0} represents this time, @samp{@@1} represents 1970-01-01
+00:00:01 @sc{utc}, and so forth. @acronym{GNU} and most other
+@acronym{POSIX}-compliant systems support such times as an extension
+to @acronym{POSIX}, using negative counts, so that @samp{@@-1}
+represents 1969-12-31 23:59:59 @sc{utc}.
+
+Traditional Unix systems count seconds with 32-bit two's-complement
+integers and can represent times from 1901-12-13 20:45:52 through
+2038-01-19 03:14:07 @sc{utc}. More modern systems use 64-bit counts
+of seconds with nanosecond subcounts, and can represent all the times
+in the known lifetime of the universe to a resolution of 1 nanosecond.
+
+On most hosts, these counts ignore the presence of leap seconds.
+For example, on most hosts @samp{@@915148799} represents 1998-12-31
+23:59:59 @sc{utc}, @samp{@@915148800} represents 1999-01-01 00:00:00
+@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 parse_datetime
+@section Authors of @code{parse_datetime}
+@c the anchor keeps the old node name, to try to avoid breaking links
+@anchor{Authors of get_date}
+
+@cindex authors of @code{parse_datetime}
+
+@cindex Bellovin, Steven M.
+@cindex Salz, Rich
+@cindex Berets, Jim
+@cindex MacKenzie, David
+@cindex Meyering, Jim
+@cindex Eggert, Paul
+@code{get_date} was originally implemented by Steven M. Bellovin
+(@email{smb@@research.att.com}) while at the University of North Carolina
+at Chapel Hill. The code was later tweaked by a couple of people on
+Usenet, then completely overhauled by Rich $alz (@email{rsalz@@bbn.com})
+and Jim Berets (@email{jberets@@bbn.com}) in August, 1990. Various
+revisions for the @sc{gnu} system were made by David MacKenzie, Jim Meyering,
+Paul Eggert and others, including renaming it to @code{parse_datetime}
+to avoid confusion with the Posix function @code{getdate}.
+
+@cindex Pinard, F.
+@cindex Berry, K.
+This chapter was originally produced by Fran@,{c}ois Pinard
+(@email{pinard@@iro.umontreal.ca}) from the @file{parse_datetime.y} source code,
+and then edited by K.@: Berry (@email{kb@@cs.umb.edu}).
MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, Cygwin, mingw, BeOS.
@end itemize
-Gnulib provides a module @code{get_date} that contains a function
-@code{get_date}
+Gnulib provides a module @code{parse-datetime} that contains a function
+@code{parse_datetime}
that has similar functionality as the @code{getdate} function.
) || func_exit 1
fi
# Need to run configure and make once, to create built files that are to be
- # distributed (such as get_date.c).
+ # distributed (such as parse-datetime.c).
sed_remove_make_variables='s,[$]([A-Za-z0-9_]*),,g'
# Extract the value of "CLEANFILES += ..." and "MOSTLYCLEANFILES += ...".
cleaned_files=`sed -e "$sed_remove_backslash_newline" < "$testdir/$sourcebase/Makefile.am" \
+++ /dev/null
-%{
-/* Parse a string into an internal time stamp.
-
- Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
- 2010 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
- the Free Software Foundation; either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-/* Originally written by Steven M. Bellovin <smb@research.att.com> while
- at the University of North Carolina at Chapel Hill. Later tweaked by
- a couple of people on Usenet. Completely overhauled by Rich $alz
- <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. 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. */
-
-#include <config.h>
-
-#include "getdate.h"
-
-#include "intprops.h"
-#include "timespec.h"
-#include "verify.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
- the code were included in the Emacs executable, it probably
- wouldn't do any harm to #undef it here; this will only cause
- problems if we try to write to a static variable, which I don't
- think this code needs to do. */
-#ifdef emacs
-# undef static
-#endif
-
-#include <c-ctype.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "xalloc.h"
-
-
-/* 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 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)
-
-/* 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_BASE 1900
-
-#define HOUR(x) ((x) * 60)
-
-/* long_time_t is a signed integer type that contains all time_t values. */
-verify (TYPE_IS_INTEGER (time_t));
-#if TIME_T_FITS_IN_LONG_INT
-typedef long int long_time_t;
-#else
-typedef time_t long_time_t;
-#endif
-
-/* Lots of this code assumes time_t and time_t-like values fit into
- long_time_t. */
-verify (TYPE_MINIMUM (long_time_t) <= TYPE_MINIMUM (time_t)
- && TYPE_MAXIMUM (time_t) <= TYPE_MAXIMUM (long_time_t));
-
-/* FIXME: It also assumes that signed integer overflow silently wraps around,
- but this is not true any more with recent versions of GCC 4. */
-
-/* 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
-{
- char const *name;
- int type;
- int value;
-} table;
-
-/* 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_time_t 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. */
-typedef struct
-{
- /* The input string remaining to be parsed. */
- const char *input;
-
- /* N, if this is the Nth Tuesday. */
- long int day_ordinal;
-
- /* Day of week; Sunday is 0. */
- int day_number;
-
- /* tm_isdst flag for the local zone. */
- int local_isdst;
-
- /* Time zone, in minutes east of UTC. */
- long int time_zone;
-
- /* Style used for time. */
- int meridian;
-
- /* 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];
-} parser_control;
-
-union YYSTYPE;
-static int yylex (union YYSTYPE *, parser_control *);
-static int yyerror (parser_control const *, char const *);
-static long int time_zone_hhmm (parser_control *, textint, long int);
-
-/* Extract into *PC any date and time info from a string of digits
- of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
- YYYY, ...). */
-static void
-digits_to_date_time (parser_control *pc, textint text_int)
-{
- if (pc->dates_seen && ! pc->year.digits
- && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
- pc->year = text_int;
- else
- {
- if (4 < text_int.digits)
- {
- pc->dates_seen++;
- pc->day = text_int.value % 100;
- pc->month = (text_int.value / 100) % 100;
- pc->year.value = text_int.value / 10000;
- pc->year.digits = text_int.digits - 4;
- }
- else
- {
- pc->times_seen++;
- if (text_int.digits <= 2)
- {
- pc->hour = text_int.value;
- pc->minutes = 0;
- }
- else
- {
- pc->hour = text_int.value / 100;
- pc->minutes = text_int.value % 100;
- }
- pc->seconds.tv_sec = 0;
- pc->seconds.tv_nsec = 0;
- pc->meridian = MER24;
- }
- }
-}
-
-/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
-static void
-apply_relative_time (parser_control *pc, relative_time rel, int factor)
-{
- pc->rel.ns += factor * rel.ns;
- pc->rel.seconds += factor * rel.seconds;
- pc->rel.minutes += factor * rel.minutes;
- pc->rel.hour += factor * rel.hour;
- pc->rel.day += factor * rel.day;
- pc->rel.month += factor * rel.month;
- pc->rel.year += factor * rel.year;
- pc->rels_seen = true;
-}
-
-/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
-static void
-set_hhmmss (parser_control *pc, long int hour, long int minutes,
- time_t sec, long int nsec)
-{
- pc->hour = hour;
- pc->minutes = minutes;
- pc->seconds.tv_sec = sec;
- pc->seconds.tv_nsec = nsec;
-}
-
-%}
-
-/* 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
-
-%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
-%token <intval> tDAY_UNIT tDAY_SHIFT
-
-%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
-%token <intval> tMONTH tORDINAL tZONE
-
-%token <textintval> tSNUMBER tUNUMBER
-%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
-
-%type <intval> o_colon_minutes o_merid
-%type <timespec> seconds signed_seconds unsigned_seconds
-
-%type <rel> relunit relunit_snumber dayshift
-
-%%
-
-spec:
- timespec
- | items
- ;
-
-timespec:
- '@' seconds
- {
- pc->seconds = $2;
- pc->timespec_seen = true;
- }
- ;
-
-items:
- /* empty */
- | items item
- ;
-
-item:
- time
- { pc->times_seen++; }
- | local_zone
- { pc->local_zones_seen++; }
- | zone
- { pc->zones_seen++; }
- | date
- { pc->dates_seen++; }
- | day
- { pc->days_seen++; }
- | rel
- | number
- | hybrid
- ;
-
-time:
- tUNUMBER tMERIDIAN
- {
- set_hhmmss (pc, $1.value, 0, 0, 0);
- pc->meridian = $2;
- }
- | tUNUMBER ':' tUNUMBER o_merid
- {
- set_hhmmss (pc, $1.value, $3.value, 0, 0);
- pc->meridian = $4;
- }
- | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
- {
- set_hhmmss (pc, $1.value, $3.value, 0, 0);
- pc->meridian = MER24;
- pc->zones_seen++;
- pc->time_zone = time_zone_hhmm (pc, $4, $5);
- }
- | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
- {
- set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
- pc->meridian = $6;
- }
- | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
- {
- set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
- pc->meridian = MER24;
- pc->zones_seen++;
- pc->time_zone = time_zone_hhmm (pc, $6, $7);
- }
- ;
-
-local_zone:
- tLOCAL_ZONE
- {
- pc->local_isdst = $1;
- pc->dsts_seen += (0 < $1);
- }
- | tLOCAL_ZONE tDST
- {
- pc->local_isdst = 1;
- pc->dsts_seen += (0 < $1) + 1;
- }
- ;
-
-zone:
- tZONE
- { pc->time_zone = $1; }
- | tZONE relunit_snumber
- { pc->time_zone = $1;
- apply_relative_time (pc, $2, 1); }
- | tZONE tSNUMBER o_colon_minutes
- { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
- | tDAYZONE
- { pc->time_zone = $1 + 60; }
- | tZONE tDST
- { pc->time_zone = $1 + 60; }
- ;
-
-day:
- tDAY
- {
- pc->day_ordinal = 0;
- pc->day_number = $1;
- }
- | tDAY ','
- {
- pc->day_ordinal = 0;
- pc->day_number = $1;
- }
- | tORDINAL tDAY
- {
- pc->day_ordinal = $1;
- pc->day_number = $2;
- }
- | tUNUMBER tDAY
- {
- pc->day_ordinal = $1.value;
- pc->day_number = $2;
- }
- ;
-
-date:
- tUNUMBER '/' tUNUMBER
- {
- pc->month = $1.value;
- pc->day = $3.value;
- }
- | tUNUMBER '/' tUNUMBER '/' tUNUMBER
- {
- /* 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 (4 <= $1.digits)
- {
- pc->year = $1;
- pc->month = $3.value;
- pc->day = $5.value;
- }
- else
- {
- 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;
- }
- | 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;
- }
- | 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.value;
- }
- | tMONTH tUNUMBER ',' tUNUMBER
- {
- pc->month = $1;
- pc->day = $2.value;
- pc->year = $4;
- }
- | tUNUMBER tMONTH
- {
- pc->day = $1.value;
- pc->month = $2;
- }
- | tUNUMBER tMONTH tUNUMBER
- {
- pc->day = $1.value;
- pc->month = $2;
- pc->year = $3;
- }
- ;
-
-rel:
- relunit tAGO
- { apply_relative_time (pc, $1, -1); }
- | relunit
- { apply_relative_time (pc, $1, 1); }
- | dayshift
- { apply_relative_time (pc, $1, 1); }
- ;
-
-relunit:
- tORDINAL tYEAR_UNIT
- { $$ = RELATIVE_TIME_0; $$.year = $1; }
- | tUNUMBER tYEAR_UNIT
- { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
- | tYEAR_UNIT
- { $$ = RELATIVE_TIME_0; $$.year = 1; }
- | tORDINAL tMONTH_UNIT
- { $$ = RELATIVE_TIME_0; $$.month = $1; }
- | tUNUMBER tMONTH_UNIT
- { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
- | tMONTH_UNIT
- { $$ = RELATIVE_TIME_0; $$.month = 1; }
- | tORDINAL tDAY_UNIT
- { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
- | tUNUMBER tDAY_UNIT
- { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
- | tDAY_UNIT
- { $$ = RELATIVE_TIME_0; $$.day = $1; }
- | tORDINAL tHOUR_UNIT
- { $$ = RELATIVE_TIME_0; $$.hour = $1; }
- | tUNUMBER tHOUR_UNIT
- { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
- | tHOUR_UNIT
- { $$ = RELATIVE_TIME_0; $$.hour = 1; }
- | tORDINAL tMINUTE_UNIT
- { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
- | tUNUMBER tMINUTE_UNIT
- { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
- | tMINUTE_UNIT
- { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
- | tORDINAL tSEC_UNIT
- { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
- | tUNUMBER tSEC_UNIT
- { $$ = 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
- { $$ = 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; }
- ;
-
-dayshift:
- tDAY_SHIFT
- { $$ = RELATIVE_TIME_0; $$.day = $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
- { digits_to_date_time (pc, $1); }
- ;
-
-hybrid:
- tUNUMBER relunit_snumber
- {
- /* Hybrid all-digit and relative offset, so that we accept e.g.,
- "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
- digits_to_date_time (pc, $1);
- apply_relative_time (pc, $2, 1);
- }
- ;
-
-o_colon_minutes:
- /* empty */
- { $$ = -1; }
- | ':' tUNUMBER
- { $$ = $2.value; }
- ;
-
-o_merid:
- /* empty */
- { $$ = MER24; }
- | tMERIDIAN
- { $$ = $1; }
- ;
-
-%%
-
-static table const meridian_table[] =
-{
- { "AM", tMERIDIAN, MERam },
- { "A.M.", tMERIDIAN, MERam },
- { "PM", tMERIDIAN, MERpm },
- { "P.M.", tMERIDIAN, MERpm },
- { NULL, 0, 0 }
-};
-
-static table const dst_table[] =
-{
- { "DST", tDST, 0 }
-};
-
-static table const month_and_day_table[] =
-{
- { "JANUARY", tMONTH, 1 },
- { "FEBRUARY", tMONTH, 2 },
- { "MARCH", tMONTH, 3 },
- { "APRIL", tMONTH, 4 },
- { "MAY", tMONTH, 5 },
- { "JUNE", tMONTH, 6 },
- { "JULY", tMONTH, 7 },
- { "AUGUST", tMONTH, 8 },
- { "SEPTEMBER",tMONTH, 9 },
- { "SEPT", tMONTH, 9 },
- { "OCTOBER", tMONTH, 10 },
- { "NOVEMBER", tMONTH, 11 },
- { "DECEMBER", tMONTH, 12 },
- { "SUNDAY", tDAY, 0 },
- { "MONDAY", tDAY, 1 },
- { "TUESDAY", tDAY, 2 },
- { "TUES", tDAY, 2 },
- { "WEDNESDAY",tDAY, 3 },
- { "WEDNES", tDAY, 3 },
- { "THURSDAY", tDAY, 4 },
- { "THUR", tDAY, 4 },
- { "THURS", tDAY, 4 },
- { "FRIDAY", tDAY, 5 },
- { "SATURDAY", tDAY, 6 },
- { NULL, 0, 0 }
-};
-
-static table const time_units_table[] =
-{
- { "YEAR", tYEAR_UNIT, 1 },
- { "MONTH", tMONTH_UNIT, 1 },
- { "FORTNIGHT",tDAY_UNIT, 14 },
- { "WEEK", tDAY_UNIT, 7 },
- { "DAY", tDAY_UNIT, 1 },
- { "HOUR", tHOUR_UNIT, 1 },
- { "MINUTE", tMINUTE_UNIT, 1 },
- { "MIN", tMINUTE_UNIT, 1 },
- { "SECOND", tSEC_UNIT, 1 },
- { "SEC", tSEC_UNIT, 1 },
- { NULL, 0, 0 }
-};
-
-/* Assorted relative-time words. */
-static table const relative_time_table[] =
-{
- { "TOMORROW", tDAY_SHIFT, 1 },
- { "YESTERDAY",tDAY_SHIFT, -1 },
- { "TODAY", tDAY_SHIFT, 0 },
- { "NOW", tDAY_SHIFT, 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 },
- { 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
- zone abbreviations are ambiguous; e.g. Australians interpret "EST"
- as Eastern time in Australia, not as US Eastern Standard Time.
- You cannot rely on getdate to handle arbitrary time zone
- abbreviations; use numeric abbreviations like `-0500' instead. */
-static table const time_zone_table[] =
-{
- { "WET", tZONE, HOUR ( 0) }, /* Western European */
- { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
- { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
- { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
- { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
- { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
- { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
- { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
- { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
- { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
- { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
- { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
- { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
- { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
- { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
- { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
- { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
- { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
- { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
- { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
- { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
- { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
- { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
- { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
- { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
- { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
- { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
- { "CET", tZONE, HOUR ( 1) }, /* Central European */
- { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
- { "MET", tZONE, HOUR ( 1) }, /* Middle European */
- { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
- { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
- { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
- { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
- { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
- { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
- { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
- { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
- { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
- { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
- { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
- { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
- { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
- { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
- { "GST", tZONE, HOUR (10) }, /* Guam Standard */
- { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
- { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
- { NULL, 0, 0 }
-};
-
-/* Military time zone table. */
-static table const military_table[] =
-{
- { "A", tZONE, -HOUR ( 1) },
- { "B", tZONE, -HOUR ( 2) },
- { "C", tZONE, -HOUR ( 3) },
- { "D", tZONE, -HOUR ( 4) },
- { "E", tZONE, -HOUR ( 5) },
- { "F", tZONE, -HOUR ( 6) },
- { "G", tZONE, -HOUR ( 7) },
- { "H", tZONE, -HOUR ( 8) },
- { "I", tZONE, -HOUR ( 9) },
- { "K", tZONE, -HOUR (10) },
- { "L", tZONE, -HOUR (11) },
- { "M", tZONE, -HOUR (12) },
- { "N", tZONE, HOUR ( 1) },
- { "O", tZONE, HOUR ( 2) },
- { "P", tZONE, HOUR ( 3) },
- { "Q", tZONE, HOUR ( 4) },
- { "R", tZONE, HOUR ( 5) },
- { "S", tZONE, HOUR ( 6) },
- { "T", tZONE, HOUR ( 7) },
- { "U", tZONE, HOUR ( 8) },
- { "V", tZONE, HOUR ( 9) },
- { "W", tZONE, HOUR (10) },
- { "X", tZONE, HOUR (11) },
- { "Y", tZONE, HOUR (12) },
- { "Z", tZONE, HOUR ( 0) },
- { NULL, 0, 0 }
-};
-
-\f
-
-/* 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. As specified in
- http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
- only valid TZ range, and consider first two digits as hours, if no
- minutes specified. */
-
-static long int
-time_zone_hhmm (parser_control *pc, textint s, long int mm)
-{
- long int n_minutes;
-
- /* If the length of S is 1 or 2 and no minutes are specified,
- interpret it as a number of hours. */
- if (s.digits <= 2 && mm < 0)
- s.value *= 100;
-
- if (mm < 0)
- n_minutes = (s.value / 100) * 60 + s.value % 100;
- else
- n_minutes = s.value * 60 + (s.negative ? -mm : mm);
-
- /* If the absolute number of minutes is larger than 24 hours,
- arrange to reject it by incrementing pc->zones_seen. Thus,
- we allow only values in the range UTC-24:00 to UTC+24:00. */
- if (24 * 60 < abs (n_minutes))
- pc->zones_seen++;
-
- return n_minutes;
-}
-
-static int
-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;
- }
-}
-
-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. */
- else if (textyear.digits == 2)
- year += year < 69 ? 2000 : 1900;
-
- return year;
-}
-
-static table const *
-lookup_zone (parser_control const *pc, char const *name)
-{
- table const *tp;
-
- 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;
-
- for (tp = time_zone_table; tp->name; tp++)
- if (strcmp (name, tp->name) == 0)
- return tp;
-
- return NULL;
-}
-
-#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)
-{
- /* 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 (parser_control const *pc, char *word)
-{
- char *p;
- char *q;
- size_t wordlen;
- table const *tp;
- bool period_found;
- bool abbrev;
-
- /* Make it uppercase. */
- for (p = word; *p; p++)
- {
- unsigned char ch = *p;
- *p = c_toupper (ch);
- }
-
- for (tp = meridian_table; tp->name; tp++)
- if (strcmp (word, tp->name) == 0)
- return tp;
-
- /* See if we have an abbreviation for a month. */
- wordlen = strlen (word);
- abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
-
- for (tp = month_and_day_table; tp->name; tp++)
- if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
- return tp;
-
- if ((tp = lookup_zone (pc, word)))
- return tp;
-
- if (strcmp (word, dst_table[0].name) == 0)
- return dst_table;
-
- for (tp = time_units_table; tp->name; tp++)
- if (strcmp (word, tp->name) == 0)
- return tp;
-
- /* Strip off any plural and try the units table again. */
- if (word[wordlen - 1] == 'S')
- {
- word[wordlen - 1] = '\0';
- for (tp = time_units_table; tp->name; tp++)
- if (strcmp (word, tp->name) == 0)
- return tp;
- word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
- }
-
- for (tp = relative_time_table; tp->name; tp++)
- if (strcmp (word, tp->name) == 0)
- return tp;
-
- /* Military time zones. */
- if (wordlen == 1)
- for (tp = military_table; tp->name; tp++)
- if (word[0] == tp->name[0])
- return tp;
-
- /* Drop out any periods and try the time zone table again. */
- for (period_found = false, p = q = word; (*p = *q); q++)
- if (*q == '.')
- period_found = true;
- else
- p++;
- if (period_found && (tp = lookup_zone (pc, word)))
- return tp;
-
- return NULL;
-}
-
-static int
-yylex (YYSTYPE *lvalp, parser_control *pc)
-{
- unsigned char c;
- size_t count;
-
- for (;;)
- {
- while (c = *pc->input, c_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;
- while (c = *++pc->input, c_isspace (c))
- continue;
- if (! ISDIGIT (c))
- /* skip the '-' sign */
- continue;
- }
- else
- sign = 0;
- 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 (c_isalpha (c))
- {
- char buff[20];
- char *p = buff;
- table const *tp;
-
- do
- {
- if (p < buff + sizeof buff - 1)
- *p++ = c;
- c = *++pc->input;
- }
- while (c_isalpha (c) || c == '.');
-
- *p = '\0';
- tp = lookup_word (pc, buff);
- if (! tp)
- return '?';
- lvalp->intval = tp->value;
- return tp->type;
- }
-
- if (c != '(')
- return *pc->input++;
- count = 0;
- do
- {
- c = *pc->input++;
- if (c == '\0')
- return c;
- if (c == '(')
- count++;
- else if (c == ')')
- count--;
- }
- while (count != 0);
- }
-}
-
-/* Do nothing if the parser reports an error. */
-static int
-yyerror (parser_control const *pc _GL_UNUSED,
- char const *s _GL_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
- *NOW as the basis for the returned time. */
-bool
-get_date (struct timespec *result, char const *p, struct timespec const *now)
-{
- time_t Start;
- long int Start_ns;
- struct tm const *tmp;
- struct tm tm;
- 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)
- {
- gettime (&gettime_buffer);
- now = &gettime_buffer;
- }
-
- Start = now->tv_sec;
- Start_ns = now->tv_nsec;
-
- tmp = localtime (&now->tv_sec);
- if (! tmp)
- return false;
-
- while (c = *p, c_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;
- /* Free tz0, in case this is the 2nd or subsequent time through. */
- free (tz0);
- 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;
- }
- }
-
- /* As documented, be careful to treat the empty string just like
- a date string of "0". Without this, an empty string would be
- declared invalid when parsed during a DST transition. */
- if (*p == '\0')
- p = "0";
-
- pc.input = p;
- 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.tv_sec = tmp->tm_sec;
- pc.seconds.tv_nsec = Start_ns;
- tm.tm_isdst = tmp->tm_isdst;
-
- pc.meridian = MER24;
- pc.rel = RELATIVE_TIME_0;
- pc.timespec_seen = false;
- pc.rels_seen = false;
- pc.dates_seen = 0;
- pc.days_seen = 0;
- pc.times_seen = 0;
- pc.local_zones_seen = 0;
- pc.dsts_seen = 0;
- pc.zones_seen = 0;
-
-#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 = NULL;
-
- /* Probe the names used in the next three calendar quarters, looking
- for a tm_isdst different from the one we already have. */
- {
- int quarter;
- for (quarter = 1; quarter <= 3; quarter++)
- {
- time_t probe = Start + quarter * (90 * 24 * 60 * 60);
- 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)
- {
- {
- 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 = NULL;
- }
- break;
- }
- }
- }
-#else
-#if HAVE_TZNAME
- {
-# if !HAVE_DECL_TZNAME
- extern char *tzname[];
-# endif
- int i;
- for (i = 0; i < 2; i++)
- {
- pc.local_time_zone_table[i].name = tzname[i];
- pc.local_time_zone_table[i].type = tLOCAL_ZONE;
- pc.local_time_zone_table[i].value = i;
- }
- pc.local_time_zone_table[i].name = NULL;
- }
-#else
- pc.local_time_zone_table[0].name = NULL;
-#endif
-#endif
-
- if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
- && ! strcmp (pc.local_time_zone_table[0].name,
- pc.local_time_zone_table[1].name))
- {
- /* This locale uses the same abbrevation for standard and
- 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 = NULL;
- }
-
- if (yyparse (&pc) != 0)
- goto fail;
-
- if (pc.timespec_seen)
- *result = pc.seconds;
- else
- {
- 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;
- }
-
- /* 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;
-
- /* 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;
-
- tm0 = tm;
-
- Start = mktime (&tm);
-
- if (! mktime_ok (&tm0, &tm, Start))
- {
- if (! pc.zones_seen)
- goto fail;
- else
- {
- /* 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_wday != pc.day_number)));
- tm.tm_isdst = -1;
- Start = mktime (&tm);
- if (Start == (time_t) -1)
- goto fail;
- }
-
- /* 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;
- }
-
- /* The only "output" of this if-block is an updated Start value,
- so this block must follow others that clobber 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;
- }
-
- /* 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_time_t d3 = pc.rel.seconds;
- long_time_t t3 = t2 + d3;
- long int d4 = (sum_ns - normalized_ns) / BILLION;
- long_time_t t4 = t3 + d4;
- time_t t5 = t4;
-
- 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))
- | (t5 != t4))
- goto fail;
-
- result->tv_sec = t5;
- result->tv_nsec = normalized_ns;
- }
- }
-
- 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
-
-int
-main (int ac, char **av)
-{
- char buff[BUFSIZ];
-
- printf ("Enter date, or blank line to exit.\n\t> ");
- fflush (stdout);
-
- buff[BUFSIZ - 1] = '\0';
- while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
- {
- 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
- {
- 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 /* TEST */
-/* Parse a string into an internal time stamp.
-
- Copyright (C) 1995, 1997, 1998, 2003, 2004, 2007, 2009, 2010 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
- the Free Software Foundation; either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include <stdbool.h>
-#include <time.h>
-
-bool get_date (struct timespec *, char const *, struct timespec const *);
+/* Obsolete; consider using parse-datetime.h instead. */
+#include "parse-datetime.h"
+#define get_date(a, b, c) parse_datetime (a, b, c)
--- /dev/null
+/* Parse a string into an internal time stamp.
+
+ Copyright (C) 1995, 1997, 1998, 2003, 2004, 2007, 2009, 2010 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
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stdbool.h>
+#include <time.h>
+
+bool parse_datetime (struct timespec *, char const *, struct timespec const *);
--- /dev/null
+%{
+/* Parse a string into an internal time stamp.
+
+ Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+ 2010 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
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Originally written by Steven M. Bellovin <smb@research.att.com> while
+ at the University of North Carolina at Chapel Hill. Later tweaked by
+ a couple of people on Usenet. Completely overhauled by Rich $alz
+ <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. 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. */
+
+#include <config.h>
+
+#include "parse-datetime.h"
+
+#include "intprops.h"
+#include "timespec.h"
+#include "verify.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
+ the code were included in the Emacs executable, it probably
+ wouldn't do any harm to #undef it here; this will only cause
+ problems if we try to write to a static variable, which I don't
+ think this code needs to do. */
+#ifdef emacs
+# undef static
+#endif
+
+#include <c-ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "xalloc.h"
+
+
+/* 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 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)
+
+/* 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_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+/* long_time_t is a signed integer type that contains all time_t values. */
+verify (TYPE_IS_INTEGER (time_t));
+#if TIME_T_FITS_IN_LONG_INT
+typedef long int long_time_t;
+#else
+typedef time_t long_time_t;
+#endif
+
+/* Lots of this code assumes time_t and time_t-like values fit into
+ long_time_t. */
+verify (TYPE_MINIMUM (long_time_t) <= TYPE_MINIMUM (time_t)
+ && TYPE_MAXIMUM (time_t) <= TYPE_MAXIMUM (long_time_t));
+
+/* FIXME: It also assumes that signed integer overflow silently wraps around,
+ but this is not true any more with recent versions of GCC 4. */
+
+/* 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
+{
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* 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_time_t 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. */
+typedef struct
+{
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ long int day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ long int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* 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];
+} parser_control;
+
+union YYSTYPE;
+static int yylex (union YYSTYPE *, parser_control *);
+static int yyerror (parser_control const *, char const *);
+static long int time_zone_hhmm (parser_control *, textint, long int);
+
+/* Extract into *PC any date and time info from a string of digits
+ of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
+ YYYY, ...). */
+static void
+digits_to_date_time (parser_control *pc, textint text_int)
+{
+ if (pc->dates_seen && ! pc->year.digits
+ && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
+ pc->year = text_int;
+ else
+ {
+ if (4 < text_int.digits)
+ {
+ pc->dates_seen++;
+ pc->day = text_int.value % 100;
+ pc->month = (text_int.value / 100) % 100;
+ pc->year.value = text_int.value / 10000;
+ pc->year.digits = text_int.digits - 4;
+ }
+ else
+ {
+ pc->times_seen++;
+ if (text_int.digits <= 2)
+ {
+ pc->hour = text_int.value;
+ pc->minutes = 0;
+ }
+ else
+ {
+ pc->hour = text_int.value / 100;
+ pc->minutes = text_int.value % 100;
+ }
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
+ }
+ }
+}
+
+/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
+static void
+apply_relative_time (parser_control *pc, relative_time rel, int factor)
+{
+ pc->rel.ns += factor * rel.ns;
+ pc->rel.seconds += factor * rel.seconds;
+ pc->rel.minutes += factor * rel.minutes;
+ pc->rel.hour += factor * rel.hour;
+ pc->rel.day += factor * rel.day;
+ pc->rel.month += factor * rel.month;
+ pc->rel.year += factor * rel.year;
+ pc->rels_seen = true;
+}
+
+/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
+static void
+set_hhmmss (parser_control *pc, long int hour, long int minutes,
+ time_t sec, long int nsec)
+{
+ pc->hour = hour;
+ pc->minutes = minutes;
+ pc->seconds.tv_sec = sec;
+ pc->seconds.tv_nsec = nsec;
+}
+
+%}
+
+/* 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
+
+%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
+%token <intval> tDAY_UNIT tDAY_SHIFT
+
+%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
+%token <intval> tMONTH tORDINAL tZONE
+
+%token <textintval> tSNUMBER tUNUMBER
+%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
+
+%type <intval> o_colon_minutes o_merid
+%type <timespec> seconds signed_seconds unsigned_seconds
+
+%type <rel> relunit relunit_snumber dayshift
+
+%%
+
+spec:
+ timespec
+ | items
+ ;
+
+timespec:
+ '@' seconds
+ {
+ pc->seconds = $2;
+ pc->timespec_seen = true;
+ }
+ ;
+
+items:
+ /* empty */
+ | items item
+ ;
+
+item:
+ time
+ { pc->times_seen++; }
+ | local_zone
+ { pc->local_zones_seen++; }
+ | zone
+ { pc->zones_seen++; }
+ | date
+ { pc->dates_seen++; }
+ | day
+ { pc->days_seen++; }
+ | rel
+ | number
+ | hybrid
+ ;
+
+time:
+ tUNUMBER tMERIDIAN
+ {
+ set_hhmmss (pc, $1.value, 0, 0, 0);
+ pc->meridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid
+ {
+ set_hhmmss (pc, $1.value, $3.value, 0, 0);
+ pc->meridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
+ {
+ set_hhmmss (pc, $1.value, $3.value, 0, 0);
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = time_zone_hhmm (pc, $4, $5);
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
+ {
+ set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+ pc->meridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
+ {
+ set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = time_zone_hhmm (pc, $6, $7);
+ }
+ ;
+
+local_zone:
+ tLOCAL_ZONE
+ {
+ pc->local_isdst = $1;
+ pc->dsts_seen += (0 < $1);
+ }
+ | tLOCAL_ZONE tDST
+ {
+ pc->local_isdst = 1;
+ pc->dsts_seen += (0 < $1) + 1;
+ }
+ ;
+
+zone:
+ tZONE
+ { pc->time_zone = $1; }
+ | tZONE relunit_snumber
+ { pc->time_zone = $1;
+ apply_relative_time (pc, $2, 1); }
+ | tZONE tSNUMBER o_colon_minutes
+ { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
+ | tDAYZONE
+ { pc->time_zone = $1 + 60; }
+ | tZONE tDST
+ { pc->time_zone = $1 + 60; }
+ ;
+
+day:
+ tDAY
+ {
+ pc->day_ordinal = 0;
+ pc->day_number = $1;
+ }
+ | tDAY ','
+ {
+ pc->day_ordinal = 0;
+ pc->day_number = $1;
+ }
+ | tORDINAL tDAY
+ {
+ pc->day_ordinal = $1;
+ pc->day_number = $2;
+ }
+ | tUNUMBER tDAY
+ {
+ pc->day_ordinal = $1.value;
+ pc->day_number = $2;
+ }
+ ;
+
+date:
+ tUNUMBER '/' tUNUMBER
+ {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER
+ {
+ /* 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 (4 <= $1.digits)
+ {
+ pc->year = $1;
+ pc->month = $3.value;
+ pc->day = $5.value;
+ }
+ else
+ {
+ 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;
+ }
+ | 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;
+ }
+ | 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.value;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER
+ {
+ pc->month = $1;
+ pc->day = $2.value;
+ pc->year = $4;
+ }
+ | tUNUMBER tMONTH
+ {
+ pc->day = $1.value;
+ pc->month = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER
+ {
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year = $3;
+ }
+ ;
+
+rel:
+ relunit tAGO
+ { apply_relative_time (pc, $1, -1); }
+ | relunit
+ { apply_relative_time (pc, $1, 1); }
+ | dayshift
+ { apply_relative_time (pc, $1, 1); }
+ ;
+
+relunit:
+ tORDINAL tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1; }
+ | tUNUMBER tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
+ | tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = 1; }
+ | tORDINAL tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1; }
+ | tUNUMBER tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
+ | tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = 1; }
+ | tORDINAL tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
+ | tUNUMBER tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
+ | tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1; }
+ | tORDINAL tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1; }
+ | tUNUMBER tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
+ | tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = 1; }
+ | tORDINAL tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
+ | tUNUMBER tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
+ | tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
+ | tORDINAL tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
+ | tUNUMBER tSEC_UNIT
+ { $$ = 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
+ { $$ = 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; }
+ ;
+
+dayshift:
+ tDAY_SHIFT
+ { $$ = RELATIVE_TIME_0; $$.day = $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
+ { digits_to_date_time (pc, $1); }
+ ;
+
+hybrid:
+ tUNUMBER relunit_snumber
+ {
+ /* Hybrid all-digit and relative offset, so that we accept e.g.,
+ "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
+ digits_to_date_time (pc, $1);
+ apply_relative_time (pc, $2, 1);
+ }
+ ;
+
+o_colon_minutes:
+ /* empty */
+ { $$ = -1; }
+ | ':' tUNUMBER
+ { $$ = $2.value; }
+ ;
+
+o_merid:
+ /* empty */
+ { $$ = MER24; }
+ | tMERIDIAN
+ { $$ = $1; }
+ ;
+
+%%
+
+static table const meridian_table[] =
+{
+ { "AM", tMERIDIAN, MERam },
+ { "A.M.", tMERIDIAN, MERam },
+ { "PM", tMERIDIAN, MERpm },
+ { "P.M.", tMERIDIAN, MERpm },
+ { NULL, 0, 0 }
+};
+
+static table const dst_table[] =
+{
+ { "DST", tDST, 0 }
+};
+
+static table const month_and_day_table[] =
+{
+ { "JANUARY", tMONTH, 1 },
+ { "FEBRUARY", tMONTH, 2 },
+ { "MARCH", tMONTH, 3 },
+ { "APRIL", tMONTH, 4 },
+ { "MAY", tMONTH, 5 },
+ { "JUNE", tMONTH, 6 },
+ { "JULY", tMONTH, 7 },
+ { "AUGUST", tMONTH, 8 },
+ { "SEPTEMBER",tMONTH, 9 },
+ { "SEPT", tMONTH, 9 },
+ { "OCTOBER", tMONTH, 10 },
+ { "NOVEMBER", tMONTH, 11 },
+ { "DECEMBER", tMONTH, 12 },
+ { "SUNDAY", tDAY, 0 },
+ { "MONDAY", tDAY, 1 },
+ { "TUESDAY", tDAY, 2 },
+ { "TUES", tDAY, 2 },
+ { "WEDNESDAY",tDAY, 3 },
+ { "WEDNES", tDAY, 3 },
+ { "THURSDAY", tDAY, 4 },
+ { "THUR", tDAY, 4 },
+ { "THURS", tDAY, 4 },
+ { "FRIDAY", tDAY, 5 },
+ { "SATURDAY", tDAY, 6 },
+ { NULL, 0, 0 }
+};
+
+static table const time_units_table[] =
+{
+ { "YEAR", tYEAR_UNIT, 1 },
+ { "MONTH", tMONTH_UNIT, 1 },
+ { "FORTNIGHT",tDAY_UNIT, 14 },
+ { "WEEK", tDAY_UNIT, 7 },
+ { "DAY", tDAY_UNIT, 1 },
+ { "HOUR", tHOUR_UNIT, 1 },
+ { "MINUTE", tMINUTE_UNIT, 1 },
+ { "MIN", tMINUTE_UNIT, 1 },
+ { "SECOND", tSEC_UNIT, 1 },
+ { "SEC", tSEC_UNIT, 1 },
+ { NULL, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static table const relative_time_table[] =
+{
+ { "TOMORROW", tDAY_SHIFT, 1 },
+ { "YESTERDAY",tDAY_SHIFT, -1 },
+ { "TODAY", tDAY_SHIFT, 0 },
+ { "NOW", tDAY_SHIFT, 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 },
+ { 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
+ zone abbreviations are ambiguous; e.g. Australians interpret "EST"
+ as Eastern time in Australia, not as US Eastern Standard Time.
+ You cannot rely on getdate to handle arbitrary time zone
+ abbreviations; use numeric abbreviations like `-0500' instead. */
+static table const time_zone_table[] =
+{
+ { "WET", tZONE, HOUR ( 0) }, /* Western European */
+ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
+ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
+ { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
+ { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
+ { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
+ { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
+ { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
+ { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
+ { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
+ { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
+ { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
+ { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
+ { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
+ { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
+ { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
+ { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
+ { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
+ { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
+ { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
+ { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
+ { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
+ { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
+ { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
+ { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
+ { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
+ { "CET", tZONE, HOUR ( 1) }, /* Central European */
+ { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
+ { "MET", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
+ { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
+ { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
+ { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
+ { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
+ { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
+ { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
+ { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
+ { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
+ { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
+ { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
+ { "GST", tZONE, HOUR (10) }, /* Guam Standard */
+ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
+ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
+ { NULL, 0, 0 }
+};
+
+/* Military time zone table. */
+static table const military_table[] =
+{
+ { "A", tZONE, -HOUR ( 1) },
+ { "B", tZONE, -HOUR ( 2) },
+ { "C", tZONE, -HOUR ( 3) },
+ { "D", tZONE, -HOUR ( 4) },
+ { "E", tZONE, -HOUR ( 5) },
+ { "F", tZONE, -HOUR ( 6) },
+ { "G", tZONE, -HOUR ( 7) },
+ { "H", tZONE, -HOUR ( 8) },
+ { "I", tZONE, -HOUR ( 9) },
+ { "K", tZONE, -HOUR (10) },
+ { "L", tZONE, -HOUR (11) },
+ { "M", tZONE, -HOUR (12) },
+ { "N", tZONE, HOUR ( 1) },
+ { "O", tZONE, HOUR ( 2) },
+ { "P", tZONE, HOUR ( 3) },
+ { "Q", tZONE, HOUR ( 4) },
+ { "R", tZONE, HOUR ( 5) },
+ { "S", tZONE, HOUR ( 6) },
+ { "T", tZONE, HOUR ( 7) },
+ { "U", tZONE, HOUR ( 8) },
+ { "V", tZONE, HOUR ( 9) },
+ { "W", tZONE, HOUR (10) },
+ { "X", tZONE, HOUR (11) },
+ { "Y", tZONE, HOUR (12) },
+ { "Z", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+\f
+
+/* 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. As specified in
+ http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
+ only valid TZ range, and consider first two digits as hours, if no
+ minutes specified. */
+
+static long int
+time_zone_hhmm (parser_control *pc, textint s, long int mm)
+{
+ long int n_minutes;
+
+ /* If the length of S is 1 or 2 and no minutes are specified,
+ interpret it as a number of hours. */
+ if (s.digits <= 2 && mm < 0)
+ s.value *= 100;
+
+ if (mm < 0)
+ n_minutes = (s.value / 100) * 60 + s.value % 100;
+ else
+ n_minutes = s.value * 60 + (s.negative ? -mm : mm);
+
+ /* If the absolute number of minutes is larger than 24 hours,
+ arrange to reject it by incrementing pc->zones_seen. Thus,
+ we allow only values in the range UTC-24:00 to UTC+24:00. */
+ if (24 * 60 < abs (n_minutes))
+ pc->zones_seen++;
+
+ return n_minutes;
+}
+
+static int
+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;
+ }
+}
+
+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. */
+ else if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
+
+ return year;
+}
+
+static table const *
+lookup_zone (parser_control const *pc, char const *name)
+{
+ table const *tp;
+
+ 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;
+
+ for (tp = time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ return NULL;
+}
+
+#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)
+{
+ /* 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 (parser_control const *pc, char *word)
+{
+ char *p;
+ char *q;
+ size_t wordlen;
+ table const *tp;
+ bool period_found;
+ bool abbrev;
+
+ /* Make it uppercase. */
+ for (p = word; *p; p++)
+ {
+ unsigned char ch = *p;
+ *p = c_toupper (ch);
+ }
+
+ for (tp = meridian_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* See if we have an abbreviation for a month. */
+ wordlen = strlen (word);
+ abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
+
+ for (tp = month_and_day_table; tp->name; tp++)
+ if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
+ return tp;
+
+ if ((tp = lookup_zone (pc, word)))
+ return tp;
+
+ if (strcmp (word, dst_table[0].name) == 0)
+ return dst_table;
+
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Strip off any plural and try the units table again. */
+ if (word[wordlen - 1] == 'S')
+ {
+ word[wordlen - 1] = '\0';
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+ word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
+ }
+
+ for (tp = relative_time_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Military time zones. */
+ if (wordlen == 1)
+ for (tp = military_table; tp->name; tp++)
+ if (word[0] == tp->name[0])
+ return tp;
+
+ /* Drop out any periods and try the time zone table again. */
+ for (period_found = false, p = q = word; (*p = *q); q++)
+ if (*q == '.')
+ period_found = true;
+ else
+ p++;
+ if (period_found && (tp = lookup_zone (pc, word)))
+ return tp;
+
+ return NULL;
+}
+
+static int
+yylex (YYSTYPE *lvalp, parser_control *pc)
+{
+ unsigned char c;
+ size_t count;
+
+ for (;;)
+ {
+ while (c = *pc->input, c_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;
+ while (c = *++pc->input, c_isspace (c))
+ continue;
+ if (! ISDIGIT (c))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+ 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 (c_isalpha (c))
+ {
+ char buff[20];
+ char *p = buff;
+ table const *tp;
+
+ do
+ {
+ if (p < buff + sizeof buff - 1)
+ *p++ = c;
+ c = *++pc->input;
+ }
+ while (c_isalpha (c) || c == '.');
+
+ *p = '\0';
+ tp = lookup_word (pc, buff);
+ if (! tp)
+ return '?';
+ lvalp->intval = tp->value;
+ return tp->type;
+ }
+
+ if (c != '(')
+ return *pc->input++;
+ count = 0;
+ do
+ {
+ c = *pc->input++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ }
+ while (count != 0);
+ }
+}
+
+/* Do nothing if the parser reports an error. */
+static int
+yyerror (parser_control const *pc _GL_UNUSED,
+ char const *s _GL_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
+ *NOW as the basis for the returned time. */
+bool
+parse_datetime (struct timespec *result, char const *p,
+ struct timespec const *now)
+{
+ time_t Start;
+ long int Start_ns;
+ struct tm const *tmp;
+ struct tm tm;
+ 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)
+ {
+ gettime (&gettime_buffer);
+ now = &gettime_buffer;
+ }
+
+ Start = now->tv_sec;
+ Start_ns = now->tv_nsec;
+
+ tmp = localtime (&now->tv_sec);
+ if (! tmp)
+ return false;
+
+ while (c = *p, c_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;
+ /* Free tz0, in case this is the 2nd or subsequent time through. */
+ free (tz0);
+ 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;
+ }
+ }
+
+ /* As documented, be careful to treat the empty string just like
+ a date string of "0". Without this, an empty string would be
+ declared invalid when parsed during a DST transition. */
+ if (*p == '\0')
+ p = "0";
+
+ pc.input = p;
+ 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.tv_sec = tmp->tm_sec;
+ pc.seconds.tv_nsec = Start_ns;
+ tm.tm_isdst = tmp->tm_isdst;
+
+ pc.meridian = MER24;
+ pc.rel = RELATIVE_TIME_0;
+ pc.timespec_seen = false;
+ pc.rels_seen = false;
+ pc.dates_seen = 0;
+ pc.days_seen = 0;
+ pc.times_seen = 0;
+ pc.local_zones_seen = 0;
+ pc.dsts_seen = 0;
+ pc.zones_seen = 0;
+
+#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 = NULL;
+
+ /* Probe the names used in the next three calendar quarters, looking
+ for a tm_isdst different from the one we already have. */
+ {
+ int quarter;
+ for (quarter = 1; quarter <= 3; quarter++)
+ {
+ time_t probe = Start + quarter * (90 * 24 * 60 * 60);
+ 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)
+ {
+ {
+ 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 = NULL;
+ }
+ break;
+ }
+ }
+ }
+#else
+#if HAVE_TZNAME
+ {
+# if !HAVE_DECL_TZNAME
+ extern char *tzname[];
+# endif
+ int i;
+ for (i = 0; i < 2; i++)
+ {
+ pc.local_time_zone_table[i].name = tzname[i];
+ pc.local_time_zone_table[i].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[i].value = i;
+ }
+ pc.local_time_zone_table[i].name = NULL;
+ }
+#else
+ pc.local_time_zone_table[0].name = NULL;
+#endif
+#endif
+
+ if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
+ && ! strcmp (pc.local_time_zone_table[0].name,
+ pc.local_time_zone_table[1].name))
+ {
+ /* This locale uses the same abbrevation for standard and
+ 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 = NULL;
+ }
+
+ if (yyparse (&pc) != 0)
+ goto fail;
+
+ if (pc.timespec_seen)
+ *result = pc.seconds;
+ else
+ {
+ 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;
+ }
+
+ /* 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;
+
+ /* 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;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (! mktime_ok (&tm0, &tm, Start))
+ {
+ if (! pc.zones_seen)
+ goto fail;
+ else
+ {
+ /* 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_wday != pc.day_number)));
+ tm.tm_isdst = -1;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1)
+ goto fail;
+ }
+
+ /* 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;
+ }
+
+ /* The only "output" of this if-block is an updated Start value,
+ so this block must follow others that clobber 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;
+ }
+
+ /* 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_time_t d3 = pc.rel.seconds;
+ long_time_t t3 = t2 + d3;
+ long int d4 = (sum_ns - normalized_ns) / BILLION;
+ long_time_t t4 = t3 + d4;
+ time_t t5 = t4;
+
+ 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))
+ | (t5 != t4))
+ goto fail;
+
+ result->tv_sec = t5;
+ result->tv_nsec = normalized_ns;
+ }
+ }
+
+ 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
+
+int
+main (int ac, char **av)
+{
+ char buff[BUFSIZ];
+
+ printf ("Enter date, or blank line to exit.\n\t> ");
+ fflush (stdout);
+
+ buff[BUFSIZ - 1] = '\0';
+ while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
+ {
+ struct timespec d;
+ struct tm const *tm;
+ if (! parse_datetime (&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
+ {
+ 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 /* TEST */
AC_DEFUN([gl_BISON],
[
- # get_date.y works with bison only.
+ # parse-datetime.y works with bison only.
: ${YACC='bison -y'}
dnl
dnl Declaring YACC & YFLAGS precious will not be necessary after GNULIB
+++ /dev/null
-# get_date.m4 serial 17
-dnl Copyright (C) 2002-2006, 2008-2010 Free Software Foundation, Inc.
-dnl This file is free software; the Free Software Foundation
-dnl gives unlimited permission to copy and/or distribute it,
-dnl with or without modifications, as long as this notice is preserved.
-
-dnl Define HAVE_COMPOUND_LITERALS if the C compiler supports compound literals
-dnl as in ISO C99.
-dnl Note that compound literals such as (struct s) { 3, 4 } can be used for
-dnl initialization of stack-allocated variables, but are not constant
-dnl expressions and therefore cannot be used as initializer for global or
-dnl static variables (even though gcc supports this in pre-C99 mode).
-AC_DEFUN([gl_C_COMPOUND_LITERALS],
-[
- AC_CACHE_CHECK([for compound literals], [gl_cv_compound_literals],
- [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[struct s { int i, j; };]],
- [[struct s t = (struct s) { 3, 4 };
- if (t.i != 0) return 0;]])],
- gl_cv_compound_literals=yes,
- gl_cv_compound_literals=no)])
- if test $gl_cv_compound_literals = yes; then
- AC_DEFINE([HAVE_COMPOUND_LITERALS], [1],
- [Define if you have compound literals.])
- fi
-])
-
-AC_DEFUN([gl_GET_DATE],
-[
- dnl Prerequisites of lib/getdate.h.
- AC_REQUIRE([AM_STDBOOL_H])
- AC_REQUIRE([gl_TIMESPEC])
-
- dnl Prerequisites of lib/get_date.y.
- AC_REQUIRE([gl_BISON])
- AC_REQUIRE([gl_C_COMPOUND_LITERALS])
- AC_STRUCT_TIMEZONE
- AC_REQUIRE([gl_CLOCK_TIME])
- AC_REQUIRE([gl_TM_GMTOFF])
- AC_COMPILE_IFELSE(
- [AC_LANG_SOURCE([[
-#include <time.h> /* for time_t */
-#include <limits.h> /* for CHAR_BIT, LONG_MIN, LONG_MAX */
-#define TYPE_MINIMUM(t) \
- ((t) ((t) 0 < (t) -1 ? (t) 0 : ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1)))
-#define TYPE_MAXIMUM(t) \
- ((t) ((t) 0 < (t) -1 ? (t) -1 : ~ (~ (t) 0 << (sizeof (t) * CHAR_BIT - 1))))
-typedef int verify_min[2 * (LONG_MIN <= TYPE_MINIMUM (time_t)) - 1];
-typedef int verify_max[2 * (TYPE_MAXIMUM (time_t) <= LONG_MAX) - 1];
- ]])],
- [AC_DEFINE([TIME_T_FITS_IN_LONG_INT], [1],
- [Define to 1 if all 'time_t' values fit in a 'long int'.])
- ])
-])
--- /dev/null
+# parse-datetime.m4 serial 18
+dnl Copyright (C) 2002-2006, 2008-2010 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl Define HAVE_COMPOUND_LITERALS if the C compiler supports compound literals
+dnl as in ISO C99.
+dnl Note that compound literals such as (struct s) { 3, 4 } can be used for
+dnl initialization of stack-allocated variables, but are not constant
+dnl expressions and therefore cannot be used as initializer for global or
+dnl static variables (even though gcc supports this in pre-C99 mode).
+AC_DEFUN([gl_C_COMPOUND_LITERALS],
+[
+ AC_CACHE_CHECK([for compound literals], [gl_cv_compound_literals],
+ [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[struct s { int i, j; };]],
+ [[struct s t = (struct s) { 3, 4 };
+ if (t.i != 0) return 0;]])],
+ gl_cv_compound_literals=yes,
+ gl_cv_compound_literals=no)])
+ if test $gl_cv_compound_literals = yes; then
+ AC_DEFINE([HAVE_COMPOUND_LITERALS], [1],
+ [Define if you have compound literals.])
+ fi
+])
+
+AC_DEFUN([gl_PARSE_DATETIME],
+[
+ dnl Prerequisites of lib/parse-datetime.h.
+ AC_REQUIRE([AM_STDBOOL_H])
+ AC_REQUIRE([gl_TIMESPEC])
+
+ dnl Prerequisites of lib/parse-datetime.y.
+ AC_REQUIRE([gl_BISON])
+ AC_REQUIRE([gl_C_COMPOUND_LITERALS])
+ AC_STRUCT_TIMEZONE
+ AC_REQUIRE([gl_CLOCK_TIME])
+ AC_REQUIRE([gl_TM_GMTOFF])
+ AC_COMPILE_IFELSE(
+ [AC_LANG_SOURCE([[
+#include <time.h> /* for time_t */
+#include <limits.h> /* for CHAR_BIT, LONG_MIN, LONG_MAX */
+#define TYPE_MINIMUM(t) \
+ ((t) ((t) 0 < (t) -1 ? (t) 0 : ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1)))
+#define TYPE_MAXIMUM(t) \
+ ((t) ((t) 0 < (t) -1 ? (t) -1 : ~ (~ (t) 0 << (sizeof (t) * CHAR_BIT - 1))))
+typedef int verify_min[2 * (LONG_MIN <= TYPE_MINIMUM (time_t)) - 1];
+typedef int verify_max[2 * (TYPE_MAXIMUM (time_t) <= LONG_MAX) - 1];
+ ]])],
+ [AC_DEFINE([TIME_T_FITS_IN_LONG_INT], [1],
+ [Define to 1 if all 'time_t' values fit in a 'long int'.])
+ ])
+])
+++ /dev/null
-Description:
-Convert a date/time string to linear time.
-
-Files:
-doc/get_date.texi
-lib/getdate.h
-lib/get_date.y
-m4/bison.m4
-m4/tm_gmtoff.m4
-m4/get_date.m4
-
-Depends-on:
-c-ctype
-stdbool
-gettime
-intprops
-mktime
-setenv
-unsetenv
-time
-verify
-xalloc
-
-configure.ac:
-gl_GET_DATE
-
-Makefile.am:
-# This rule overrides the Automake generated .y.c rule, to ensure that the
-# get_date.c file gets generated in the source directory, not in the build
-# directory.
-get_date.c: get_date.y
- $(AM_V_GEN)$(SHELL) $(YLWRAP) $(srcdir)/get_date.y \
- y.tab.c get_date.c \
- y.tab.h getdate.h \
- y.output get_date.output \
- -- $(YACC) $(YFLAGS) $(AM_YFLAGS) && \
- mv get_date.c get_date.c-t && \
- mv get_date.c-t $(srcdir)/get_date.c
-lib_SOURCES += get_date.y
-BUILT_SOURCES += get_date.c
-MOSTLYCLEANFILES += get_date.c-t
-MAINTAINERCLEANFILES += get_date.c
-EXTRA_DIST += get_date.c
-
-Include:
-"getdate.h"
-
-License:
-GPL
-
-Maintainer:
-Paul Eggert
+++ /dev/null
-Files:
-tests/test-get_date.c
-tests/macros.h
-
-Depends-on:
-progname
-setenv
-
-configure.ac:
-
-Makefile.am:
-TESTS += test-get_date
-check_PROGRAMS += test-get_date
-test_get_date_LDADD = $(LDADD) @LIBINTL@ $(LIB_CLOCK_GETTIME)
obsolete
Notice:
-This module is obsolete. Use the module 'get_date' instead.
+This module is obsolete. Use the module 'parse-datetime' instead.
Files:
+doc/getdate.texi
+lib/getdate.h
Depends-on:
-get_date
+parse-datetime
configure.ac:
--- /dev/null
+Description:
+Convert a date/time string to linear time.
+
+Files:
+doc/parse-datetime.texi
+lib/parse-datetime.h
+lib/parse-datetime.y
+m4/bison.m4
+m4/tm_gmtoff.m4
+m4/parse-datetime.m4
+
+Depends-on:
+c-ctype
+stdbool
+gettime
+intprops
+mktime
+setenv
+unsetenv
+time
+verify
+xalloc
+
+configure.ac:
+gl_PARSE_DATETIME
+
+Makefile.am:
+# This rule overrides the Automake generated .y.c rule, to ensure that the
+# parse-datetime.c file gets generated in the source directory, not in the
+# build directory.
+parse-datetime.c: parse-datetime.y
+ $(AM_V_GEN)$(SHELL) $(YLWRAP) $(srcdir)/parse-datetime.y \
+ y.tab.c parse-datetime.c \
+ y.tab.h parse-datetime.h \
+ y.output parse-datetime.output \
+ -- $(YACC) $(YFLAGS) $(AM_YFLAGS) && \
+ mv parse-datetime.c parse-datetime.c-t && \
+ mv parse-datetime.c-t $(srcdir)/parse-datetime.c
+lib_SOURCES += parse-datetime.y
+BUILT_SOURCES += parse-datetime.c
+MOSTLYCLEANFILES += parse-datetime.c-t
+MAINTAINERCLEANFILES += parse-datetime.c
+EXTRA_DIST += parse-datetime.c
+
+Include:
+"parse-datetime.h"
+
+License:
+GPL
+
+Maintainer:
+Paul Eggert
--- /dev/null
+Files:
+tests/test-parse-datetime.c
+tests/macros.h
+
+Depends-on:
+progname
+setenv
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-parse-datetime
+check_PROGRAMS += test-parse-datetime
+test_parse_datetime_LDADD = $(LDADD) @LIBINTL@ $(LIB_CLOCK_GETTIME)
+++ /dev/null
-/* Test of getdate() function.
- Copyright (C) 2008, 2009, 2010 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
- the Free Software Foundation; either version 3, or (at your option)
- any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
-
-/* Written by Simon Josefsson <simon@josefsson.org>, 2008. */
-
-#include <config.h>
-
-#include "getdate.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "progname.h"
-#include "macros.h"
-
-#ifdef DEBUG
-#define LOG(str, now, res) \
- printf ("string `%s' diff %d %d\n", \
- str, res.tv_sec - now.tv_sec, res.tv_nsec - now.tv_nsec);
-#else
-#define LOG(str, now, res) (void) 0
-#endif
-
-static const char* const day_table[] =
-{
- "SUNDAY",
- "MONDAY",
- "TUESDAY",
- "WEDNESDAY",
- "THURSDAY",
- "FRIDAY",
- "SATURDAY",
- NULL
-};
-
-int
-main (int argc _GL_UNUSED, char **argv)
-{
- struct timespec result;
- struct timespec result2;
- struct timespec now;
- const char *p;
- int i;
-
- set_program_name (argv[0]);
-
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "now";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
-
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "tomorrow";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
- && now.tv_nsec == result.tv_nsec);
-
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "yesterday";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
- && now.tv_nsec == result.tv_nsec);
-
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "4 hours";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
- && now.tv_nsec == result.tv_nsec);
-
- /* test if timezone is not being ignored for day offset */
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC+400 +24 hours";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- p = "UTC+400 +1 day";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
-
- /* test if several time zones formats are handled same way */
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC+14:00";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- p = "UTC+14";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
- p = "UTC+1400";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
-
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC-14:00";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- p = "UTC-14";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
- p = "UTC-1400";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
-
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC+0:15";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- p = "UTC+0015";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
-
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC-1:30";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- p = "UTC-130";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
-
-
- /* TZ out of range should cause get_date failure */
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC+25:00";
- ASSERT (!get_date (&result, p, &now));
-
- /* Check for several invalid countable dayshifts */
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC+4:00 +40 yesterday";
- ASSERT (!get_date (&result, p, &now));
- p = "UTC+4:00 next yesterday";
- ASSERT (!get_date (&result, p, &now));
- p = "UTC+4:00 tomorrow ago";
- ASSERT (!get_date (&result, p, &now));
- p = "UTC+4:00 40 now ago";
- ASSERT (!get_date (&result, p, &now));
- p = "UTC+4:00 last tomorrow";
- ASSERT (!get_date (&result, p, &now));
- p = "UTC+4:00 -4 today";
- ASSERT (!get_date (&result, p, &now));
-
- /* And check correct usage of dayshifts */
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC+400 tomorrow";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- p = "UTC+400 +1 day";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC+400 yesterday";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- p = "UTC+400 1 day ago";
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
- now.tv_sec = 4711;
- now.tv_nsec = 1267;
- p = "UTC+400 now";
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
- ASSERT (get_date (&result2, p, &now));
- LOG (p, now, result2);
- ASSERT (result.tv_sec == result2.tv_sec
- && result.tv_nsec == result2.tv_nsec);
-
- /* Check that some "next Monday", "last Wednesday", etc. are correct. */
- setenv ("TZ", "UTC0", 1);
- for (i = 0; day_table[i]; i++)
- {
- unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */
- char tmp[32];
- sprintf (tmp, "NEXT %s", day_table[i]);
- now.tv_sec = thur2 + 4711;
- now.tv_nsec = 1267;
- ASSERT (get_date (&result, tmp, &now));
- LOG (tmp, now, result);
- ASSERT (result.tv_nsec == 0);
- ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
-
- sprintf (tmp, "LAST %s", day_table[i]);
- now.tv_sec = thur2 + 4711;
- now.tv_nsec = 1267;
- ASSERT (get_date (&result, tmp, &now));
- LOG (tmp, now, result);
- ASSERT (result.tv_nsec == 0);
- ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
- }
-
- p = "THURSDAY UTC+00"; /* The epoch was on Thursday. */
- now.tv_sec = 0;
- now.tv_nsec = 0;
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- ASSERT (result.tv_sec == now.tv_sec
- && result.tv_nsec == now.tv_nsec);
-
- p = "FRIDAY UTC+00";
- now.tv_sec = 0;
- now.tv_nsec = 0;
- ASSERT (get_date (&result, p, &now));
- LOG (p, now, result);
- ASSERT (result.tv_sec == 24 * 3600
- && result.tv_nsec == now.tv_nsec);
-
- return 0;
-}
--- /dev/null
+/* Test of parse_datetime() function.
+ Copyright (C) 2008, 2009, 2010 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
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* Written by Simon Josefsson <simon@josefsson.org>, 2008. */
+
+#include <config.h>
+
+#include "parse-datetime.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "progname.h"
+#include "macros.h"
+
+#ifdef DEBUG
+#define LOG(str, now, res) \
+ printf ("string `%s' diff %d %d\n", \
+ str, res.tv_sec - now.tv_sec, res.tv_nsec - now.tv_nsec);
+#else
+#define LOG(str, now, res) (void) 0
+#endif
+
+static const char* const day_table[] =
+{
+ "SUNDAY",
+ "MONDAY",
+ "TUESDAY",
+ "WEDNESDAY",
+ "THURSDAY",
+ "FRIDAY",
+ "SATURDAY",
+ NULL
+};
+
+int
+main (int argc _GL_UNUSED, char **argv)
+{
+ struct timespec result;
+ struct timespec result2;
+ struct timespec now;
+ const char *p;
+ int i;
+
+ set_program_name (argv[0]);
+
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "now";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
+
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "tomorrow";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
+ && now.tv_nsec == result.tv_nsec);
+
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "yesterday";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
+ && now.tv_nsec == result.tv_nsec);
+
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "4 hours";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
+ && now.tv_nsec == result.tv_nsec);
+
+ /* test if timezone is not being ignored for day offset */
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC+400 +24 hours";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ p = "UTC+400 +1 day";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+
+ /* test if several time zones formats are handled same way */
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC+14:00";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ p = "UTC+14";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+ p = "UTC+1400";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC-14:00";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ p = "UTC-14";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+ p = "UTC-1400";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC+0:15";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ p = "UTC+0015";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC-1:30";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ p = "UTC-130";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+
+
+ /* TZ out of range should cause parse_datetime failure */
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC+25:00";
+ ASSERT (!parse_datetime (&result, p, &now));
+
+ /* Check for several invalid countable dayshifts */
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC+4:00 +40 yesterday";
+ ASSERT (!parse_datetime (&result, p, &now));
+ p = "UTC+4:00 next yesterday";
+ ASSERT (!parse_datetime (&result, p, &now));
+ p = "UTC+4:00 tomorrow ago";
+ ASSERT (!parse_datetime (&result, p, &now));
+ p = "UTC+4:00 40 now ago";
+ ASSERT (!parse_datetime (&result, p, &now));
+ p = "UTC+4:00 last tomorrow";
+ ASSERT (!parse_datetime (&result, p, &now));
+ p = "UTC+4:00 -4 today";
+ ASSERT (!parse_datetime (&result, p, &now));
+
+ /* And check correct usage of dayshifts */
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC+400 tomorrow";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ p = "UTC+400 +1 day";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC+400 yesterday";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ p = "UTC+400 1 day ago";
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+ now.tv_sec = 4711;
+ now.tv_nsec = 1267;
+ p = "UTC+400 now";
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
+ ASSERT (parse_datetime (&result2, p, &now));
+ LOG (p, now, result2);
+ ASSERT (result.tv_sec == result2.tv_sec
+ && result.tv_nsec == result2.tv_nsec);
+
+ /* Check that some "next Monday", "last Wednesday", etc. are correct. */
+ setenv ("TZ", "UTC0", 1);
+ for (i = 0; day_table[i]; i++)
+ {
+ unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */
+ char tmp[32];
+ sprintf (tmp, "NEXT %s", day_table[i]);
+ now.tv_sec = thur2 + 4711;
+ now.tv_nsec = 1267;
+ ASSERT (parse_datetime (&result, tmp, &now));
+ LOG (tmp, now, result);
+ ASSERT (result.tv_nsec == 0);
+ ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
+
+ sprintf (tmp, "LAST %s", day_table[i]);
+ now.tv_sec = thur2 + 4711;
+ now.tv_nsec = 1267;
+ ASSERT (parse_datetime (&result, tmp, &now));
+ LOG (tmp, now, result);
+ ASSERT (result.tv_nsec == 0);
+ ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
+ }
+
+ p = "THURSDAY UTC+00"; /* The epoch was on Thursday. */
+ now.tv_sec = 0;
+ now.tv_nsec = 0;
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ ASSERT (result.tv_sec == now.tv_sec
+ && result.tv_nsec == now.tv_nsec);
+
+ p = "FRIDAY UTC+00";
+ now.tv_sec = 0;
+ now.tv_nsec = 0;
+ ASSERT (parse_datetime (&result, p, &now));
+ LOG (p, now, result);
+ ASSERT (result.tv_sec == 24 * 3600
+ && result.tv_nsec == now.tv_nsec);
+
+ return 0;
+}