bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Bug-gnulib] getdate.y question


From: Paul Eggert
Subject: Re: [Bug-gnulib] getdate.y question
Date: Fri, 29 Oct 2004 14:00:26 -0700
User-agent: Gnus/5.1006 (Gnus v5.10.6) Emacs/21.3 (gnu/linux)

Bruno Haible <address@hidden> writes:

> The getdate module thus should depend on the 'setenv' module. But I don't see
> a #include "setenv.h".

Thanks for catching that.  I fixed that, and twiddled a few other things,
and installed the following into gnulib.

This patch adds a file doc/getdate.texi, which documents the module.

2004-10-29  Paul Eggert  <address@hidden>

        * doc/getdate.texi: New file, from coreutils with modifications for
        the new TZ parsing.

        * modules/getdate (Files): Add doc/getdate.texi.
        (Depends-on): Add setenv, xalloc.

        * lib/getdate.y: Add support for TZ="foo" within a date string.
        Fix some bugs near time_t boundaries.  Reject dates with
        out-of-range components, e.g., "Sept 31".
        Include <stdlib.h>, "setenv.h", "xalloc.h".
        (ISDIGIT_LOCALE): Remove; unused.
        Note that the TZ and time functions used here are not reentrant.
        (mktime_ok, get_tz): New functions.
        (TZBUFSIZE): New constant.
        (get_date): Parse leading TZ="foo".  Reject out-of-range components;.
        This requires that we sometimes generate our own TZ="XXX..." setting.

Index: modules/getdate
===================================================================
RCS file: /cvsroot/gnulib/gnulib/modules/getdate,v
retrieving revision 1.8
diff -p -u -r1.8 getdate
--- modules/getdate     4 Oct 2004 20:17:39 -0000       1.8
+++ modules/getdate     29 Oct 2004 20:56:48 -0000
@@ -2,6 +2,7 @@ Description:
 Convert a date/time string to linear time.
 
 Files:
+doc/getdate.texi
 lib/getdate.h
 lib/getdate.y
 m4/bison.m4
@@ -13,6 +14,8 @@ timespec
 stdbool
 gettime
 mktime
+setenv
+xalloc
 alloca
 
 configure.ac:
Index: lib/getdate.y
===================================================================
RCS file: /cvsroot/gnulib/gnulib/lib/getdate.y,v
retrieving revision 1.85
diff -p -u -r1.85 getdate.y
--- lib/getdate.y       25 Oct 2004 05:50:16 -0000      1.85
+++ lib/getdate.y       29 Oct 2004 20:56:48 -0000
@@ -22,16 +22,13 @@
    <address@hidden> and Jim Berets <address@hidden> in August, 1990.
 
    Modified by Paul Eggert <address@hidden> in August 1999 to do
-   the right thing about local DST, and in February 2004 to support
-   nanosecond-resolution time stamps.  Unlike previous versions, this
-   version is reentrant.  */
+   the right thing about local DST.  Also modified by Paul Eggert
+   <address@hidden> in February 2004 to support
+   nanosecond-resolution time stamps, and in October 2004 to support
+   TZ strings in dates.  */
 
 /* FIXME: Check for arithmetic overflow in all cases, not just
-   some of them.
-
-   FIXME: The current code uses 'int' to count seconds; it should use
-   something like 'intmax_t' to support time stamps that don't fit in
-   32 bits.  */
+   some of them.  */
 
 #ifdef HAVE_CONFIG_H
 # include <config.h>
@@ -53,6 +50,11 @@
 
 #include <ctype.h>
 #include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "setenv.h"
+#include "xalloc.h"
 
 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
 # define IN_CTYPE_DOMAIN(c) 1
@@ -63,19 +65,16 @@
 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
-#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
 
-/* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
+/* ISDIGIT differs from isdigit, as follows:
    - Its arg may be any int or unsigned int; it need not be an unsigned char.
    - It's guaranteed to evaluate its argument exactly once.
    - It's typically faster.
    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
-   ISDIGIT_LOCALE unless it's important to use the locale's definition
+   isdigit unless it's important to use the locale's definition
    of `digit' even when the host does not conform to POSIX.  */
 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
 
-#include <string.h>
-
 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
 # define __attribute__(x)
 #endif
