Include <stdlib.h>.
[gnulib.git] / lib / strftime.c
index b9b468a..1e02664 100644 (file)
@@ -3,20 +3,19 @@
    NOTE: The canonical source of this file is maintained with the GNU C Library.
    Bugs can be reported to bug-glibc@prep.ai.mit.edu.
 
-   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 2, or (at your option) any
-   later version.
+   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 2, 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
-   Library General Public License for more details.
+   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
-   USA.  */
+   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
 #ifdef HAVE_CONFIG_H
 # include <config.h>
@@ -168,44 +167,21 @@ extern char *tzname[];
 
 
 #ifdef _LIBC
-# define my_strftime_gmtime_r __gmtime_r
-# define my_strftime_localtime_r __localtime_r
 # define tzname __tzname
 # define tzset __tzset
-#else
-
-/* If we're a strftime substitute in a GNU program, then prefer gmtime
-   to gmtime_r, since many gmtime_r implementations are buggy.
-   Similarly for localtime_r.  */
-
-# if ! HAVE_TM_GMTOFF
-static struct tm *my_strftime_gmtime_r __P ((const time_t *, struct tm *));
-static struct tm *
-my_strftime_gmtime_r (t, tp)
-     const time_t *t;
-     struct tm *tp;
-{
-  struct tm *l = gmtime (t);
-  if (! l)
-    return 0;
-  *tp = *l;
-  return tp;
-}
+#endif
 
-static struct tm *my_strftime_localtime_r __P ((const time_t *, struct tm *));
-static struct tm *
-my_strftime_localtime_r (t, tp)
-     const time_t *t;
-     struct tm *tp;
-{
-  struct tm *l = localtime (t);
-  if (! l)
-    return 0;
-  *tp = *l;
-  return tp;
-}
-# endif /* ! HAVE_TM_GMTOFF */
-#endif /* ! defined _LIBC */
+#if !HAVE_TM_GMTOFF
+/* Portable standalone applications should supply a "time_r.h" that
+   declares a POSIX-compliant localtime_r, for the benefit of older
+   implementations that lack localtime_r or have a nonstandard one.
+   See the gnulib time_r module for one way to implement this.  */
+# include "time_r.h"
+# undef __gmtime_r
+# undef __localtime_r
+# define __gmtime_r gmtime_r
+# define __localtime_r localtime_r
+#endif
 
 
 #if !defined memset && !defined HAVE_MEMSET && !defined _LIBC
@@ -265,7 +241,7 @@ static const CHAR_T zeroes[16] = /* "0000000000000000" */
       int _n = (n);                                                          \
       int _delta = width - _n;                                               \
       int _incr = _n + (_delta > 0 ? _delta : 0);                            \
-      if (i + _incr >= maxsize)                                                      \
+      if ((size_t) _incr >= maxsize - i)                                     \
        return 0;                                                             \
       if (p)                                                                 \
        {                                                                     \
@@ -292,27 +268,19 @@ static const CHAR_T zeroes[16] = /* "0000000000000000" */
           MEMCPY ((PTR) p, (const PTR) (s), _n))
 
 #ifdef COMPILE_WIDE
-# ifdef USE_IN_EXTENDED_LOCALE_MODEL
-#  define widen(os, ws, l) \
+# ifndef USE_IN_EXTENDED_LOCALE_MODEL
+#  undef __mbsrtowcs_l
+#  define __mbsrtowcs_l(d, s, l, st, loc) __mbsrtowcs (d, s, l, st)
+# endif
+# define widen(os, ws, l) \
   {                                                                          \
     mbstate_t __st;                                                          \
     const char *__s = os;                                                    \
     memset (&__st, '\0', sizeof (__st));                                     \
     l = __mbsrtowcs_l (NULL, &__s, 0, &__st, loc);                           \
-    ws = alloca ((l + 1) * sizeof (wchar_t));                                \
+    ws = (wchar_t *) alloca ((l + 1) * sizeof (wchar_t));                    \
     (void) __mbsrtowcs_l (ws, &__s, l, &__st, loc);                          \
   }
