This is the mail archive of the newlib@sourceware.org mailing list for the newlib project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [RFA] strftime: Add POSIX flags, width, and handling for E and O modifier


On 22/02/10 08:59 AM, Corinna Vinschen wrote:
Hi,

below is a rather big patch to libc/time/strftime.c.  It reworks
strftime and wcsftime to accomplish the following:

- Add the POSIX-1.2008 '0' and '+' padding flags

- Add a minimal field width specifier, also according to POSIX-1.2008.

- Add handling for E modifier to allow era specific date and time
   printing in locales supporting eras.  Along the POSIX-1.2008
   definition the E modifier is only supported for the c, C, x, X, y, and
   Y conversion specifiers.

- Add handling for O modifier to allow printing of values using
   alternative digits in locales supporting that.  Along the POSIX-1.2008
   definition the O modifier is only supported for the d, e, H, I, m, M,
   S, u, U, V, w, W, and y conversion specifiers.

- Support for E and O requires to create temporary data structures.  To
   be able to reuse them in recursive calls, required to split strftime
   into the official API and an internal function __strftime.  The
   advantage is that the datastructures for E and O can be created on
   demand at the first occurence of an E or O modifier, and then can be
   re-used in the recursivce calls.  Only at the end, the strftime
   function frees the datastructures, if they have been allocated.

- Fix %F according to the POSIX-1.2008 requirements.  %F is supposed to
   be equivalent to %+4Y-%m-%d.  That sounds like the old %Y-%m-%d, but ...

- ... %Y has been redefined according to POSIX as well, which states

     Y    Replaced by the year as a decimal number (for example, 1997).
          [...]

   It does *not* state that the year has to be 0-padded.  That's the job
   of the padding modifiers '0' and '+'.

- Rework %G to work more nicely with width and padding.

- Add a default case so that invalid conversion specifiers result in
   strftime returning 0.

- Use GNU formatting rules throughout the strftime function, except in
   case of the CQ() macro, which is sort of like L for wide strings.

I tested this extensively on Cygwin, which is obviously the only target
for now which supports E and O modifiers, especially with locales supporting
eras and alt_digits (ja_JP, th_TH and others).

I'm also planning to rework strptime to allow E and O modifiers, if
nobody beats me to it.  It's just that strftime was a lot of work and
I'm slightly fed up with all the localization stuff right now.  I guess
I need a pause...


Ok to apply?



Yes. If you can cordone off major functional sections using a flag to save space for cross-platforms that are ok with current functionality: e.g. #ifdef __POSIX_2008_STRFTIME__, that would be helpful.


-- Jeff J.


Thanks, Corinna


* libc/time/strftime.c (STRTOUL): Define differently depending on 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): Renamed from strftime and made static. 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. (strftime): New function. Provide era_info and alt_digits pointers and call __strftime. Free era_info and alt_digits pointers, if they have been allocated in __strftime.


Index: libc/time/strftime.c =================================================================== RCS file: /cvs/src/src/newlib/libc/time/strftime.c,v retrieving revision 1.13 diff -u -p -r1.13 strftime.c --- libc/time/strftime.c 22 Jan 2010 13:03:42 -0000 1.13 +++ libc/time/strftime.c 22 Feb 2010 13:54:24 -0000 @@ -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,259 @@ _DEFUN (iso_year_adjust, (tim_p),
  #undef PACK
  }

