xref: /minix3/lib/libc/time/strftime.c (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
1*0a6a1f1dSLionel Sambuc /*	$NetBSD: strftime.c,v 1.35 2015/10/09 17:21:45 christos Exp $	*/
22fe8fb19SBen Gras 
32fe8fb19SBen Gras #include <sys/cdefs.h>
42fe8fb19SBen Gras #if defined(LIBC_SCCS) && !defined(lint)
52fe8fb19SBen Gras #if 0
62fe8fb19SBen Gras static char	elsieid[] = "@(#)strftime.c	7.64";
72fe8fb19SBen Gras static char	elsieid[] = "@(#)strftime.c	8.3";
82fe8fb19SBen Gras #else
9*0a6a1f1dSLionel Sambuc __RCSID("$NetBSD: strftime.c,v 1.35 2015/10/09 17:21:45 christos Exp $");
102fe8fb19SBen Gras #endif
112fe8fb19SBen Gras #endif /* LIBC_SCCS and not lint */
122fe8fb19SBen Gras 
132fe8fb19SBen Gras #include "namespace.h"
142fe8fb19SBen Gras 
1584d9c625SLionel Sambuc #include <stddef.h>
16*0a6a1f1dSLionel Sambuc #include <assert.h>
1784d9c625SLionel Sambuc #include <locale.h>
1884d9c625SLionel Sambuc #include "setlocale_local.h"
1984d9c625SLionel Sambuc 
202fe8fb19SBen Gras /*
2184d9c625SLionel Sambuc ** Based on the UCB version with the copyright notice and sccsid
2284d9c625SLionel Sambuc ** appearing below.
2384d9c625SLionel Sambuc **
242fe8fb19SBen Gras ** This is ANSIish only when "multibyte character == plain character".
252fe8fb19SBen Gras */
262fe8fb19SBen Gras 
272fe8fb19SBen Gras #include "private.h"
282fe8fb19SBen Gras 
292fe8fb19SBen Gras /*
302fe8fb19SBen Gras ** We don't use these extensions in strftime operation even when
312fe8fb19SBen Gras ** supported by the local tzcode configuration.  A strictly
322fe8fb19SBen Gras ** conforming C application may leave them in undefined state.
332fe8fb19SBen Gras */
342fe8fb19SBen Gras 
352fe8fb19SBen Gras #ifdef _LIBC
362fe8fb19SBen Gras #undef TM_ZONE
372fe8fb19SBen Gras #undef TM_GMTOFF
382fe8fb19SBen Gras #endif
392fe8fb19SBen Gras 
402fe8fb19SBen Gras /*
412fe8fb19SBen Gras ** Copyright (c) 1989, 1993
422fe8fb19SBen Gras **	The Regents of the University of California.  All rights reserved.
432fe8fb19SBen Gras **
442fe8fb19SBen Gras ** Redistribution and use in source and binary forms, with or without
452fe8fb19SBen Gras ** modification, are permitted provided that the following conditions
462fe8fb19SBen Gras ** are met:
472fe8fb19SBen Gras ** 1. Redistributions of source code must retain the above copyright
482fe8fb19SBen Gras **    notice, this list of conditions and the following disclaimer.
492fe8fb19SBen Gras ** 2. Redistributions in binary form must reproduce the above copyright
502fe8fb19SBen Gras **    notice, this list of conditions and the following disclaimer in the
512fe8fb19SBen Gras **    documentation and/or other materials provided with the distribution.
522fe8fb19SBen Gras ** 3. All advertising materials mentioning features or use of this software
532fe8fb19SBen Gras **    must display the following acknowledgement:
542fe8fb19SBen Gras **	This product includes software developed by the University of
552fe8fb19SBen Gras **	California, Berkeley and its contributors.
562fe8fb19SBen Gras ** 4. Neither the name of the University nor the names of its contributors
572fe8fb19SBen Gras **    may be used to endorse or promote products derived from this software
582fe8fb19SBen Gras **    without specific prior written permission.
592fe8fb19SBen Gras **
60*0a6a1f1dSLionel Sambuc ** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
612fe8fb19SBen Gras ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
622fe8fb19SBen Gras ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
632fe8fb19SBen Gras ** ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
642fe8fb19SBen Gras ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
652fe8fb19SBen Gras ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
662fe8fb19SBen Gras ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
672fe8fb19SBen Gras ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
682fe8fb19SBen Gras ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
692fe8fb19SBen Gras ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
702fe8fb19SBen Gras ** SUCH DAMAGE.
712fe8fb19SBen Gras */
722fe8fb19SBen Gras 
732fe8fb19SBen Gras #include "tzfile.h"
742fe8fb19SBen Gras #include "fcntl.h"
752fe8fb19SBen Gras #include "locale.h"
762fe8fb19SBen Gras 
772fe8fb19SBen Gras #ifdef __weak_alias
7884d9c625SLionel Sambuc __weak_alias(strftime_l, _strftime_l)
7984d9c625SLionel Sambuc __weak_alias(strftime_lz, _strftime_lz)
802fe8fb19SBen Gras __weak_alias(strftime_z, _strftime_z)
812fe8fb19SBen Gras #endif
822fe8fb19SBen Gras 
832fe8fb19SBen Gras #include "sys/localedef.h"
8484d9c625SLionel Sambuc #define _TIME_LOCALE(loc) \
8584d9c625SLionel Sambuc     ((_TimeLocale *)((loc)->part_impl[(size_t)LC_TIME]))
862fe8fb19SBen Gras #define c_fmt   d_t_fmt
872fe8fb19SBen Gras 
882fe8fb19SBen Gras static char *	_add(const char *, char *, const char *);
892fe8fb19SBen Gras static char *	_conv(int, const char *, char *, const char *);
902fe8fb19SBen Gras static char *	_fmt(const timezone_t, const char *, const struct tm *, char *,
9184d9c625SLionel Sambuc 			const char *, int *, locale_t);
92*0a6a1f1dSLionel Sambuc static char *	_yconv(int, int, bool, bool, char *, const char *);
932fe8fb19SBen Gras 
942fe8fb19SBen Gras extern char *	tzname[];
952fe8fb19SBen Gras 
962fe8fb19SBen Gras #ifndef YEAR_2000_NAME
972fe8fb19SBen Gras #define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
982fe8fb19SBen Gras #endif /* !defined YEAR_2000_NAME */
992fe8fb19SBen Gras 
1002fe8fb19SBen Gras #define IN_NONE	0
1012fe8fb19SBen Gras #define IN_SOME	1
1022fe8fb19SBen Gras #define IN_THIS	2
1032fe8fb19SBen Gras #define IN_ALL	3
1042fe8fb19SBen Gras 
1052fe8fb19SBen Gras size_t
strftime_z(const timezone_t sp,char * __restrict s,size_t maxsize,const char * __restrict format,const struct tm * __restrict t)10684d9c625SLionel Sambuc strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize,
10784d9c625SLionel Sambuc     const char * __restrict format, const struct tm * __restrict t)
10884d9c625SLionel Sambuc {
10984d9c625SLionel Sambuc 	return strftime_lz(sp, s, maxsize, format, t, _current_locale());
11084d9c625SLionel Sambuc }
11184d9c625SLionel Sambuc 
112*0a6a1f1dSLionel Sambuc #if HAVE_STRFTIME_L
113*0a6a1f1dSLionel Sambuc size_t
strftime_l(char * s,size_t maxsize,char const * format,struct tm const * t,locale_t locale)114*0a6a1f1dSLionel Sambuc strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
115*0a6a1f1dSLionel Sambuc 	   locale_t locale)
116*0a6a1f1dSLionel Sambuc {
117*0a6a1f1dSLionel Sambuc   /* Just call strftime, as only the C locale is supported.  */
118*0a6a1f1dSLionel Sambuc   return strftime(s, maxsize, format, t);
119*0a6a1f1dSLionel Sambuc }
120*0a6a1f1dSLionel Sambuc #endif
121*0a6a1f1dSLionel Sambuc 
12284d9c625SLionel Sambuc size_t
strftime_lz(const timezone_t sp,char * const s,const size_t maxsize,const char * const format,const struct tm * const t,locale_t loc)12384d9c625SLionel Sambuc strftime_lz(const timezone_t sp, char *const s, const size_t maxsize,
12484d9c625SLionel Sambuc     const char *const format, const struct tm *const t, locale_t loc)
1252fe8fb19SBen Gras {
1262fe8fb19SBen Gras 	char *	p;
1272fe8fb19SBen Gras 	int	warn;
1282fe8fb19SBen Gras 
1292fe8fb19SBen Gras 	warn = IN_NONE;
1302fe8fb19SBen Gras 	p = _fmt(sp, ((format == NULL) ? "%c" : format), t, s, s + maxsize,
13184d9c625SLionel Sambuc 	    &warn, loc);
1322fe8fb19SBen Gras #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
1332fe8fb19SBen Gras 	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
1342fe8fb19SBen Gras 		(void) fprintf(stderr, "\n");
1352fe8fb19SBen Gras 		if (format == NULL)
1362fe8fb19SBen Gras 			(void) fprintf(stderr, "NULL strftime format ");
1372fe8fb19SBen Gras 		else	(void) fprintf(stderr, "strftime format \"%s\" ",
1382fe8fb19SBen Gras 				format);
1392fe8fb19SBen Gras 		(void) fprintf(stderr, "yields only two digits of years in ");
1402fe8fb19SBen Gras 		if (warn == IN_SOME)
1412fe8fb19SBen Gras 			(void) fprintf(stderr, "some locales");
1422fe8fb19SBen Gras 		else if (warn == IN_THIS)
1432fe8fb19SBen Gras 			(void) fprintf(stderr, "the current locale");
1442fe8fb19SBen Gras 		else	(void) fprintf(stderr, "all locales");
1452fe8fb19SBen Gras 		(void) fprintf(stderr, "\n");
1462fe8fb19SBen Gras 	}
1472fe8fb19SBen Gras #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
1482fe8fb19SBen Gras 	if (p == s + maxsize)
1492fe8fb19SBen Gras 		return 0;
1502fe8fb19SBen Gras 	*p = '\0';
1512fe8fb19SBen Gras 	return p - s;
1522fe8fb19SBen Gras }
1532fe8fb19SBen Gras 
1542fe8fb19SBen Gras static char *
_fmt(const timezone_t sp,const char * format,const struct tm * t,char * pt,const char * ptlim,int * warnp,locale_t loc)155*0a6a1f1dSLionel Sambuc _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
156*0a6a1f1dSLionel Sambuc      const char *ptlim, int *warnp, locale_t loc)
1572fe8fb19SBen Gras {
1582fe8fb19SBen Gras 	for ( ; *format; ++format) {
1592fe8fb19SBen Gras 		if (*format == '%') {
1602fe8fb19SBen Gras label:
1612fe8fb19SBen Gras 			switch (*++format) {
1622fe8fb19SBen Gras 			case '\0':
1632fe8fb19SBen Gras 				--format;
1642fe8fb19SBen Gras 				break;
1652fe8fb19SBen Gras 			case 'A':
1662fe8fb19SBen Gras 				pt = _add((t->tm_wday < 0 ||
1672fe8fb19SBen Gras 					t->tm_wday >= DAYSPERWEEK) ?
16884d9c625SLionel Sambuc 					"?" : _TIME_LOCALE(loc)->day[t->tm_wday],
1692fe8fb19SBen Gras 					pt, ptlim);
1702fe8fb19SBen Gras 				continue;
1712fe8fb19SBen Gras 			case 'a':
1722fe8fb19SBen Gras 				pt = _add((t->tm_wday < 0 ||
1732fe8fb19SBen Gras 					t->tm_wday >= DAYSPERWEEK) ?
17484d9c625SLionel Sambuc 					"?" : _TIME_LOCALE(loc)->abday[t->tm_wday],
1752fe8fb19SBen Gras 					pt, ptlim);
1762fe8fb19SBen Gras 				continue;
1772fe8fb19SBen Gras 			case 'B':
1782fe8fb19SBen Gras 				pt = _add((t->tm_mon < 0 ||
1792fe8fb19SBen Gras 					t->tm_mon >= MONSPERYEAR) ?
18084d9c625SLionel Sambuc 					"?" : _TIME_LOCALE(loc)->mon[t->tm_mon],
1812fe8fb19SBen Gras 					pt, ptlim);
1822fe8fb19SBen Gras 				continue;
1832fe8fb19SBen Gras 			case 'b':
1842fe8fb19SBen Gras 			case 'h':
1852fe8fb19SBen Gras 				pt = _add((t->tm_mon < 0 ||
1862fe8fb19SBen Gras 					t->tm_mon >= MONSPERYEAR) ?
18784d9c625SLionel Sambuc 					"?" : _TIME_LOCALE(loc)->abmon[t->tm_mon],
1882fe8fb19SBen Gras 					pt, ptlim);
1892fe8fb19SBen Gras 				continue;
1902fe8fb19SBen Gras 			case 'C':
1912fe8fb19SBen Gras 				/*
1922fe8fb19SBen Gras 				** %C used to do a...
1932fe8fb19SBen Gras 				**	_fmt("%a %b %e %X %Y", t);
1942fe8fb19SBen Gras 				** ...whereas now POSIX 1003.2 calls for
1952fe8fb19SBen Gras 				** something completely different.
1962fe8fb19SBen Gras 				** (ado, 1993-05-24)
1972fe8fb19SBen Gras 				*/
198*0a6a1f1dSLionel Sambuc 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
199*0a6a1f1dSLionel Sambuc 					    true, false, pt, ptlim);
2002fe8fb19SBen Gras 				continue;
2012fe8fb19SBen Gras 			case 'c':
2022fe8fb19SBen Gras 				{
2032fe8fb19SBen Gras 				int warn2 = IN_SOME;
2042fe8fb19SBen Gras 
20584d9c625SLionel Sambuc 				pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt,
20684d9c625SLionel Sambuc 				    ptlim, &warn2, loc);
2072fe8fb19SBen Gras 				if (warn2 == IN_ALL)
2082fe8fb19SBen Gras 					warn2 = IN_THIS;
2092fe8fb19SBen Gras 				if (warn2 > *warnp)
2102fe8fb19SBen Gras 					*warnp = warn2;
2112fe8fb19SBen Gras 				}
2122fe8fb19SBen Gras 				continue;
2132fe8fb19SBen Gras 			case 'D':
21484d9c625SLionel Sambuc 				pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
21584d9c625SLionel Sambuc 				    loc);
2162fe8fb19SBen Gras 				continue;
2172fe8fb19SBen Gras 			case 'd':
2182fe8fb19SBen Gras 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
2192fe8fb19SBen Gras 				continue;
2202fe8fb19SBen Gras 			case 'E':
2212fe8fb19SBen Gras 			case 'O':
2222fe8fb19SBen Gras 				/*
2232fe8fb19SBen Gras 				** C99 locale modifiers.
2242fe8fb19SBen Gras 				** The sequences
2252fe8fb19SBen Gras 				**	%Ec %EC %Ex %EX %Ey %EY
2262fe8fb19SBen Gras 				**	%Od %oe %OH %OI %Om %OM
2272fe8fb19SBen Gras 				**	%OS %Ou %OU %OV %Ow %OW %Oy
2282fe8fb19SBen Gras 				** are supposed to provide alternate
2292fe8fb19SBen Gras 				** representations.
2302fe8fb19SBen Gras 				*/
2312fe8fb19SBen Gras 				goto label;
2322fe8fb19SBen Gras 			case 'e':
2332fe8fb19SBen Gras 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
2342fe8fb19SBen Gras 				continue;
2352fe8fb19SBen Gras 			case 'F':
23684d9c625SLionel Sambuc 				pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
23784d9c625SLionel Sambuc 				    loc);
2382fe8fb19SBen Gras 				continue;
2392fe8fb19SBen Gras 			case 'H':
2402fe8fb19SBen Gras 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
2412fe8fb19SBen Gras 				continue;
2422fe8fb19SBen Gras 			case 'I':
2432fe8fb19SBen Gras 				pt = _conv((t->tm_hour % 12) ?
2442fe8fb19SBen Gras 					(t->tm_hour % 12) : 12,
2452fe8fb19SBen Gras 					"%02d", pt, ptlim);
2462fe8fb19SBen Gras 				continue;
2472fe8fb19SBen Gras 			case 'j':
2482fe8fb19SBen Gras 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
2492fe8fb19SBen Gras 				continue;
2502fe8fb19SBen Gras 			case 'k':
2512fe8fb19SBen Gras 				/*
2522fe8fb19SBen Gras 				** This used to be...
2532fe8fb19SBen Gras 				**	_conv(t->tm_hour % 12 ?
2542fe8fb19SBen Gras 				**		t->tm_hour % 12 : 12, 2, ' ');
2552fe8fb19SBen Gras 				** ...and has been changed to the below to
2562fe8fb19SBen Gras 				** match SunOS 4.1.1 and Arnold Robbins'
2572fe8fb19SBen Gras 				** strftime version 3.0. That is, "%k" and
2582fe8fb19SBen Gras 				** "%l" have been swapped.
2592fe8fb19SBen Gras 				** (ado, 1993-05-24)
2602fe8fb19SBen Gras 				*/
2612fe8fb19SBen Gras 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
2622fe8fb19SBen Gras 				continue;
2632fe8fb19SBen Gras #ifdef KITCHEN_SINK
2642fe8fb19SBen Gras 			case 'K':
2652fe8fb19SBen Gras 				/*
2662fe8fb19SBen Gras 				** After all this time, still unclaimed!
2672fe8fb19SBen Gras 				*/
2682fe8fb19SBen Gras 				pt = _add("kitchen sink", pt, ptlim);
2692fe8fb19SBen Gras 				continue;
2702fe8fb19SBen Gras #endif /* defined KITCHEN_SINK */
2712fe8fb19SBen Gras 			case 'l':
2722fe8fb19SBen Gras 				/*
2732fe8fb19SBen Gras 				** This used to be...
2742fe8fb19SBen Gras 				**	_conv(t->tm_hour, 2, ' ');
2752fe8fb19SBen Gras 				** ...and has been changed to the below to
2762fe8fb19SBen Gras 				** match SunOS 4.1.1 and Arnold Robbin's
2772fe8fb19SBen Gras 				** strftime version 3.0. That is, "%k" and
2782fe8fb19SBen Gras 				** "%l" have been swapped.
2792fe8fb19SBen Gras 				** (ado, 1993-05-24)
2802fe8fb19SBen Gras 				*/
2812fe8fb19SBen Gras 				pt = _conv((t->tm_hour % 12) ?
2822fe8fb19SBen Gras 					(t->tm_hour % 12) : 12,
2832fe8fb19SBen Gras 					"%2d", pt, ptlim);
2842fe8fb19SBen Gras 				continue;
2852fe8fb19SBen Gras 			case 'M':
2862fe8fb19SBen Gras 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
2872fe8fb19SBen Gras 				continue;
2882fe8fb19SBen Gras 			case 'm':
2892fe8fb19SBen Gras 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
2902fe8fb19SBen Gras 				continue;
2912fe8fb19SBen Gras 			case 'n':
2922fe8fb19SBen Gras 				pt = _add("\n", pt, ptlim);
2932fe8fb19SBen Gras 				continue;
2942fe8fb19SBen Gras 			case 'p':
2952fe8fb19SBen Gras 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
29684d9c625SLionel Sambuc 					_TIME_LOCALE(loc)->am_pm[1] :
29784d9c625SLionel Sambuc 					_TIME_LOCALE(loc)->am_pm[0],
2982fe8fb19SBen Gras 					pt, ptlim);
2992fe8fb19SBen Gras 				continue;
3002fe8fb19SBen Gras 			case 'R':
30184d9c625SLionel Sambuc 				pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
30284d9c625SLionel Sambuc 				    loc);
3032fe8fb19SBen Gras 				continue;
3042fe8fb19SBen Gras 			case 'r':
30584d9c625SLionel Sambuc 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t,
30684d9c625SLionel Sambuc 				    pt, ptlim, warnp, loc);
3072fe8fb19SBen Gras 				continue;
3082fe8fb19SBen Gras 			case 'S':
3092fe8fb19SBen Gras 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
3102fe8fb19SBen Gras 				continue;
3112fe8fb19SBen Gras 			case 's':
3122fe8fb19SBen Gras 				{
3132fe8fb19SBen Gras 					struct tm	tm;
3142fe8fb19SBen Gras 					char		buf[INT_STRLEN_MAXIMUM(
3152fe8fb19SBen Gras 								time_t) + 1];
3162fe8fb19SBen Gras 					time_t		mkt;
3172fe8fb19SBen Gras 
3182fe8fb19SBen Gras 					tm = *t;
3192fe8fb19SBen Gras 					mkt = mktime(&tm);
3202fe8fb19SBen Gras 					/* CONSTCOND */
3212fe8fb19SBen Gras 					if (TYPE_SIGNED(time_t))
3222fe8fb19SBen Gras 						(void)snprintf(buf, sizeof(buf),
32384d9c625SLionel Sambuc 						    "%jd", (intmax_t) mkt);
3242fe8fb19SBen Gras 					else	(void)snprintf(buf, sizeof(buf),
32584d9c625SLionel Sambuc 						    "%ju", (uintmax_t) mkt);
3262fe8fb19SBen Gras 					pt = _add(buf, pt, ptlim);
3272fe8fb19SBen Gras 				}
3282fe8fb19SBen Gras 				continue;
3292fe8fb19SBen Gras 			case 'T':
33084d9c625SLionel Sambuc 				pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
33184d9c625SLionel Sambuc 				    loc);
3322fe8fb19SBen Gras 				continue;
3332fe8fb19SBen Gras 			case 't':
3342fe8fb19SBen Gras 				pt = _add("\t", pt, ptlim);
3352fe8fb19SBen Gras 				continue;
3362fe8fb19SBen Gras 			case 'U':
3372fe8fb19SBen Gras 				pt = _conv((t->tm_yday + DAYSPERWEEK -
3382fe8fb19SBen Gras 					t->tm_wday) / DAYSPERWEEK,
3392fe8fb19SBen Gras 					"%02d", pt, ptlim);
3402fe8fb19SBen Gras 				continue;
3412fe8fb19SBen Gras 			case 'u':
3422fe8fb19SBen Gras 				/*
3432fe8fb19SBen Gras 				** From Arnold Robbins' strftime version 3.0:
3442fe8fb19SBen Gras 				** "ISO 8601: Weekday as a decimal number
3452fe8fb19SBen Gras 				** [1 (Monday) - 7]"
3462fe8fb19SBen Gras 				** (ado, 1993-05-24)
3472fe8fb19SBen Gras 				*/
3482fe8fb19SBen Gras 				pt = _conv((t->tm_wday == 0) ?
3492fe8fb19SBen Gras 					DAYSPERWEEK : t->tm_wday,
3502fe8fb19SBen Gras 					"%d", pt, ptlim);
3512fe8fb19SBen Gras 				continue;
3522fe8fb19SBen Gras 			case 'V':	/* ISO 8601 week number */
3532fe8fb19SBen Gras 			case 'G':	/* ISO 8601 year (four digits) */
3542fe8fb19SBen Gras 			case 'g':	/* ISO 8601 year (two digits) */
3552fe8fb19SBen Gras /*
3562fe8fb19SBen Gras ** From Arnold Robbins' strftime version 3.0: "the week number of the
3572fe8fb19SBen Gras ** year (the first Monday as the first day of week 1) as a decimal number
3582fe8fb19SBen Gras ** (01-53)."
3592fe8fb19SBen Gras ** (ado, 1993-05-24)
3602fe8fb19SBen Gras **
361*0a6a1f1dSLionel Sambuc ** From <http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html> by Markus Kuhn:
3622fe8fb19SBen Gras ** "Week 01 of a year is per definition the first week which has the
3632fe8fb19SBen Gras ** Thursday in this year, which is equivalent to the week which contains
3642fe8fb19SBen Gras ** the fourth day of January. In other words, the first week of a new year
3652fe8fb19SBen Gras ** is the week which has the majority of its days in the new year. Week 01
3662fe8fb19SBen Gras ** might also contain days from the previous year and the week before week
3672fe8fb19SBen Gras ** 01 of a year is the last week (52 or 53) of the previous year even if
3682fe8fb19SBen Gras ** it contains days from the new year. A week starts with Monday (day 1)
3692fe8fb19SBen Gras ** and ends with Sunday (day 7). For example, the first week of the year
3702fe8fb19SBen Gras ** 1997 lasts from 1996-12-30 to 1997-01-05..."
3712fe8fb19SBen Gras ** (ado, 1996-01-02)
3722fe8fb19SBen Gras */
3732fe8fb19SBen Gras 				{
3742fe8fb19SBen Gras 					int	year;
3752fe8fb19SBen Gras 					int	base;
3762fe8fb19SBen Gras 					int	yday;
3772fe8fb19SBen Gras 					int	wday;
3782fe8fb19SBen Gras 					int	w;
3792fe8fb19SBen Gras 
3802fe8fb19SBen Gras 					year = t->tm_year;
3812fe8fb19SBen Gras 					base = TM_YEAR_BASE;
3822fe8fb19SBen Gras 					yday = t->tm_yday;
3832fe8fb19SBen Gras 					wday = t->tm_wday;
3842fe8fb19SBen Gras 					for ( ; ; ) {
3852fe8fb19SBen Gras 						int	len;
3862fe8fb19SBen Gras 						int	bot;
3872fe8fb19SBen Gras 						int	top;
3882fe8fb19SBen Gras 
3892fe8fb19SBen Gras 						len = isleap_sum(year, base) ?
3902fe8fb19SBen Gras 							DAYSPERLYEAR :
3912fe8fb19SBen Gras 							DAYSPERNYEAR;
3922fe8fb19SBen Gras 						/*
3932fe8fb19SBen Gras 						** What yday (-3 ... 3) does
3942fe8fb19SBen Gras 						** the ISO year begin on?
3952fe8fb19SBen Gras 						*/
3962fe8fb19SBen Gras 						bot = ((yday + 11 - wday) %
3972fe8fb19SBen Gras 							DAYSPERWEEK) - 3;
3982fe8fb19SBen Gras 						/*
3992fe8fb19SBen Gras 						** What yday does the NEXT
4002fe8fb19SBen Gras 						** ISO year begin on?
4012fe8fb19SBen Gras 						*/
4022fe8fb19SBen Gras 						top = bot -
4032fe8fb19SBen Gras 							(len % DAYSPERWEEK);
4042fe8fb19SBen Gras 						if (top < -3)
4052fe8fb19SBen Gras 							top += DAYSPERWEEK;
4062fe8fb19SBen Gras 						top += len;
4072fe8fb19SBen Gras 						if (yday >= top) {
4082fe8fb19SBen Gras 							++base;
4092fe8fb19SBen Gras 							w = 1;
4102fe8fb19SBen Gras 							break;
4112fe8fb19SBen Gras 						}
4122fe8fb19SBen Gras 						if (yday >= bot) {
4132fe8fb19SBen Gras 							w = 1 + ((yday - bot) /
4142fe8fb19SBen Gras 								DAYSPERWEEK);
4152fe8fb19SBen Gras 							break;
4162fe8fb19SBen Gras 						}
4172fe8fb19SBen Gras 						--base;
4182fe8fb19SBen Gras 						yday += isleap_sum(year, base) ?
4192fe8fb19SBen Gras 							DAYSPERLYEAR :
4202fe8fb19SBen Gras 							DAYSPERNYEAR;
4212fe8fb19SBen Gras 					}
4222fe8fb19SBen Gras #ifdef XPG4_1994_04_09
4232fe8fb19SBen Gras 					if ((w == 52 &&
4242fe8fb19SBen Gras 						t->tm_mon == TM_JANUARY) ||
4252fe8fb19SBen Gras 						(w == 1 &&
4262fe8fb19SBen Gras 						t->tm_mon == TM_DECEMBER))
4272fe8fb19SBen Gras 							w = 53;
4282fe8fb19SBen Gras #endif /* defined XPG4_1994_04_09 */
4292fe8fb19SBen Gras 					if (*format == 'V')
4302fe8fb19SBen Gras 						pt = _conv(w, "%02d",
4312fe8fb19SBen Gras 							pt, ptlim);
4322fe8fb19SBen Gras 					else if (*format == 'g') {
4332fe8fb19SBen Gras 						*warnp = IN_ALL;
434*0a6a1f1dSLionel Sambuc 						pt = _yconv(year, base,
435*0a6a1f1dSLionel Sambuc 							false, true,
4362fe8fb19SBen Gras 							pt, ptlim);
437*0a6a1f1dSLionel Sambuc 					} else	pt = _yconv(year, base,
438*0a6a1f1dSLionel Sambuc 							true, true,
4392fe8fb19SBen Gras 							pt, ptlim);
4402fe8fb19SBen Gras 				}
4412fe8fb19SBen Gras 				continue;
4422fe8fb19SBen Gras 			case 'v':
4432fe8fb19SBen Gras 				/*
4442fe8fb19SBen Gras 				** From Arnold Robbins' strftime version 3.0:
4452fe8fb19SBen Gras 				** "date as dd-bbb-YYYY"
4462fe8fb19SBen Gras 				** (ado, 1993-05-24)
4472fe8fb19SBen Gras 				*/
44884d9c625SLionel Sambuc 				pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
44984d9c625SLionel Sambuc 				    loc);
4502fe8fb19SBen Gras 				continue;
4512fe8fb19SBen Gras 			case 'W':
4522fe8fb19SBen Gras 				pt = _conv((t->tm_yday + DAYSPERWEEK -
4532fe8fb19SBen Gras 					(t->tm_wday ?
4542fe8fb19SBen Gras 					(t->tm_wday - 1) :
4552fe8fb19SBen Gras 					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
4562fe8fb19SBen Gras 					"%02d", pt, ptlim);
4572fe8fb19SBen Gras 				continue;
4582fe8fb19SBen Gras 			case 'w':
4592fe8fb19SBen Gras 				pt = _conv(t->tm_wday, "%d", pt, ptlim);
4602fe8fb19SBen Gras 				continue;
4612fe8fb19SBen Gras 			case 'X':
46284d9c625SLionel Sambuc 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt,
46384d9c625SLionel Sambuc 				    ptlim, warnp, loc);
4642fe8fb19SBen Gras 				continue;
4652fe8fb19SBen Gras 			case 'x':
4662fe8fb19SBen Gras 				{
4672fe8fb19SBen Gras 				int	warn2 = IN_SOME;
4682fe8fb19SBen Gras 
46984d9c625SLionel Sambuc 				pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt,
47084d9c625SLionel Sambuc 				    ptlim, &warn2, loc);
4712fe8fb19SBen Gras 				if (warn2 == IN_ALL)
4722fe8fb19SBen Gras 					warn2 = IN_THIS;
4732fe8fb19SBen Gras 				if (warn2 > *warnp)
4742fe8fb19SBen Gras 					*warnp = warn2;
4752fe8fb19SBen Gras 				}
4762fe8fb19SBen Gras 				continue;
4772fe8fb19SBen Gras 			case 'y':
4782fe8fb19SBen Gras 				*warnp = IN_ALL;
479*0a6a1f1dSLionel Sambuc 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
480*0a6a1f1dSLionel Sambuc 					false, true,
4812fe8fb19SBen Gras 					pt, ptlim);
4822fe8fb19SBen Gras 				continue;
4832fe8fb19SBen Gras 			case 'Y':
484*0a6a1f1dSLionel Sambuc 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
485*0a6a1f1dSLionel Sambuc 					true, true,
4862fe8fb19SBen Gras 					pt, ptlim);
4872fe8fb19SBen Gras 				continue;
4882fe8fb19SBen Gras 			case 'Z':
4892fe8fb19SBen Gras #ifdef TM_ZONE
4902fe8fb19SBen Gras 				pt = _add(t->TM_ZONE, pt, ptlim);
4912fe8fb19SBen Gras #endif /* defined TM_ZONE */
4922fe8fb19SBen Gras 				if (t->tm_isdst >= 0)
4932fe8fb19SBen Gras 					pt = _add((sp ?
4942fe8fb19SBen Gras 					    tzgetname(sp, t->tm_isdst) :
4952fe8fb19SBen Gras 					    tzname[t->tm_isdst != 0]),
4962fe8fb19SBen Gras 					    pt, ptlim);
4972fe8fb19SBen Gras 				/*
4982fe8fb19SBen Gras 				** C99 says that %Z must be replaced by the
4992fe8fb19SBen Gras 				** empty string if the time zone is not
5002fe8fb19SBen Gras 				** determinable.
5012fe8fb19SBen Gras 				*/
5022fe8fb19SBen Gras 				continue;
5032fe8fb19SBen Gras 			case 'z':
5042fe8fb19SBen Gras 				{
505*0a6a1f1dSLionel Sambuc 				long		diff;
5062fe8fb19SBen Gras 				char const *	sign;
5072fe8fb19SBen Gras 
5082fe8fb19SBen Gras 				if (t->tm_isdst < 0)
5092fe8fb19SBen Gras 					continue;
5102fe8fb19SBen Gras #ifdef TM_GMTOFF
5112fe8fb19SBen Gras 				diff = (int)t->TM_GMTOFF;
5122fe8fb19SBen Gras #else /* !defined TM_GMTOFF */
5132fe8fb19SBen Gras 				/*
51484d9c625SLionel Sambuc 				** C99 says that the UT offset must
5152fe8fb19SBen Gras 				** be computed by looking only at
5162fe8fb19SBen Gras 				** tm_isdst. This requirement is
5172fe8fb19SBen Gras 				** incorrect, since it means the code
5182fe8fb19SBen Gras 				** must rely on magic (in this case
5192fe8fb19SBen Gras 				** altzone and timezone), and the
5202fe8fb19SBen Gras 				** magic might not have the correct
5212fe8fb19SBen Gras 				** offset. Doing things correctly is
5222fe8fb19SBen Gras 				** tricky and requires disobeying C99;
5232fe8fb19SBen Gras 				** see GNU C strftime for details.
5242fe8fb19SBen Gras 				** For now, punt and conform to the
5252fe8fb19SBen Gras 				** standard, even though it's incorrect.
5262fe8fb19SBen Gras 				**
5272fe8fb19SBen Gras 				** C99 says that %z must be replaced by the
5282fe8fb19SBen Gras 				** empty string if the time zone is not
5292fe8fb19SBen Gras 				** determinable, so output nothing if the
5302fe8fb19SBen Gras 				** appropriate variables are not available.
5312fe8fb19SBen Gras 				*/
5322fe8fb19SBen Gras #ifndef STD_INSPIRED
5332fe8fb19SBen Gras 				if (t->tm_isdst == 0)
5342fe8fb19SBen Gras #ifdef USG_COMPAT
5352fe8fb19SBen Gras 					diff = -timezone;
5362fe8fb19SBen Gras #else /* !defined USG_COMPAT */
5372fe8fb19SBen Gras 					continue;
5382fe8fb19SBen Gras #endif /* !defined USG_COMPAT */
5392fe8fb19SBen Gras 				else
5402fe8fb19SBen Gras #ifdef ALTZONE
5412fe8fb19SBen Gras 					diff = -altzone;
5422fe8fb19SBen Gras #else /* !defined ALTZONE */
5432fe8fb19SBen Gras 					continue;
5442fe8fb19SBen Gras #endif /* !defined ALTZONE */
5452fe8fb19SBen Gras #else /* defined STD_INSPIRED */
5462fe8fb19SBen Gras 				{
5472fe8fb19SBen Gras 					struct tm tmp;
5482fe8fb19SBen Gras 					time_t lct, gct;
5492fe8fb19SBen Gras 
5502fe8fb19SBen Gras 					/*
5512fe8fb19SBen Gras 					** Get calendar time from t
5522fe8fb19SBen Gras 					** being treated as local.
5532fe8fb19SBen Gras 					*/
5542fe8fb19SBen Gras 					tmp = *t; /* mktime discards const */
5552fe8fb19SBen Gras 					lct = mktime(&tmp);
5562fe8fb19SBen Gras 
5572fe8fb19SBen Gras 					if (lct == (time_t)-1)
5582fe8fb19SBen Gras 						continue;
5592fe8fb19SBen Gras 
5602fe8fb19SBen Gras 					/*
5612fe8fb19SBen Gras 					** Get calendar time from t
5622fe8fb19SBen Gras 					** being treated as GMT.
5632fe8fb19SBen Gras 					**/
5642fe8fb19SBen Gras 					tmp = *t; /* mktime discards const */
5652fe8fb19SBen Gras 					gct = timegm(&tmp);
5662fe8fb19SBen Gras 
5672fe8fb19SBen Gras 					if (gct == (time_t)-1)
5682fe8fb19SBen Gras 						continue;
5692fe8fb19SBen Gras 
5702fe8fb19SBen Gras 					/* LINTED difference will fit int */
5712fe8fb19SBen Gras 					diff = (intmax_t)gct - (intmax_t)lct;
5722fe8fb19SBen Gras 				}
5732fe8fb19SBen Gras #endif /* defined STD_INSPIRED */
5742fe8fb19SBen Gras #endif /* !defined TM_GMTOFF */
5752fe8fb19SBen Gras 				if (diff < 0) {
5762fe8fb19SBen Gras 					sign = "-";
5772fe8fb19SBen Gras 					diff = -diff;
5782fe8fb19SBen Gras 				} else	sign = "+";
5792fe8fb19SBen Gras 				pt = _add(sign, pt, ptlim);
5802fe8fb19SBen Gras 				diff /= SECSPERMIN;
5812fe8fb19SBen Gras 				diff = (diff / MINSPERHOUR) * 100 +
5822fe8fb19SBen Gras 					(diff % MINSPERHOUR);
583*0a6a1f1dSLionel Sambuc 				_DIAGASSERT(__type_fit(int, diff));
584*0a6a1f1dSLionel Sambuc 				pt = _conv((int)diff, "%04d", pt, ptlim);
5852fe8fb19SBen Gras 				}
5862fe8fb19SBen Gras 				continue;
58784d9c625SLionel Sambuc #if defined(__minix)
5882fe8fb19SBen Gras 			case '+':
58984d9c625SLionel Sambuc 				pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt, ptlim, warnp, loc);
5902fe8fb19SBen Gras 				continue;
59184d9c625SLionel Sambuc #endif /* defined(__minix) */
5922fe8fb19SBen Gras #if 0
5932fe8fb19SBen Gras 			case '+':
59484d9c625SLionel Sambuc 				pt = _fmt(sp, _TIME_LOCALE(loc)->date_fmt, t,
59584d9c625SLionel Sambuc 				    pt, ptlim, warnp, loc);
5962fe8fb19SBen Gras 				continue;
5972fe8fb19SBen Gras #endif
5982fe8fb19SBen Gras 			case '%':
5992fe8fb19SBen Gras 			/*
6002fe8fb19SBen Gras 			** X311J/88-090 (4.12.3.5): if conversion char is
6012fe8fb19SBen Gras 			** undefined, behavior is undefined. Print out the
6022fe8fb19SBen Gras 			** character itself as printf(3) also does.
6032fe8fb19SBen Gras 			*/
6042fe8fb19SBen Gras 			default:
6052fe8fb19SBen Gras 				break;
6062fe8fb19SBen Gras 			}
6072fe8fb19SBen Gras 		}
6082fe8fb19SBen Gras 		if (pt == ptlim)
6092fe8fb19SBen Gras 			break;
6102fe8fb19SBen Gras 		*pt++ = *format;
6112fe8fb19SBen Gras 	}
6122fe8fb19SBen Gras 	return pt;
6132fe8fb19SBen Gras }
6142fe8fb19SBen Gras 
6152fe8fb19SBen Gras size_t
strftime(char * s,size_t maxsize,const char * format,const struct tm * t)616*0a6a1f1dSLionel Sambuc strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
6172fe8fb19SBen Gras {
6182fe8fb19SBen Gras 	tzset();
6192fe8fb19SBen Gras 	return strftime_z(NULL, s, maxsize, format, t);
6202fe8fb19SBen Gras }
6212fe8fb19SBen Gras 
62284d9c625SLionel Sambuc size_t
strftime_l(char * __restrict s,size_t maxsize,const char * __restrict format,const struct tm * __restrict t,locale_t loc)62384d9c625SLionel Sambuc strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
62484d9c625SLionel Sambuc     const struct tm * __restrict t, locale_t loc)
62584d9c625SLionel Sambuc {
62684d9c625SLionel Sambuc 	tzset();
62784d9c625SLionel Sambuc 	return strftime_lz(NULL, s, maxsize, format, t, loc);
62884d9c625SLionel Sambuc }
62984d9c625SLionel Sambuc 
6302fe8fb19SBen Gras static char *
_conv(int n,const char * format,char * pt,const char * ptlim)631*0a6a1f1dSLionel Sambuc _conv(int n, const char *format, char *pt, const char *ptlim)
6322fe8fb19SBen Gras {
6332fe8fb19SBen Gras 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
6342fe8fb19SBen Gras 
6352fe8fb19SBen Gras 	(void) snprintf(buf, sizeof(buf), format, n);
6362fe8fb19SBen Gras 	return _add(buf, pt, ptlim);
6372fe8fb19SBen Gras }
6382fe8fb19SBen Gras 
6392fe8fb19SBen Gras static char *
_add(const char * str,char * pt,const char * ptlim)640*0a6a1f1dSLionel Sambuc _add(const char *str, char *pt, const char *ptlim)
6412fe8fb19SBen Gras {
6422fe8fb19SBen Gras 	while (pt < ptlim && (*pt = *str++) != '\0')
6432fe8fb19SBen Gras 		++pt;
6442fe8fb19SBen Gras 	return pt;
6452fe8fb19SBen Gras }
6462fe8fb19SBen Gras 
6472fe8fb19SBen Gras /*
6482fe8fb19SBen Gras ** POSIX and the C Standard are unclear or inconsistent about
6492fe8fb19SBen Gras ** what %C and %y do if the year is negative or exceeds 9999.
6502fe8fb19SBen Gras ** Use the convention that %C concatenated with %y yields the
6512fe8fb19SBen Gras ** same output as %Y, and that %Y contains at least 4 bytes,
6522fe8fb19SBen Gras ** with more only if necessary.
6532fe8fb19SBen Gras */
6542fe8fb19SBen Gras 
6552fe8fb19SBen Gras static char *
_yconv(int a,int b,bool convert_top,bool convert_yy,char * pt,const char * ptlim)656*0a6a1f1dSLionel Sambuc _yconv(int a, int b, bool convert_top, bool convert_yy,
657*0a6a1f1dSLionel Sambuc     char *pt, const char * ptlim)
6582fe8fb19SBen Gras {
65984d9c625SLionel Sambuc 	int	lead;
66084d9c625SLionel Sambuc 	int	trail;
6612fe8fb19SBen Gras 
6622fe8fb19SBen Gras #define DIVISOR	100
6632fe8fb19SBen Gras 	trail = a % DIVISOR + b % DIVISOR;
6642fe8fb19SBen Gras 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
6652fe8fb19SBen Gras 	trail %= DIVISOR;
6662fe8fb19SBen Gras 	if (trail < 0 && lead > 0) {
6672fe8fb19SBen Gras 		trail += DIVISOR;
6682fe8fb19SBen Gras 		--lead;
6692fe8fb19SBen Gras 	} else if (lead < 0 && trail > 0) {
6702fe8fb19SBen Gras 		trail -= DIVISOR;
6712fe8fb19SBen Gras 		++lead;
6722fe8fb19SBen Gras 	}
6732fe8fb19SBen Gras 	if (convert_top) {
6742fe8fb19SBen Gras 		if (lead == 0 && trail < 0)
6752fe8fb19SBen Gras 			pt = _add("-0", pt, ptlim);
6762fe8fb19SBen Gras 		else	pt = _conv(lead, "%02d", pt, ptlim);
6772fe8fb19SBen Gras 	}
6782fe8fb19SBen Gras 	if (convert_yy)
6792fe8fb19SBen Gras 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
6802fe8fb19SBen Gras 	return pt;
6812fe8fb19SBen Gras }
682