OSDN Git Service

* libc/time/strftime.c: Add support for era and alt_digits data from
authorcorinna <corinna>
Fri, 26 Feb 2010 18:41:43 +0000 (18:41 +0000)
committercorinna <corinna>
Fri, 26 Feb 2010 18:41:43 +0000 (18:41 +0000)
LC_TIME locale category.  Conditionalize using _WANT_C99_TIME_FORMATS
flag.
(STRTOUL): Define differently for building strftime or wcsftime.
(STRCPY): Ditto.
(STRCHR): Ditto.
(STRLEN): Ditto.
(CHECK_LENGTH): Define to simplify code.
(era_info_t): New type to store era info.
(get_era_info): New function to fetch era info matching incoming
struct tm.
(free_era_info): New function to free era info.
(alt_digits_t): New type to store alternative digits.
(get_alt_digits): New function to convert alt_digits string into
alt_digits_t structure.
(free_alt_digits): New function to free alt_digits info.
(conv_to_alt_digits): New function to convert unsigned value into
alternative digits.
(strftime): Conditionalize on _WANT_C99_TIME_FORMATS.  If
_WANT_C99_TIME_FORMATS is defined, define as just a wrapper function
providing era_info and alt_digits pointers and call ...
(__strftime): Rename from strftime and make static if
_WANT_C99_TIME_FORMATS is defined.  Add parameters for era_info and
alt_digits pointers.  Handle conversion modifiers according to
POSIX-1.2008.  Redefine %F and %Y according to POSIX.  Add default case
to allow to bail out on invalid conversion specifiers.
* libc/include/sys/config.h: Move Cygwin build flags to Cygwin's
config.h.

* libc/include/stdio.h: Remove __CYGWIN_USE_BIG_TYPES__ condition.

newlib/ChangeLog
newlib/libc/include/stdio.h
newlib/libc/include/sys/config.h
newlib/libc/time/strftime.c

index 486da7e..0be03a0 100644 (file)
@@ -1,3 +1,36 @@
+2010-02-26  Corinna Vinschen  <corinna@vinschen.de>
+
+       * libc/time/strftime.c: Add support for era and alt_digits data from
+       LC_TIME locale category.  Conditionalize using _WANT_C99_TIME_FORMATS
+       flag.
+       (STRTOUL): Define differently for building strftime or wcsftime.
+       (STRCPY): Ditto.
+       (STRCHR): Ditto.
+       (STRLEN): Ditto.
+       (CHECK_LENGTH): Define to simplify code.
+       (era_info_t): New type to store era info.
+       (get_era_info): New function to fetch era info matching incoming
+       struct tm.
+       (free_era_info): New function to free era info.
+       (alt_digits_t): New type to store alternative digits.
+       (get_alt_digits): New function to convert alt_digits string into
+       alt_digits_t structure.
+       (free_alt_digits): New function to free alt_digits info.
+       (conv_to_alt_digits): New function to convert unsigned value into
+       alternative digits.
+       (strftime): Conditionalize on _WANT_C99_TIME_FORMATS.  If
+       _WANT_C99_TIME_FORMATS is defined, define as just a wrapper function
+       providing era_info and alt_digits pointers and call ...
+       (__strftime): Rename from strftime and make static if
+       _WANT_C99_TIME_FORMATS is defined.  Add parameters for era_info and
+       alt_digits pointers.  Handle conversion modifiers according to
+       POSIX-1.2008.  Redefine %F and %Y according to POSIX.  Add default case
+       to allow to bail out on invalid conversion specifiers.
+       * libc/include/sys/config.h: Move Cygwin build flags to Cygwin's
+       config.h.
+
+       * libc/include/stdio.h: Remove __CYGWIN_USE_BIG_TYPES__ condition.
+
 2010-02-25  Corinna Vinschen  <corinna@vinschen.de>
 
        * libc/locale/locale.c (loadlocale): Fix typo in comment.
index 8389449..2912eaf 100644 (file)
@@ -50,13 +50,9 @@ _BEGIN_STD_C
 typedef __FILE FILE;
 
 #ifdef __CYGWIN__
-#ifdef __CYGWIN_USE_BIG_TYPES__
 typedef _fpos64_t fpos_t;
 #else
 typedef _fpos_t fpos_t;
-#endif
-#else
-typedef _fpos_t fpos_t;
 #ifdef __LARGE64_FILES
 typedef _fpos64_t fpos64_t;
 #endif
index 64a3fe6..49a3d8a 100644 (file)
 
 #if defined(__CYGWIN__)
 #include <cygwin/config.h>
-#define __LINUX_ERRNO_EXTENSIONS__ 1
-#define _MB_EXTENDED_CHARSETS_ALL 1
-#define __HAVE_LOCALE_INFO__ 1
 #if !defined (__STRICT_ANSI__) || (__STDC_VERSION__ >= 199901L)
 #define __USE_XOPEN2K 1
 #endif