-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)
+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;
+#ifdef MAKE_WCSFTIME
+#endif
+
+	  /* 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 *s, size_t maxsize, const CHAR *format,
+	    const struct tm *tim_p, era_info_t **era_info,
+	    alt_digits_t **alt_digits)
  {
    size_t count = 0;
    int i, len;
@@ -375,6 +633,9 @@ _DEFUN (strftime, (s, maxsize, format, t
    CHAR ctlocbuf[CTLOCBUFLEN];
  #endif
    size_t ctloclen;
+  CHAR pad;
+  CHAR alt;
+  unsigned long width;

    struct lc_time_T *_CurrentTimeLocale = __get_current_time_locale ();
    for (;;)
@@ -386,13 +647,35 @@ _DEFUN (strftime, (s, maxsize, format, t
  	  else
  	    return 0;
  	}
-
        if (*format == CQ('\0'))
  	break;
-
        format++;
-      if (*format == CQ('E') || *format == CQ('O'))
-	format++;
+
+      pad = '\0';
+      if (*format == CQ('0') || *format == CQ('+'))
+	pad = *format++;
+
+      width = 0;
+      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++;
+	  if (!*era_info&&  *_CurrentTimeLocale->era)
+	    *era_info = get_era_info (tim_p, _CurrentTimeLocale->era);
+	}
+      else if (*format == CQ('O'))
+	{
+	  alt = *format++;
+	  if (!*alt_digits&&  *_CurrentTimeLocale->alt_digits)
+	    *alt_digits = get_alt_digits (_CurrentTimeLocale->alt_digits);
+	}

        switch (*format)
  	{
@@ -438,24 +721,33 @@ _DEFUN (strftime, (s, maxsize, format, t
  	    }
  	  break;
  	case CQ('c'):
-	  _ctloc (c_fmt);
+	  if (alt == 'E'&&  *era_info&&  *_CurrentTimeLocale->era_d_t_fmt)
+	    _ctloc (era_d_t_fmt);
+	  else
+	    _ctloc (c_fmt);
  	  goto recurse;
  	case CQ('r'):
  	  _ctloc (ampm_fmt);
  	  goto recurse;
  	case CQ('x'):
-	  _ctloc (x_fmt);
+	  if (alt == 'E'&&  *era_info&&  *_CurrentTimeLocale->era_d_fmt)
+	    _ctloc (era_d_fmt);
+	  else
+	    _ctloc (x_fmt);
  	  goto recurse;
  	case CQ('X'):
-	  _ctloc (X_fmt);
+	  if (alt == 'E'&&  *era_info&&  *_CurrentTimeLocale->era_t_fmt)
+	    _ctloc (era_t_fmt);
+	  else
+	    _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 +774,93 @@ 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;
+	    if (alt == 'E'&&  *era_info)
+	      len = snprintf (&s[count], maxsize - count, CQ("%" SFLG "s"),
+			      (*era_info)->era_C);
+	    else
+	      {
+		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'):
+	  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;
+	    }
  	  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 +877,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 +886,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 +894,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 +908,80 @@ 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'):
+	  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;
+	    }
+	  /*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;
+	    if (alt != CQ('O') || !*alt_digits
+		|| !(len = conv_to_alt_digits (&s[count], maxsize - count,
+					       h12, *alt_digits)))
+	      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;
+	  if (alt != CQ('O') || !*alt_digits
+	      || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+					     tim_p->tm_mon + 1, *alt_digits)))
+	    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;
+	  if (alt != CQ('O') || !*alt_digits
+	      || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+					     tim_p->tm_min, *alt_digits)))
+	    len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
+			    tim_p->tm_min);
+          CHECK_LENGTH ();
  	  break;
  	case CQ('n'):
  	  if (count<  maxsize - 1)
@@ -621,13 +1003,16 @@ 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;
+	  if (alt != CQ('O') || !*alt_digits
+	      || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+					     tim_p->tm_sec, *alt_digits)))
+	    len = snprintf (&s[count], maxsize - count, CQ("%.2d"),
+			    tim_p->tm_sec);
+          CHECK_LENGTH ();
  	  break;
  	case CQ('t'):
  	  if (count<  maxsize - 1)
@@ -637,10 +1022,20 @@ 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'):
+	  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;
+	    }
            if (count<  maxsize - 1)
              {
                if (tim_p->tm_wday == 0)
@@ -652,10 +1047,15 @@ 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;
+	  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)))
+	    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 +1073,22 @@ 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;
+	    if (alt != CQ('O') || !*alt_digits
+		|| !(len = conv_to_alt_digits (&s[count], maxsize - count,
+					       week, *alt_digits)))
+	      len = snprintf (&s[count], maxsize - count, CQ("%.2d"), week);
+            CHECK_LENGTH ();
  	  }
            break;
  	case CQ('w'):
+	  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;
+	    }
  	  if (count<  maxsize - 1)
              s[count++] = CQ('0') + tim_p->tm_wday;
  	  else
@@ -686,37 +1097,67 @@ 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;
+	    if (alt != CQ('O') || !*alt_digits
+		|| !(len = conv_to_alt_digits (&s[count], maxsize - count,
+					       wday, *alt_digits)))
+	      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;
+	      if (alt == 'E'&&  *era_info)
+		len = snprintf (&s[count], maxsize - count, CQ("%d"),
+				(*era_info)->year);
+	      else
+		{
+		  /* 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;
+		  if (alt != CQ('O') || !*alt_digits
+		      || !(len = conv_to_alt_digits (&s[count], maxsize - count,
+						     year, *alt_digits)))
+		    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;
+	  if (alt == 'E'&&  *era_info)
+	    {
+	      ctloc = (*era_info)->era_Y;
+	      goto recurse;
+	    }
+	  else
+	    {
+	      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 +1171,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 +1201,8 @@ recurse:
  	  else
  	    return 0;
  	  break;
+	default:
+	  return 0;
  	}
        if (*format)
  	format++;
@@ -771,6 +1214,23 @@ recurse:

    return count;
  }
+
+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;
+}

  /* The remainder of this file can serve as a regression test.  Compile
   *  with -D_REGRESSION_TEST.  */




Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]