bug-coreutils
[Top][All Lists]
Advanced

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

strftime fix for big years etc.; plus documentation cleanup


From: Paul Eggert
Subject: strftime fix for big years etc.; plus documentation cleanup
Date: Wed, 23 Feb 2005 16:39:26 -0800

I installed this coreutils patch to fix some problems with outlandish
years (which are possible on 64-bit hosts with "date") and other
problems, and to bring the documentation back in sync with the code.

2005-02-23  Paul Eggert  <address@hidden>

        * doc/coreutils.texi: Adjust to match current strftime.c.
        (Time directives): Say that %k, %l, and %P are GNU extensions.
        For %p and %P, mention handling of noon and midnight.
        For %s, use ISO 8601, and mention handling of leap seconds.
        For %S, clarify mention of leap seconds.
        For %T, say that it's the same as %H:%M:%S.
        For %X, don't say that it must be the same as %H:%M:%S.
        For %z and %Z, clarify which time zones are used.  These options
        are now affected by --date, so don't claim that they're not.
        (Date directives): %C is now all but the last two chars of %Y.
        For %D, say that it's equivalent to %m/%d/%y.
        For %e, use blank in example.
        For %h, use @code for %b.
        For %Y, mention what happens with outlandish years.
        (Padding and other flags): Renamed from Padding.
        Mention that the flags are GNU extensions.
        Mention the 0 and ^ flags.
        Mention field widths an modifiers.
        (Examples of date): - is a flag, not a modifier.

        * lib/strftime.c: Include <stdbool.h>.  Use bool where appropriate,
        instead of int.
        (my_strftime): Do not mishandle years close to INT_MAX, by doing
        the right thing even if adding 1900 would overflow.  Similarly
        for tm_mon + 1 and tm_yday + 1.
        Make %Y always equivalent to %C%y, and similarly for %G and %g.
        (DO_NUMBER, DO_NUMBER_SPACEPAD): Set digits to d, not a conditional.
        (DO_SIGNED_NUMBER): New macro.
        (my_strftime) [HAVE_TZNAME]: Don't dump core if tp->tm_dst > 1.

Index: doc/coreutils.texi
===================================================================
RCS file: /fetish/cu/doc/coreutils.texi,v
retrieving revision 1.241
diff -p -u -r1.241 coreutils.texi
--- doc/coreutils.texi  8 Feb 2005 21:38:04 -0000       1.241
+++ doc/coreutils.texi  24 Feb 2005 00:26:11 -0000
@@ -398,7 +398,7 @@ System context
 * Time directives::              Time directives
 * Date directives::              Date directives
 * Literal directives::           Literal directives
-* Padding::                      Padding
+* Padding and other flags::      Padding and other flags
 * Setting the time::             Setting the time
 * Options for date::             Options for @command{date}
 * Examples of date::             Examples of @command{date}
@@ -11441,7 +11441,7 @@ are printed unchanged.  The directives a
 * Time directives::             %[HIklMprsSTXzZ]
 * Date directives::             %[aAbBcCdDhjmUwWxyY]
 * Literal directives::          %[%nt]
-* Padding::                     Pad with zeroes, spaces (%_), or nothing (%-).
+* Padding and other flags::     Pad with zeroes, spaces (%_), etc.
 * Setting the time::            Changing the system clock.
 * Options for date::            Instead of the current time.
 * Examples of date::            Examples.
@@ -11461,44 +11461,51 @@ hour (address@hidden)
 @item %I
 hour (address@hidden)
 @item %k
-hour ( address@hidden)
+hour ( address@hidden).
+This is a @acronym{GNU} extension.
 @item %l
-hour ( address@hidden)
+hour ( address@hidden).
+This is a @acronym{GNU} extension.
 @item %M
 minute (address@hidden)
 @item %N
 nanoseconds (address@hidden)
 @item %p
-locale's upper case @samp{AM} or @samp{PM} (blank in many locales)
+locale's upper case @samp{AM} or @samp{PM} (blank in many locales).
+Noon is treated as @samp{PM} and midnight as @samp{AM}.
 @item %P