index 95fb301..1df353a 100644 (file)
@@ -284,6 +284,10 @@ the "C" locale settings.
 #  define SFLG                         /* %s flag (null for normal char) */
 #  define _ctloc(x) (ctloclen = strlen (ctloc = _CurrentTimeLocale->x), ctloc)
 #  define TOLOWER(c)   tolower((int)(unsigned char)(c))
+#  define STRTOUL(c,p,b) strtoul((c),(p),(b))
+#  define STRCPY(a,b)  strcpy((a),(b))
+#  define STRCHR(a,b)  strchr((a),(b))
+#  define STRLEN(a)    strlen(a)
 # else
 #  define strftime     wcsftime        /* Alternate function name */
 #  define CHAR         wchar_t         /* string type basis */
@@ -291,6 +295,10 @@ the "C" locale settings.
 #  define snprintf     swprintf        /* wide-char equivalent function name */
 #  define strncmp      wcsncmp         /* wide-char equivalent function name */
 #  define TOLOWER(c)   towlower((wint_t)(c))
+#  define STRTOUL(c,p,b) wcstoul((c),(p),(b))
+#  define STRCPY(a,b)  wcscpy((a),(b))
+#  define STRCHR(a,b)  wcschr((a),(b))
+#  define STRLEN(a)    wcslen(a)
 #  define SFLG         "l"             /* %s flag (l for wide char) */
 #  define CTLOCBUFLEN   256            /* Arbitrary big buffer size */
    const wchar_t *
@@ -306,6 +314,9 @@ the "C" locale settings.
                     &ctloclen))
 #endif  /* MAKE_WCSFTIME */
 
+#define CHECK_LENGTH() if (len < 0 || (count += len) >= maxsize) \
+                         return 0
+
 /* Enforce the coding assumptions that YEAR_BASE is positive.  (%C, %Y, etc.) */
 #if YEAR_BASE < 0
 #  error "YEAR_BASE < 0"
@@ -361,12 +372,288 @@ _DEFUN (iso_year_adjust, (tim_p),
 #undef PACK
 }
 