@@ -167,7 +166,8 @@ static int yyerror (parser_control *, ch
 
 %}
 
-/* We want a reentrant parser.  */
+/* We want a reentrant parser, even if the TZ manipulation and the calls to
+   localtime and gmtime are not reentrant.  */
 %pure-parser
 %parse-param { parser_control *pc }
 %lex-param { parser_control *pc }
@@ -990,6 +990,51 @@ yyerror (parser_control *pc ATTRIBUTE_UN
   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
@@ -1004,6 +1049,11 @@ get_date (struct timespec *result, char 
   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)
     {
@@ -1019,6 +1069,44 @@ get_date (struct timespec *result, char 
   if (! tmp)
     return false;
 
+  while (c = *p, ISSPACE (c))
+    p++;
+
+  if (strncmp (p, "TZ=\"", 4) == 0)
+    {
+      char const *tzbase = p + 4;
+      size_t tzsize = 1;
+      char const *s;
+      
+      for (s = tzbase; *s; s++, tzsize++)
+       if (*s == '\\')
+         {
+           s++;
+           if (! (*s == '\\' || *s == '"'))
+             break;
+         }
+       else if (*s == '"')
+         {
+           char *z;
+           char *tz1;
+           char tz1buf[TZBUFSIZE];
+           bool large_tz = TZBUFSIZE < tzsize;
+           bool setenv_ok;
+           tz0 = get_tz (tz0buf);
+           z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
+           for (s = tzbase; *s != '"'; s++)
+             *z++ = *(s += *s == '\\');
+           *z = '\0';
+           setenv_ok = setenv ("TZ", tz1, 1) == 0;
+           if (large_tz)
+             free (tz1);
+           if (!setenv_ok)
+             goto fail;
+           tz_was_altered = true;
+           p = s + 1;
+         }
+    }
+
   pc.input = p;
   pc.year.value = tmp->tm_year;
   pc.year.value += TM_YEAR_BASE;
@@ -1106,142 +1194,173 @@ get_date (struct timespec *result, char 
     }
 
   if (yyparse (&pc) != 0)
-    return false;
+    goto fail;
 
   if (pc.timespec_seen)
-    {
-      *result = pc.seconds;
-      return true;
-    }
-
-  if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
-      || 1 < (pc.local_zones_seen + pc.zones_seen)
-      || (pc.local_zones_seen && 1 < pc.local_isdst))
-    return false;
-
-  tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
-  tm.tm_mon = pc.month - 1 + pc.rel_month;
-  tm.tm_mday = pc.day + pc.rel_day;
-  if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
-    {
-      tm.tm_hour = to_hour (pc.hour, pc.meridian);
-      if (tm.tm_hour < 0)
-       return false;
-      tm.tm_min = pc.minutes;
-      tm.tm_sec = pc.seconds.tv_sec;
-    }
+    *result = pc.seconds;
   else
     {
-      tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
-      pc.seconds.tv_nsec = 0;
-    }
-
-  /* Let mktime deduce tm_isdst if we have an absolute time stamp,
-     or if the relative time stamp mentions days, months, or years.  */
-  if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
-      | pc.rel_month | pc.rel_year)
-    tm.tm_isdst = -1;
-
-  /* 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;
+      if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
+         || 1 < (pc.local_zones_seen + pc.zones_seen)
+         || (pc.local_zones_seen && 1 < pc.local_isdst))
+       goto fail;
+
+      tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
+      tm.tm_mon = pc.month - 1;
+      tm.tm_mday = pc.day;
+      if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
+       {
+         tm.tm_hour = to_hour (pc.hour, pc.meridian);
+         if (tm.tm_hour < 0)
+           goto fail;
+         tm.tm_min = pc.minutes;
+         tm.tm_sec = pc.seconds.tv_sec;
+       }
+      else
+       {
+         tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+         pc.seconds.tv_nsec = 0;
+       }
 
-  tm0 = tm;
+      /* 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;
 
-  Start = mktime (&tm);
+      tm0 = tm;
 
-  if (Start == (time_t) -1)
-    {
+      Start = mktime (&tm);
 
-      /* Guard against falsely reporting errors near the time_t boundaries
-        when parsing times in other time zones.  For example, if the min
-        time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
-        of UTC, then the min localtime value is 1970-01-01 08:00:00; if
-        we apply mktime to 1970-01-01 00:00:00 we will get an error, so
-        we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
-        zone by 24 hours to compensate.  This algorithm assumes that
-        there is no DST transition within a day of the time_t boundaries.  */
-      if (pc.zones_seen)
+      if (! mktime_ok (&tm0, &tm, Start))
        {
-         tm = tm0;
-         if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
-           {
-             tm.tm_mday++;
-             pc.time_zone += 24 * 60;
-           }
+         if (! pc.zones_seen)
+           goto fail;
          else
            {
-             tm.tm_mday--;
-             pc.time_zone -= 24 * 60;
+             /* Guard against falsely reporting errors near the time_t
+                boundaries when parsing times in other time zones.  For
+                example, suppose the input string "1969-12-31 23:00:00 -0100",
+                the current time zone is 8 hours ahead of UTC, and the min
+                time_t value is 1970-01-01 00:00:00 UTC.  Then the min
+                localtime value is 1970-01-01 08:00:00, and mktime will
+                therefore fail on 1969-12-31 23:00:00.  To work around the
+                problem, set the time zone to 1 hour behind UTC temporarily
+                by setting TZ="XXX1:00" and try mktime again.  */
+
+             long int time_zone = pc.time_zone;
+             long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
+             long int abs_time_zone_hour = abs_time_zone / 60;
+             int abs_time_zone_min = abs_time_zone % 60;
+             char tz1buf[sizeof "XXX+0:00"
+                         + sizeof pc.time_zone * CHAR_BIT / 3];
+             if (!tz_was_altered)
+               tz0 = get_tz (tz0buf);
+             sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
+                      abs_time_zone_hour, abs_time_zone_min);
+             if (setenv ("TZ", tz1buf, 1) != 0)
+               goto fail;
+             tz_was_altered = true;
+             tm = tm0;
+             Start = mktime (&tm);
+             if (! mktime_ok (&tm0, &tm, Start))
+               goto fail;
            }
-         Start = mktime (&tm);
        }
 
