diff options
Diffstat (limited to 'time/strftime.c')
-rw-r--r-- | time/strftime.c | 399 |
1 files changed, 301 insertions, 98 deletions
diff --git a/time/strftime.c b/time/strftime.c index 129fd1412c..26f4b7f354 100644 --- a/time/strftime.c +++ b/time/strftime.c @@ -23,13 +23,13 @@ Cambridge, MA 02139, USA. */ #ifdef _LIBC # define HAVE_LIMITS_H 1 # define HAVE_MBLEN 1 +# define HAVE_TM_GMTOFF 1 # define HAVE_TM_ZONE 1 # define STDC_HEADERS 1 # include <ansidecl.h> # include "../locale/localeinfo.h" #endif -#include <stdio.h> #include <sys/types.h> /* Some systems define `time_t' here. */ #ifdef TIME_WITH_SYS_TIME @@ -75,9 +75,22 @@ Cambridge, MA 02139, USA. */ #endif #endif -/* Uncomment following line in the production version. */ -/* #define NDEBUG */ -#include <assert.h> +#ifndef CHAR_BIT +#define CHAR_BIT 8 +#endif + +#define TYPE_SIGNED(t) ((t) -1 < 0) + +/* Bound on length of the string representing an integer value of type t. + Subtract one for the sign bit if t is signed; + 302 / 1000 is log10 (2) rounded up; + add one for integer division truncation; + add one more for a minus sign if t is signed. */ +#define INT_STRLEN_BOUND(t) \ + ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 100 + 1 + TYPE_SIGNED (t)) + +#define TM_YEAR_BASE 1900 + static unsigned int week __P ((const struct tm *const, int, int)); @@ -97,11 +110,30 @@ static unsigned int week __P ((const struct tm *const, int, int)); } while (0) #define cpy(n, s) add ((n), memcpy((PTR) p, (PTR) (s), (n))) -#ifdef _LIBC -#define fmt(n, args) add((n), if (sprintf args != (n)) return 0) -#else -#define fmt(n, args) add((n), sprintf args; if (strlen (p) != (n)) return 0) -#endif +#if ! HAVE_TM_GMTOFF +/* Yield the difference between *A and *B, + measured in seconds, ignoring leap seconds. */ +static int tm_diff __P ((const struct tm *, const struct tm *)); +static int +tm_diff (a, b) + const struct tm *a; + const struct tm *b; +{ + int ay = a->tm_year + TM_YEAR_BASE - 1; + int by = b->tm_year + TM_YEAR_BASE - 1; + /* Divide years by 100, rounding towards minus infinity. */ + int ac = ay / 100 - (ay % 100 < 0); + int bc = by / 100 - (by % 100 < 0); + int intervening_leap_days = + ((ay >> 2) - (by >> 2)) - (ac - bc) + ((ac >> 2) - (bc >> 2)); + int years = ay - by; + int days = (365 * years + intervening_leap_days + + (a->tm_yday - b->tm_yday)); + return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour)) + + (a->tm_min - b->tm_min)) + + (a->tm_sec - b->tm_sec)); +} +#endif /* ! HAVE_TM_GMTOFF */ @@ -181,6 +213,9 @@ strftime (s, maxsize, format, tp) size_t aw_len = strlen(a_wkday); size_t am_len = strlen(a_month); size_t ap_len = strlen (ampm); + + const char * const*alt_digits = &_NL_CURRENT (LC_TIME, ALT_DIGITS); + int nr_alt_digits = (_NL_CURRENT (LC_TIME, ALT_DIGITS + 1) - *alt_digits); #else const char *const f_wkday = weekday_name[tp->tm_wday]; const char *const f_month = month_name[tp->tm_mon]; @@ -201,10 +236,6 @@ strftime (s, maxsize, format, tp) register size_t i = 0; register char *p = s; register const char *f; - char number_fmt[5]; - - /* Initialize the buffer we will use for the sprintf format for numbers. */ - number_fmt[0] = '%'; zone = 0; #if HAVE_TM_ZONE @@ -227,9 +258,15 @@ strftime (s, maxsize, format, tp) for (f = format; *f != '\0'; ++f) { enum { pad_zero, pad_space, pad_none } pad; /* Padding for number. */ - unsigned int maxdigits; /* Max digits for numeric format. */ + unsigned int digits; /* Max digits for numeric format. */ unsigned int number_value; /* Numeric value to be printed. */ - const char *subfmt; + int negative_number; /* 1 if the number is negative. */ + const char *subfmt = ""; + enum { none, alternate, era } modifier; + char *bufp; + char buf[1 + (sizeof (int) < sizeof (time_t) + ? INT_STRLEN_BOUND (time_t) + : INT_STRLEN_BOUND (int))]; #if HAVE_MBLEN if (!isascii (*f)) @@ -267,37 +304,76 @@ strftime (s, maxsize, format, tp) break; } + /* Check for modifiers. */ + switch (*f) + { + case 'E': + ++f; + modifier = era; + break; + case 'O': + ++f; + modifier = alternate; + break; + default: + modifier = none; + break; + } + /* Now do the specified format. */ switch (*f) { - case '\0': +#define DO_NUMBER(d, v) \ + digits = d; number_value = v; goto do_number +#define DO_NUMBER_SPACEPAD(d, v) \ + digits = d; number_value = v; goto do_number_spacepad + + case '\0': /* GNU extension: % at end of format. */ + --f; + /* Fall through. */ case '%': + if (modifier != none) + goto bad_format; add (1, *p = *f); break; case 'a': + if (modifier != none) + goto bad_format; cpy (aw_len, a_wkday); break; case 'A': + if (modifier != none) + goto bad_format; cpy (wkday_len, f_wkday); break; case 'b': case 'h': /* GNU extension. */ + if (modifier != none) + goto bad_format; cpy (am_len, a_month); break; case 'B': + if (modifier != none) + goto bad_format; cpy (month_len, f_month); break; case 'c': + if (modifier == alternate) + goto bad_format; #ifdef _NL_CURRENT - subfmt = _NL_CURRENT (LC_TIME, D_T_FMT); + if (modifier == era) + subfmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT); + if (*subfmt == '\0') + subfmt = _NL_CURRENT (LC_TIME, D_T_FMT); #else - subfmt = "%a %b %d %H:%M:%S %Z %Y"; + subfmt = "%a %b %e %H:%M:%S %Z %Y"; #endif + subformat: { size_t len = strftime (p, maxsize - i, subfmt, tp); @@ -307,17 +383,25 @@ strftime (s, maxsize, format, tp) } break; -#define DO_NUMBER(digits, value) \ - maxdigits = digits; number_value = value; goto do_number -#define DO_NUMBER_SPACEPAD(digits, value) \ - maxdigits = digits; number_value = value; goto do_number_spacepad - case 'C': + if (modifier == alternate) + goto bad_format; +#ifdef _NL_CURRENT + /* XXX I'm not sure about this. --drepper@gnu */ + if (modifier == era && + *(subfmt = _NL_CURRENT (LC_TIME, ERA)) != '\0') + goto subformat; +#endif DO_NUMBER (2, (1900 + tp->tm_year) / 100); case 'x': + if (modifier == alternate) + goto bad_format; #ifdef _NL_CURRENT - subfmt = _NL_CURRENT (LC_TIME, D_FMT); + if (modifier == era) + subfmt = _NL_CURRENT (LC_TIME, ERA_D_FMT); + if (*subfmt == '\0') + subfmt = _NL_CURRENT (LC_TIME, D_FMT); goto subformat; #endif /* Fall through. */ @@ -326,12 +410,18 @@ strftime (s, maxsize, format, tp) goto subformat; case 'd': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, tp->tm_mday); case 'e': /* GNU extension: %d, but blank-padded. */ + if (modifier == era) + goto bad_format; + DO_NUMBER_SPACEPAD (2, tp->tm_mday); - /* All numeric formats set MAXDIGITS and NUMBER_VALUE and then + /* All numeric formats set DIGITS and NUMBER_VALUE and then jump to one of these two labels. */ do_number_spacepad: @@ -339,56 +429,106 @@ strftime (s, maxsize, format, tp) pad = pad_space; do_number: + /* Format the number according to the MODIFIER flag. */ + +#ifdef _NL_CURRENT + if (modifier == alternate && 0 <= number_value + && number_value < (unsigned int) nr_alt_digits) + { + /* ALT_DIGITS is the first entry in an array with + alternative digit symbols. */ + size_t digitlen = strlen (*(alt_digits + number_value)); + if (digitlen == 0) + break; + cpy (digitlen, *(alt_digits + number_value)); + goto done_with_number; + } +#endif { - /* Format the number according to the PAD flag. */ + unsigned int u = number_value; - register char *nf = &number_fmt[1]; - int printed = maxdigits; + bufp = buf + sizeof (buf); + negative_number = number_value < 0; - switch (pad) - { - case pad_zero: - *nf++ = '0'; - case pad_space: - *nf++ = '0' + maxdigits; - case pad_none: - *nf++ = 'u'; - *nf = '\0'; - } + if (negative_number) + u = -u; -#ifdef _LIBC - add (maxdigits, printed = sprintf (p, number_fmt, number_value)); -#else - add (maxdigits, sprintf (p, number_fmt, number_value); - printed = strlen (p)); -#endif - /* Back up if fewer than MAXDIGITS chars written for pad_none. */ - p -= maxdigits - printed; - i -= maxdigits - printed; + do + *--bufp = u % 10 + '0'; + while ((u /= 10) != 0); + } - break; - } + do_number_sign_and_padding: + if (negative_number) + *--bufp = '-'; + + if (pad != pad_none) + { + int padding = digits - (buf + sizeof (buf) - bufp); + + if (pad == pad_space) + { + while (0 < padding--) + *--bufp = ' '; + } + else + { + bufp += negative_number; + while (0 < padding--) + *--bufp = '0'; + if (negative_number) + *--bufp = '-'; + } + } + + cpy (buf + sizeof (buf) - bufp, bufp); + +#ifdef _NL_CURRENT + done_with_number: +#endif + break; case 'H': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, tp->tm_hour); case 'I': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, hour12); case 'k': /* GNU extension. */ + if (modifier == era) + goto bad_format; + DO_NUMBER_SPACEPAD (2, tp->tm_hour); case 'l': /* GNU extension. */ + if (modifier == era) + goto bad_format; + DO_NUMBER_SPACEPAD (2, hour12); case 'j': + if (modifier == era) + goto bad_format; + DO_NUMBER (3, 1 + tp->tm_yday); case 'M': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, tp->tm_min); case 'm': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, tp->tm_mon + 1); case 'n': /* GNU extension. */ @@ -408,32 +548,55 @@ strftime (s, maxsize, format, tp) goto subformat; case 'S': + if (modifier == era) + return 0; + DO_NUMBER (2, tp->tm_sec); case 's': /* GNU extension. */ - { - struct tm writable_tm = *tp; - unsigned long int num = (unsigned long int) mktime (&writable_tm); - /* `3 * sizeof (unsigned long int)' is an approximation of - the size of the decimal representation of NUM, valid - for sizes <= 16. */ - int printed = 3 * sizeof (unsigned long int); - maxdigits = printed; - assert (sizeof (unsigned long int) <= 16); -#ifdef _LIBC - add (maxdigits, printed = sprintf (p, "%lu", num)); -#else - add (maxdigits, sprintf (p, "%lu", num); printed = strlen (p)); -#endif - /* Back up if fewer than MAXDIGITS chars written for pad_none. */ - p -= maxdigits - printed; - i -= maxdigits - printed; + { + struct tm ltm = *tp; + time_t t = mktime (<m); + + /* Generate string value for T using time_t arithmetic; + this works even if sizeof (long) < sizeof (time_t). */ + + bufp = buf + sizeof (buf); + negative_number = t < 0; + + do + { + int d = t % 10; + t /= 10; + + if (negative_number) + { + d = -d; + + /* Adjust if division truncates to minus infinity. */ + if (0 < -1 % 10 && d < 0) + { + t++; + d += 10; + } + } + + *--bufp = d + '0'; + } + while (t != 0); + + digits = 1; + goto do_number_sign_and_padding; } - break; case 'X': + if (modifier == alternate) + goto bad_format; #ifdef _NL_CURRENT - subfmt = _NL_CURRENT (LC_TIME, T_FMT); + if (modifier == era) + subfmt = _NL_CURRENT (LC_TIME, ERA_T_FMT); + if (*subfmt == '\0') + subfmt = _NL_CURRENT (LC_TIME, T_FMT); goto subformat; #endif /* Fall through. */ @@ -446,61 +609,88 @@ strftime (s, maxsize, format, tp) break; case 'U': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, y_week0); case 'V': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, y_week2); case 'W': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, y_week1); case 'w': + if (modifier == era) + goto bad_format; + DO_NUMBER (2, tp->tm_wday); case 'Y': - DO_NUMBER (4, 1900 + tp->tm_year); +#ifdef _NL_CURRENT + if (modifier == era + && *(subfmt = _NL_CURRENT (LC_TIME, ERA_YEAR)) != '\0') + goto subformat; + else +#endif + if (modifier == alternate) + goto bad_format; + else + DO_NUMBER (4, 1900 + tp->tm_year); case 'y': +#ifdef _NL_CURRENT + if (modifier == era + && *(subfmt = _NL_CURRENT (LC_TIME, ERA_YEAR)) != '\0') + goto subformat; +#endif DO_NUMBER (2, tp->tm_year % 100); case 'Z': cpy(zonelen, zone); break; - case 'z': + case 'z': /* GNU extension. */ + if (tp->tm_isdst < 0) + break; + { - struct tm tml = *tp; - struct tm tmg; - time_t t; - time_t offset = 0; int diff; +#if HAVE_TM_GMTOFF + diff = tp->tm_gmtoff; +#else + struct tm gtm; + struct tm ltm = *tp; + time_t lt = mktime (<m); - t = __mktime_internal (&tml, __localtime_r, &offset); - - /* Canonicalize the local time. */ - if (t == (time_t) -1 || __localtime_r (&t, &tml) == NULL) - /* We didn't managed to get the local time. Assume it - GMT as a reasonable default value. */ - diff = 0; - else + if (lt == (time_t) -1) { - __gmtime_r (&t, &tmg); + /* mktime returns -1 for errors, but -1 is also a + valid time_t value. Check whether an error really + occurred. */ + struct tm tm; + localtime_r (<, &tm); + + if ((ltm.tm_sec ^ tm.tm_sec) + | (ltm.tm_min ^ tm.tm_min) + | (ltm.tm_hour ^ tm.tm_hour) + | (ltm.tm_mday ^ tm.tm_mday) + | (ltm.tm_mon ^ tm.tm_mon) + | (ltm.tm_year ^ tm.tm_year)) + break; + } - /* Compute the difference. */ - diff = tml.tm_min - tmg.tm_min; - diff += 60 * (tml.tm_hour - tmg.tm_hour); + if (! gmtime_r (<, >m)) + break; - if (tml.tm_mon != tmg.tm_mon) - { - /* We assume no timezone differs from UTC by more - than +- 23 hours. This should be safe. */ - if (tmg.tm_mday == 1) - tml.tm_mday = 0; - else /* tml.tm_mday == 1 */ - tmg.tm_mday = 0; - } - diff += 1440 * (tml.tm_mday - tmg.tm_mday); - } + diff = tm_diff (<m, >m); +#endif if (diff < 0) { @@ -511,11 +701,24 @@ strftime (s, maxsize, format, tp) add (1, *p = '+'); pad = pad_zero; - DO_NUMBER (4, ((diff / 60) % 24) * 100 + diff % 60); + + diff /= 60; + DO_NUMBER (4, (diff / 60) * 100 + diff % 60); } default: /* Bad format. */ + bad_format: + if (pad == pad_space) + add (1, *p = '_'); + else if (pad == pad_zero) + add (1, *p = '0'); + + if (modifier == era) + add (1, *p = 'E'); + else if (modifier == alternate) + add (1, *p = 'O'); + add (1, *p = *f); break; } |