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]

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


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?


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.  */


-- 
Corinna Vinschen
Cygwin Project Co-Leader
Red Hat


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