-locale's lower case @samp{am} or @samp{pm} (blank in many locales)
+locale's lower case @samp{am} or @samp{pm} (blank in many locales).
+Noon is treated as @samp{pm} and midnight as @samp{am}.
+This is a @acronym{GNU} extension.
 @item %r
-time, 12-hour (hh:mm:ss [AP]M)
+locale's 12-hour time (hh:mm:ss [AP]M)
 @item %R
-time, 24-hour (hh:mm).  Same as @code{%H:%M}.
+24-hour hour and minute.  Same as @code{%H:%M}.
 @item %s
 @cindex epoch, seconds since
 @cindex seconds since the epoch
 @cindex beginning of time
-seconds since the epoch, i.e., 1 January 1970 00:00:00 UTC (a GNU extension).
-For examples, @xref{%s-examples}.
+seconds since the epoch, i.e., since 1970-01-01 00:00:00 UTC.
+Leap seconds are not counted unless leap second support is available.
+This is a @acronym{GNU} extension.
address@hidden, for examples.
 @item %S
-second (address@hidden).  The range is address@hidden, and not address@hidden,
-in order to accommodate the occasional positive leap second.
+second (address@hidden).  This may be @samp{60} if leap seconds are supported.
 @item %T
-time, 24-hour (hh:mm:ss)
+24-hour hour, minute, and second.  Same as @code{%H:%M:%S}.
 @item %X
-locale's time representation (%H:%M:%S)
+locale's time representation (hh:mm:ss)
 @item %z
-RFC-2822 style numeric time zone (e.g., @samp{-0600} or @samp{+0100}),
-or nothing if no
-time zone is determinable.  This value reflects the @emph{current} time
-zone.  It isn't changed by the @option{--date} option.
address@hidden 2822}/@w{ISO 8601} style numeric time zone (e.g., @samp{-0600}
+or @samp{+0100}), or nothing if no
+time zone is determinable.  This value reflects the numeric time zone
+appropriate for the current time, using the time zone rules specified
+by the @env{TZ} environment variable.
+The time (and optionally, the time zone rules) can be overridden
+by the @option{--date} option.
 @item %Z
-time zone (e.g., EDT), or nothing if no time zone is
-determinable.
-Note that this value reflects the @emph{current} time zone.
-It isn't changed by the @option{--date} option.
+alphabetic time zone abbreviation (e.g., @samp{EDT}), or nothing if no
+time zone is determinable.  See @samp{%z} for how it is determined.
 @end table
 
 
@@ -11522,13 +11529,16 @@ locale's full month name, variable lengt
 @item %c
 locale's date and time (Sat Nov 04 12:02:33 EST 1989)
 @item %C
-century (year divided by 100 and truncated to an integer) (address@hidden)
+century.  This is like @code{%Y}, except the last two digits are omitted.
+For example, it is @samp{20} if @code{%Y} is @samp{2000},
+and is @samp{-0} if @code{%Y} is @samp{-001}.
+It is normally at least two characters, but it may be more.
 @item %d
 day of month (address@hidden)
 @item %D
-date (mm/dd/yy)
+date (equivalent to @code{%m/%d/%y})
 @item %e
-blank-padded day of month (address@hidden)
+blank-padded day of month ( address@hidden)
 @item %F
 the @w{ISO 8601} standard date format: @code{%Y-%m-%d}.
 This is the preferred form for all uses.
@@ -11543,7 +11553,7 @@ and value as @code{%Y}, except that if t
 @code{%V}) belongs to the previous or next year, that year is used
 instead.
 @item %h
-same as %b
+same as @code{%b}
 @item %j
 day of year (address@hidden)
 @item %m
@@ -11569,7 +11579,8 @@ locale's date representation (mm/dd/yy)
 @item %y
 last two digits of year (address@hidden)
 @item %Y
-year (address@hidden)
+year.  This is normally at least four characters, but it may be more.
+Year 0000 precedes year 0001, and year -001 precedes year 0000.
 @end table
 
 
@@ -11591,8 +11602,8 @@ a horizontal tab
 @end table
 
 
address@hidden Padding
address@hidden Padding
address@hidden Padding and other flags
address@hidden Padding and other flags
 
 @cindex numeric field padding
 @cindex padding of numeric fields