-      if (Start == (time_t) -1)
-       return false;
-    }
-
-  if (pc.days_seen && ! pc.dates_seen)
-    {
-      tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
-                    + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
-      tm.tm_isdst = -1;
-      Start = mktime (&tm);
-      if (Start == (time_t) -1)
-       return false;
-    }
+      if (pc.days_seen && ! pc.dates_seen)
+       {
+         tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
+                        + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
+         tm.tm_isdst = -1;
+         Start = mktime (&tm);
+         if (Start == (time_t) -1)
+           goto fail;
+       }
 
-  if (pc.zones_seen)
-    {
-      long int delta = pc.time_zone * 60;
-      time_t t1;
+      if (pc.zones_seen)
+       {
+         long int delta = pc.time_zone * 60;
+         time_t t1;
 #ifdef HAVE_TM_GMTOFF
-      delta -= tm.tm_gmtoff;
+         delta -= tm.tm_gmtoff;
 #else
-      time_t t = Start;
-      struct tm const *gmt = gmtime (&t);
-      if (! gmt)
-       return false;
-      delta -= tm_diff (&tm, gmt);
+         time_t t = Start;
+         struct tm const *gmt = gmtime (&t);
+         if (! gmt)
+           goto fail;
+         delta -= tm_diff (&tm, gmt);
 #endif
-      t1 = Start - delta;
-      if ((Start < t1) != (delta < 0))
-       return false;   /* time_t overflow */
-      Start = t1;
+         t1 = Start - delta;
+         if ((Start < t1) != (delta < 0))
+           goto fail;  /* time_t overflow */
+         Start = t1;
+       }
+
+      /* Add relative date.  */
+      if (pc.rel_year | pc.rel_month | pc.rel_day)
+       {
+         int year = tm.tm_year + pc.rel_year;
+         int month = tm.tm_mon + pc.rel_month;
+         int day = tm.tm_mday + pc.rel_day;
+         if (((year < tm.tm_year) ^ (pc.rel_year < 0))
+             | (month < tm.tm_mon) ^ (pc.rel_month < 0)
+             | (day < tm.tm_mday) ^ (pc.rel_day < 0))
+           goto fail;
+         tm.tm_year = year;
+         tm.tm_mon = month;
+         tm.tm_mday = day;
+         Start = mktime (&tm);
+         if (Start == (time_t) -1)
+           goto fail;
+       }
+
+      /* Add relative hours, minutes, and seconds.  On hosts that support
+        leap seconds, ignore the possibility of leap seconds; e.g.,
+        "+ 10 minutes" adds 600 seconds, even if one of them is a
+        leap second.  Typically this is not what the user wants, but it's
+        too hard to do it the other way, because the time zone indicator
+        must be applied before relative times, and if mktime is applied
+        again the time zone will be lost.  */
+      {
+       long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
+       long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
+       time_t t0 = Start;
+       long int d1 = 60 * 60 * pc.rel_hour;
+       time_t t1 = t0 + d1;
+       long int d2 = 60 * pc.rel_minutes;
+       time_t t2 = t1 + d2;
+       long int d3 = pc.rel_seconds;
+       time_t t3 = t2 + d3;
+       long int d4 = (sum_ns - normalized_ns) / BILLION;
+       time_t t4 = t3 + d4;
+
+       if ((d1 / (60 * 60) ^ pc.rel_hour)
+           | (d2 / 60 ^ pc.rel_minutes)
+           | ((t1 < t0) ^ (d1 < 0))
+           | ((t2 < t1) ^ (d2 < 0))
+           | ((t3 < t2) ^ (d3 < 0))
+           | ((t4 < t3) ^ (d4 < 0)))
+         goto fail;
+
+       result->tv_sec = t4;
+       result->tv_nsec = normalized_ns;
+      }
     }
 
