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

Jeff Johnston jjohnstn@redhat.com
Wed Feb 24 19:17:00 GMT 2010


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



More information about the Newlib mailing list