-# else
-#  define widen(os, ws, l) \
-  {                                                                          \
-    mbstate_t __st;                                                          \
-    const char *__s = os;                                                    \
-    memset (&__st, '\0', sizeof (__st));                                     \
-    l = __mbsrtowcs (NULL, &__s, 0, &__st);                                  \
-    ws = alloca ((l + 1) * sizeof (wchar_t));                                \
-    (void) __mbsrtowcs (ws, &__s, l, &__st);                                 \
-  }
-# endif
 #endif
 
 
@@ -488,8 +456,10 @@ static CHAR_T const month_name[][10] =
 #else
 # ifdef COMPILE_WIDE
 #  define my_strftime wcsftime
+#  define nl_get_alt_digit _nl_get_walt_digit
 # else
 #  define my_strftime strftime
+#  define nl_get_alt_digit _nl_get_alt_digit
 # endif
 # define extra_args
 # define extra_args_spec
@@ -499,25 +469,11 @@ static CHAR_T const month_name[][10] =
 # define ns 0
 #endif
 
-#if !defined _LIBC && HAVE_TZNAME && HAVE_TZSET
-  /* Solaris 2.5 tzset sometimes modifies the storage returned by localtime.
-     Work around this bug by copying *tp before it might be munged.  */
-  size_t _strftime_copytm __P ((char *, size_t, const char *,
-                               const struct tm * extra_args_spec_iso));
-  size_t
-  my_strftime (s, maxsize, format, tp extra_args)
-      CHAR_T *s;
-      size_t maxsize;
-      const CHAR_T *format;
-      const struct tm *tp;
-      extra_args_spec
-  {
-    struct tm tmcopy;
-    tmcopy = *tp;
-    return _strftime_copytm (s, maxsize, format, &tmcopy extra_args);
-  }
-# undef my_strftime
-# define my_strftime _strftime_copytm
+#if ! defined _LIBC && ! HAVE_RUN_TZSET_TEST
+/* Solaris 2.5.x and 2.6 tzset sometimes modify the storage returned
+   by localtime.  On such systems, we must use the tzset and localtime
+   wrappers to work around the bug.  */
+"you must run the autoconf test for a working tzset function"
 #endif
 
 
@@ -621,7 +577,7 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
       int pad = 0;             /* Padding for number ('-', '_', or 0).  */
       int modifier;            /* Field modifier ('E', 'O', or 0).  */
       int digits;              /* Max digits for numeric format.  */
-      int number_value;        /* Numeric value to be printed.  */
+      int number_value;                /* Numeric value to be printed.  */
       int negative_number;     /* 1 if the number is negative.  */
       const CHAR_T *subfmt;
       CHAR_T *bufp;
@@ -754,8 +710,15 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
          width = 0;
          do
            {
-             width *= 10;
-             width += *f - L_('0');
+             if (width > INT_MAX / 10
+                 || (width == INT_MAX / 10 && *f - L_('0') > INT_MAX % 10))
+               /* Avoid overflow.  */
+               width = INT_MAX;
+             else
+               {
+                 width *= 10;
+                 width += *f - L_('0');
+               }
              ++f;
            }
          while (ISDIGIT (*f));