@@ -11600,9 +11611,11 @@ a horizontal tab
 
 By default, @command{date} pads numeric fields with zeroes, so that, for
 example, numeric months are always output as two digits.
address@hidden @command{date}
-recognizes the following numeric modifiers between the @samp{%} and the
-directive.
+Numbers that do not have a range are never
+padded, since there is no natural width for them.
+
+As a @acronym{GNU} extension, @command{date} recognizes any of the
+following optional flags after the @samp{%}:
 
 @table @samp
 @item -
@@ -11611,12 +11624,14 @@ human consumption.
 @item _
 (underscore) pad the field with spaces; useful if you need a fixed
 number of characters in the output, but zeroes are too distracting.
address@hidden 0
+(zero) Pad with zeros even if the format specifies padding with spaces.
address@hidden ^
+Use upper case characters if possible.
 @end table
 
 @noindent
-These are GNU extensions.
-
-Here is an example illustrating the differences:
+Here are some examples of padding:
 
 @example
 date +%d/%m -d "Feb 1"
@@ -11627,6 +11642,31 @@ date +%_d/%_m -d "Feb 1"
 @result{}  1/ 2
 @end example
 
+As a @acronym{GNU} extension, you can specify the field width
+after any flag, as a decimal number.  If the natural size of the
+output is of the field has less than the specified number of characters,
+the result is written right adjusted and space padded to the given
+size.
+
+An optional modifier can follow the optional flag and width
+specification.  The modifiers are:
+
address@hidden @code
address@hidden E
+Use the locale's alternate representation for date and time.  This
+modifier applies to the @code{%c}, @code{%C}, @code{%x}, @code{%X},
address@hidden and @code{%Y} format specifiers.  In a Japanese locale, for
+example, @code{%Ex} might yield a date format based on the Japanese
+Emperors' reigns.
+
address@hidden O
+Use the locale's alternate numeric symbols for numbers.  This modifier
+applies only to numeric format specifiers.
address@hidden table
+
+If the format supports the modifier but no alternate representation
+is available, it is ignored.
+
 
 @node Setting the time
 @subsection Setting the time
@@ -11837,7 +11877,8 @@ for example @samp{date -d 1may '+%B %d'}
 
 @item
 To print a date without the leading zero for one-digit days
-of the month, you can use the (GNU extension) @code{-} modifier to suppress
+of the month, you can use the (@acronym{GNU} extension)
address@hidden flag to suppress
 the padding altogether:
 
 @example
@@ -11846,7 +11887,7 @@ date -d 1may '+%B %-d
 
 @item
 To print the current date and time in the format required by many
-non-GNU versions of @command{date} when setting the system clock:
address@hidden versions of @command{date} when setting the system clock:
 
 @example
 date +%m%d%H%M%Y.%S
Index: lib/strftime.c
===================================================================
RCS file: /fetish/cu/lib/strftime.c,v
retrieving revision 1.78
diff -p -u -r1.78 strftime.c
--- lib/strftime.c      11 Nov 2004 06:07:57 -0000      1.78
+++ lib/strftime.c      24 Feb 2005 00:26:12 -0000
@@ -1,4 +1,4 @@
-/* Copyright (C) 1991-1999, 2000, 2001, 2003, 2004 Free Software
+/* Copyright (C) 1991-1999, 2000, 2001, 2003, 2004, 2005 Free Software
    Foundation, Inc.
 
    NOTE: The canonical source of this file is maintained with the GNU C 
Library.
@@ -72,6 +72,7 @@ extern char *tzname[];
 #endif
 
 #include <limits.h>
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
@@ -479,16 +480,17 @@ my_strftime (CHAR_T *s, size_t maxsize, 
       int modifier;            /* Field modifier ('E', 'O', or 0).  */
       int digits;              /* Max digits for numeric format.  */
       int number_value;                /* Numeric value to be printed.  */
