strftime.c revision 1.54 1 1.54 christos /* $NetBSD: strftime.c,v 1.54 2024/02/17 14:54:47 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.54 christos __RCSID("$NetBSD: strftime.c,v 1.54 2024/02/17 14:54:47 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.40 christos #ifndef DEPRECATE_TWO_DIGIT_YEARS
73 1.40 christos # define DEPRECATE_TWO_DIGIT_YEARS false
74 1.40 christos #endif
75 1.12 kleink
76 1.21 christos #ifdef __weak_alias
77 1.25 joerg __weak_alias(strftime_l, _strftime_l)
78 1.25 joerg __weak_alias(strftime_lz, _strftime_lz)
79 1.21 christos __weak_alias(strftime_z, _strftime_z)
80 1.21 christos #endif
81 1.21 christos
82 1.12 kleink #include "sys/localedef.h"
83 1.25 joerg #define _TIME_LOCALE(loc) \
84 1.25 joerg ((_TimeLocale *)((loc)->part_impl[(size_t)LC_TIME]))
85 1.20 mlelstv #define c_fmt d_t_fmt
86 1.12 kleink
87 1.40 christos enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
88 1.40 christos
89 1.20 mlelstv static char * _add(const char *, char *, const char *);
90 1.44 christos static char * _conv(int, const char *, char *, const char *, locale_t);
91 1.21 christos static char * _fmt(const timezone_t, const char *, const struct tm *, char *,
92 1.40 christos const char *, enum warn *, locale_t);
93 1.44 christos static char * _yconv(int, int, bool, bool, char *, const char *, locale_t);
94 1.12 kleink
95 1.12 kleink #ifndef YEAR_2000_NAME
96 1.50 christos # define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
97 1.12 kleink #endif /* !defined YEAR_2000_NAME */
98 1.12 kleink
99 1.44 christos #define IN_NONE 0
100 1.44 christos #define IN_SOME 1
101 1.44 christos #define IN_THIS 2
102 1.44 christos #define IN_ALL 3
103 1.44 christos
104 1.44 christos #define PAD_DEFAULT 0
105 1.44 christos #define PAD_LESS 1
106 1.44 christos #define PAD_SPACE 2
107 1.44 christos #define PAD_ZERO 3
108 1.44 christos
109 1.44 christos static const char fmt_padding[][4][5] = {
110 1.44 christos /* DEFAULT, LESS, SPACE, ZERO */
111 1.44 christos #define PAD_FMT_MONTHDAY 0
112 1.44 christos #define PAD_FMT_HMS 0
113 1.44 christos #define PAD_FMT_CENTURY 0
114 1.44 christos #define PAD_FMT_SHORTYEAR 0
115 1.44 christos #define PAD_FMT_MONTH 0
116 1.44 christos #define PAD_FMT_WEEKOFYEAR 0
117 1.44 christos #define PAD_FMT_DAYOFMONTH 0
118 1.44 christos { "%02d", "%d", "%2d", "%02d" },
119 1.44 christos #define PAD_FMT_SDAYOFMONTH 1
120 1.44 christos #define PAD_FMT_SHMS 1
121 1.44 christos { "%2d", "%d", "%2d", "%02d" },
122 1.44 christos #define PAD_FMT_DAYOFYEAR 2
123 1.44 christos { "%03d", "%d", "%3d", "%03d" },
124 1.44 christos #define PAD_FMT_YEAR 3
125 1.44 christos { "%04d", "%d", "%4d", "%04d" }
126 1.44 christos };
127 1.44 christos
128 1.1 mrg size_t
129 1.25 joerg strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize,
130 1.25 joerg const char * __restrict format, const struct tm * __restrict t)
131 1.25 joerg {
132 1.26 joerg return strftime_lz(sp, s, maxsize, format, t, _current_locale());
133 1.25 joerg }
134 1.25 joerg
135 1.33 christos #if HAVE_STRFTIME_L
136 1.33 christos size_t
137 1.52 christos strftime_l(char *restrict s, size_t maxsize, char const *restrict format,
138 1.52 christos struct tm const *restrict t,
139 1.51 christos ATTRIBUTE_MAYBE_UNUSED locale_t locale)
140 1.33 christos {
141 1.33 christos /* Just call strftime, as only the C locale is supported. */
142 1.33 christos return strftime(s, maxsize, format, t);
143 1.33 christos }
144 1.33 christos #endif
145 1.33 christos
146 1.25 joerg size_t
147 1.25 joerg strftime_lz(const timezone_t sp, char *const s, const size_t maxsize,
148 1.25 joerg const char *const format, const struct tm *const t, locale_t loc)
149 1.1 mrg {
150 1.12 kleink char * p;
151 1.48 christos int saved_errno = errno;
152 1.40 christos enum warn warn = IN_NONE;
153 1.5 kleink
154 1.37 christos p = _fmt(sp, format, t, s, s + maxsize, &warn, loc);
155 1.48 christos if (!p) {
156 1.48 christos errno = EOVERFLOW;
157 1.48 christos return 0;
158 1.48 christos }
159 1.40 christos if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS
160 1.40 christos && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
161 1.12 kleink (void) fprintf(stderr, "\n");
162 1.37 christos (void) fprintf(stderr, "strftime format \"%s\" ", format);
163 1.12 kleink (void) fprintf(stderr, "yields only two digits of years in ");
164 1.12 kleink if (warn == IN_SOME)
165 1.12 kleink (void) fprintf(stderr, "some locales");
166 1.12 kleink else if (warn == IN_THIS)
167 1.12 kleink (void) fprintf(stderr, "the current locale");
168 1.12 kleink else (void) fprintf(stderr, "all locales");
169 1.12 kleink (void) fprintf(stderr, "\n");
170 1.12 kleink }
171 1.48 christos if (p == s + maxsize) {
172 1.48 christos errno = ERANGE;
173 1.12 kleink return 0;
174 1.48 christos }
175 1.12 kleink *p = '\0';
176 1.48 christos errno = saved_errno;
177 1.12 kleink return p - s;
178 1.1 mrg }
179 1.1 mrg
180 1.12 kleink static char *
181 1.34 christos _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
182 1.40 christos const char *ptlim, enum warn *warnp, locale_t loc)
183 1.1 mrg {
184 1.44 christos int Ealternative, Oalternative, PadIndex;
185 1.44 christos _TimeLocale *tptr = _TIME_LOCALE(loc);
186 1.44 christos
187 1.12 kleink for ( ; *format; ++format) {
188 1.1 mrg if (*format == '%') {
189 1.44 christos Ealternative = 0;
190 1.44 christos Oalternative = 0;
191 1.44 christos PadIndex = PAD_DEFAULT;
192 1.12 kleink label:
193 1.12 kleink switch (*++format) {
194 1.1 mrg case '\0':
195 1.1 mrg --format;
196 1.1 mrg break;
197 1.1 mrg case 'A':
198 1.12 kleink pt = _add((t->tm_wday < 0 ||
199 1.12 kleink t->tm_wday >= DAYSPERWEEK) ?
200 1.44 christos "?" : tptr->day[t->tm_wday],
201 1.12 kleink pt, ptlim);
202 1.1 mrg continue;
203 1.1 mrg case 'a':
204 1.12 kleink pt = _add((t->tm_wday < 0 ||
205 1.12 kleink t->tm_wday >= DAYSPERWEEK) ?
206 1.44 christos "?" : tptr->abday[t->tm_wday],
207 1.12 kleink pt, ptlim);
208 1.1 mrg continue;
209 1.1 mrg case 'B':
210 1.12 kleink pt = _add((t->tm_mon < 0 ||
211 1.12 kleink t->tm_mon >= MONSPERYEAR) ?
212 1.44 christos "?" :
213 1.44 christos /* no alt_month in _TimeLocale */
214 1.44 christos (Oalternative ? tptr->mon/*alt_month*/:
215 1.44 christos tptr->mon)[t->tm_mon],
216 1.12 kleink pt, ptlim);
217 1.1 mrg continue;
218 1.1 mrg case 'b':
219 1.1 mrg case 'h':
220 1.12 kleink pt = _add((t->tm_mon < 0 ||
221 1.12 kleink t->tm_mon >= MONSPERYEAR) ?
222 1.44 christos "?" : tptr->abmon[t->tm_mon],
223 1.12 kleink pt, ptlim);
224 1.1 mrg continue;
225 1.1 mrg case 'C':
226 1.12 kleink /*
227 1.12 kleink ** %C used to do a...
228 1.12 kleink ** _fmt("%a %b %e %X %Y", t);
229 1.12 kleink ** ...whereas now POSIX 1003.2 calls for
230 1.12 kleink ** something completely different.
231 1.12 kleink ** (ado, 1993-05-24)
232 1.12 kleink */
233 1.33 christos pt = _yconv(t->tm_year, TM_YEAR_BASE,
234 1.44 christos true, false, pt, ptlim, loc);
235 1.1 mrg continue;
236 1.1 mrg case 'c':
237 1.12 kleink {
238 1.40 christos enum warn warn2 = IN_SOME;
239 1.12 kleink
240 1.44 christos pt = _fmt(sp, tptr->c_fmt, t, pt,
241 1.25 joerg ptlim, &warn2, loc);
242 1.12 kleink if (warn2 == IN_ALL)
243 1.12 kleink warn2 = IN_THIS;
244 1.12 kleink if (warn2 > *warnp)
245 1.12 kleink *warnp = warn2;
246 1.12 kleink }
247 1.1 mrg continue;
248 1.1 mrg case 'D':
249 1.25 joerg pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
250 1.25 joerg loc);
251 1.1 mrg continue;
252 1.1 mrg case 'd':
253 1.44 christos pt = _conv(t->tm_mday,
254 1.44 christos fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex],
255 1.44 christos pt, ptlim, loc);
256 1.1 mrg continue;
257 1.12 kleink case 'E':
258 1.44 christos if (Ealternative || Oalternative)
259 1.44 christos break;
260 1.44 christos Ealternative++;
261 1.44 christos goto label;
262 1.12 kleink case 'O':
263 1.12 kleink /*
264 1.40 christos ** Locale modifiers of C99 and later.
265 1.12 kleink ** The sequences
266 1.12 kleink ** %Ec %EC %Ex %EX %Ey %EY
267 1.12 kleink ** %Od %oe %OH %OI %Om %OM
268 1.12 kleink ** %OS %Ou %OU %OV %Ow %OW %Oy
269 1.41 christos ** are supposed to provide alternative
270 1.12 kleink ** representations.
271 1.12 kleink */
272 1.44 christos if (Ealternative || Oalternative)
273 1.44 christos break;
274 1.44 christos Oalternative++;
275 1.12 kleink goto label;
276 1.1 mrg case 'e':
277 1.44 christos pt = _conv(t->tm_mday,
278 1.44 christos fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex],
279 1.44 christos pt, ptlim, loc);
280 1.10 kleink continue;
281 1.10 kleink case 'F':
282 1.25 joerg pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
283 1.25 joerg loc);
284 1.1 mrg continue;
285 1.1 mrg case 'H':
286 1.44 christos pt = _conv(t->tm_hour,
287 1.44 christos fmt_padding[PAD_FMT_HMS][PadIndex],
288 1.44 christos pt, ptlim, loc);
289 1.1 mrg continue;
290 1.1 mrg case 'I':
291 1.12 kleink pt = _conv((t->tm_hour % 12) ?
292 1.44 christos (t->tm_hour % 12) : 12,
293 1.44 christos fmt_padding[PAD_FMT_HMS][PadIndex],
294 1.44 christos pt, ptlim, loc);
295 1.1 mrg continue;
296 1.1 mrg case 'j':
297 1.44 christos pt = _conv(t->tm_yday + 1,
298 1.44 christos fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex],
299 1.44 christos pt, ptlim, loc);
300 1.1 mrg continue;
301 1.1 mrg case 'k':
302 1.12 kleink /*
303 1.12 kleink ** This used to be...
304 1.12 kleink ** _conv(t->tm_hour % 12 ?
305 1.12 kleink ** t->tm_hour % 12 : 12, 2, ' ');
306 1.12 kleink ** ...and has been changed to the below to
307 1.12 kleink ** match SunOS 4.1.1 and Arnold Robbins'
308 1.20 mlelstv ** strftime version 3.0. That is, "%k" and
309 1.12 kleink ** "%l" have been swapped.
310 1.12 kleink ** (ado, 1993-05-24)
311 1.12 kleink */
312 1.44 christos pt = _conv(t->tm_hour,
313 1.44 christos fmt_padding[PAD_FMT_SHMS][PadIndex],
314 1.44 christos pt, ptlim, loc);
315 1.12 kleink continue;
316 1.12 kleink #ifdef KITCHEN_SINK
317 1.12 kleink case 'K':
318 1.12 kleink /*
319 1.12 kleink ** After all this time, still unclaimed!
320 1.12 kleink */
321 1.12 kleink pt = _add("kitchen sink", pt, ptlim);
322 1.1 mrg continue;
323 1.12 kleink #endif /* defined KITCHEN_SINK */
324 1.1 mrg case 'l':
325 1.12 kleink /*
326 1.12 kleink ** This used to be...
327 1.12 kleink ** _conv(t->tm_hour, 2, ' ');
328 1.12 kleink ** ...and has been changed to the below to
329 1.12 kleink ** match SunOS 4.1.1 and Arnold Robbin's
330 1.20 mlelstv ** strftime version 3.0. That is, "%k" and
331 1.12 kleink ** "%l" have been swapped.
332 1.12 kleink ** (ado, 1993-05-24)
333 1.12 kleink */
334 1.12 kleink pt = _conv((t->tm_hour % 12) ?
335 1.12 kleink (t->tm_hour % 12) : 12,
336 1.44 christos fmt_padding[PAD_FMT_SHMS][PadIndex],
337 1.44 christos pt, ptlim, loc);
338 1.1 mrg continue;
339 1.1 mrg case 'M':
340 1.44 christos pt = _conv(t->tm_min,
341 1.44 christos fmt_padding[PAD_FMT_HMS][PadIndex],
342 1.44 christos pt, ptlim, loc);
343 1.1 mrg continue;
344 1.1 mrg case 'm':
345 1.44 christos pt = _conv(t->tm_mon + 1,
346 1.44 christos fmt_padding[PAD_FMT_MONTH][PadIndex],
347 1.44 christos pt, ptlim, loc);
348 1.1 mrg continue;
349 1.1 mrg case 'n':
350 1.12 kleink pt = _add("\n", pt, ptlim);
351 1.1 mrg continue;
352 1.1 mrg case 'p':
353 1.12 kleink pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
354 1.44 christos tptr->am_pm[1] :
355 1.44 christos tptr->am_pm[0],
356 1.12 kleink pt, ptlim);
357 1.1 mrg continue;
358 1.1 mrg case 'R':
359 1.25 joerg pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
360 1.25 joerg loc);
361 1.1 mrg continue;
362 1.1 mrg case 'r':
363 1.44 christos pt = _fmt(sp, tptr->t_fmt_ampm, t,
364 1.25 joerg pt, ptlim, warnp, loc);
365 1.1 mrg continue;
366 1.1 mrg case 'S':
367 1.44 christos pt = _conv(t->tm_sec,
368 1.44 christos fmt_padding[PAD_FMT_HMS][PadIndex],
369 1.44 christos pt, ptlim, loc);
370 1.1 mrg continue;
371 1.1 mrg case 's':
372 1.12 kleink {
373 1.12 kleink struct tm tm;
374 1.12 kleink char buf[INT_STRLEN_MAXIMUM(
375 1.12 kleink time_t) + 1];
376 1.12 kleink time_t mkt;
377 1.12 kleink
378 1.51 christos tm.tm_sec = t->tm_sec;
379 1.51 christos tm.tm_min = t->tm_min;
380 1.51 christos tm.tm_hour = t->tm_hour;
381 1.51 christos tm.tm_mday = t->tm_mday;
382 1.51 christos tm.tm_mon = t->tm_mon;
383 1.51 christos tm.tm_year = t->tm_year;
384 1.54 christos #ifdef TM_GMTOFF
385 1.54 christos mkt = timeoff(&tm, t->TM_GMTOFF);
386 1.54 christos #else
387 1.51 christos tm.tm_isdst = t->tm_isdst;
388 1.51 christos #if defined TM_GMTOFF && ! UNINIT_TRAP
389 1.51 christos tm.TM_GMTOFF = t->TM_GMTOFF;
390 1.51 christos #endif
391 1.43 christos mkt = mktime_z(sp, &tm);
392 1.54 christos #endif
393 1.51 christos /* If mktime fails, %s expands to the
394 1.51 christos value of (time_t) -1 as a failure
395 1.51 christos marker; this is better in practice
396 1.51 christos than strftime failing. */
397 1.12 kleink /* CONSTCOND */
398 1.49 christos if (TYPE_SIGNED(time_t)) {
399 1.49 christos intmax_t n = mkt;
400 1.27 christos (void)snprintf(buf, sizeof(buf),
401 1.49 christos "%"PRIdMAX, n);
402 1.49 christos } else {
403 1.53 christos /*LINTED possibly unreached*/
404 1.49 christos uintmax_t n = mkt;
405 1.49 christos (void)snprintf(buf, sizeof(buf),
406 1.49 christos "%"PRIuMAX, n);
407 1.49 christos }
408 1.12 kleink pt = _add(buf, pt, ptlim);
409 1.12 kleink }
410 1.1 mrg continue;
411 1.1 mrg case 'T':
412 1.25 joerg pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
413 1.25 joerg loc);
414 1.1 mrg continue;
415 1.1 mrg case 't':
416 1.12 kleink pt = _add("\t", pt, ptlim);
417 1.1 mrg continue;
418 1.1 mrg case 'U':
419 1.12 kleink pt = _conv((t->tm_yday + DAYSPERWEEK -
420 1.44 christos t->tm_wday) / DAYSPERWEEK,
421 1.44 christos fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
422 1.44 christos pt, ptlim, loc);
423 1.1 mrg continue;
424 1.1 mrg case 'u':
425 1.12 kleink /*
426 1.12 kleink ** From Arnold Robbins' strftime version 3.0:
427 1.12 kleink ** "ISO 8601: Weekday as a decimal number
428 1.12 kleink ** [1 (Monday) - 7]"
429 1.12 kleink ** (ado, 1993-05-24)
430 1.12 kleink */
431 1.12 kleink pt = _conv((t->tm_wday == 0) ?
432 1.12 kleink DAYSPERWEEK : t->tm_wday,
433 1.44 christos "%d", pt, ptlim, loc);
434 1.1 mrg continue;
435 1.8 augustss case 'V': /* ISO 8601 week number */
436 1.8 augustss case 'G': /* ISO 8601 year (four digits) */
437 1.8 augustss case 'g': /* ISO 8601 year (two digits) */
438 1.8 augustss /*
439 1.20 mlelstv ** From Arnold Robbins' strftime version 3.0: "the week number of the
440 1.8 augustss ** year (the first Monday as the first day of week 1) as a decimal number
441 1.8 augustss ** (01-53)."
442 1.8 augustss ** (ado, 1993-05-24)
443 1.8 augustss **
444 1.40 christos ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
445 1.8 augustss ** "Week 01 of a year is per definition the first week which has the
446 1.8 augustss ** Thursday in this year, which is equivalent to the week which contains
447 1.8 augustss ** the fourth day of January. In other words, the first week of a new year
448 1.8 augustss ** is the week which has the majority of its days in the new year. Week 01
449 1.8 augustss ** might also contain days from the previous year and the week before week
450 1.8 augustss ** 01 of a year is the last week (52 or 53) of the previous year even if
451 1.8 augustss ** it contains days from the new year. A week starts with Monday (day 1)
452 1.20 mlelstv ** and ends with Sunday (day 7). For example, the first week of the year
453 1.8 augustss ** 1997 lasts from 1996-12-30 to 1997-01-05..."
454 1.8 augustss ** (ado, 1996-01-02)
455 1.8 augustss */
456 1.1 mrg {
457 1.8 augustss int year;
458 1.20 mlelstv int base;
459 1.8 augustss int yday;
460 1.8 augustss int wday;
461 1.8 augustss int w;
462 1.8 augustss
463 1.20 mlelstv year = t->tm_year;
464 1.20 mlelstv base = TM_YEAR_BASE;
465 1.8 augustss yday = t->tm_yday;
466 1.8 augustss wday = t->tm_wday;
467 1.8 augustss for ( ; ; ) {
468 1.8 augustss int len;
469 1.8 augustss int bot;
470 1.8 augustss int top;
471 1.8 augustss
472 1.20 mlelstv len = isleap_sum(year, base) ?
473 1.8 augustss DAYSPERLYEAR :
474 1.8 augustss DAYSPERNYEAR;
475 1.8 augustss /*
476 1.8 augustss ** What yday (-3 ... 3) does
477 1.8 augustss ** the ISO year begin on?
478 1.8 augustss */
479 1.8 augustss bot = ((yday + 11 - wday) %
480 1.8 augustss DAYSPERWEEK) - 3;
481 1.8 augustss /*
482 1.8 augustss ** What yday does the NEXT
483 1.8 augustss ** ISO year begin on?
484 1.8 augustss */
485 1.8 augustss top = bot -
486 1.8 augustss (len % DAYSPERWEEK);
487 1.8 augustss if (top < -3)
488 1.8 augustss top += DAYSPERWEEK;
489 1.8 augustss top += len;
490 1.8 augustss if (yday >= top) {
491 1.20 mlelstv ++base;
492 1.8 augustss w = 1;
493 1.8 augustss break;
494 1.8 augustss }
495 1.8 augustss if (yday >= bot) {
496 1.8 augustss w = 1 + ((yday - bot) /
497 1.8 augustss DAYSPERWEEK);
498 1.8 augustss break;
499 1.8 augustss }
500 1.20 mlelstv --base;
501 1.20 mlelstv yday += isleap_sum(year, base) ?
502 1.8 augustss DAYSPERLYEAR :
503 1.8 augustss DAYSPERNYEAR;
504 1.8 augustss }
505 1.8 augustss #ifdef XPG4_1994_04_09
506 1.20 mlelstv if ((w == 52 &&
507 1.20 mlelstv t->tm_mon == TM_JANUARY) ||
508 1.20 mlelstv (w == 1 &&
509 1.20 mlelstv t->tm_mon == TM_DECEMBER))
510 1.20 mlelstv w = 53;
511 1.8 augustss #endif /* defined XPG4_1994_04_09 */
512 1.12 kleink if (*format == 'V')
513 1.44 christos pt = _conv(w,
514 1.44 christos fmt_padding[
515 1.44 christos PAD_FMT_WEEKOFYEAR][
516 1.44 christos PadIndex], pt, ptlim, loc);
517 1.12 kleink else if (*format == 'g') {
518 1.12 kleink *warnp = IN_ALL;
519 1.33 christos pt = _yconv(year, base,
520 1.33 christos false, true,
521 1.44 christos pt, ptlim, loc);
522 1.33 christos } else pt = _yconv(year, base,
523 1.33 christos true, true,
524 1.44 christos pt, ptlim, loc);
525 1.1 mrg }
526 1.1 mrg continue;
527 1.12 kleink case 'v':
528 1.12 kleink /*
529 1.12 kleink ** From Arnold Robbins' strftime version 3.0:
530 1.12 kleink ** "date as dd-bbb-YYYY"
531 1.12 kleink ** (ado, 1993-05-24)
532 1.12 kleink */
533 1.25 joerg pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
534 1.25 joerg loc);
535 1.12 kleink continue;
536 1.1 mrg case 'W':
537 1.12 kleink pt = _conv((t->tm_yday + DAYSPERWEEK -
538 1.44 christos (t->tm_wday ?
539 1.44 christos (t->tm_wday - 1) :
540 1.44 christos (DAYSPERWEEK - 1))) / DAYSPERWEEK,
541 1.44 christos fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
542 1.44 christos pt, ptlim, loc);
543 1.1 mrg continue;
544 1.1 mrg case 'w':
545 1.44 christos pt = _conv(t->tm_wday, "%d", pt, ptlim, loc);
546 1.12 kleink continue;
547 1.12 kleink case 'X':
548 1.44 christos pt = _fmt(sp, tptr->t_fmt, t, pt,
549 1.25 joerg ptlim, warnp, loc);
550 1.1 mrg continue;
551 1.1 mrg case 'x':
552 1.12 kleink {
553 1.40 christos enum warn warn2 = IN_SOME;
554 1.12 kleink
555 1.44 christos pt = _fmt(sp, tptr->d_fmt, t, pt,
556 1.25 joerg ptlim, &warn2, loc);
557 1.12 kleink if (warn2 == IN_ALL)
558 1.12 kleink warn2 = IN_THIS;
559 1.12 kleink if (warn2 > *warnp)
560 1.12 kleink *warnp = warn2;
561 1.12 kleink }
562 1.1 mrg continue;
563 1.1 mrg case 'y':
564 1.12 kleink *warnp = IN_ALL;
565 1.33 christos pt = _yconv(t->tm_year, TM_YEAR_BASE,
566 1.33 christos false, true,
567 1.44 christos pt, ptlim, loc);
568 1.1 mrg continue;
569 1.1 mrg case 'Y':
570 1.33 christos pt = _yconv(t->tm_year, TM_YEAR_BASE,
571 1.33 christos true, true,
572 1.44 christos pt, ptlim, loc);
573 1.1 mrg continue;
574 1.1 mrg case 'Z':
575 1.11 taca #ifdef TM_ZONE
576 1.35 christos pt = _add(t->TM_ZONE, pt, ptlim);
577 1.40 christos #elif HAVE_TZNAME
578 1.45 christos if (t->tm_isdst >= 0) {
579 1.45 christos int oerrno = errno, dst = t->tm_isdst;
580 1.45 christos const char *z =
581 1.45 christos tzgetname(sp, dst);
582 1.45 christos if (z == NULL)
583 1.45 christos z = tzgetname(sp, !dst);
584 1.46 christos if (z != NULL)
585 1.46 christos pt = _add(z, pt, ptlim);
586 1.45 christos errno = oerrno;
587 1.45 christos }
588 1.40 christos #endif
589 1.12 kleink /*
590 1.40 christos ** C99 and later say that %Z must be
591 1.40 christos ** replaced by the empty string if the
592 1.42 christos ** time zone abbreviation is not
593 1.42 christos ** determinable.
594 1.12 kleink */
595 1.12 kleink continue;
596 1.12 kleink case 'z':
597 1.47 christos #if defined TM_GMTOFF || USG_COMPAT || ALTZONE
598 1.12 kleink {
599 1.30 christos long diff;
600 1.12 kleink char const * sign;
601 1.39 christos bool negative;
602 1.12 kleink
603 1.12 kleink if (t->tm_isdst < 0)
604 1.12 kleink continue;
605 1.38 christos # ifdef TM_GMTOFF
606 1.12 kleink diff = (int)t->TM_GMTOFF;
607 1.38 christos # else
608 1.12 kleink /*
609 1.40 christos ** C99 and later say that the UT offset must
610 1.12 kleink ** be computed by looking only at
611 1.20 mlelstv ** tm_isdst. This requirement is
612 1.12 kleink ** incorrect, since it means the code
613 1.12 kleink ** must rely on magic (in this case
614 1.12 kleink ** altzone and timezone), and the
615 1.12 kleink ** magic might not have the correct
616 1.20 mlelstv ** offset. Doing things correctly is
617 1.40 christos ** tricky and requires disobeying the standard;
618 1.12 kleink ** see GNU C strftime for details.
619 1.12 kleink ** For now, punt and conform to the
620 1.12 kleink ** standard, even though it's incorrect.
621 1.12 kleink **
622 1.40 christos ** C99 and later say that %z must be replaced by
623 1.40 christos ** the empty string if the time zone is not
624 1.12 kleink ** determinable, so output nothing if the
625 1.12 kleink ** appropriate variables are not available.
626 1.12 kleink */
627 1.38 christos # ifndef STD_INSPIRED
628 1.12 kleink if (t->tm_isdst == 0)
629 1.40 christos # if USG_COMPAT
630 1.12 kleink diff = -timezone;
631 1.38 christos # else
632 1.12 kleink continue;
633 1.38 christos # endif
634 1.12 kleink else
635 1.47 christos # if ALTZONE
636 1.12 kleink diff = -altzone;
637 1.38 christos # else
638 1.12 kleink continue;
639 1.38 christos # endif
640 1.38 christos # else
641 1.16 kleink {
642 1.16 kleink struct tm tmp;
643 1.16 kleink time_t lct, gct;
644 1.16 kleink
645 1.16 kleink /*
646 1.16 kleink ** Get calendar time from t
647 1.16 kleink ** being treated as local.
648 1.16 kleink */
649 1.16 kleink tmp = *t; /* mktime discards const */
650 1.43 christos lct = mktime_z(sp, &tmp);
651 1.16 kleink
652 1.16 kleink if (lct == (time_t)-1)
653 1.16 kleink continue;
654 1.16 kleink
655 1.16 kleink /*
656 1.16 kleink ** Get calendar time from t
657 1.16 kleink ** being treated as GMT.
658 1.16 kleink **/
659 1.16 kleink tmp = *t; /* mktime discards const */
660 1.16 kleink gct = timegm(&tmp);
661 1.16 kleink
662 1.16 kleink if (gct == (time_t)-1)
663 1.16 kleink continue;
664 1.16 kleink
665 1.16 kleink /* LINTED difference will fit int */
666 1.16 kleink diff = (intmax_t)gct - (intmax_t)lct;
667 1.16 kleink }
668 1.38 christos # endif
669 1.38 christos # endif
670 1.39 christos negative = diff < 0;
671 1.39 christos if (diff == 0) {
672 1.50 christos # ifdef TM_ZONE
673 1.39 christos negative = t->TM_ZONE[0] == '-';
674 1.50 christos # else
675 1.40 christos negative = t->tm_isdst < 0;
676 1.50 christos # if HAVE_TZNAME
677 1.40 christos if (tzname[t->tm_isdst != 0][0] == '-')
678 1.40 christos negative = true;
679 1.50 christos # endif
680 1.40 christos # endif
681 1.39 christos }
682 1.39 christos if (negative) {
683 1.12 kleink sign = "-";
684 1.12 kleink diff = -diff;
685 1.12 kleink } else sign = "+";
686 1.12 kleink pt = _add(sign, pt, ptlim);
687 1.20 mlelstv diff /= SECSPERMIN;
688 1.20 mlelstv diff = (diff / MINSPERHOUR) * 100 +
689 1.20 mlelstv (diff % MINSPERHOUR);
690 1.30 christos _DIAGASSERT(__type_fit(int, diff));
691 1.44 christos pt = _conv((int)diff,
692 1.44 christos fmt_padding[PAD_FMT_YEAR][PadIndex],
693 1.44 christos pt, ptlim, loc);
694 1.12 kleink }
695 1.38 christos #endif
696 1.1 mrg continue;
697 1.12 kleink case '+':
698 1.44 christos #ifdef notyet
699 1.44 christos /* XXX: no date_fmt in _TimeLocale */
700 1.44 christos pt = _fmt(sp, tptr->date_fmt, t,
701 1.25 joerg pt, ptlim, warnp, loc);
702 1.44 christos #else
703 1.44 christos pt = _fmt(sp, "%a %b %e %H:%M:%S %Z %Y", t,
704 1.44 christos pt, ptlim, warnp, loc);
705 1.44 christos #endif
706 1.12 kleink continue;
707 1.44 christos case '-':
708 1.44 christos if (PadIndex != PAD_DEFAULT)
709 1.44 christos break;
710 1.44 christos PadIndex = PAD_LESS;
711 1.44 christos goto label;
712 1.44 christos case '_':
713 1.44 christos if (PadIndex != PAD_DEFAULT)
714 1.44 christos break;
715 1.44 christos PadIndex = PAD_SPACE;
716 1.44 christos goto label;
717 1.44 christos case '0':
718 1.44 christos if (PadIndex != PAD_DEFAULT)
719 1.44 christos break;
720 1.44 christos PadIndex = PAD_ZERO;
721 1.44 christos goto label;
722 1.1 mrg case '%':
723 1.1 mrg /*
724 1.12 kleink ** X311J/88-090 (4.12.3.5): if conversion char is
725 1.20 mlelstv ** undefined, behavior is undefined. Print out the
726 1.12 kleink ** character itself as printf(3) also does.
727 1.12 kleink */
728 1.1 mrg default:
729 1.1 mrg break;
730 1.1 mrg }
731 1.1 mrg }
732 1.12 kleink if (pt == ptlim)
733 1.12 kleink break;
734 1.12 kleink *pt++ = *format;
735 1.1 mrg }
736 1.12 kleink return pt;
737 1.1 mrg }
738 1.1 mrg
739 1.21 christos size_t
740 1.52 christos strftime(char *restrict s, size_t maxsize, char const *restrict format,
741 1.52 christos struct tm const *restrict t)
742 1.21 christos {
743 1.43 christos size_t r;
744 1.43 christos
745 1.43 christos rwlock_wrlock(&__lcl_lock);
746 1.43 christos tzset_unlocked();
747 1.43 christos r = strftime_z(__lclptr, s, maxsize, format, t);
748 1.43 christos rwlock_unlock(&__lcl_lock);
749 1.43 christos
750 1.43 christos return r;
751 1.21 christos }
752 1.21 christos
753 1.25 joerg size_t
754 1.25 joerg strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
755 1.25 joerg const struct tm * __restrict t, locale_t loc)
756 1.25 joerg {
757 1.43 christos size_t r;
758 1.43 christos
759 1.43 christos rwlock_wrlock(&__lcl_lock);
760 1.43 christos tzset_unlocked();
761 1.43 christos r = strftime_lz(__lclptr, s, maxsize, format, t, loc);
762 1.43 christos rwlock_unlock(&__lcl_lock);
763 1.43 christos
764 1.43 christos return r;
765 1.25 joerg }
766 1.25 joerg
767 1.12 kleink static char *
768 1.44 christos _conv(int n, const char *format, char *pt, const char *ptlim, locale_t loc)
769 1.1 mrg {
770 1.12 kleink char buf[INT_STRLEN_MAXIMUM(int) + 1];
771 1.1 mrg
772 1.44 christos (void) snprintf_l(buf, sizeof(buf), loc, format, n);
773 1.12 kleink return _add(buf, pt, ptlim);
774 1.1 mrg }
775 1.1 mrg
776 1.12 kleink static char *
777 1.34 christos _add(const char *str, char *pt, const char *ptlim)
778 1.1 mrg {
779 1.12 kleink while (pt < ptlim && (*pt = *str++) != '\0')
780 1.12 kleink ++pt;
781 1.12 kleink return pt;
782 1.1 mrg }
783 1.20 mlelstv
784 1.20 mlelstv /*
785 1.20 mlelstv ** POSIX and the C Standard are unclear or inconsistent about
786 1.20 mlelstv ** what %C and %y do if the year is negative or exceeds 9999.
787 1.20 mlelstv ** Use the convention that %C concatenated with %y yields the
788 1.20 mlelstv ** same output as %Y, and that %Y contains at least 4 bytes,
789 1.20 mlelstv ** with more only if necessary.
790 1.20 mlelstv */
791 1.20 mlelstv
792 1.20 mlelstv static char *
793 1.33 christos _yconv(int a, int b, bool convert_top, bool convert_yy,
794 1.44 christos char *pt, const char * ptlim, locale_t loc)
795 1.20 mlelstv {
796 1.28 christos int lead;
797 1.28 christos int trail;
798 1.20 mlelstv
799 1.50 christos int DIVISOR = 100;
800 1.20 mlelstv trail = a % DIVISOR + b % DIVISOR;
801 1.20 mlelstv lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
802 1.20 mlelstv trail %= DIVISOR;
803 1.20 mlelstv if (trail < 0 && lead > 0) {
804 1.20 mlelstv trail += DIVISOR;
805 1.20 mlelstv --lead;
806 1.20 mlelstv } else if (lead < 0 && trail > 0) {
807 1.20 mlelstv trail -= DIVISOR;
808 1.20 mlelstv ++lead;
809 1.20 mlelstv }
810 1.20 mlelstv if (convert_top) {
811 1.20 mlelstv if (lead == 0 && trail < 0)
812 1.20 mlelstv pt = _add("-0", pt, ptlim);
813 1.44 christos else pt = _conv(lead, "%02d", pt, ptlim, loc);
814 1.20 mlelstv }
815 1.20 mlelstv if (convert_yy)
816 1.44 christos pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim,
817 1.44 christos loc);
818 1.20 mlelstv return pt;
819 1.20 mlelstv }
820