+#ifdef _WANT_C99_TIME_FORMATS
+typedef struct {
+  int   year;
+  CHAR *era_C;
+  CHAR *era_Y;
+} era_info_t;
+
+static era_info_t *
+get_era_info (const struct tm *tim_p, const char *era)
+{
+  char *c;
+  const char *dir;
+  long offset;
+  struct tm stm, etm;
+  era_info_t *ei;
+
+  ei = (era_info_t *) calloc (1, sizeof (era_info_t));
+  if (!ei)
+    return NULL;
+
+  stm.tm_isdst = etm.tm_isdst = 0;
+  while (era)
+    {
+      dir = era;
+      era += 2;
+      offset = strtol (era, &c, 10);
+      era = c + 1;
+      stm.tm_year = strtol (era, &c, 10) - YEAR_BASE;
+      /* Adjust offset for negative gregorian dates. */
+      if (stm.tm_year <= -YEAR_BASE)
+       ++stm.tm_year;
+      stm.tm_mon = strtol (c + 1, &c, 10);
+      stm.tm_mday = strtol (c + 1, &c, 10);
+      stm.tm_hour = stm.tm_min = stm.tm_sec = 0;
+      era = c + 1;
+      if (era[0] == '-' && era[1] == '*')
+       {
+         etm = stm;
+         stm.tm_year = INT_MIN;
+         stm.tm_mon = stm.tm_mday = stm.tm_hour = stm.tm_min = stm.tm_sec = 0;
+         era += 3;
+       }
+      else if (era[0] == '+' && era[1] == '*')
+       {
+         etm.tm_year = INT_MAX;
+         etm.tm_mon = 12;
+         etm.tm_mday = 31;
+         etm.tm_hour = 23;
+         etm.tm_min = etm.tm_sec = 59;
+         era += 3;
+       }
+      else
+       {
+         etm.tm_year = strtol (era, &c, 10) - YEAR_BASE;
+         /* Adjust offset for negative gregorian dates. */
+         if (etm.tm_year <= -YEAR_BASE)
+           ++etm.tm_year;
+         etm.tm_mon = strtol (c + 1, &c, 10);
+         etm.tm_mday = strtol (c + 1, &c, 10);
+         etm.tm_mday = 31;
+         etm.tm_hour = 23;
+         etm.tm_min = etm.tm_sec = 59;
+         era = c + 1;
+       }
+      if ((tim_p->tm_year > stm.tm_year
+          || (tim_p->tm_year == stm.tm_year
+              && (tim_p->tm_mon > stm.tm_mon
+                  || (tim_p->tm_mon == stm.tm_mon
+                      && tim_p->tm_mday >= stm.tm_mday))))
+         && (tim_p->tm_year < etm.tm_year
+             || (tim_p->tm_year == etm.tm_year
+                 && (tim_p->tm_mon < etm.tm_mon
+                     || (tim_p->tm_mon == etm.tm_mon
+                         && tim_p->tm_mday <= etm.tm_mday)))))
+       {
+         /* Gotcha */
+         size_t len;
+
+         /* year */
+         if (*dir == '+' && stm.tm_year != INT_MIN)
+           ei->year = tim_p->tm_year - stm.tm_year + offset;
+         else
+           ei->year = etm.tm_year - tim_p->tm_year + offset;
+         /* era_C */
+         c = strchr (era, ':');
+#ifdef MAKE_WCSFTIME
+         len = mbsnrtowcs (NULL, &era, c - era, 0, NULL);
+         if (len == (size_t) -1)
+           {
+             free (ei);
+             return NULL;
+           }
+#else
+         len = c - era;
+#endif
+         ei->era_C = (CHAR *) malloc ((len + 1) * sizeof (CHAR));
+         if (!ei->era_C)
+           {
+             free (ei);
+             return NULL;
+           }
+#ifdef MAKE_WCSFTIME
+         len = mbsnrtowcs (ei->era_C, &era, c - era, len + 1, NULL);
+#else
+         strncpy (ei->era_C, era, len);
+         era += len;
+#endif
+         ei->era_C[len] = CQ('\0');
+         /* era_Y */
+         ++era;
+         c = strchr (era, ';');
+         if (!c)
+           c = strchr (era, '\0');
+#ifdef MAKE_WCSFTIME
+         len = mbsnrtowcs (NULL, &era, c - era, 0, NULL);
+         if (len == (size_t) -1)
+           {
+             free (ei->era_C);
+             free (ei);
+             return NULL;
+           }
+#else
+         len = c - era;
+#endif
+         ei->era_Y = (CHAR *) malloc ((len + 1) * sizeof (CHAR));
+         if (!ei->era_Y)
+           {
+             free (ei->era_C);
+             free (ei);
+             return NULL;
+           }
+#ifdef MAKE_WCSFTIME
+         len = mbsnrtowcs (ei->era_Y, &era, c - era, len + 1, NULL);
+#else
+         strncpy (ei->era_Y, era, len);
+         era += len;
+#endif
+         ei->era_Y[len] = CQ('\0');
+         return ei;
+       }
+      else
+       era = strchr (era, ';');
+      if (era)
+       ++era;
+    }
+  return NULL;
+}
+
+static void
+free_era_info (era_info_t *ei)
+{
+  free (ei->era_C);
+  free (ei->era_Y);
+  free (ei);
+}
+
+typedef struct {
+  size_t num;
+  CHAR **digit;
+  CHAR *buffer;
+} alt_digits_t;
+
+static alt_digits_t *
+get_alt_digits (const char *alt_digits)
+{
+  alt_digits_t *adi;
+  const char *a, *e;
+  CHAR *aa, *ae;
+  size_t len;
+
+  adi = (alt_digits_t *) calloc (1, sizeof (alt_digits_t));
+  if (!adi)
+    return NULL;
+
+  /* Compute number of alt_digits. */
+  adi->num = 1;
+  for (a = alt_digits; (e = strchr (a, ';')) != NULL; a = e + 1)
+      ++adi->num;
+  /* Allocate the `digit' array, which is an array of `num' pointers into
+     `buffer'. */
+  adi->digit = (CHAR **) calloc (adi->num, sizeof (CHAR **));
+  if (!adi->digit)
+    {
+      free (adi);
+      return NULL;
+    }
+  /* Compute memory required for `buffer'. */
+#ifdef MAKE_WCSFTIME
+  len = mbstowcs (NULL, alt_digits, 0);
+  if (len == (size_t) -1)
+    {
+      free (adi->digit);
+      free (adi);
+      return NULL;
+    }
+#else
+  len = strlen (alt_digits);
+#endif
+  /* Allocate it. */
+  adi->buffer = (CHAR *) malloc ((len + 1) * sizeof (CHAR));
+  if (!adi->buffer)
+    {
+      free (adi->digit);
+      free (adi);
+      return NULL;
+    }
+  /* Store digits in it. */
+#ifdef MAKE_WCSFTIME
+  mbstowcs (adi->buffer, alt_digits, len + 1);
+#else
+  strcpy (adi->buffer, alt_digits);
+#endif
+  /* Store the pointers into `buffer' into the appropriate `digit' slot. */
+  for (len = 0, aa = adi->buffer; (ae = STRCHR (aa, CQ(';'))) != NULL;
+       ++len, aa = ae + 1)
+    {
+      *ae = '\0';
+      adi->digit[len] = aa;
+    }
+  adi->digit[len] = aa;
+  return adi;
+}
+
+static void
+free_alt_digits (alt_digits_t *adi)
+{
+  free (adi->digit);
+  free (adi->buffer);
+  free (adi);
+}
+
+/* Return 0 if no alt_digit is available for a number.
+   Return -1 if buffer size isn't sufficient to hold alternative digit.
+   Return length of new digit otherwise. */
+static int
+conv_to_alt_digits (CHAR *buf, size_t bufsiz, unsigned num, alt_digits_t *adi)
+{
+  if (num < adi->num)
+    {
+      size_t len = STRLEN (adi->digit[num]);
+      if (bufsiz < len)
+       return -1;
+      STRCPY (buf, adi->digit[num]);
+      return (int) len;
+    }
+  return 0;
+}
+
+static size_t __strftime (CHAR *, size_t, const CHAR *, const struct tm *,
+                         era_info_t **, alt_digits_t **);
+
+size_t
+_DEFUN (strftime, (s, maxsize, format, tim_p),
+       CHAR *s _AND
+       size_t maxsize _AND
+       _CONST CHAR *format _AND
+       _CONST struct tm *tim_p)
+{
+  era_info_t *era_info = NULL;
+  alt_digits_t *alt_digits = NULL;
+  size_t ret = __strftime (s, maxsize, format, tim_p, &era_info, &alt_digits);
+  if (era_info)
+    free_era_info (era_info);
+  if (alt_digits)
+    free_alt_digits (alt_digits);
+  return ret;
+}
+
+static size_t
+__strftime (CHAR *s, size_t maxsize, const CHAR *format,
+           const struct tm *tim_p, era_info_t **era_info,
+           alt_digits_t **alt_digits)
+#else /* !_WANT_C99_TIME_FORMATS */
+# define __strftime(s,m,f,t,e,a)       strftime((s),(m),(f),(t))
+
 size_t
 _DEFUN (strftime, (s, maxsize, format, tim_p),
        CHAR *s _AND
        size_t maxsize _AND
        _CONST CHAR *format _AND
        _CONST struct tm *tim_p)
