[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