From: Eric Blake Date: Tue, 5 Oct 2010 17:40:11 +0000 (-0600) Subject: parse-datetime: better name than get_date X-Git-Tag: v0.1~3718 X-Git-Url: http://erislabs.org.uk/gitweb/?a=commitdiff_plain;h=2bb63bfb25474ea147ee9f1523c0337997359a4c;p=gnulib.git 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 ): 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 --- diff --git a/ChangeLog b/ChangeLog index 6cf569a79..4345d5e86 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,37 @@ 2010-10-05 Eric Blake + 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 ): 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. diff --git a/DEPENDENCIES b/DEPENDENCIES index b4ec63066..2117e7dbc 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -101,7 +101,7 @@ at any time. * 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: diff --git a/MODULES.html.sh b/MODULES.html.sh index c356b0157..eb5b642d3 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -1835,7 +1835,7 @@ func_all_modules () func_echo "$element" func_begin_table - func_module get_date + func_module parse-datetime func_module timegm func_module tzset func_end_table diff --git a/NEWS b/NEWS index 20cc20ded..76239ddb0 100644 --- a/NEWS +++ b/NEWS @@ -12,10 +12,12 @@ User visible incompatible changes 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 diff --git a/doc/get_date.texi b/doc/get_date.texi deleted file mode 100644 index 85c39f5a9..000000000 --- a/doc/get_date.texi +++ /dev/null @@ -1,556 +0,0 @@ -@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}). diff --git a/doc/getdate.texi b/doc/getdate.texi new file mode 100644 index 000000000..099b1bb01 --- /dev/null +++ b/doc/getdate.texi @@ -0,0 +1 @@ +@include parse-datetime.texi diff --git a/doc/parse-datetime.texi b/doc/parse-datetime.texi new file mode 100644 index 000000000..496709cc3 --- /dev/null +++ b/doc/parse-datetime.texi @@ -0,0 +1,559 @@ +@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}). diff --git a/doc/posix-functions/getdate.texi b/doc/posix-functions/getdate.texi index 156713aeb..144c2c740 100644 --- a/doc/posix-functions/getdate.texi +++ b/doc/posix-functions/getdate.texi @@ -17,6 +17,6 @@ This function is missing on some platforms: 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. diff --git a/gnulib-tool b/gnulib-tool index afd649369..0e1694d76 100755 --- a/gnulib-tool +++ b/gnulib-tool @@ -5354,7 +5354,7 @@ func_create_testdir () ) || 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" \ diff --git a/lib/get_date.y b/lib/get_date.y deleted file mode 100644 index 445865bdb..000000000 --- a/lib/get_date.y +++ /dev/null @@ -1,1572 +0,0 @@ -%{ -/* 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 . */ - -/* Originally written by Steven M. Bellovin while - at the University of North Carolina at Chapel Hill. Later tweaked by - a couple of people on Usenet. Completely overhauled by Rich $alz - and Jim Berets in August, 1990. - - Modified by Paul Eggert in August 1999 to do - the right thing about local DST. Also modified by Paul Eggert - in February 2004 to support - nanosecond-resolution time stamps, and in October 2004 to support - TZ strings in dates. */ - -/* FIXME: Check for arithmetic overflow in all cases, not just - some of them. */ - -#include - -#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 -#include -#include -#include -#include - -#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 tDAY_UNIT tDAY_SHIFT - -%token tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN -%token tMONTH tORDINAL tZONE - -%token tSNUMBER tUNUMBER -%token tSDECIMAL_NUMBER tUDECIMAL_NUMBER - -%type o_colon_minutes o_merid -%type seconds signed_seconds unsigned_seconds - -%type relunit relunit_snumber 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 } -}; - - - -/* 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 */ diff --git a/lib/getdate.h b/lib/getdate.h index 22cc2c02a..9f5d1a9d5 100644 --- a/lib/getdate.h +++ b/lib/getdate.h @@ -1,22 +1,3 @@ -/* 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 . */ - -#include -#include - -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) diff --git a/lib/parse-datetime.h b/lib/parse-datetime.h new file mode 100644 index 000000000..9685d9ba4 --- /dev/null +++ b/lib/parse-datetime.h @@ -0,0 +1,22 @@ +/* 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 . */ + +#include +#include + +bool parse_datetime (struct timespec *, char const *, struct timespec const *); diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y new file mode 100644 index 000000000..f22c754e0 --- /dev/null +++ b/lib/parse-datetime.y @@ -0,0 +1,1573 @@ +%{ +/* 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 . */ + +/* Originally written by Steven M. Bellovin while + at the University of North Carolina at Chapel Hill. Later tweaked by + a couple of people on Usenet. Completely overhauled by Rich $alz + and Jim Berets in August, 1990. + + Modified by Paul Eggert in August 1999 to do + the right thing about local DST. Also modified by Paul Eggert + in February 2004 to support + nanosecond-resolution time stamps, and in October 2004 to support + TZ strings in dates. */ + +/* FIXME: Check for arithmetic overflow in all cases, not just + some of them. */ + +#include + +#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 +#include +#include +#include +#include + +#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 tDAY_UNIT tDAY_SHIFT + +%token tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN +%token tMONTH tORDINAL tZONE + +%token tSNUMBER tUNUMBER +%token tSDECIMAL_NUMBER tUDECIMAL_NUMBER + +%type o_colon_minutes o_merid +%type seconds signed_seconds unsigned_seconds + +%type relunit relunit_snumber 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 } +}; + + + +/* 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 */ diff --git a/m4/bison.m4 b/m4/bison.m4 index e9272cbfa..bd8225dd5 100644 --- a/m4/bison.m4 +++ b/m4/bison.m4 @@ -7,7 +7,7 @@ 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 diff --git a/m4/get_date.m4 b/m4/get_date.m4 deleted file mode 100644 index b71da8217..000000000 --- a/m4/get_date.m4 +++ /dev/null @@ -1,53 +0,0 @@ -# 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 /* for time_t */ -#include /* 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'.]) - ]) -]) diff --git a/m4/parse-datetime.m4 b/m4/parse-datetime.m4 new file mode 100644 index 000000000..d7812922b --- /dev/null +++ b/m4/parse-datetime.m4 @@ -0,0 +1,53 @@ +# 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 /* for time_t */ +#include /* 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'.]) + ]) +]) diff --git a/modules/get_date b/modules/get_date deleted file mode 100644 index e3997ac5c..000000000 --- a/modules/get_date +++ /dev/null @@ -1,52 +0,0 @@ -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 diff --git a/modules/get_date-tests b/modules/get_date-tests deleted file mode 100644 index cb96ff6e1..000000000 --- a/modules/get_date-tests +++ /dev/null @@ -1,14 +0,0 @@ -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) diff --git a/modules/getdate b/modules/getdate index 2f2303615..5dca5cfdc 100644 --- a/modules/getdate +++ b/modules/getdate @@ -5,12 +5,14 @@ Status: 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: diff --git a/modules/parse-datetime b/modules/parse-datetime new file mode 100644 index 000000000..484270686 --- /dev/null +++ b/modules/parse-datetime @@ -0,0 +1,52 @@ +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 diff --git a/modules/parse-datetime-tests b/modules/parse-datetime-tests new file mode 100644 index 000000000..04feed2f0 --- /dev/null +++ b/modules/parse-datetime-tests @@ -0,0 +1,14 @@ +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) diff --git a/tests/test-get_date.c b/tests/test-get_date.c deleted file mode 100644 index 5f7095c5c..000000000 --- a/tests/test-get_date.c +++ /dev/null @@ -1,255 +0,0 @@ -/* 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 , 2008. */ - -#include - -#include "getdate.h" - -#include -#include -#include - -#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; -} diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c new file mode 100644 index 000000000..bc90209c4 --- /dev/null +++ b/tests/test-parse-datetime.c @@ -0,0 +1,255 @@ +/* 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 , 2008. */ + +#include + +#include "parse-datetime.h" + +#include +#include +#include + +#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; +}