+#endif /* !_WANT_C99_TIME_FORMATS */
 {
   size_t count = 0;
   int i, len;
@@ -375,6 +662,9 @@ _DEFUN (strftime, (s, maxsize, format, tim_p),
   CHAR ctlocbuf[CTLOCBUFLEN];
 #endif
   size_t ctloclen;
+  CHAR alt;
+  CHAR pad;
+  unsigned long width;
 
   struct lc_time_T *_CurrentTimeLocale = __get_current_time_locale ();
   for (;;)
@@ -386,13 +676,42 @@ _DEFUN (strftime, (s, maxsize, format, tim_p),
          else
            return 0;
        }
-
       if (*format == CQ('\0'))
        break;
-
       format++;
-      if (*format == CQ('E') || *format == CQ('O'))
-       format++;
+      pad = '\0';
+      width = 0;
+
+      /* POSIX-1.2008 feature: '0' and '+' modifiers require 0-padding with
+         slightly different semantics. */
+      if (*format == CQ('0') || *format == CQ('+'))
+       pad = *format++;
+
+      /* POSIX-1.2008 feature: A minimum field width can be specified. */
+      if (*format >= CQ('1') && *format <= CQ('9'))
+       {
+         CHAR *fp;
+         width = STRTOUL (format, &fp, 10);
+         format = fp;
+       }
+
+      alt = CQ('\0');
+      if (*format == CQ('E'))
+       {
+         alt = *format++;
+#ifdef _WANT_C99_TIME_FORMATS      
+         if (!*era_info && *_CurrentTimeLocale->era)
+           *era_info = get_era_info (tim_p, _CurrentTimeLocale->era);
+#endif /* _WANT_C99_TIME_FORMATS */
+       }
+      else if (*format == CQ('O'))
+       {
+         alt = *format++;
+#ifdef _WANT_C99_TIME_FORMATS      
+         if (!*alt_digits && *_CurrentTimeLocale->alt_digits)
+           *alt_digits = get_alt_digits (_CurrentTimeLocale->alt_digits);
+#endif /* _WANT_C99_TIME_FORMATS */
+       }
 
       switch (*format)
        {
@@ -438,24 +757,39 @@ _DEFUN (strftime, (s, maxsize, format, tim_p),
            }
          break;
        case CQ('c'):
-         _ctloc (c_fmt);
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt == 'E' && *era_info && *_CurrentTimeLocale->era_d_t_fmt)
+           _ctloc (era_d_t_fmt);
+         else
+#endif /* _WANT_C99_TIME_FORMATS */
+           _ctloc (c_fmt);
          goto recurse;
        case CQ('r'):
          _ctloc (ampm_fmt);
          goto recurse;
        case CQ('x'):
-         _ctloc (x_fmt);
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt == 'E' && *era_info && *_CurrentTimeLocale->era_d_fmt)
+           _ctloc (era_d_fmt);
+         else
+#endif /* _WANT_C99_TIME_FORMATS */
+           _ctloc (x_fmt);
          goto recurse;
        case CQ('X'):
-         _ctloc (X_fmt);
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt == 'E' && *era_info && *_CurrentTimeLocale->era_t_fmt)
+           _ctloc (era_t_fmt);
+         else
+#endif /* _WANT_C99_TIME_FORMATS */
+           _ctloc (X_fmt);
 recurse:
          if (*ctloc)
            {
              /* Recurse to avoid need to replicate %Y formation. */
-             size_t adjust = strftime (&s[count], maxsize - count, ctloc,
-                                       tim_p);
-             if (adjust > 0)
-               count += adjust;
+             len = __strftime (&s[count], maxsize - count, ctloc, tim_p,
+                               era_info, alt_digits);
+             if (len > 0)
+               count += len;
              else
                return 0;
            }