-      int negative_number;     /* 1 if the number is negative.  */
+      unsigned int u_number_value; /* (unsigned int) number_value.  */
+      bool negative_number;    /* 1 if the number is negative.  */
       const CHAR_T *subfmt;
       CHAR_T *bufp;
       CHAR_T buf[1 + (sizeof (int) < sizeof (time_t)
                      ? INT_STRLEN_BOUND (time_t)
                      : INT_STRLEN_BOUND (int))];
       int width = -1;
-      int to_lowcase = 0;
-      int to_uppcase = 0;
-      int change_case = 0;
+      bool to_lowcase = false;
+      bool to_uppcase = false;
+      bool change_case = false;
       int format_char;
 
 #if DO_MULTIBYTE && !defined COMPILE_WIDE
@@ -593,10 +595,10 @@ my_strftime (CHAR_T *s, size_t maxsize, 
 
              /* This changes textual output.  */
            case L_('^'):
-             to_uppcase = 1;
+             to_uppcase = true;
              continue;
            case L_('#'):
-             change_case = 1;
+             change_case = true;
              continue;
 
            default:
@@ -643,10 +645,14 @@ my_strftime (CHAR_T *s, size_t maxsize, 
       switch (format_char)
        {
 #define DO_NUMBER(d, v) \
-         digits = d > width ? d : width;                                     \
+         digits = d;                                                         \
          number_value = v; goto do_number
+#define DO_SIGNED_NUMBER(d, negative, v) \
+         digits = d;                                                         \
+         negative_number = negative;                                         \
+         u_number_value = v; goto do_signed_number
 #define DO_NUMBER_SPACEPAD(d, v) \
-         digits = d > width ? d : width;                                     \
+         digits = d;                                                         \
          number_value = v; goto do_number_spacepad
 
        case L_('%'):
@@ -660,8 +666,8 @@ my_strftime (CHAR_T *s, size_t maxsize, 
            goto bad_format;
          if (change_case)
            {
-             to_uppcase = 1;
-             to_lowcase = 0;
+             to_uppcase = true;
+             to_lowcase = false;
            }
 #if defined _NL_CURRENT || !HAVE_STRFTIME
          cpy (aw_len, a_wkday);
@@ -675,8 +681,8 @@ my_strftime (CHAR_T *s, size_t maxsize, 
            goto bad_format;
          if (change_case)
            {
-             to_uppcase = 1;
-             to_lowcase = 0;
+             to_uppcase = true;
+             to_lowcase = false;
            }
 #if defined _NL_CURRENT || !HAVE_STRFTIME
          cpy (STRLEN (f_wkday), f_wkday);
@@ -689,8 +695,8 @@ my_strftime (CHAR_T *s, size_t maxsize, 
        case L_('h'):
          if (change_case)
            {
-             to_uppcase = 1;
-             to_lowcase = 0;
+             to_uppcase = true;
+             to_lowcase = false;
            }
          if (modifier != 0)
            goto bad_format;
@@ -706,8 +712,8 @@ my_strftime (CHAR_T *s, size_t maxsize, 
            goto bad_format;
          if (change_case)
            {
-             to_uppcase = 1;
-             to_lowcase = 0;
+             to_uppcase = true;
+             to_lowcase = false;
            }
 #if defined _NL_CURRENT || !HAVE_STRFTIME
          cpy (STRLEN (f_month), f_month);
@@ -807,8 +813,9 @@ my_strftime (CHAR_T *s, size_t maxsize, 
            }
 
          {
-           int year = tp->tm_year + TM_YEAR_BASE;
-           DO_NUMBER (1, year / 100 - (year % 100 < 0));
+           int century = tp->tm_year / 100 + TM_YEAR_BASE / 100;
+           century -= tp->tm_year % 100 < 0 && 0 < century;
+           DO_SIGNED_NUMBER (2, tp->tm_year < - TM_YEAR_BASE, century);
          }
 
        case L_('x'):
@@ -846,8 +853,8 @@ my_strftime (CHAR_T *s, size_t maxsize, 
 
          DO_NUMBER_SPACEPAD (2, tp->tm_mday);
 
-         /* All numeric formats set DIGITS and NUMBER_VALUE and then
-            jump to one of these two labels.  */
+         /* All numeric formats set DIGITS and NUMBER_VALUE (or U_NUMBER_VALUE)
+            and then jump to one of these three labels.  */
 
        do_number_spacepad:
          /* Force `_' flag unless overridden by `0' or `-' flag.  */
@@ -855,14 +862,22 @@ my_strftime (CHAR_T *s, size_t maxsize, 
            pad = L_('_');
 
        do_number:
-         /* Format the number according to the MODIFIER flag.  */
-
-         if (modifier == L_('O') && 0 <= number_value)
+         /* Format NUMBER_VALUE according to the MODIFIER flag.  */
+         negative_number = number_value < 0;
+         u_number_value = number_value;
+
+       do_signed_number:
+         /* Format U_NUMBER_VALUE according to the MODIFIER flag.
+            NEGATIVE_NUMBER is nonzero if the original number was
+            negative; in this case it was converted directly to
+            unsigned int (i.e., modulo (UINT_MAX + 1)) without
+            negating it.  */
+         if (modifier == L_('O') && !negative_number)
            {
 #ifdef _NL_CURRENT
              /* Get the locale specific alternate representation of
-                the number NUMBER_VALUE.  If none exist NULL is returned.  */
-             const CHAR_T *cp = nl_get_alt_digit (number_value
+                the number.  If none exist NULL is returned.  */
+             const CHAR_T *cp = nl_get_alt_digit (u_number_value
                                                   HELPER_LOCALE_ARG);
 
              if (cp != NULL)
@@ -880,19 +895,21 @@ my_strftime (CHAR_T *s, size_t maxsize, 
 # endif
 #endif
            }
-         {
-           unsigned int u = number_value;
 
-           bufp = buf + sizeof (buf) / sizeof (buf[0]);
-           negative_number = number_value < 0;
+         bufp = buf + sizeof (buf) / sizeof (buf[0]);
 
-           if (negative_number)
-             u = -u;
+         if (negative_number)
+           u_number_value = - u_number_value;
 
-           do
-             *--bufp = u % 10 + L_('0');
-           while ((u /= 10) != 0);
-         }
+         do
+           {
+             *--bufp = u_number_value % 10 + L_('0');
+             u_number_value /= 10;
+           }
+         while (u_number_value != 0);
+
+         if (digits < width)
+           digits = width;
 
        do_number_sign_and_padding:
          if (negative_number)
@@ -974,7 +991,7 @@ my_strftime (CHAR_T *s, size_t maxsize, 
          if (modifier == L_('E'))
            goto bad_format;
 
-         DO_NUMBER (3, 1 + tp->tm_yday);
+         DO_SIGNED_NUMBER (3, tp->tm_yday < -1, tp->tm_yday + 1U);
 
        case L_('M'):
          if (modifier == L_('E'))
@@ -986,7 +1003,7 @@ my_strftime (CHAR_T *s, size_t maxsize, 
          if (modifier == L_('E'))
            goto bad_format;
 
-         DO_NUMBER (2, tp->tm_mon + 1);
+         DO_SIGNED_NUMBER (2, tp->tm_mon < -1, tp->tm_mon + 1U);
 
 #ifndef _LIBC
        case L_('N'):           /* GNU extension.  */
@@ -1010,7 +1027,7 @@ my_strftime (CHAR_T *s, size_t maxsize, 
          break;
 
        case L_('P'):
-         to_lowcase = 1;
+         to_lowcase = true;
 #if !defined _NL_CURRENT && HAVE_STRFTIME
          format_char = L_('p');
 #endif
@@ -1019,8 +1036,8 @@ my_strftime (CHAR_T *s, size_t maxsize, 
        case L_('p'):
          if (change_case)
            {
-             to_uppcase = 0;
-             to_lowcase = 1;
+             to_uppcase = false;
+             to_lowcase = true;
            }
 #if defined _NL_CURRENT || !HAVE_STRFTIME
          cpy (ap_len, ampm);
@@ -1070,20 +1087,7 @@ my_strftime (CHAR_T *s, size_t maxsize, 
              {
                int d = t % 10;
                t /= 10;
-
-               if (negative_number)
-                 {
-                   d = -d;
-
-                   /* Adjust if division truncates to minus infinity.  */
-                   if (0 < -1 % 10 && d < 0)
-                     {
-                       t++;
-                       d += 10;
-                     }
-                 }
-
-               *--bufp = d + L_('0');
+               *--bufp = (negative_number ? -d : d) + L_('0');
              }
            while (t != 0);
 
@@ -1131,14 +1135,22 @@ my_strftime (CHAR_T *s, size_t maxsize, 
          if (modifier == L_('E'))
            goto bad_format;
          {
-           int year = tp->tm_year + TM_YEAR_BASE;
+           /* YEAR is a leap year if and only if (tp->tm_year + TM_YEAR_BASE)
+              is a leap year, except that YEAR and YEAR - 1 both work
+              correctly even when (tp->tm_year + TM_YEAR_BASE) would
+              overflow.  */
+           int year = (tp->tm_year
+                       + (tp->tm_year < 0
+                          ? TM_YEAR_BASE % 400
+                          : TM_YEAR_BASE % 400 - 400));
+           int year_adjust = 0;
            int days = iso_week_days (tp->tm_yday, tp->tm_wday);
 
            if (days < 0)
              {
                /* This ISO week belongs to the previous year.  */
-               year--;
-               days = iso_week_days (tp->tm_yday + (365 + __isleap (year)),
+               year_adjust = -1;
+               days = iso_week_days (tp->tm_yday + (365 + __isleap (year - 1)),
                                      tp->tm_wday);
              }
            else
@@ -1148,7 +1160,7 @@ my_strftime (CHAR_T *s, size_t maxsize, 
                if (0 <= d)
                  {
                    /* This ISO week belongs to the next year.  */
-                   year++;
+                   year_adjust = 1;
                    days = d;
                  }
              }
@@ -1156,10 +1168,19 @@ my_strftime (CHAR_T *s, size_t maxsize, 
            switch (*f)
              {
              case L_('g'):
-               DO_NUMBER (2, (year % 100 + 100) % 100);
+               {
+                 int yy = (tp->tm_year % 100 + year_adjust) % 100;
+                 DO_NUMBER (2, (0 <= yy
+                                ? yy
+                                : tp->tm_year < -TM_YEAR_BASE - year_adjust
+                                ? -yy
+                                : yy + 100));
+               }
 
              case L_('G'):
-               DO_NUMBER (1, year);
+               DO_SIGNED_NUMBER (4, tp->tm_year < -TM_YEAR_BASE - year_adjust,
+                                 (tp->tm_year + (unsigned int) TM_YEAR_BASE
+                                  + year_adjust));
 
              default:
                DO_NUMBER (2, days / 7 + 1);
@@ -1201,7 +1222,8 @@ my_strftime (CHAR_T *s, size_t maxsize, 
          if (modifier == L_('O'))
            goto bad_format;
          else
-           DO_NUMBER (1, tp->tm_year + TM_YEAR_BASE);
+           DO_SIGNED_NUMBER (4, tp->tm_year < -TM_YEAR_BASE,
+                             tp->tm_year + (unsigned int) TM_YEAR_BASE);
 
        case L_('y'):
          if (modifier == L_('E'))
@@ -1220,19 +1242,25 @@ my_strftime (CHAR_T *s, size_t maxsize, 
 # endif
 #endif
            }
-         DO_NUMBER (2, (tp->tm_year % 100 + 100) % 100);
+
+         {
+           int yy = tp->tm_year % 100;
+           if (yy < 0)
+             yy = tp->tm_year < - TM_YEAR_BASE ? -yy : yy + 100;
+           DO_NUMBER (2, yy);
+         }
 
        case L_('Z'):
          if (change_case)
            {
-             to_uppcase = 0;
-             to_lowcase = 1;
+             to_uppcase = false;
+             to_lowcase = true;
            }
 
 #if HAVE_TZNAME
          /* The tzset() call might have changed the value.  */
          if (!(zone && *zone) && tp->tm_isdst >= 0)
-           zone = tzname[tp->tm_isdst];
+           zone = tzname[tp->tm_isdst != 0];
 #endif
          if (! zone)
            zone = "";




reply via email to

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