-  /* Add relative hours, minutes, and seconds.  Ignore leap seconds;
-     i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
-     leap second.  Typically this is not what the user wants, but it's
-     too hard to do it the other way, because the time zone indicator
-     must be applied before relative times, and if mktime is applied
-     again the time zone will be lost.  */
-  {
-    long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
-    long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
-    time_t t0 = Start;
-    long int d1 = 60 * 60 * pc.rel_hour;
-    time_t t1 = t0 + d1;
-    long int d2 = 60 * pc.rel_minutes;
-    time_t t2 = t1 + d2;
-    long int d3 = pc.rel_seconds;
-    time_t t3 = t2 + d3;
-    long int d4 = (sum_ns - normalized_ns) / BILLION;
-    time_t t4 = t3 + d4;
-
-    if ((d1 / (60 * 60) ^ pc.rel_hour)
-       | (d2 / 60 ^ pc.rel_minutes)
-       | ((t1 < t0) ^ (d1 < 0))
-       | ((t2 < t1) ^ (d2 < 0))
-       | ((t3 < t2) ^ (d3 < 0))
-       | ((t4 < t3) ^ (d4 < 0)))
-      return false;
-
-    result->tv_sec = t4;
-    result->tv_nsec = normalized_ns;
-    return true;
-  }
+  goto done;
+
+ fail:
+  ok = false;
+ done:
+  if (tz_was_altered)
+    ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
+  if (tz0 != tz0buf)
+    free (tz0);
+  return ok;
 }
 
 #if TEST