@@ -482,38 +816,97 @@ recurse:
               Be careful of both overflow and sign adjustment due to the
               asymmetric range of years.
            */
-           int neg = tim_p->tm_year < -YEAR_BASE;
-           int century = tim_p->tm_year >= 0
-             ? tim_p->tm_year / 100 + YEAR_BASE / 100
-             : abs (tim_p->tm_year + YEAR_BASE) / 100;
-            len = snprintf (&s[count], maxsize - count, CQ("%s%.*d"),
-                               neg ? CQ("-") : CQ(""), 2 - neg, century);
-            if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+           if (alt == 'E' && *era_info)
+             len = snprintf (&s[count], maxsize - count, CQ("%" SFLG "s"),
+                             (*era_info)->era_C);
+           else
+#endif /* _WANT_C99_TIME_FORMATS */
+             {
+               CHAR *fmt = CQ("%s%.*d");
+               char *pos = "";
+               int neg = tim_p->tm_year < -YEAR_BASE;
+               int century = tim_p->tm_year >= 0
+                 ? tim_p->tm_year / 100 + YEAR_BASE / 100
+                 : abs (tim_p->tm_year + YEAR_BASE) / 100;
+               if (pad) /* '0' or '+' */
+                 {
+                   fmt = CQ("%s%0.*d");
+                   if (century >= 100 && pad == CQ('+'))
+                     pos = "+";
+                 }
+               if (width < 2)
+                 width = 2;
+               len = snprintf (&s[count], maxsize - count, fmt,
+                               neg ? "-" : pos, width - neg, century);
+             }
+            CHECK_LENGTH ();
          }
          break;
        case CQ('d'):
        case CQ('e'):
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt == CQ('O') && *alt_digits)
+           {
+             if (tim_p->tm_mday < 10)
+               {
+                 if (*format == CQ('d'))
+                   {
+                     if (maxsize - count < 2) return 0;
+                     len = conv_to_alt_digits (&s[count], maxsize - count,
+                                               0, *alt_digits);
+                     CHECK_LENGTH ();
+                   }
+                 if (*format == CQ('e') || len == 0)
+                   s[count++] = CQ(' ');
+               }
+             len = conv_to_alt_digits (&s[count], maxsize - count,
+                                       tim_p->tm_mday, *alt_digits);
+             CHECK_LENGTH ();
+             if (len > 0)
+               break;
+           }
+#endif /* _WANT_C99_TIME_FORMATS */
          len = snprintf (&s[count], maxsize - count,
-                       *format == CQ('d') ? CQ("%.2d") : CQ("%2d"),
-                       tim_p->tm_mday);
-         if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+                         *format == CQ('d') ? CQ("%.2d") : CQ("%2d"),
+                         tim_p->tm_mday);
+         CHECK_LENGTH ();
          break;
        case CQ('D'):
          /* %m/%d/%y */
          len = snprintf (&s[count], maxsize - count,
-                       CQ("%.2d/%.2d/%.2d"),
-                       tim_p->tm_mon + 1, tim_p->tm_mday,
-                       tim_p->tm_year >= 0 ? tim_p->tm_year % 100
-                       : abs (tim_p->tm_year + YEAR_BASE) % 100);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+                         CQ("%.2d/%.2d/%.2d"),
+                         tim_p->tm_mon + 1, tim_p->tm_mday,
+                         tim_p->tm_year >= 0 ? tim_p->tm_year % 100
+                         : abs (tim_p->tm_year + YEAR_BASE) % 100);
+          CHECK_LENGTH ();
          break;
        case CQ('F'):
