Home | History | Annotate | Line # | Download | only in time
      1  1.57  christos /*	$NetBSD: strftime.c,v 1.57 2025/01/23 22:44:22 christos Exp $	*/
      2  1.36  christos 
      3  1.39  christos /* Convert a broken-down timestamp to a string.  */
      4  1.36  christos 
      5  1.36  christos /* Copyright 1989 The Regents of the University of California.
      6  1.36  christos    All rights reserved.
      7  1.36  christos 
      8  1.36  christos    Redistribution and use in source and binary forms, with or without
      9  1.36  christos    modification, are permitted provided that the following conditions
     10  1.36  christos    are met:
     11  1.36  christos    1. Redistributions of source code must retain the above copyright
     12  1.36  christos       notice, this list of conditions and the following disclaimer.
     13  1.36  christos    2. Redistributions in binary form must reproduce the above copyright
     14  1.36  christos       notice, this list of conditions and the following disclaimer in the
     15  1.36  christos       documentation and/or other materials provided with the distribution.
     16  1.36  christos    3. Neither the name of the University nor the names of its contributors
     17  1.36  christos       may be used to endorse or promote products derived from this software
     18  1.36  christos       without specific prior written permission.
     19  1.36  christos 
     20  1.36  christos    THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
     21  1.36  christos    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     22  1.36  christos    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     23  1.36  christos    ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     24  1.36  christos    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     25  1.36  christos    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     26  1.36  christos    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     27  1.36  christos    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     28  1.36  christos    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     29  1.36  christos    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     30  1.36  christos    SUCH DAMAGE.  */
     31   1.1       mrg 
     32   1.3  christos #include <sys/cdefs.h>
     33   1.1       mrg #if defined(LIBC_SCCS) && !defined(lint)
     34   1.3  christos #if 0
     35  1.13    kleink static char	elsieid[] = "@(#)strftime.c	7.64";
     36  1.20   mlelstv static char	elsieid[] = "@(#)strftime.c	8.3";
     37   1.3  christos #else
     38  1.57  christos __RCSID("$NetBSD: strftime.c,v 1.57 2025/01/23 22:44:22 christos Exp $");
     39   1.3  christos #endif
     40   1.1       mrg #endif /* LIBC_SCCS and not lint */
     41   1.1       mrg 
     42   1.4       jtc #include "namespace.h"
     43  1.12    kleink 
     44  1.25     joerg #include <stddef.h>
     45  1.30  christos #include <assert.h>
     46  1.25     joerg #include <locale.h>
     47  1.25     joerg #include "setlocale_local.h"
     48  1.25     joerg 
     49  1.12    kleink /*
     50  1.36  christos ** Based on the UCB version with the copyright notice appearing above.
     51  1.24  christos **
     52  1.12    kleink ** This is ANSIish only when "multibyte character == plain character".
     53  1.12    kleink */
     54  1.12    kleink 
     55  1.11      taca #include "private.h"
     56  1.12    kleink 
     57  1.16    kleink /*
     58  1.16    kleink ** We don't use these extensions in strftime operation even when
     59  1.16    kleink ** supported by the local tzcode configuration.  A strictly
     60  1.16    kleink ** conforming C application may leave them in undefined state.
     61  1.16    kleink */
     62  1.16    kleink 
     63  1.15    kleink #ifdef _LIBC
     64  1.15    kleink #undef TM_ZONE
     65  1.16    kleink #undef TM_GMTOFF
     66  1.15    kleink #endif
     67  1.15    kleink 
     68  1.39  christos #include <fcntl.h>
     69  1.39  christos #include <locale.h>
     70  1.40  christos #include <stdio.h>
     71  1.40  christos 
     72  1.57  christos /* If true, the value returned by an idealized unlimited-range mktime
     73  1.57  christos    always fits into an integer type with bounds MIN and MAX.
     74  1.57  christos    If false, the value might not fit.
     75  1.57  christos    This macro is usable in #if if its arguments are.
     76  1.57  christos    Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file,
     77  1.57  christos    divide by the maximum number of non-leap seconds in a year,
     78  1.57  christos    divide again by two just to be safe,
     79  1.57  christos    and account for the tm_year origin (1900) and time_t origin (1970).  */
     80  1.57  christos #define MKTIME_FITS_IN(min, max) \
     81  1.57  christos   ((min) < 0 \
     82  1.57  christos    && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \
     83  1.57  christos    && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900)
     84  1.57  christos 
     85  1.57  christos /* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow
     86  1.57  christos    or if it is not known whether mktime can fail,
     87  1.57  christos    and is false if mktime definitely cannot fail.
     88  1.57  christos    This macro is usable in #if, and so does not use TIME_T_MAX or sizeof.
     89  1.57  christos    If the builder has not configured this macro, guess based on what
     90  1.57  christos    known platforms do.  When in doubt, guess true.  */
     91  1.57  christos #ifndef MKTIME_MIGHT_OVERFLOW
     92  1.57  christos # if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__
     93  1.57  christos #  include <sys/param.h>
     94  1.57  christos # endif
     95  1.57  christos # if ((/* The following heuristics assume native time_t.  */ \
     96  1.57  christos        defined_time_tz) \
     97  1.57  christos       || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \
     98  1.57  christos 	      assume overflow unless we're on a known-safe host.  */ \
     99  1.57  christos 	   !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \
    100  1.57  christos 	  && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \
    101  1.57  christos 		 if __TIMESIZE is 64.  */ \
    102  1.57  christos 	      !defined __TIMESIZE || __TIMESIZE < 64) \
    103  1.57  christos 	  && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \
    104  1.57  christos 		 and later has 64-bit time_t on all platforms but i386 which \
    105  1.57  christos 		 is currently scheduled for end-of-life on 2028-11-30.  */ \
    106  1.57  christos 	      !defined __FreeBSD_version || __FreeBSD_version < 1200036 \
    107  1.57  christos 	      || defined __i386) \
    108  1.57  christos 	  && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t.  */ \
    109  1.57  christos 	      !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \
    110  1.57  christos 	  && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t.  */ \
    111  1.57  christos 	      !defined OpenBSD || OpenBSD < 201405)))
    112  1.57  christos #  define MKTIME_MIGHT_OVERFLOW 1
    113  1.57  christos # else
    114  1.57  christos #  define MKTIME_MIGHT_OVERFLOW 0
    115  1.57  christos # endif
    116  1.57  christos #endif
    117  1.57  christos /* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range.  */
    118  1.57  christos static_assert(MKTIME_MIGHT_OVERFLOW
    119  1.57  christos 	      || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX));
    120  1.57  christos 
    121  1.57  christos #if MKTIME_MIGHT_OVERFLOW
    122  1.57  christos /* Do this after system includes as it redefines time_t, mktime, timeoff.  */
    123  1.57  christos # define USE_TIMEX_T true
    124  1.57  christos # include "localtime.c"
    125  1.57  christos #endif
    126  1.57  christos 
    127  1.40  christos #ifndef DEPRECATE_TWO_DIGIT_YEARS
    128  1.57  christos # define DEPRECATE_TWO_DIGIT_YEARS 0
    129  1.40  christos #endif
    130  1.12    kleink 
    131  1.21  christos #ifdef __weak_alias
    132  1.25     joerg __weak_alias(strftime_l, _strftime_l)
    133  1.25     joerg __weak_alias(strftime_lz, _strftime_lz)
    134  1.21  christos __weak_alias(strftime_z, _strftime_z)
    135  1.21  christos #endif
    136  1.21  christos 
    137  1.12    kleink #include "sys/localedef.h"
    138  1.25     joerg #define _TIME_LOCALE(loc) \
    139  1.56  riastrad     ((_TimeLocale *)((loc)->part_impl[LC_TIME]))
    140  1.20   mlelstv #define c_fmt   d_t_fmt
    141  1.12    kleink 
    142  1.40  christos enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
    143  1.40  christos 
    144  1.20   mlelstv static char *	_add(const char *, char *, const char *);
    145  1.44  christos static char *	_conv(int, const char *, char *, const char *, locale_t);
    146  1.21  christos static char *	_fmt(const timezone_t, const char *, const struct tm *, char *,
    147  1.40  christos 			const char *, enum warn *, locale_t);
    148  1.44  christos static char *	_yconv(int, int, bool, bool, char *, const char *, locale_t);
    149  1.12    kleink 
    150  1.12    kleink #ifndef YEAR_2000_NAME
    151  1.50  christos # define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
    152  1.12    kleink #endif /* !defined YEAR_2000_NAME */
    153  1.12    kleink 
    154  1.44  christos #define	IN_NONE	0
    155  1.44  christos #define	IN_SOME	1
    156  1.44  christos #define	IN_THIS	2
    157  1.44  christos #define	IN_ALL	3
    158  1.44  christos 
    159  1.44  christos #define	PAD_DEFAULT	0
    160  1.44  christos #define	PAD_LESS	1
    161  1.44  christos #define	PAD_SPACE	2
    162  1.44  christos #define	PAD_ZERO	3
    163  1.44  christos 
    164  1.44  christos static const char fmt_padding[][4][5] = {
    165  1.44  christos 	/* DEFAULT,	LESS,	SPACE,	ZERO */
    166  1.44  christos #define	PAD_FMT_MONTHDAY	0
    167  1.44  christos #define	PAD_FMT_HMS		0
    168  1.44  christos #define	PAD_FMT_CENTURY		0
    169  1.44  christos #define	PAD_FMT_SHORTYEAR	0
    170  1.44  christos #define	PAD_FMT_MONTH		0
    171  1.44  christos #define	PAD_FMT_WEEKOFYEAR	0
    172  1.44  christos #define	PAD_FMT_DAYOFMONTH	0
    173  1.44  christos 	{ "%02d",	"%d",	"%2d",	"%02d" },
    174  1.44  christos #define	PAD_FMT_SDAYOFMONTH	1
    175  1.44  christos #define	PAD_FMT_SHMS		1
    176  1.44  christos 	{ "%2d",	"%d",	"%2d",	"%02d" },
    177  1.44  christos #define	PAD_FMT_DAYOFYEAR	2
    178  1.44  christos 	{ "%03d",	"%d",	"%3d",	"%03d" },
    179  1.44  christos #define	PAD_FMT_YEAR		3
    180  1.44  christos 	{ "%04d",	"%d",	"%4d",	"%04d" }
    181  1.44  christos };
    182  1.44  christos 
    183   1.1       mrg size_t
    184  1.25     joerg strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize,
    185  1.25     joerg     const char * __restrict format, const struct tm * __restrict t)
    186  1.25     joerg {
    187  1.26     joerg 	return strftime_lz(sp, s, maxsize, format, t, _current_locale());
    188  1.25     joerg }
    189  1.25     joerg 
    190  1.33  christos #if HAVE_STRFTIME_L
    191  1.33  christos size_t
    192  1.52  christos strftime_l(char *restrict s, size_t maxsize, char const *restrict format,
    193  1.52  christos 	   struct tm const *restrict t,
    194  1.51  christos 	   ATTRIBUTE_MAYBE_UNUSED locale_t locale)
    195  1.33  christos {
    196  1.33  christos   /* Just call strftime, as only the C locale is supported.  */
    197  1.33  christos   return strftime(s, maxsize, format, t);
    198  1.33  christos }
    199  1.33  christos #endif
    200  1.33  christos 
    201  1.25     joerg size_t
    202  1.25     joerg strftime_lz(const timezone_t sp, char *const s, const size_t maxsize,
    203  1.25     joerg     const char *const format, const struct tm *const t, locale_t loc)
    204   1.1       mrg {
    205  1.12    kleink 	char *	p;
    206  1.48  christos 	int saved_errno = errno;
    207  1.40  christos 	enum warn warn = IN_NONE;
    208   1.5    kleink 
    209  1.37  christos 	p = _fmt(sp, format, t, s, s + maxsize, &warn, loc);
    210  1.40  christos 	if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS
    211  1.40  christos 	    && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
    212  1.12    kleink 		(void) fprintf(stderr, "\n");
    213  1.37  christos 		(void) fprintf(stderr, "strftime format \"%s\" ", format);
    214  1.12    kleink 		(void) fprintf(stderr, "yields only two digits of years in ");
    215  1.12    kleink 		if (warn == IN_SOME)
    216  1.12    kleink 			(void) fprintf(stderr, "some locales");
    217  1.12    kleink 		else if (warn == IN_THIS)
    218  1.12    kleink 			(void) fprintf(stderr, "the current locale");
    219  1.12    kleink 		else	(void) fprintf(stderr, "all locales");
    220  1.12    kleink 		(void) fprintf(stderr, "\n");
    221  1.12    kleink 	}
    222  1.48  christos 	if (p == s + maxsize) {
    223  1.48  christos 		errno = ERANGE;
    224  1.12    kleink 		return 0;
    225  1.48  christos 	}
    226  1.12    kleink 	*p = '\0';
    227  1.48  christos 	errno = saved_errno;
    228  1.12    kleink 	return p - s;
    229   1.1       mrg }
    230   1.1       mrg 
    231  1.12    kleink static char *
    232  1.34  christos _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
    233  1.40  christos      const char *ptlim, enum warn *warnp, locale_t loc)
    234   1.1       mrg {
    235  1.44  christos 	int Ealternative, Oalternative, PadIndex;
    236  1.44  christos 	_TimeLocale *tptr = _TIME_LOCALE(loc);
    237  1.44  christos 
    238  1.12    kleink 	for ( ; *format; ++format) {
    239   1.1       mrg 		if (*format == '%') {
    240  1.44  christos 			Ealternative = 0;
    241  1.44  christos 			Oalternative = 0;
    242  1.44  christos 			PadIndex = PAD_DEFAULT;
    243  1.12    kleink label:
    244  1.12    kleink 			switch (*++format) {
    245   1.1       mrg 			case '\0':
    246  1.57  christos 				/* Output unknown conversion specifiers as-is,
    247  1.57  christos 				   to aid debugging.  This includes '%' at
    248  1.57  christos 				   format end.  This conforms to C23 section
    249  1.57  christos 				   7.29.3.5 paragraph 6, which says behavior
    250  1.57  christos 				   is undefined here.  */
    251  1.57  christos 			default:
    252   1.1       mrg 				--format;
    253   1.1       mrg 				break;
    254   1.1       mrg 			case 'A':
    255  1.12    kleink 				pt = _add((t->tm_wday < 0 ||
    256  1.12    kleink 					t->tm_wday >= DAYSPERWEEK) ?
    257  1.44  christos 					"?" : tptr->day[t->tm_wday],
    258  1.12    kleink 					pt, ptlim);
    259   1.1       mrg 				continue;
    260   1.1       mrg 			case 'a':
    261  1.12    kleink 				pt = _add((t->tm_wday < 0 ||
    262  1.12    kleink 					t->tm_wday >= DAYSPERWEEK) ?
    263  1.44  christos 					"?" : tptr->abday[t->tm_wday],
    264  1.12    kleink 					pt, ptlim);
    265   1.1       mrg 				continue;
    266   1.1       mrg 			case 'B':
    267  1.12    kleink 				pt = _add((t->tm_mon < 0 ||
    268  1.12    kleink 					t->tm_mon >= MONSPERYEAR) ?
    269  1.44  christos 					"?" :
    270  1.44  christos 					/* no alt_month in _TimeLocale */
    271  1.44  christos 					(Oalternative ? tptr->mon/*alt_month*/:
    272  1.44  christos 					tptr->mon)[t->tm_mon],
    273  1.12    kleink 					pt, ptlim);
    274   1.1       mrg 				continue;
    275   1.1       mrg 			case 'b':
    276   1.1       mrg 			case 'h':
    277  1.12    kleink 				pt = _add((t->tm_mon < 0 ||
    278  1.12    kleink 					t->tm_mon >= MONSPERYEAR) ?
    279  1.44  christos 					"?" : tptr->abmon[t->tm_mon],
    280  1.12    kleink 					pt, ptlim);
    281   1.1       mrg 				continue;
    282   1.1       mrg 			case 'C':
    283  1.12    kleink 				/*
    284  1.12    kleink 				** %C used to do a...
    285  1.12    kleink 				**	_fmt("%a %b %e %X %Y", t);
    286  1.12    kleink 				** ...whereas now POSIX 1003.2 calls for
    287  1.12    kleink 				** something completely different.
    288  1.12    kleink 				** (ado, 1993-05-24)
    289  1.12    kleink 				*/
    290  1.33  christos 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    291  1.44  christos 					    true, false, pt, ptlim, loc);
    292   1.1       mrg 				continue;
    293   1.1       mrg 			case 'c':
    294  1.12    kleink 				{
    295  1.40  christos 				enum warn warn2 = IN_SOME;
    296  1.12    kleink 
    297  1.44  christos 				pt = _fmt(sp, tptr->c_fmt, t, pt,
    298  1.25     joerg 				    ptlim, &warn2, loc);
    299  1.12    kleink 				if (warn2 == IN_ALL)
    300  1.12    kleink 					warn2 = IN_THIS;
    301  1.12    kleink 				if (warn2 > *warnp)
    302  1.12    kleink 					*warnp = warn2;
    303  1.12    kleink 				}
    304   1.1       mrg 				continue;
    305   1.1       mrg 			case 'D':
    306  1.25     joerg 				pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
    307  1.25     joerg 				    loc);
    308   1.1       mrg 				continue;
    309   1.1       mrg 			case 'd':
    310  1.44  christos 				pt = _conv(t->tm_mday,
    311  1.44  christos 				    fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex],
    312  1.44  christos 				    pt, ptlim, loc);
    313   1.1       mrg 				continue;
    314  1.12    kleink 			case 'E':
    315  1.44  christos 				if (Ealternative || Oalternative)
    316  1.44  christos 					break;
    317  1.44  christos 				Ealternative++;
    318  1.44  christos 				goto label;
    319  1.12    kleink 			case 'O':
    320  1.12    kleink 				/*
    321  1.40  christos 				** Locale modifiers of C99 and later.
    322  1.12    kleink 				** The sequences
    323  1.12    kleink 				**	%Ec %EC %Ex %EX %Ey %EY
    324  1.12    kleink 				**	%Od %oe %OH %OI %Om %OM
    325  1.12    kleink 				**	%OS %Ou %OU %OV %Ow %OW %Oy
    326  1.41  christos 				** are supposed to provide alternative
    327  1.12    kleink 				** representations.
    328  1.12    kleink 				*/
    329  1.44  christos 				if (Ealternative || Oalternative)
    330  1.44  christos 					break;
    331  1.44  christos 				Oalternative++;
    332  1.12    kleink 				goto label;
    333   1.1       mrg 			case 'e':
    334  1.44  christos 				pt = _conv(t->tm_mday,
    335  1.44  christos 				    fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex],
    336  1.44  christos 				    pt, ptlim, loc);
    337  1.10    kleink 				continue;
    338  1.10    kleink 			case 'F':
    339  1.25     joerg 				pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
    340  1.25     joerg 				    loc);
    341   1.1       mrg 				continue;
    342   1.1       mrg 			case 'H':
    343  1.44  christos 				pt = _conv(t->tm_hour,
    344  1.44  christos 				    fmt_padding[PAD_FMT_HMS][PadIndex],
    345  1.44  christos 				    pt, ptlim, loc);
    346   1.1       mrg 				continue;
    347   1.1       mrg 			case 'I':
    348  1.12    kleink 				pt = _conv((t->tm_hour % 12) ?
    349  1.44  christos 				    (t->tm_hour % 12) : 12,
    350  1.44  christos 				    fmt_padding[PAD_FMT_HMS][PadIndex],
    351  1.44  christos 				    pt, ptlim, loc);
    352   1.1       mrg 				continue;
    353   1.1       mrg 			case 'j':
    354  1.44  christos 				pt = _conv(t->tm_yday + 1,
    355  1.44  christos 				    fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex],
    356  1.44  christos 				    pt, ptlim, loc);
    357   1.1       mrg 				continue;
    358   1.1       mrg 			case 'k':
    359  1.12    kleink 				/*
    360  1.12    kleink 				** This used to be...
    361  1.12    kleink 				**	_conv(t->tm_hour % 12 ?
    362  1.12    kleink 				**		t->tm_hour % 12 : 12, 2, ' ');
    363  1.12    kleink 				** ...and has been changed to the below to
    364  1.12    kleink 				** match SunOS 4.1.1 and Arnold Robbins'
    365  1.20   mlelstv 				** strftime version 3.0. That is, "%k" and
    366  1.12    kleink 				** "%l" have been swapped.
    367  1.12    kleink 				** (ado, 1993-05-24)
    368  1.12    kleink 				*/
    369  1.44  christos 				pt = _conv(t->tm_hour,
    370  1.44  christos 				    fmt_padding[PAD_FMT_SHMS][PadIndex],
    371  1.44  christos 				    pt, ptlim, loc);
    372  1.12    kleink 				continue;
    373  1.12    kleink #ifdef KITCHEN_SINK
    374  1.12    kleink 			case 'K':
    375  1.12    kleink 				/*
    376  1.12    kleink 				** After all this time, still unclaimed!
    377  1.12    kleink 				*/
    378  1.12    kleink 				pt = _add("kitchen sink", pt, ptlim);
    379   1.1       mrg 				continue;
    380  1.12    kleink #endif /* defined KITCHEN_SINK */
    381   1.1       mrg 			case 'l':
    382  1.12    kleink 				/*
    383  1.12    kleink 				** This used to be...
    384  1.12    kleink 				**	_conv(t->tm_hour, 2, ' ');
    385  1.12    kleink 				** ...and has been changed to the below to
    386  1.12    kleink 				** match SunOS 4.1.1 and Arnold Robbin's
    387  1.20   mlelstv 				** strftime version 3.0. That is, "%k" and
    388  1.12    kleink 				** "%l" have been swapped.
    389  1.12    kleink 				** (ado, 1993-05-24)
    390  1.12    kleink 				*/
    391  1.12    kleink 				pt = _conv((t->tm_hour % 12) ?
    392  1.12    kleink 					(t->tm_hour % 12) : 12,
    393  1.44  christos 					fmt_padding[PAD_FMT_SHMS][PadIndex],
    394  1.44  christos 					pt, ptlim, loc);
    395   1.1       mrg 				continue;
    396   1.1       mrg 			case 'M':
    397  1.44  christos 				pt = _conv(t->tm_min,
    398  1.44  christos 				    fmt_padding[PAD_FMT_HMS][PadIndex],
    399  1.44  christos 				    pt, ptlim, loc);
    400   1.1       mrg 				continue;
    401   1.1       mrg 			case 'm':
    402  1.44  christos 				pt = _conv(t->tm_mon + 1,
    403  1.44  christos 				    fmt_padding[PAD_FMT_MONTH][PadIndex],
    404  1.44  christos 				    pt, ptlim, loc);
    405   1.1       mrg 				continue;
    406   1.1       mrg 			case 'n':
    407  1.12    kleink 				pt = _add("\n", pt, ptlim);
    408   1.1       mrg 				continue;
    409   1.1       mrg 			case 'p':
    410  1.12    kleink 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
    411  1.44  christos 					tptr->am_pm[1] :
    412  1.44  christos 					tptr->am_pm[0],
    413  1.12    kleink 					pt, ptlim);
    414   1.1       mrg 				continue;
    415   1.1       mrg 			case 'R':
    416  1.25     joerg 				pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
    417  1.25     joerg 				    loc);
    418   1.1       mrg 				continue;
    419   1.1       mrg 			case 'r':
    420  1.44  christos 				pt = _fmt(sp, tptr->t_fmt_ampm, t,
    421  1.25     joerg 				    pt, ptlim, warnp, loc);
    422   1.1       mrg 				continue;
    423   1.1       mrg 			case 'S':
    424  1.44  christos 				pt = _conv(t->tm_sec,
    425  1.44  christos 				    fmt_padding[PAD_FMT_HMS][PadIndex],
    426  1.44  christos 				    pt, ptlim, loc);
    427   1.1       mrg 				continue;
    428   1.1       mrg 			case 's':
    429  1.12    kleink 				{
    430  1.12    kleink 					struct tm	tm;
    431  1.12    kleink 					char		buf[INT_STRLEN_MAXIMUM(
    432  1.12    kleink 								time_t) + 1];
    433  1.12    kleink 					time_t		mkt;
    434  1.12    kleink 
    435  1.51  christos 					tm.tm_sec = t->tm_sec;
    436  1.51  christos 					tm.tm_min = t->tm_min;
    437  1.51  christos 					tm.tm_hour = t->tm_hour;
    438  1.51  christos 					tm.tm_mday = t->tm_mday;
    439  1.51  christos 					tm.tm_mon = t->tm_mon;
    440  1.51  christos 					tm.tm_year = t->tm_year;
    441  1.57  christos 
    442  1.57  christos 					/* Get the time_t value for TM.
    443  1.57  christos 					   Native time_t, or its redefinition
    444  1.57  christos 					   by localtime.c above, is wide enough
    445  1.57  christos 					   so that this cannot overflow.  */
    446  1.57  christos #if defined TM_GMTOFF
    447  1.57  christos 					mkt = timeoff(&tm, t->TM_GMTOFF);
    448  1.57  christos #else
    449  1.51  christos 					tm.tm_isdst = t->tm_isdst;
    450  1.57  christos 					mkt = mktime_z(sp, &tm);
    451  1.51  christos #endif
    452  1.12    kleink 					/* CONSTCOND */
    453  1.49  christos 					if (TYPE_SIGNED(time_t)) {
    454  1.49  christos 						intmax_t n = mkt;
    455  1.27  christos 						(void)snprintf(buf, sizeof(buf),
    456  1.49  christos 						    "%"PRIdMAX, n);
    457  1.49  christos 					} else {
    458  1.53  christos 						/*LINTED possibly unreached*/
    459  1.49  christos 						uintmax_t n = mkt;
    460  1.49  christos 						(void)snprintf(buf, sizeof(buf),
    461  1.49  christos 						    "%"PRIuMAX, n);
    462  1.49  christos 					}
    463  1.12    kleink 					pt = _add(buf, pt, ptlim);
    464  1.12    kleink 				}
    465   1.1       mrg 				continue;
    466   1.1       mrg 			case 'T':
    467  1.25     joerg 				pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
    468  1.25     joerg 				    loc);
    469   1.1       mrg 				continue;
    470   1.1       mrg 			case 't':
    471  1.12    kleink 				pt = _add("\t", pt, ptlim);
    472   1.1       mrg 				continue;
    473   1.1       mrg 			case 'U':
    474  1.12    kleink 				pt = _conv((t->tm_yday + DAYSPERWEEK -
    475  1.44  christos 				    t->tm_wday) / DAYSPERWEEK,
    476  1.44  christos 				    fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
    477  1.44  christos 				    pt, ptlim, loc);
    478   1.1       mrg 				continue;
    479   1.1       mrg 			case 'u':
    480  1.12    kleink 				/*
    481  1.12    kleink 				** From Arnold Robbins' strftime version 3.0:
    482  1.12    kleink 				** "ISO 8601: Weekday as a decimal number
    483  1.12    kleink 				** [1 (Monday) - 7]"
    484  1.12    kleink 				** (ado, 1993-05-24)
    485  1.12    kleink 				*/
    486  1.12    kleink 				pt = _conv((t->tm_wday == 0) ?
    487  1.12    kleink 					DAYSPERWEEK : t->tm_wday,
    488  1.44  christos 					"%d", pt, ptlim, loc);
    489   1.1       mrg 				continue;
    490   1.8  augustss 			case 'V':	/* ISO 8601 week number */
    491   1.8  augustss 			case 'G':	/* ISO 8601 year (four digits) */
    492   1.8  augustss 			case 'g':	/* ISO 8601 year (two digits) */
    493   1.8  augustss /*
    494  1.20   mlelstv ** From Arnold Robbins' strftime version 3.0: "the week number of the
    495   1.8  augustss ** year (the first Monday as the first day of week 1) as a decimal number
    496   1.8  augustss ** (01-53)."
    497   1.8  augustss ** (ado, 1993-05-24)
    498   1.8  augustss **
    499  1.40  christos ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
    500   1.8  augustss ** "Week 01 of a year is per definition the first week which has the
    501   1.8  augustss ** Thursday in this year, which is equivalent to the week which contains
    502   1.8  augustss ** the fourth day of January. In other words, the first week of a new year
    503   1.8  augustss ** is the week which has the majority of its days in the new year. Week 01
    504   1.8  augustss ** might also contain days from the previous year and the week before week
    505   1.8  augustss ** 01 of a year is the last week (52 or 53) of the previous year even if
    506   1.8  augustss ** it contains days from the new year. A week starts with Monday (day 1)
    507  1.20   mlelstv ** and ends with Sunday (day 7). For example, the first week of the year
    508   1.8  augustss ** 1997 lasts from 1996-12-30 to 1997-01-05..."
    509   1.8  augustss ** (ado, 1996-01-02)
    510   1.8  augustss */
    511   1.1       mrg 				{
    512   1.8  augustss 					int	year;
    513  1.20   mlelstv 					int	base;
    514   1.8  augustss 					int	yday;
    515   1.8  augustss 					int	wday;
    516   1.8  augustss 					int	w;
    517   1.8  augustss 
    518  1.20   mlelstv 					year = t->tm_year;
    519  1.20   mlelstv 					base = TM_YEAR_BASE;
    520   1.8  augustss 					yday = t->tm_yday;
    521   1.8  augustss 					wday = t->tm_wday;
    522   1.8  augustss 					for ( ; ; ) {
    523   1.8  augustss 						int	len;
    524   1.8  augustss 						int	bot;
    525   1.8  augustss 						int	top;
    526   1.8  augustss 
    527  1.20   mlelstv 						len = isleap_sum(year, base) ?
    528   1.8  augustss 							DAYSPERLYEAR :
    529   1.8  augustss 							DAYSPERNYEAR;
    530   1.8  augustss 						/*
    531   1.8  augustss 						** What yday (-3 ... 3) does
    532   1.8  augustss 						** the ISO year begin on?
    533   1.8  augustss 						*/
    534   1.8  augustss 						bot = ((yday + 11 - wday) %
    535   1.8  augustss 							DAYSPERWEEK) - 3;
    536   1.8  augustss 						/*
    537   1.8  augustss 						** What yday does the NEXT
    538   1.8  augustss 						** ISO year begin on?
    539   1.8  augustss 						*/
    540   1.8  augustss 						top = bot -
    541   1.8  augustss 							(len % DAYSPERWEEK);
    542   1.8  augustss 						if (top < -3)
    543   1.8  augustss 							top += DAYSPERWEEK;
    544   1.8  augustss 						top += len;
    545   1.8  augustss 						if (yday >= top) {
    546  1.20   mlelstv 							++base;
    547   1.8  augustss 							w = 1;
    548   1.8  augustss 							break;
    549   1.8  augustss 						}
    550   1.8  augustss 						if (yday >= bot) {
    551   1.8  augustss 							w = 1 + ((yday - bot) /
    552   1.8  augustss 								DAYSPERWEEK);
    553   1.8  augustss 							break;
    554   1.8  augustss 						}
    555  1.20   mlelstv 						--base;
    556  1.20   mlelstv 						yday += isleap_sum(year, base) ?
    557   1.8  augustss 							DAYSPERLYEAR :
    558   1.8  augustss 							DAYSPERNYEAR;
    559   1.8  augustss 					}
    560   1.8  augustss #ifdef XPG4_1994_04_09
    561  1.20   mlelstv 					if ((w == 52 &&
    562  1.20   mlelstv 						t->tm_mon == TM_JANUARY) ||
    563  1.20   mlelstv 						(w == 1 &&
    564  1.20   mlelstv 						t->tm_mon == TM_DECEMBER))
    565  1.20   mlelstv 							w = 53;
    566   1.8  augustss #endif /* defined XPG4_1994_04_09 */
    567  1.12    kleink 					if (*format == 'V')
    568  1.44  christos 						pt = _conv(w,
    569  1.44  christos 						    fmt_padding[
    570  1.44  christos 						    PAD_FMT_WEEKOFYEAR][
    571  1.44  christos 						    PadIndex], pt, ptlim, loc);
    572  1.12    kleink 					else if (*format == 'g') {
    573  1.12    kleink 						*warnp = IN_ALL;
    574  1.33  christos 						pt = _yconv(year, base,
    575  1.33  christos 							false, true,
    576  1.44  christos 							pt, ptlim, loc);
    577  1.33  christos 					} else	pt = _yconv(year, base,
    578  1.33  christos 							true, true,
    579  1.44  christos 							pt, ptlim, loc);
    580   1.1       mrg 				}
    581   1.1       mrg 				continue;
    582  1.12    kleink 			case 'v':
    583  1.12    kleink 				/*
    584  1.12    kleink 				** From Arnold Robbins' strftime version 3.0:
    585  1.12    kleink 				** "date as dd-bbb-YYYY"
    586  1.12    kleink 				** (ado, 1993-05-24)
    587  1.12    kleink 				*/
    588  1.25     joerg 				pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
    589  1.25     joerg 				    loc);
    590  1.12    kleink 				continue;
    591   1.1       mrg 			case 'W':
    592  1.12    kleink 				pt = _conv((t->tm_yday + DAYSPERWEEK -
    593  1.44  christos 				    (t->tm_wday ?
    594  1.44  christos 				    (t->tm_wday - 1) :
    595  1.44  christos 				    (DAYSPERWEEK - 1))) / DAYSPERWEEK,
    596  1.44  christos 				    fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
    597  1.44  christos 				    pt, ptlim, loc);
    598   1.1       mrg 				continue;
    599   1.1       mrg 			case 'w':
    600  1.44  christos 				pt = _conv(t->tm_wday, "%d", pt, ptlim, loc);
    601  1.12    kleink 				continue;
    602  1.12    kleink 			case 'X':
    603  1.44  christos 				pt = _fmt(sp, tptr->t_fmt, t, pt,
    604  1.25     joerg 				    ptlim, warnp, loc);
    605   1.1       mrg 				continue;
    606   1.1       mrg 			case 'x':
    607  1.12    kleink 				{
    608  1.40  christos 				enum warn warn2 = IN_SOME;
    609  1.12    kleink 
    610  1.44  christos 				pt = _fmt(sp, tptr->d_fmt, t, pt,
    611  1.25     joerg 				    ptlim, &warn2, loc);
    612  1.12    kleink 				if (warn2 == IN_ALL)
    613  1.12    kleink 					warn2 = IN_THIS;
    614  1.12    kleink 				if (warn2 > *warnp)
    615  1.12    kleink 					*warnp = warn2;
    616  1.12    kleink 				}
    617   1.1       mrg 				continue;
    618   1.1       mrg 			case 'y':
    619  1.12    kleink 				*warnp = IN_ALL;
    620  1.33  christos 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    621  1.33  christos 					false, true,
    622  1.44  christos 					pt, ptlim, loc);
    623   1.1       mrg 				continue;
    624   1.1       mrg 			case 'Y':
    625  1.33  christos 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    626  1.33  christos 					true, true,
    627  1.44  christos 					pt, ptlim, loc);
    628   1.1       mrg 				continue;
    629   1.1       mrg 			case 'Z':
    630  1.11      taca #ifdef TM_ZONE
    631  1.35  christos 				pt = _add(t->TM_ZONE, pt, ptlim);
    632  1.40  christos #elif HAVE_TZNAME
    633  1.45  christos 				if (t->tm_isdst >= 0) {
    634  1.45  christos 					int oerrno = errno, dst = t->tm_isdst;
    635  1.45  christos 					const char *z =
    636  1.45  christos 					    tzgetname(sp, dst);
    637  1.45  christos 					if (z == NULL)
    638  1.45  christos 						z = tzgetname(sp, !dst);
    639  1.46  christos 					if (z != NULL)
    640  1.46  christos 						pt = _add(z, pt, ptlim);
    641  1.45  christos 					errno = oerrno;
    642  1.45  christos 				}
    643  1.40  christos #endif
    644  1.12    kleink 				/*
    645  1.40  christos 				** C99 and later say that %Z must be
    646  1.40  christos 				** replaced by the empty string if the
    647  1.42  christos 				** time zone abbreviation is not
    648  1.42  christos 				** determinable.
    649  1.12    kleink 				*/
    650  1.12    kleink 				continue;
    651  1.12    kleink 			case 'z':
    652  1.47  christos #if defined TM_GMTOFF || USG_COMPAT || ALTZONE
    653  1.12    kleink 				{
    654  1.30  christos 				long		diff;
    655  1.12    kleink 				char const *	sign;
    656  1.39  christos 				bool negative;
    657  1.12    kleink 
    658  1.12    kleink 				if (t->tm_isdst < 0)
    659  1.12    kleink 					continue;
    660  1.38  christos # ifdef TM_GMTOFF
    661  1.12    kleink 				diff = (int)t->TM_GMTOFF;
    662  1.38  christos # else
    663  1.12    kleink 				/*
    664  1.40  christos 				** C99 and later say that the UT offset must
    665  1.12    kleink 				** be computed by looking only at
    666  1.20   mlelstv 				** tm_isdst. This requirement is
    667  1.12    kleink 				** incorrect, since it means the code
    668  1.12    kleink 				** must rely on magic (in this case
    669  1.12    kleink 				** altzone and timezone), and the
    670  1.12    kleink 				** magic might not have the correct
    671  1.20   mlelstv 				** offset. Doing things correctly is
    672  1.40  christos 				** tricky and requires disobeying the standard;
    673  1.12    kleink 				** see GNU C strftime for details.
    674  1.12    kleink 				** For now, punt and conform to the
    675  1.12    kleink 				** standard, even though it's incorrect.
    676  1.12    kleink 				**
    677  1.40  christos 				** C99 and later say that %z must be replaced by
    678  1.40  christos 				** the empty string if the time zone is not
    679  1.12    kleink 				** determinable, so output nothing if the
    680  1.12    kleink 				** appropriate variables are not available.
    681  1.12    kleink 				*/
    682  1.38  christos #  ifndef STD_INSPIRED
    683  1.12    kleink 				if (t->tm_isdst == 0)
    684  1.40  christos #   if USG_COMPAT
    685  1.12    kleink 					diff = -timezone;
    686  1.38  christos #   else
    687  1.12    kleink 					continue;
    688  1.38  christos #   endif
    689  1.12    kleink 				else
    690  1.47  christos #   if ALTZONE
    691  1.12    kleink 					diff = -altzone;
    692  1.38  christos #   else
    693  1.12    kleink 					continue;
    694  1.38  christos #   endif
    695  1.38  christos #  else
    696  1.16    kleink 				{
    697  1.16    kleink 					struct tm tmp;
    698  1.16    kleink 					time_t lct, gct;
    699  1.16    kleink 
    700  1.16    kleink 					/*
    701  1.16    kleink 					** Get calendar time from t
    702  1.16    kleink 					** being treated as local.
    703  1.16    kleink 					*/
    704  1.16    kleink 					tmp = *t; /* mktime discards const */
    705  1.43  christos 					lct = mktime_z(sp, &tmp);
    706  1.16    kleink 
    707  1.16    kleink 					if (lct == (time_t)-1)
    708  1.16    kleink 						continue;
    709  1.16    kleink 
    710  1.16    kleink 					/*
    711  1.16    kleink 					** Get calendar time from t
    712  1.16    kleink 					** being treated as GMT.
    713  1.16    kleink 					**/
    714  1.16    kleink 					tmp = *t; /* mktime discards const */
    715  1.16    kleink 					gct = timegm(&tmp);
    716  1.16    kleink 
    717  1.16    kleink 					if (gct == (time_t)-1)
    718  1.16    kleink 						continue;
    719  1.16    kleink 
    720  1.16    kleink 					/* LINTED difference will fit int */
    721  1.16    kleink 					diff = (intmax_t)gct - (intmax_t)lct;
    722  1.16    kleink 				}
    723  1.38  christos #  endif
    724  1.38  christos # endif
    725  1.39  christos 				negative = diff < 0;
    726  1.39  christos 				if (diff == 0) {
    727  1.50  christos # ifdef TM_ZONE
    728  1.39  christos 				  negative = t->TM_ZONE[0] == '-';
    729  1.50  christos # else
    730  1.40  christos 				  negative = t->tm_isdst < 0;
    731  1.50  christos #  if HAVE_TZNAME
    732  1.40  christos 				  if (tzname[t->tm_isdst != 0][0] == '-')
    733  1.40  christos 				    negative = true;
    734  1.50  christos #  endif
    735  1.40  christos # endif
    736  1.39  christos 				}
    737  1.39  christos 				if (negative) {
    738  1.12    kleink 					sign = "-";
    739  1.12    kleink 					diff = -diff;
    740  1.12    kleink 				} else	sign = "+";
    741  1.12    kleink 				pt = _add(sign, pt, ptlim);
    742  1.20   mlelstv 				diff /= SECSPERMIN;
    743  1.20   mlelstv 				diff = (diff / MINSPERHOUR) * 100 +
    744  1.20   mlelstv 					(diff % MINSPERHOUR);
    745  1.30  christos 				_DIAGASSERT(__type_fit(int, diff));
    746  1.44  christos 				pt = _conv((int)diff,
    747  1.44  christos 				    fmt_padding[PAD_FMT_YEAR][PadIndex],
    748  1.44  christos 				    pt, ptlim, loc);
    749  1.12    kleink 				}
    750  1.38  christos #endif
    751   1.1       mrg 				continue;
    752  1.12    kleink 			case '+':
    753  1.44  christos #ifdef notyet
    754  1.44  christos 				/* XXX: no date_fmt in _TimeLocale */
    755  1.44  christos 				pt = _fmt(sp, tptr->date_fmt, t,
    756  1.25     joerg 				    pt, ptlim, warnp, loc);
    757  1.44  christos #else
    758  1.44  christos 				pt = _fmt(sp, "%a %b %e %H:%M:%S %Z %Y", t,
    759  1.44  christos 				    pt, ptlim, warnp, loc);
    760  1.44  christos #endif
    761  1.12    kleink 				continue;
    762  1.44  christos 			case '-':
    763  1.44  christos 				if (PadIndex != PAD_DEFAULT)
    764  1.44  christos 					break;
    765  1.44  christos 				PadIndex = PAD_LESS;
    766  1.44  christos 				goto label;
    767  1.44  christos 			case '_':
    768  1.44  christos 				if (PadIndex != PAD_DEFAULT)
    769  1.44  christos 					break;
    770  1.44  christos 				PadIndex = PAD_SPACE;
    771  1.44  christos 				goto label;
    772  1.44  christos 			case '0':
    773  1.44  christos 				if (PadIndex != PAD_DEFAULT)
    774  1.44  christos 					break;
    775  1.44  christos 				PadIndex = PAD_ZERO;
    776  1.44  christos 				goto label;
    777   1.1       mrg 			case '%':
    778   1.1       mrg 				break;
    779   1.1       mrg 			}
    780   1.1       mrg 		}
    781  1.12    kleink 		if (pt == ptlim)
    782  1.12    kleink 			break;
    783  1.12    kleink 		*pt++ = *format;
    784   1.1       mrg 	}
    785  1.12    kleink 	return pt;
    786   1.1       mrg }
    787   1.1       mrg 
    788  1.21  christos size_t
    789  1.52  christos strftime(char *restrict s, size_t maxsize, char const *restrict format,
    790  1.52  christos 	 struct tm const *restrict t)
    791  1.21  christos {
    792  1.43  christos 	size_t r;
    793  1.43  christos 
    794  1.43  christos 	rwlock_wrlock(&__lcl_lock);
    795  1.43  christos 	tzset_unlocked();
    796  1.43  christos 	r = strftime_z(__lclptr, s, maxsize, format, t);
    797  1.43  christos 	rwlock_unlock(&__lcl_lock);
    798  1.43  christos 
    799  1.43  christos 	return r;
    800  1.21  christos }
    801  1.21  christos 
    802  1.25     joerg size_t
    803  1.25     joerg strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
    804  1.25     joerg     const struct tm * __restrict t, locale_t loc)
    805  1.25     joerg {
    806  1.43  christos 	size_t r;
    807  1.43  christos 
    808  1.43  christos 	rwlock_wrlock(&__lcl_lock);
    809  1.43  christos 	tzset_unlocked();
    810  1.43  christos 	r = strftime_lz(__lclptr, s, maxsize, format, t, loc);
    811  1.43  christos 	rwlock_unlock(&__lcl_lock);
    812  1.43  christos 
    813  1.43  christos 	return r;
    814  1.25     joerg }
    815  1.25     joerg 
    816  1.12    kleink static char *
    817  1.44  christos _conv(int n, const char *format, char *pt, const char *ptlim, locale_t loc)
    818   1.1       mrg {
    819  1.12    kleink 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
    820   1.1       mrg 
    821  1.44  christos 	(void) snprintf_l(buf, sizeof(buf), loc, format, n);
    822  1.12    kleink 	return _add(buf, pt, ptlim);
    823   1.1       mrg }
    824   1.1       mrg 
    825  1.12    kleink static char *
    826  1.34  christos _add(const char *str, char *pt, const char *ptlim)
    827   1.1       mrg {
    828  1.12    kleink 	while (pt < ptlim && (*pt = *str++) != '\0')
    829  1.12    kleink 		++pt;
    830  1.12    kleink 	return pt;
    831   1.1       mrg }
    832  1.20   mlelstv 
    833  1.20   mlelstv /*
    834  1.20   mlelstv ** POSIX and the C Standard are unclear or inconsistent about
    835  1.20   mlelstv ** what %C and %y do if the year is negative or exceeds 9999.
    836  1.20   mlelstv ** Use the convention that %C concatenated with %y yields the
    837  1.20   mlelstv ** same output as %Y, and that %Y contains at least 4 bytes,
    838  1.20   mlelstv ** with more only if necessary.
    839  1.20   mlelstv */
    840  1.20   mlelstv 
    841  1.20   mlelstv static char *
    842  1.33  christos _yconv(int a, int b, bool convert_top, bool convert_yy,
    843  1.44  christos     char *pt, const char * ptlim, locale_t loc)
    844  1.20   mlelstv {
    845  1.28  christos 	int	lead;
    846  1.28  christos 	int	trail;
    847  1.20   mlelstv 
    848  1.50  christos 	int DIVISOR = 100;
    849  1.20   mlelstv 	trail = a % DIVISOR + b % DIVISOR;
    850  1.20   mlelstv 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
    851  1.20   mlelstv 	trail %= DIVISOR;
    852  1.20   mlelstv 	if (trail < 0 && lead > 0) {
    853  1.20   mlelstv 		trail += DIVISOR;
    854  1.20   mlelstv 		--lead;
    855  1.20   mlelstv 	} else if (lead < 0 && trail > 0) {
    856  1.20   mlelstv 		trail -= DIVISOR;
    857  1.20   mlelstv 		++lead;
    858  1.20   mlelstv 	}
    859  1.20   mlelstv 	if (convert_top) {
    860  1.20   mlelstv 		if (lead == 0 && trail < 0)
    861  1.20   mlelstv 			pt = _add("-0", pt, ptlim);
    862  1.44  christos 		else	pt = _conv(lead, "%02d", pt, ptlim, loc);
    863  1.20   mlelstv 	}
    864  1.20   mlelstv 	if (convert_yy)
    865  1.44  christos 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim,
    866  1.44  christos 		    loc);
    867  1.20   mlelstv 	return pt;
    868  1.20   mlelstv }
    869