--- /dev/null   2003-03-18 13:55:57 -0800
+++ doc/getdate.texi    2004-10-29 13:47:12 -0700
@@ -0,0 +1,536 @@
address@hidden GNU date syntax documentation
+
address@hidden Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 
2002,
address@hidden 2003, 2004 Free Software Foundation, Inc.
+
address@hidden Permission is granted to copy, distribute and/or modify this 
document
address@hidden under the terms of the GNU Free Documentation License, Version 
1.1 or
address@hidden any later version published by the Free Software Foundation; 
with no
address@hidden Invariant Sections, with no Front-Cover Texts, and with no 
Back-Cover
address@hidden Texts.  A copy of the license is included in the ``GNU Free
address@hidden Documentation License'' file as part of this distribution.
+
address@hidden Date input formats
address@hidden Date input formats
+
address@hidden date input formats
address@hidden get_date
+
+First, a quote:
+
address@hidden
+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.
+
address@hidden  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}.
address@hidden 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
address@hidden function) is not described here.
+
address@hidden
+* 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.
address@hidden menu
+
+
address@hidden General date syntax
address@hidden General date syntax
+
address@hidden general date syntax
+
address@hidden 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:
+
address@hidden @bullet
address@hidden calendar date items
address@hidden time of day items
address@hidden time zone items
address@hidden day of the week items
address@hidden relative items
address@hidden pure numbers.
address@hidden itemize
+
address@hidden We describe each of these item types in turn, below.
+
address@hidden numbers, written-out
address@hidden ordinal numbers
address@hidden first @r{in date strings}
address@hidden next @r{in date strings}
address@hidden last @r{in date strings}
+A few numbers may be written out in words in most contexts.  This is
+most useful for specifying day of the week items or relative items (see
+below).  Here is the list: @samp{first} for 1, @samp{next} for 2,
address@hidden for 3, @samp{fourth} for 4, @samp{fifth} for 5,
address@hidden for 6, @samp{seventh} for 7, @samp{eighth} for 8,
address@hidden for 9, @samp{tenth} for 10, @samp{eleventh} for 11 and
address@hidden for 12.  Also, @samp{last} means exactly @math{-1}.
+
address@hidden 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.
+
address@hidden language, in dates
+In the current implementation, only English is supported for words and
+abbreviations like @samp{AM}, @samp{DST}, @samp{EST}, @samp{first},
address@hidden, @samp{Sunday}, @samp{tomorrow}, and @samp{year}.
+
address@hidden language, in dates
address@hidden 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
address@hidden 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:
+
address@hidden
+$ 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  # a GNU extension
+2004-02-29T16: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
address@hidden example
+
address@hidden case, ignored in dates
address@hidden 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.
+
+
address@hidden Calendar date items
address@hidden Calendar date items
+
address@hidden 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:
+
address@hidden
+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
address@hidden example
+
+The year can also be omitted.  In this case, the last specified year is
+used, or the current year if none.  For example:
+
address@hidden
+9/24
+sep 24
address@hidden example
+
+Here are the rules.
+
address@hidden @sc{iso} 8601 date format
address@hidden date format, @sc{iso} 8601
+For numeric months, the @sc{iso} 8601 format
address@hidden@address@hidden@var{day}} is allowed, where @var{year} is
+any positive number, @var{month} is a number between 01 and 12, and
address@hidden 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
address@hidden@var{month}/@var{day}/@var{year}}, popular in the United States,
+is accepted.  Also @address@hidden/@var{day}}, omitting the year.
+
address@hidden month names in date strings
address@hidden abbreviations for months
+Literal months may be spelled out in full: @samp{January},
address@hidden, @samp{March}, @samp{April}, @samp{May}, @samp{June},
address@hidden, @samp{August}, @samp{September}, @samp{October},
address@hidden 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:
+
address@hidden
address@hidden @var{month} @var{year}
address@hidden @var{month}
address@hidden @var{day} @var{year}
address@hidden@address@hidden
address@hidden example
+
+Or, omitting the year:
+
address@hidden
address@hidden @var{day}
address@hidden example
+
+
address@hidden Time of day items
address@hidden Time of day items
+
address@hidden 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:
+
address@hidden
+20:02:00.000000
+20:02
+8:02pm
+20:02-0500      # In @sc{est} (U.S. Eastern Standard Time).
address@hidden example
+
+More generally, the time of day may be given as
address@hidden@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
address@hidden or @samp{,} and a fraction containing one or more digits.
+Alternatively,
address@hidden:@var{second}} can be omitted, in which case it is taken to
+be zero.
+
address@hidden am @r{in date strings}
address@hidden pm @r{in date strings}
address@hidden midnight @r{in date strings}
address@hidden 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
address@hidden:@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.)
+
address@hidden time zone correction
address@hidden minutes, time zone correction by
+The time may alternatively be followed by a time zone correction,
+expressed as @address@hidden@address@hidden, where @var{s} is @samp{+}
+or @samp{-}, @var{hh} is a number of zone hours and @var{mm} is a number
+of zone minutes.  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.  The @var{minute}
+part of the time of day may not be elided when a time zone correction
+is used.  This is the best way to specify a time zone correction by
+fractional parts of an hour.
+
+Either @samp{am}/@samp{pm} or a time zone correction may be specified,
+but not both.
+
+
address@hidden Time zone items
address@hidden Time zone items
+
address@hidden 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.
+
+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}).
+
+
address@hidden Day of week items
address@hidden Day of week items
+
address@hidden 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},
address@hidden, @samp{Tuesday}, @samp{Wednesday}, @samp{Thursday},
address@hidden 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
address@hidden and @samp{Thur} or @samp{Thurs} for @samp{Thursday} are
+also allowed.
+
address@hidden next @var{day}
address@hidden 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
address@hidden 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.
+
+
address@hidden Relative items in date strings
address@hidden Relative items in date strings
+
address@hidden relative items in date strings
address@hidden displacement of dates
+
address@hidden items} adjust a date (or the current date if none) forward
+or backward.  The effects of relative items accumulate.  Here are some
+examples:
+
address@hidden
+1 year
+1 year ago
+3 years
+2 days
address@hidden example
+
address@hidden year @r{in date strings}
address@hidden month @r{in date strings}
address@hidden fortnight @r{in date strings}
address@hidden week @r{in date strings}
address@hidden day @r{in date strings}
address@hidden hour @r{in date strings}
address@hidden 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,
address@hidden or @samp{min} worth 60 seconds, and @samp{second} or
address@hidden worth one second.  An @samp{s} suffix on these units is
+accepted and ignored.
+
address@hidden 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}.
+
address@hidden day @r{in date strings}
address@hidden tomorrow @r{in date strings}
address@hidden 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}).
+
address@hidden now @r{in date strings}
address@hidden today @r{in date strings}
address@hidden 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:
+
address@hidden
+$ 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!
address@hidden 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
address@hidden before embarking on calendrical calculations.
+
address@hidden Pure numbers in date strings
address@hidden Pure numbers in date strings
+
address@hidden 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 @address@hidden@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 @address@hidden 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.
+
+
address@hidden Seconds since the Epoch
address@hidden 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.
+
address@hidden beginning of time, for @acronym{POSIX}
address@hidden 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
address@hidden systems, the epoch is 1970-01-01 00:00:00 @sc{utc}, so
address@hidden@@0} represents this time, @samp{@@1} represents 1970-01-01
+00:00:01 @sc{utc}, and so forth.  @acronym{GNU} and most other
address@hidden 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 systems, these counts ignore the presence of leap seconds.
+For example, on most systems @samp{@@915148799} represents 1998-12-31
+23:59:59 @sc{utc}, @samp{@@915148800} represents 1999-01-01 00:00:00
address@hidden, and there is no way to represent the intervening leap second
+1998-12-31 23:59:60 @sc{utc}.
+
address@hidden Specifying time zone rules
address@hidden Specifying time zone rules
+
address@hidden 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
address@hidden"Europe/Paris"} as shown in the following shell transcript:
+
address@hidden
+$ export TZ="America/New_York"
+$ date --date='TZ="Europe/Paris" 2004-10-31 06:30'
+Sun Oct 31 01:30:00 EDT 2004
address@hidden example
+
+In this example, the @option{--date} operand begins with its own
address@hidden 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
address@hidden 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
address@hidden://www.twinsun.com/tz/tz-link.htm, @samp{tz} database}.
+A recent catalog of location names appears in the
address@hidden://twiki.org/cgi-bin/xtra/tzdate, TWiki Date and Time
+Gateway}.  A few address@hidden hosts require a colon before a
+location name in a @env{TZ} setting, e.g.,
address@hidden":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 address@hidden host that does not support the @samp{tz}
+database, you may need to use a @acronym{POSIX} rule instead.  Simple
address@hidden 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}.
+
address@hidden Authors of get_date
address@hidden Authors of @code{get_date}
+
address@hidden authors of @code{get_date}
+
address@hidden Bellovin, Steven M.
address@hidden Salz, Rich
address@hidden Berets, Jim
address@hidden MacKenzie, David
address@hidden Meyering, Jim
address@hidden Eggert, Paul
address@hidden 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.
+
address@hidden Pinard, F.
address@hidden Berry, K.
+This chapter was originally produced by Fran@,{c}ois Pinard
+(@email{pinard@@iro.umontreal.ca}) from the @file{getdate.y} source code,
+and then edited by K.@: Berry (@email{kb@@cs.umb.edu}).





reply via email to

[Prev in Thread] Current Thread [Next in Thread]