-         { /* %F is equivalent to "%Y-%m-%d" */
-           /* Recurse to avoid need to replicate %Y formation. */
-           size_t adjust = strftime (&s[count], maxsize - count,
-                                     CQ("%Y-%m-%d"), tim_p);
-           if (adjust > 0)
-             count += adjust;
+         { /* %F is equivalent to "%+4Y-%m-%d", flags and width can change
+              that.  Recurse to avoid need to replicate %Y formation. */
+           CHAR fmtbuf[32], *fmt = fmtbuf;
+           
+           *fmt++ = CQ('%');
+           if (pad) /* '0' or '+' */
+             *fmt++ = pad;
+           else
+             *fmt++ = '+';
+           if (!pad)
+             width = 10;
+           if (width < 6)
+             width = 6;
+           width -= 6;
+           if (width)
+             {
+               len = snprintf (fmt, fmtbuf + 32 - fmt, CQ("%lu"), width);
+               if (len > 0)
+                 fmt += len;
+             }
+           STRCPY (fmt, CQ("Y-%m-%d"));
+           len = __strftime (&s[count], maxsize - count, fmtbuf, tim_p,
+                             era_info, alt_digits);
+           if (len > 0)
+             count += len;
            else
              return 0;
          }
@@ -530,8 +923,8 @@ recurse:
            else if (adjust > 0 && tim_p->tm_year < -YEAR_BASE)
                adjust = -1;
            len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
-                      ((year + adjust) % 100 + 100) % 100);
-            if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+                           ((year + adjust) % 100 + 100) % 100);
+            CHECK_LENGTH ();
          }
           break;
        case CQ('G'):
@@ -539,7 +932,7 @@ recurse:
            /* See the comments for 'C' and 'Y'; this is a variable length
               field.  Although there is no requirement for a minimum number
               of digits, we use 4 for consistency with 'Y'.  */
-           int neg = tim_p->tm_year < -YEAR_BASE;
+           int sign = tim_p->tm_year < -YEAR_BASE;
            int adjust = iso_year_adjust (tim_p);
            int century = tim_p->tm_year >= 0
              ? tim_p->tm_year / 100 + YEAR_BASE / 100
@@ -547,8 +940,8 @@ recurse:
            int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100
              : abs (tim_p->tm_year + YEAR_BASE) % 100;
            if (adjust < 0 && tim_p->tm_year <= -YEAR_BASE)
-             neg = adjust = 1;
-           else if (adjust > 0 && neg)
+             sign = adjust = 1;
+           else if (adjust > 0 && sign)
              adjust = -1;
            year += adjust;
            if (year == -1)
@@ -561,45 +954,88 @@ recurse:
                year = 0;
                ++century;
              }
-            len = snprintf (&s[count], maxsize - count, CQ("%s%.*d%.2d"),
-                               neg ? CQ("-") : CQ(""), 2 - neg, century, year);
+           CHAR fmtbuf[10], *fmt = fmtbuf;
+           /* int potentially overflows, so use unsigned instead.  */
+           unsigned p_year = century * 100 + year;
+           if (sign)
+             *fmt++ = CQ('-');
+           else if (pad == CQ('+') && p_year >= 10000)
+             {
+               *fmt++ = CQ('+');
+               sign = 1;
+             }
+           if (width && sign)
+             --width;
+           *fmt++ = CQ('%');
+           if (pad)
+             *fmt++ = CQ('0');
+           STRCPY (fmt, CQ(".*u"));
+           len = snprintf (&s[count], maxsize - count, fmtbuf, width, p_year);
             if (len < 0  ||  (count+=len) >= maxsize)
               return 0;
          }
           break;
        case CQ('H'):
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt == CQ('O') && *alt_digits)
+           {
+             len = conv_to_alt_digits (&s[count], maxsize - count,
+                                       tim_p->tm_hour, *alt_digits);
+             CHECK_LENGTH ();
+             if (len > 0)
+               break;
+           }
+#endif /* _WANT_C99_TIME_FORMATS */
+         /*FALLTHRU*/
        case CQ('k'):   /* newlib extension */
          len = snprintf (&s[count], maxsize - count,
-                       *format == CQ('k') ? CQ("%2d") : CQ("%.2d"),
-                       tim_p->tm_hour);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+                         *format == CQ('k') ? CQ("%2d") : CQ("%.2d"),
+                         tim_p->tm_hour);
+          CHECK_LENGTH ();
          break;
-       case CQ('I'):
        case CQ('l'):   /* newlib extension */
+         if (alt == CQ('O'))
+           alt = CQ('\0');
+         /*FALLTHRU*/
+       case CQ('I'):
          {
            register int  h12;
            h12 = (tim_p->tm_hour == 0 || tim_p->tm_hour == 12)  ?
                                                12  :  tim_p->tm_hour % 12;
-           len = snprintf (&s[count], maxsize - count,
-                       *format == CQ('I') ? CQ("%.2d") : CQ("%2d"),
-                       h12);
-           if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+           if (alt != CQ('O') || !*alt_digits
+               || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+                                              h12, *alt_digits)))
+#endif /* _WANT_C99_TIME_FORMATS */
+             len = snprintf (&s[count], maxsize - count,
+                             *format == CQ('I') ? CQ("%.2d") : CQ("%2d"), h12);
+           CHECK_LENGTH ();
          }
          break;
        case CQ('j'):
          len = snprintf (&s[count], maxsize - count, CQ("%.3d"),
-                       tim_p->tm_yday + 1);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+                         tim_p->tm_yday + 1);
+          CHECK_LENGTH ();
          break;
        case CQ('m'):
