diff options
Diffstat (limited to 'strftime.c')
| -rw-r--r-- | strftime.c | 409 |
1 files changed, 148 insertions, 261 deletions
diff --git a/strftime.c b/strftime.c index d036d07690f6..deba2b5e7a48 100644 --- a/strftime.c +++ b/strftime.c @@ -1,41 +1,47 @@ -#ifndef lint -#ifndef NOID -static char elsieid[] = "@(#)strftime.c 8.3"; +/* Convert a broken-down timestamp to a string. */ + +/* Copyright 1989 The Regents of the University of California. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. */ + /* -** Based on the UCB version with the ID appearing below. +** Based on the UCB version with the copyright notice appearing above. +** ** This is ANSIish only when "multibyte character == plain character". */ -#endif /* !defined NOID */ -#endif /* !defined lint */ #include "private.h" -/* -** Copyright (c) 1989 The Regents of the University of California. -** All rights reserved. -** -** Redistribution and use in source and binary forms are permitted -** provided that the above copyright notice and this paragraph are -** duplicated in all such forms and that any documentation, -** advertising materials, and other materials related to such -** distribution and use acknowledge that the software was developed -** by the University of California, Berkeley. The name of the -** University may not be used to endorse or promote products derived -** from this software without specific prior written permission. -** THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -*/ - -#ifndef LIBC_SCCS -#ifndef lint -static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89"; -#endif /* !defined lint */ -#endif /* !defined LIBC_SCCS */ +#include <fcntl.h> +#include <locale.h> +#include <stdio.h> -#include "tzfile.h" -#include "fcntl.h" -#include "locale.h" +#ifndef DEPRECATE_TWO_DIGIT_YEARS +# define DEPRECATE_TWO_DIGIT_YEARS false +#endif struct lc_time_T { const char * mon[MONSPERYEAR]; @@ -50,16 +56,6 @@ struct lc_time_T { const char * date_fmt; }; -#ifdef LOCALE_HOME -#include "sys/stat.h" -static struct lc_time_T localebuf; -static struct lc_time_T * _loc(void); -#define Locale _loc() -#endif /* defined LOCALE_HOME */ -#ifndef LOCALE_HOME -#define Locale (&C_time_locale) -#endif /* !defined LOCALE_HOME */ - static const struct lc_time_T C_time_locale = { { "Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -80,7 +76,7 @@ static const struct lc_time_T C_time_locale = { /* ** x_fmt - ** C99 requires this format. + ** C99 and later require this format. ** Using just numbers (as here) makes Quakers happier; ** it's also compatible with SVR4. */ @@ -88,7 +84,7 @@ static const struct lc_time_T C_time_locale = { /* ** c_fmt - ** C99 requires this format. + ** C99 and later require this format. ** Previously this code used "%D %X", but we now conform to C99. ** Note that ** "%a %b %d %H:%M:%S %Y" @@ -106,69 +102,68 @@ static const struct lc_time_T C_time_locale = { "%a %b %e %H:%M:%S %Z %Y" }; +enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; + static char * _add(const char *, char *, const char *); static char * _conv(int, const char *, char *, const char *); static char * _fmt(const char *, const struct tm *, char *, const char *, - int *); -static char * _yconv(int, int, int, int, char *, const char *); - -extern char * tzname[]; + enum warn *); +static char * _yconv(int, int, bool, bool, char *, char const *); #ifndef YEAR_2000_NAME -#define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" +# define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" #endif /* !defined YEAR_2000_NAME */ -#define IN_NONE 0 -#define IN_SOME 1 -#define IN_THIS 2 -#define IN_ALL 3 +#if HAVE_STRFTIME_L +size_t +strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t, + locale_t locale) +{ + /* Just call strftime, as only the C locale is supported. */ + return strftime(s, maxsize, format, t); +} +#endif size_t -strftime(s, maxsize, format, t) -char * const s; -const size_t maxsize; -const char * const format; -const struct tm * const t; +strftime(char *s, size_t maxsize, const char *format, const struct tm *t) { char * p; - int warn; + int saved_errno = errno; + enum warn warn = IN_NONE; tzset(); -#ifdef LOCALE_HOME - localebuf.mon[0] = 0; -#endif /* defined LOCALE_HOME */ - warn = IN_NONE; - p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn); -#ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU - if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) { - (void) fprintf(stderr, "\n"); - if (format == NULL) - (void) fprintf(stderr, "NULL strftime format "); - else (void) fprintf(stderr, "strftime format \"%s\" ", - format); - (void) fprintf(stderr, "yields only two digits of years in "); + p = _fmt(format, t, s, s + maxsize, &warn); + if (!p) { + errno = EOVERFLOW; + return 0; + } + if (DEPRECATE_TWO_DIGIT_YEARS + && warn != IN_NONE && getenv(YEAR_2000_NAME)) { + fprintf(stderr, "\n"); + fprintf(stderr, "strftime format \"%s\" ", format); + fprintf(stderr, "yields only two digits of years in "); if (warn == IN_SOME) - (void) fprintf(stderr, "some locales"); + fprintf(stderr, "some locales"); else if (warn == IN_THIS) - (void) fprintf(stderr, "the current locale"); - else (void) fprintf(stderr, "all locales"); - (void) fprintf(stderr, "\n"); + fprintf(stderr, "the current locale"); + else fprintf(stderr, "all locales"); + fprintf(stderr, "\n"); } -#endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */ - if (p == s + maxsize) + if (p == s + maxsize) { + errno = ERANGE; return 0; + } *p = '\0'; + errno = saved_errno; return p - s; } static char * -_fmt(format, t, pt, ptlim, warnp) -const char * format; -const struct tm * const t; -char * pt; -const char * const ptlim; -int * warnp; +_fmt(const char *format, const struct tm *t, char *pt, + const char *ptlim, enum warn *warnp) { + struct lc_time_T const *Locale = &C_time_locale; + for ( ; *format; ++format) { if (*format == '%') { label: @@ -209,12 +204,12 @@ label: ** something completely different. ** (ado, 1993-05-24) */ - pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0, - pt, ptlim); + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, false, pt, ptlim); continue; case 'c': { - int warn2 = IN_SOME; + enum warn warn2 = IN_SOME; pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); if (warn2 == IN_ALL) @@ -232,12 +227,12 @@ label: case 'E': case 'O': /* - ** C99 locale modifiers. + ** Locale modifiers of C99 and later. ** The sequences ** %Ec %EC %Ex %EX %Ey %EY ** %Od %oe %OH %OI %Om %OM ** %OS %Ou %OU %OV %Ow %OW %Oy - ** are supposed to provide alternate + ** are supposed to provide alternative ** representations. */ goto label; @@ -326,11 +321,17 @@ label: tm = *t; mkt = mktime(&tm); - if (TYPE_SIGNED(time_t)) - (void) sprintf(buf, "%ld", - (long) mkt); - else (void) sprintf(buf, "%lu", - (unsigned long) mkt); + /* There is no portable, definitive + test for whether whether mktime + succeeded, so treat (time_t) -1 as + the success that it might be. */ + if (TYPE_SIGNED(time_t)) { + intmax_t n = mkt; + sprintf(buf, "%"PRIdMAX, n); + } else { + uintmax_t n = mkt; + sprintf(buf, "%"PRIuMAX, n); + } pt = _add(buf, pt, ptlim); } continue; @@ -365,7 +366,7 @@ label: ** (01-53)." ** (ado, 1993-05-24) ** -** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: +** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: ** "Week 01 of a year is per definition the first week which has the ** Thursday in this year, which is equivalent to the week which contains ** the fourth day of January. In other words, the first week of a new year @@ -438,9 +439,11 @@ label: pt, ptlim); else if (*format == 'g') { *warnp = IN_ALL; - pt = _yconv(year, base, 0, 1, + pt = _yconv(year, base, + false, true, pt, ptlim); - } else pt = _yconv(year, base, 1, 1, + } else pt = _yconv(year, base, + true, true, pt, ptlim); } continue; @@ -467,7 +470,7 @@ label: continue; case 'x': { - int warn2 = IN_SOME; + enum warn warn2 = IN_SOME; pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); if (warn2 == IN_ALL) @@ -478,40 +481,42 @@ label: continue; case 'y': *warnp = IN_ALL; - pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1, + pt = _yconv(t->tm_year, TM_YEAR_BASE, + false, true, pt, ptlim); continue; case 'Y': - pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1, + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, true, pt, ptlim); continue; case 'Z': #ifdef TM_ZONE - if (t->TM_ZONE != NULL) - pt = _add(t->TM_ZONE, pt, ptlim); - else -#endif /* defined TM_ZONE */ + pt = _add(t->TM_ZONE, pt, ptlim); +#elif HAVE_TZNAME if (t->tm_isdst >= 0) pt = _add(tzname[t->tm_isdst != 0], pt, ptlim); +#endif /* - ** C99 says that %Z must be replaced by the - ** empty string if the time zone is not + ** C99 and later say that %Z must be + ** replaced by the empty string if the + ** time zone abbreviation is not ** determinable. */ continue; case 'z': +#if defined TM_GMTOFF || USG_COMPAT || ALTZONE { - int diff; + long diff; char const * sign; + bool negative; - if (t->tm_isdst < 0) - continue; -#ifdef TM_GMTOFF +# ifdef TM_GMTOFF diff = t->TM_GMTOFF; -#else /* !defined TM_GMTOFF */ +# else /* - ** C99 says that the UTC offset must + ** C99 and later say that the UT offset must ** be computed by looking only at ** tm_isdst. This requirement is ** incorrect, since it means the code @@ -519,30 +524,44 @@ label: ** altzone and timezone), and the ** magic might not have the correct ** offset. Doing things correctly is - ** tricky and requires disobeying C99; + ** tricky and requires disobeying the standard; ** see GNU C strftime for details. ** For now, punt and conform to the ** standard, even though it's incorrect. ** - ** C99 says that %z must be replaced by the - ** empty string if the time zone is not + ** C99 and later say that %z must be replaced by + ** the empty string if the time zone is not ** determinable, so output nothing if the ** appropriate variables are not available. */ + if (t->tm_isdst < 0) + continue; if (t->tm_isdst == 0) -#ifdef USG_COMPAT +# if USG_COMPAT diff = -timezone; -#else /* !defined USG_COMPAT */ +# else continue; -#endif /* !defined USG_COMPAT */ +# endif else -#ifdef ALTZONE +# if ALTZONE diff = -altzone; -#else /* !defined ALTZONE */ +# else continue; -#endif /* !defined ALTZONE */ -#endif /* !defined TM_GMTOFF */ - if (diff < 0) { +# endif +# endif + negative = diff < 0; + if (diff == 0) { +# ifdef TM_ZONE + negative = t->TM_ZONE[0] == '-'; +# else + negative = t->tm_isdst < 0; +# if HAVE_TZNAME + if (tzname[t->tm_isdst != 0][0] == '-') + negative = true; +# endif +# endif + } + if (negative) { sign = "-"; diff = -diff; } else sign = "+"; @@ -552,6 +571,7 @@ label: (diff % MINSPERHOUR); pt = _conv(diff, "%04d", pt, ptlim); } +#endif continue; case '+': pt = _fmt(Locale->date_fmt, t, pt, ptlim, @@ -575,23 +595,16 @@ label: } static char * -_conv(n, format, pt, ptlim) -const int n; -const char * const format; -char * const pt; -const char * const ptlim; +_conv(int n, const char *format, char *pt, const char *ptlim) { char buf[INT_STRLEN_MAXIMUM(int) + 1]; - (void) sprintf(buf, format, n); + sprintf(buf, format, n); return _add(buf, pt, ptlim); } static char * -_add(str, pt, ptlim) -const char * str; -char * pt; -const char * const ptlim; +_add(const char *str, char *pt, const char *ptlim) { while (pt < ptlim && (*pt = *str++) != '\0') ++pt; @@ -607,18 +620,13 @@ const char * const ptlim; */ static char * -_yconv(a, b, convert_top, convert_yy, pt, ptlim) -const int a; -const int b; -const int convert_top; -const int convert_yy; -char * pt; -const char * const ptlim; +_yconv(int a, int b, bool convert_top, bool convert_yy, + char *pt, const char *ptlim) { register int lead; register int trail; -#define DIVISOR 100 + int DIVISOR = 100; trail = a % DIVISOR + b % DIVISOR; lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; trail %= DIVISOR; @@ -638,124 +646,3 @@ const char * const ptlim; pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); return pt; } - -#ifdef LOCALE_HOME -static struct lc_time_T * -_loc(void) -{ - static const char locale_home[] = LOCALE_HOME; - static const char lc_time[] = "LC_TIME"; - static char * locale_buf; - - int fd; - int oldsun; /* "...ain't got nothin' to do..." */ - char * lbuf; - char * name; - char * p; - const char ** ap; - const char * plim; - char filename[FILENAME_MAX]; - struct stat st; - size_t namesize; - size_t bufsize; - - /* - ** Use localebuf.mon[0] to signal whether locale is already set up. - */ - if (localebuf.mon[0]) - return &localebuf; - name = setlocale(LC_TIME, (char *) NULL); - if (name == NULL || *name == '\0') - goto no_locale; - /* - ** If the locale name is the same as our cache, use the cache. - */ - lbuf = locale_buf; - if (lbuf != NULL && strcmp(name, lbuf) == 0) { - p = lbuf; - for (ap = (const char **) &localebuf; - ap < (const char **) (&localebuf + 1); - ++ap) - *ap = p += strlen(p) + 1; - return &localebuf; - } - /* - ** Slurp the locale file into the cache. - */ - namesize = strlen(name) + 1; - if (sizeof filename < - ((sizeof locale_home) + namesize + (sizeof lc_time))) - goto no_locale; - oldsun = 0; - (void) sprintf(filename, "%s/%s/%s", locale_home, name, lc_time); - fd = open(filename, O_RDONLY); - if (fd < 0) { - /* - ** Old Sun systems have a different naming and data convention. - */ - oldsun = 1; - (void) sprintf(filename, "%s/%s/%s", locale_home, - lc_time, name); - fd = open(filename, O_RDONLY); - if (fd < 0) - goto no_locale; - } - if (fstat(fd, &st) != 0) - goto bad_locale; - if (st.st_size <= 0) - goto bad_locale; - bufsize = namesize + st.st_size; - locale_buf = NULL; - lbuf = (lbuf == NULL) ? malloc(bufsize) : realloc(lbuf, bufsize); - if (lbuf == NULL) - goto bad_locale; - (void) strcpy(lbuf, name); - p = lbuf + namesize; - plim = p + st.st_size; - if (read(fd, p, (size_t) st.st_size) != st.st_size) - goto bad_lbuf; - if (close(fd) != 0) - goto bad_lbuf; - /* - ** Parse the locale file into localebuf. - */ - if (plim[-1] != '\n') - goto bad_lbuf; - for (ap = (const char **) &localebuf; - ap < (const char **) (&localebuf + 1); - ++ap) { - if (p == plim) - goto bad_lbuf; - *ap = p; - while (*p != '\n') - ++p; - *p++ = '\0'; - } - if (oldsun) { - /* - ** SunOS 4 used an obsolescent format; see localdtconv(3). - ** c_fmt had the ``short format for dates and times together'' - ** (SunOS 4 date, "%a %b %e %T %Z %Y" in the C locale); - ** date_fmt had the ``long format for dates'' - ** (SunOS 4 strftime %C, "%A, %B %e, %Y" in the C locale). - ** Discard the latter in favor of the former. - */ - localebuf.date_fmt = localebuf.c_fmt; - } - /* - ** Record the successful parse in the cache. - */ - locale_buf = lbuf; - - return &localebuf; - -bad_lbuf: - free(lbuf); -bad_locale: - (void) close(fd); -no_locale: - localebuf = C_time_locale; - locale_buf = NULL; - return &localebuf; -} -#endif /* defined LOCALE_HOME */ |