@@ -779,10 +742,10 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
       switch (format_char)
        {
 #define DO_NUMBER(d, v) \
-         digits = width == -1 ? d : width;                                   \
+         digits = d > width ? d : width;                                     \
          number_value = v; goto do_number
 #define DO_NUMBER_SPACEPAD(d, v) \
-         digits = width == -1 ? d : width;                                   \
+         digits = d > width ? d : width;                                     \
          number_value = v; goto do_number_spacepad
 
        case L_('%'):
@@ -986,8 +949,8 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
             jump to one of these two labels.  */
 
        do_number_spacepad:
-         /* Force `_' flag unless overwritten by `0' flag.  */
-         if (pad != L_('0'))
+         /* Force `_' flag unless overridden by `0' or `-' flag.  */
+         if (pad != L_('0') && pad != L_('-'))
            pad = L_('_');
 
        do_number:
@@ -998,13 +961,8 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
 #ifdef _NL_CURRENT
              /* Get the locale specific alternate representation of
                 the number NUMBER_VALUE.  If none exist NULL is returned.  */
-# ifdef COMPILE_WIDE
-             const wchar_t *cp = _nl_get_walt_digit (number_value
-                                                     HELPER_LOCALE_ARG);
-# else
-             const char *cp = _nl_get_alt_digit (number_value
-                                                 HELPER_LOCALE_ARG);
-# endif
+             const CHAR_T *cp = nl_get_alt_digit (number_value
+                                                  HELPER_LOCALE_ARG);
 
              if (cp != NULL)
                {
@@ -1033,7 +991,7 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
            do
              *--bufp = u % 10 + L_('0');
            while ((u /= 10) != 0);
-         }
+         }
 
        do_number_sign_and_padding:
          if (negative_number)
@@ -1044,18 +1002,37 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
              int padding = digits - (buf + (sizeof (buf) / sizeof (buf[0]))
                                      - bufp);
 
-             if (pad == L_('_'))
-               {
-                 while (0 < padding--)
-                   *--bufp = L_(' ');
-               }
-             else
+             if (padding > 0)
                {
-                 bufp += negative_number;
-                 while (0 < padding--)
-                   *--bufp = L_('0');
-                 if (negative_number)
-                   *--bufp = L_('-');
+                 if (pad == L_('_'))
+                   {
+                     if ((size_t) padding >= maxsize - i)
+                       return 0;
+
+                     if (p)
+                       memset_space (p, padding);
+                     i += padding;
+                     width = width > padding ? width - padding : 0;
+                   }
+                 else
+                   {
+                     if ((size_t) digits >= maxsize - i)
+                       return 0;
+
+                     if (negative_number)
+                       {
+                         ++bufp;
+
+                         if (p)
+                           *p++ = L_('-');
+                         ++i;
+                       }
+
+                     if (p)
+                       memset_zero (p, padding);
+                     i += padding;
+                     width = 0;
+                   }
                }
            }
 
@@ -1156,13 +1133,17 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
          goto subformat;
 
        case L_('r'):
-#ifdef _NL_CURRENT
+#if !defined _NL_CURRENT && HAVE_STRFTIME
+         goto underlying_strftime;
+#else
+# ifdef _NL_CURRENT
          if (*(subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME,
                                                       NLW(T_FMT_AMPM)))
              == L_('\0'))
-#endif
+# endif
            subfmt = L_("%I:%M:%S %p");
          goto subformat;
+#endif
 
        case L_('S'):
          if (modifier == L_('E'))
@@ -1171,7 +1152,7 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
          DO_NUMBER (2, tp->tm_sec);
 
        case L_('s'):           /* GNU extension.  */
-         {
+         {
            struct tm ltm;
            time_t t;
 
@@ -1396,7 +1377,7 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
                       occurred.  */
                    struct tm tm;
 
-                   if (! my_strftime_localtime_r (&lt, &tm)
+                   if (! __localtime_r (&lt, &tm)
                        || ((ltm.tm_sec ^ tm.tm_sec)
                            | (ltm.tm_min ^ tm.tm_min)
                            | (ltm.tm_hour ^ tm.tm_hour)
@@ -1406,7 +1387,7 @@ my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM)
                      break;
                  }
 
-               if (! my_strftime_gmtime_r (&lt, &gtm))
+               if (! __gmtime_r (&lt, &gtm))
                  break;
 
                diff = tm_diff (&ltm, &gtm);