-         len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
-                       tim_p->tm_mon + 1);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt != CQ('O') || !*alt_digits
+             || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+                                            tim_p->tm_mon + 1, *alt_digits)))
+#endif /* _WANT_C99_TIME_FORMATS */
+           len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
+                           tim_p->tm_mon + 1);
+          CHECK_LENGTH ();
          break;
        case CQ('M'):
-         len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
-                       tim_p->tm_min);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt != CQ('O') || !*alt_digits
+             || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+                                            tim_p->tm_min, *alt_digits)))
+#endif /* _WANT_C99_TIME_FORMATS */
+           len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
+                           tim_p->tm_min);
+          CHECK_LENGTH ();
          break;
        case CQ('n'):
          if (count < maxsize - 1)
@@ -621,13 +1057,18 @@ recurse:
          break;
        case CQ('R'):
           len = snprintf (&s[count], maxsize - count, CQ("%.2d:%.2d"),
-                       tim_p->tm_hour, tim_p->tm_min);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+                         tim_p->tm_hour, tim_p->tm_min);
+          CHECK_LENGTH ();
           break;
        case CQ('S'):
-         len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
-                       tim_p->tm_sec);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt != CQ('O') || !*alt_digits
+             || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+                                            tim_p->tm_sec, *alt_digits)))
+#endif /* _WANT_C99_TIME_FORMATS */
+           len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
+                           tim_p->tm_sec);
+          CHECK_LENGTH ();
          break;
        case CQ('t'):
          if (count < maxsize - 1)
@@ -637,10 +1078,22 @@ recurse:
          break;
        case CQ('T'):
           len = snprintf (&s[count], maxsize - count, CQ("%.2d:%.2d:%.2d"),
-                       tim_p->tm_hour, tim_p->tm_min, tim_p->tm_sec);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+                         tim_p->tm_hour, tim_p->tm_min, tim_p->tm_sec);
+          CHECK_LENGTH ();
           break;
        case CQ('u'):
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt == CQ('O') && *alt_digits)
+           {
+             len = conv_to_alt_digits (&s[count], maxsize - count,
+                                       tim_p->tm_wday == 0 ? 7
+                                                           : tim_p->tm_wday,
+                                       *alt_digits);
+             CHECK_LENGTH ();
+             if (len > 0)
+               break;
+           }
+#endif /* _WANT_C99_TIME_FORMATS */
           if (count < maxsize - 1)
             {
               if (tim_p->tm_wday == 0)
@@ -652,10 +1105,17 @@ recurse:
             return 0;
           break;
        case CQ('U'):
-         len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
-                      (tim_p->tm_yday + 7 -
-                       tim_p->tm_wday) / 7);
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt != CQ('O') || !*alt_digits
+             || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+                                            (tim_p->tm_yday + 7 -
+                                             tim_p->tm_wday) / 7,
+                                            *alt_digits)))
+#endif /* _WANT_C99_TIME_FORMATS */
+           len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
+                        (tim_p->tm_yday + 7 -
+                         tim_p->tm_wday) / 7);
+          CHECK_LENGTH ();
          break;
        case CQ('V'):
          {
@@ -673,11 +1133,26 @@ recurse:
                                             + (YEAR_BASE - 1
                                                - (tim_p->tm_year < 0
                                                   ? 0 : 2000)))));
-           len = snprintf (&s[count], maxsize - count, CQ("%.2d"), week);
-            if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+           if (alt != CQ('O') || !*alt_digits
+               || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+                                              week, *alt_digits)))
+#endif /* _WANT_C99_TIME_FORMATS */
+             len = snprintf (&s[count], maxsize - count, CQ("%.2d"), week);
+            CHECK_LENGTH ();
          }
           break;
        case CQ('w'):
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt == CQ('O') && *alt_digits)
+           {
+             len = conv_to_alt_digits (&s[count], maxsize - count,
+                                       tim_p->tm_wday, *alt_digits);
+             CHECK_LENGTH ();
+             if (len > 0)
+               break;
+           }
+#endif /* _WANT_C99_TIME_FORMATS */
          if (count < maxsize - 1)
             s[count++] = CQ('0') + tim_p->tm_wday;
          else
@@ -686,37 +1161,75 @@ recurse:
        case CQ('W'):
          {
            int wday = (tim_p->tm_wday) ? tim_p->tm_wday - 1 : 6;
-           len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
-                       (tim_p->tm_yday + 7 - wday) / 7);
-            if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+           wday = (tim_p->tm_yday + 7 - wday) / 7;
+#ifdef _WANT_C99_TIME_FORMATS
+           if (alt != CQ('O') || !*alt_digits
+               || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+                                              wday, *alt_digits)))
+#endif /* _WANT_C99_TIME_FORMATS */
+             len = snprintf (&s[count], maxsize - count, CQ("%.2d"), wday);
+            CHECK_LENGTH ();
          }
          break;
        case CQ('y'):
            {
-             /* Be careful of both overflow and negative years, thanks to
-                the asymmetric range of years.  */
-             int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100
-               : abs (tim_p->tm_year + YEAR_BASE) % 100;
-             len = snprintf (&s[count], maxsize - count, CQ("%.2d"), year);
-              if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+             if (alt == 'E' && *era_info)
+               len = snprintf (&s[count], maxsize - count, CQ("%d"),
+                               (*era_info)->year);
+             else
+#endif /* _WANT_C99_TIME_FORMATS */
+               {
+                 /* Be careful of both overflow and negative years, thanks to
+                    the asymmetric range of years.  */
+                 int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100
+                            : abs (tim_p->tm_year + YEAR_BASE) % 100;
+#ifdef _WANT_C99_TIME_FORMATS
+                 if (alt != CQ('O') || !*alt_digits
+                     || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+                                                    year, *alt_digits)))
+#endif /* _WANT_C99_TIME_FORMATS */
+                   len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
+                                   year);
+               }
+              CHECK_LENGTH ();
            }
          break;
        case CQ('Y'):
-         /* An implementation choice is to have %Y match %C%y, so that it
-          * gives at least 4 digits, with leading zeros as needed.  */
-         if(tim_p->tm_year <= INT_MAX-YEAR_BASE)  {
-           /* For normal, non-overflow case.  */
-           len = snprintf (&s[count], maxsize - count, CQ("%04d"),
-                               tim_p->tm_year + YEAR_BASE);
-         }
-         else  {
-           /* int would overflow, so use unsigned instead.  */
-           register unsigned year;
-           year = (unsigned) tim_p->tm_year + (unsigned) YEAR_BASE;
-           len = snprintf (&s[count], maxsize - count, CQ("%04u"),
-                               tim_p->tm_year + YEAR_BASE);
-         }
-          if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+#ifdef _WANT_C99_TIME_FORMATS
+         if (alt == 'E' && *era_info)
+           {
+             ctloc = (*era_info)->era_Y;
+             goto recurse;
+           }
+         else
+#endif /* _WANT_C99_TIME_FORMATS */
+           {
+             CHAR fmtbuf[10], *fmt = fmtbuf;
+             int sign = tim_p->tm_year < -YEAR_BASE;
+             /* int potentially overflows, so use unsigned instead.  */
+             register unsigned year = (unsigned) tim_p->tm_year
+                                      + (unsigned) YEAR_BASE;
+             if (sign)
+               {
+                 *fmt++ = CQ('-');
+                 year = UINT_MAX - year + 1;
+               }
+             else if (pad == CQ('+') && year >= 10000)
+               {
+                 *fmt++ = CQ('+');
+                 sign = 1;
+               }
+             if (width && sign)
+               --width;
+             *fmt++ = CQ('%');
+             if (pad)
+               *fmt++ = CQ('0');
+             STRCPY (fmt, CQ(".*u"));
+             len = snprintf (&s[count], maxsize - count, fmtbuf, width,
+                             year);
+             CHECK_LENGTH ();
+           }
          break;
        case CQ('z'):
           if (tim_p->tm_isdst >= 0)
@@ -730,9 +1243,9 @@ recurse:
              offset = -tz->__tzrule[tim_p->tm_isdst > 0].offset;
              TZ_UNLOCK;
              len = snprintf (&s[count], maxsize - count, CQ("%+03ld%.2ld"),
-                       offset / SECSPERHOUR,
-                       labs (offset / SECSPERMIN) % 60L);
-              if (len < 0  ||  (count+=len) >= maxsize)  return 0;
+                             offset / SECSPERHOUR,
+                             labs (offset / SECSPERMIN) % 60L);
+              CHECK_LENGTH ();
             }
           break;
        case CQ('Z'):
@@ -760,6 +1273,8 @@ recurse:
          else
            return 0;
          break;
+       default:
+         return 0;
        }
       if (*format)
        format++;
@@ -771,7 +1286,7 @@ recurse:
 
   return count;
 }
+
 /* The remainder of this file can serve as a regression test.  Compile
  *  with -D_REGRESSION_TEST.  */
 #if defined(_REGRESSION_TEST)  /* [Test code:  */