blob: 4cde556b057ae70b16fb66502cbd76f5a3e32cdb [file] [log] [blame]
Almaz Mingaleev5411aff2022-02-11 12:27:12 +00001/* Convert a broken-down timestamp to a string. */
Elliott Hughes0a610d02016-07-29 14:04:17 -07002
3/* Copyright 1989 The Regents of the University of California.
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9 1. Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 2. Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
14 3. Neither the name of the University nor the names of its contributors
15 may be used to endorse or promote products derived from this software
16 without specific prior written permission.
17
18 THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 SUCH DAMAGE. */
29
The Android Open Source Project1dc9e472009-03-03 19:28:35 -080030/*
Elliott Hughes0a610d02016-07-29 14:04:17 -070031** Based on the UCB version with the copyright notice appearing above.
Elliott Hughes5f564542014-06-19 13:54:10 -070032**
The Android Open Source Project1dc9e472009-03-03 19:28:35 -080033** This is ANSIish only when "multibyte character == plain character".
34*/
The Android Open Source Project1dc9e472009-03-03 19:28:35 -080035
36#include "private.h"
37
Almaz Mingaleev5411aff2022-02-11 12:27:12 +000038#include <fcntl.h>
39#include <locale.h>
40#include <stdio.h>
41
42#ifndef DEPRECATE_TWO_DIGIT_YEARS
43# define DEPRECATE_TWO_DIGIT_YEARS false
44#endif
Elliott Hughes5f564542014-06-19 13:54:10 -070045
Elliott Hughesa9209d72016-09-16 18:16:47 -070046#if defined(__BIONIC__)
Elliott Hughes905e6d52014-07-25 11:55:59 -070047
Elliott Hughes905e6d52014-07-25 11:55:59 -070048/* LP32 had a 32-bit time_t, so we need to work around that here. */
Elliott Hughes52defb72014-05-05 17:14:02 -070049#if defined(__LP64__)
50#define time64_t time_t
51#define mktime64 mktime
Almaz Mingaleev5411aff2022-02-11 12:27:12 +000052#define localtime64_r localtime_r
Elliott Hughes52defb72014-05-05 17:14:02 -070053#else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070054#include <time64.h>
Elliott Hughes52defb72014-05-05 17:14:02 -070055#endif
Elliott Hughes905e6d52014-07-25 11:55:59 -070056
Elliott Hughes5f564542014-06-19 13:54:10 -070057#include <ctype.h>
Elliott Hughes905e6d52014-07-25 11:55:59 -070058
Elliott Hughes39d903a2014-07-25 15:50:31 -070059#endif
Elliott Hughes905e6d52014-07-25 11:55:59 -070060
The Android Open Source Project1dc9e472009-03-03 19:28:35 -080061struct lc_time_T {
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070062 const char * mon[MONSPERYEAR];
63 const char * month[MONSPERYEAR];
64 const char * wday[DAYSPERWEEK];
65 const char * weekday[DAYSPERWEEK];
66 const char * X_fmt;
67 const char * x_fmt;
68 const char * c_fmt;
69 const char * am;
70 const char * pm;
71 const char * date_fmt;
72};
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070073
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070074static const struct lc_time_T C_time_locale = {
75 {
76 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
77 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
78 }, {
79 "January", "February", "March", "April", "May", "June",
80 "July", "August", "September", "October", "November", "December"
81 }, {
82 "Sun", "Mon", "Tue", "Wed",
83 "Thu", "Fri", "Sat"
84 }, {
85 "Sunday", "Monday", "Tuesday", "Wednesday",
86 "Thursday", "Friday", "Saturday"
87 },
88
89 /* X_fmt */
90 "%H:%M:%S",
91
92 /*
93 ** x_fmt
Almaz Mingaleev5411aff2022-02-11 12:27:12 +000094 ** C99 and later require this format.
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070095 ** Using just numbers (as here) makes Quakers happier;
96 ** it's also compatible with SVR4.
97 */
98 "%m/%d/%y",
99
100 /*
101 ** c_fmt
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000102 ** C99 and later require this format.
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700103 ** Previously this code used "%D %X", but we now conform to C99.
104 ** Note that
105 ** "%a %b %d %H:%M:%S %Y"
106 ** is used by Solaris 2.3.
107 */
108 "%a %b %e %T %Y",
109
110 /* am */
111 "AM",
112
113 /* pm */
114 "PM",
115
116 /* date_fmt */
117 "%a %b %e %H:%M:%S %Z %Y"
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800118};
119
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000120enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
121
Elliott Hughesce4783c2013-07-12 17:31:11 -0700122static char * _add(const char *, char *, const char *, int);
123static char * _conv(int, const char *, char *, const char *);
124static char * _fmt(const char *, const struct tm *, char *, const char *,
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000125 enum warn *);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700126static char * _yconv(int, int, bool, bool, char *, const char *, int);
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800127
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800128#ifndef YEAR_2000_NAME
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100129# define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800130#endif /* !defined YEAR_2000_NAME */
131
Elliott Hughes0a610d02016-07-29 14:04:17 -0700132#if HAVE_STRFTIME_L
133size_t
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100134strftime_l(char *restrict s, size_t maxsize, char const *restrict format,
135 struct tm const *restrict t,
136 ATTRIBUTE_MAYBE_UNUSED locale_t locale)
Elliott Hughes0a610d02016-07-29 14:04:17 -0700137{
138 /* Just call strftime, as only the C locale is supported. */
139 return strftime(s, maxsize, format, t);
140}
141#endif
142
143#define FORCE_LOWER_CASE 0x100 /* Android extension. */
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800144
145size_t
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100146strftime(char *restrict s, size_t maxsize, char const *restrict format,
147 struct tm const *restrict t)
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700148{
149 char * p;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000150 int saved_errno = errno;
151 enum warn warn = IN_NONE;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700152
153 tzset();
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000154 p = _fmt(format, t, s, s + maxsize, &warn);
155 if (!p) {
156 errno = EOVERFLOW;
157 return 0;
158 }
159 if (DEPRECATE_TWO_DIGIT_YEARS
160 && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700161 fprintf(stderr, "\n");
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000162 fprintf(stderr, "strftime format \"%s\" ", format);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700163 fprintf(stderr, "yields only two digits of years in ");
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700164 if (warn == IN_SOME)
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700165 fprintf(stderr, "some locales");
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700166 else if (warn == IN_THIS)
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700167 fprintf(stderr, "the current locale");
168 else fprintf(stderr, "all locales");
169 fprintf(stderr, "\n");
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700170 }
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000171 if (p == s + maxsize) {
172 errno = ERANGE;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700173 return 0;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000174 }
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700175 *p = '\0';
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000176 errno = saved_errno;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700177 return p - s;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800178}
179
180static char *getformat(int modifier, char *normal, char *underscore,
181 char *dash, char *zero) {
182 switch (modifier) {
183 case '_':
184 return underscore;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800185 case '-':
186 return dash;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800187 case '0':
188 return zero;
189 }
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800190 return normal;
191}
192
Almaz Mingaleev24bfd8e2022-07-15 11:01:53 +0100193// Android-added: fall back mechanism when TM_ZONE is not initialized.
194#ifdef TM_ZONE
195static const char* _safe_tm_zone(const struct tm* tm) {
196 const char* zone = tm->TM_ZONE;
197 if (!zone || !*zone) {
198 // "The value of tm_isdst shall be positive if Daylight Savings Time is
199 // in effect, 0 if Daylight Savings Time is not in effect, and negative
200 // if the information is not available."
201 if (tm->tm_isdst == 0) {
202 zone = tzname[0];
203 } else if (tm->tm_isdst > 0) {
204 zone = tzname[1];
205 }
206
207 // "Replaced by the timezone name or abbreviation, or by no bytes if no
208 // timezone information exists."
209 if (!zone || !*zone) zone = "";
210 }
211
212 return zone;
213}
214#endif
215
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800216static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700217_fmt(const char *format, const struct tm *t, char *pt,
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000218 const char *ptlim, enum warn *warnp)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800219{
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100220 struct lc_time_T const *Locale = &C_time_locale;
221
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700222 for ( ; *format; ++format) {
223 if (*format == '%') {
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800224 int modifier = 0;
225label:
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700226 switch (*++format) {
227 case '\0':
228 --format;
229 break;
230 case 'A':
231 pt = _add((t->tm_wday < 0 ||
232 t->tm_wday >= DAYSPERWEEK) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700233 "?" : Locale->weekday[t->tm_wday],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700234 pt, ptlim, modifier);
235 continue;
236 case 'a':
237 pt = _add((t->tm_wday < 0 ||
238 t->tm_wday >= DAYSPERWEEK) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700239 "?" : Locale->wday[t->tm_wday],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700240 pt, ptlim, modifier);
241 continue;
242 case 'B':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700243 pt = _add((t->tm_mon < 0 ||
Eric Fischera48fa7f2009-05-15 13:33:20 -0700244 t->tm_mon >= MONSPERYEAR) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700245 "?" : Locale->month[t->tm_mon],
Eric Fischera48fa7f2009-05-15 13:33:20 -0700246 pt, ptlim, modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700247 continue;
248 case 'b':
249 case 'h':
250 pt = _add((t->tm_mon < 0 ||
251 t->tm_mon >= MONSPERYEAR) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700252 "?" : Locale->mon[t->tm_mon],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700253 pt, ptlim, modifier);
254 continue;
255 case 'C':
256 /*
257 ** %C used to do a...
258 ** _fmt("%a %b %e %X %Y", t);
259 ** ...whereas now POSIX 1003.2 calls for
260 ** something completely different.
261 ** (ado, 1993-05-24)
262 */
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700263 pt = _yconv(t->tm_year, TM_YEAR_BASE,
264 true, false, pt, ptlim, modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700265 continue;
266 case 'c':
267 {
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000268 enum warn warn2 = IN_SOME;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800269
Elliott Hughes39d903a2014-07-25 15:50:31 -0700270 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700271 if (warn2 == IN_ALL)
272 warn2 = IN_THIS;
273 if (warn2 > *warnp)
274 *warnp = warn2;
275 }
276 continue;
277 case 'D':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700278 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700279 continue;
280 case 'd':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800281 pt = _conv(t->tm_mday, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
282 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700283 case 'E':
284 case 'O':
285 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000286 ** Locale modifiers of C99 and later.
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700287 ** The sequences
288 ** %Ec %EC %Ex %EX %Ey %EY
289 ** %Od %oe %OH %OI %Om %OM
290 ** %OS %Ou %OU %OV %Ow %OW %Oy
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000291 ** are supposed to provide alternative
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700292 ** representations.
293 */
294 goto label;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800295 case '_':
296 case '-':
297 case '0':
298 case '^':
299 case '#':
300 modifier = *format;
301 goto label;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700302 case 'e':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800303 pt = _conv(t->tm_mday, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
304 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700305 case 'F':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700306 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700307 continue;
308 case 'H':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800309 pt = _conv(t->tm_hour, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
310 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700311 case 'I':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800312 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
313 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
314 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700315 case 'j':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800316 pt = _conv(t->tm_yday + 1, getformat(modifier, "03", " 3", " ", "03"), pt, ptlim);
317 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700318 case 'k':
319 /*
320 ** This used to be...
321 ** _conv(t->tm_hour % 12 ?
322 ** t->tm_hour % 12 : 12, 2, ' ');
323 ** ...and has been changed to the below to
324 ** match SunOS 4.1.1 and Arnold Robbins'
325 ** strftime version 3.0. That is, "%k" and
326 ** "%l" have been swapped.
327 ** (ado, 1993-05-24)
328 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800329 pt = _conv(t->tm_hour, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700330 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800331#ifdef KITCHEN_SINK
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700332 case 'K':
333 /*
334 ** After all this time, still unclaimed!
335 */
Elliott Hughes39d903a2014-07-25 15:50:31 -0700336 pt = _add("kitchen sink", pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700337 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800338#endif /* defined KITCHEN_SINK */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700339 case 'l':
340 /*
341 ** This used to be...
342 ** _conv(t->tm_hour, 2, ' ');
343 ** ...and has been changed to the below to
344 ** match SunOS 4.1.1 and Arnold Robbin's
345 ** strftime version 3.0. That is, "%k" and
346 ** "%l" have been swapped.
347 ** (ado, 1993-05-24)
348 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800349 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
350 getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700351 continue;
352 case 'M':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800353 pt = _conv(t->tm_min, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
354 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700355 case 'm':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800356 pt = _conv(t->tm_mon + 1, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
357 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700358 case 'n':
359 pt = _add("\n", pt, ptlim, modifier);
360 continue;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700361 case 'P':
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700362 case 'p':
363 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700364 Locale->pm :
365 Locale->am,
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700366 pt, ptlim, (*format == 'P') ? FORCE_LOWER_CASE : modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700367 continue;
368 case 'R':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700369 pt = _fmt("%H:%M", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700370 continue;
371 case 'r':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700372 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700373 continue;
374 case 'S':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800375 pt = _conv(t->tm_sec, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
376 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700377 case 's':
378 {
379 struct tm tm;
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800380 char buf[INT_STRLEN_MAXIMUM(time64_t) + 1] __attribute__((__uninitialized__));
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700381 time64_t mkt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800382
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100383 tm.tm_sec = t->tm_sec;
384 tm.tm_min = t->tm_min;
385 tm.tm_hour = t->tm_hour;
386 tm.tm_mday = t->tm_mday;
387 tm.tm_mon = t->tm_mon;
388 tm.tm_year = t->tm_year;
389 tm.tm_isdst = t->tm_isdst;
390#if defined TM_GMTOFF && ! UNINIT_TRAP
391 tm.TM_GMTOFF = t->TM_GMTOFF;
392#endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700393 mkt = mktime64(&tm);
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100394 /* If mktime fails, %s expands to the
395 value of (time_t) -1 as a failure
396 marker; this is better in practice
397 than strftime failing. */
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000398 if (TYPE_SIGNED(time64_t)) {
399 intmax_t n = mkt;
400 sprintf(buf, "%"PRIdMAX, n);
401 } else {
402 uintmax_t n = mkt;
403 sprintf(buf, "%"PRIuMAX, n);
404 }
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700405 pt = _add(buf, pt, ptlim, modifier);
406 }
407 continue;
408 case 'T':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700409 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700410 continue;
411 case 't':
412 pt = _add("\t", pt, ptlim, modifier);
413 continue;
414 case 'U':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800415 pt = _conv((t->tm_yday + DAYSPERWEEK - t->tm_wday) / DAYSPERWEEK,
416 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
417 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700418 case 'u':
419 /*
420 ** From Arnold Robbins' strftime version 3.0:
421 ** "ISO 8601: Weekday as a decimal number
422 ** [1 (Monday) - 7]"
423 ** (ado, 1993-05-24)
424 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800425 pt = _conv((t->tm_wday == 0) ? DAYSPERWEEK : t->tm_wday, " ", pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700426 continue;
427 case 'V': /* ISO 8601 week number */
428 case 'G': /* ISO 8601 year (four digits) */
429 case 'g': /* ISO 8601 year (two digits) */
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800430/*
431** From Arnold Robbins' strftime version 3.0: "the week number of the
432** year (the first Monday as the first day of week 1) as a decimal number
433** (01-53)."
434** (ado, 1993-05-24)
435**
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000436** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800437** "Week 01 of a year is per definition the first week which has the
438** Thursday in this year, which is equivalent to the week which contains
439** the fourth day of January. In other words, the first week of a new year
440** is the week which has the majority of its days in the new year. Week 01
441** might also contain days from the previous year and the week before week
442** 01 of a year is the last week (52 or 53) of the previous year even if
443** it contains days from the new year. A week starts with Monday (day 1)
444** and ends with Sunday (day 7). For example, the first week of the year
445** 1997 lasts from 1996-12-30 to 1997-01-05..."
446** (ado, 1996-01-02)
447*/
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700448 {
449 int year;
450 int base;
451 int yday;
452 int wday;
453 int w;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800454
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700455 year = t->tm_year;
456 base = TM_YEAR_BASE;
457 yday = t->tm_yday;
458 wday = t->tm_wday;
459 for ( ; ; ) {
460 int len;
461 int bot;
462 int top;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800463
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700464 len = isleap_sum(year, base) ?
465 DAYSPERLYEAR :
466 DAYSPERNYEAR;
467 /*
468 ** What yday (-3 ... 3) does
469 ** the ISO year begin on?
470 */
471 bot = ((yday + 11 - wday) %
472 DAYSPERWEEK) - 3;
473 /*
474 ** What yday does the NEXT
475 ** ISO year begin on?
476 */
477 top = bot -
478 (len % DAYSPERWEEK);
479 if (top < -3)
480 top += DAYSPERWEEK;
481 top += len;
482 if (yday >= top) {
483 ++base;
484 w = 1;
485 break;
486 }
487 if (yday >= bot) {
488 w = 1 + ((yday - bot) /
489 DAYSPERWEEK);
490 break;
491 }
492 --base;
493 yday += isleap_sum(year, base) ?
494 DAYSPERLYEAR :
495 DAYSPERNYEAR;
496 }
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800497#ifdef XPG4_1994_04_09
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700498 if ((w == 52 &&
499 t->tm_mon == TM_JANUARY) ||
500 (w == 1 &&
501 t->tm_mon == TM_DECEMBER))
502 w = 53;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800503#endif /* defined XPG4_1994_04_09 */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700504 if (*format == 'V')
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800505 pt = _conv(w, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700506 else if (*format == 'g') {
507 *warnp = IN_ALL;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700508 pt = _yconv(year, base,
509 false, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700510 pt, ptlim, modifier);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700511 } else pt = _yconv(year, base,
512 true, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700513 pt, ptlim, modifier);
514 }
515 continue;
516 case 'v':
517 /*
518 ** From Arnold Robbins' strftime version 3.0:
519 ** "date as dd-bbb-YYYY"
520 ** (ado, 1993-05-24)
521 */
Elliott Hughes39d903a2014-07-25 15:50:31 -0700522 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700523 continue;
524 case 'W':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800525 pt = _conv(
526 (t->tm_yday + DAYSPERWEEK - (t->tm_wday ? (t->tm_wday - 1) : (DAYSPERWEEK - 1))) /
527 DAYSPERWEEK,
528 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
529 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700530 case 'w':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800531 pt = _conv(t->tm_wday, " ", pt, ptlim);
532 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700533 case 'X':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700534 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700535 continue;
536 case 'x':
537 {
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000538 enum warn warn2 = IN_SOME;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800539
Elliott Hughes39d903a2014-07-25 15:50:31 -0700540 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700541 if (warn2 == IN_ALL)
542 warn2 = IN_THIS;
543 if (warn2 > *warnp)
544 *warnp = warn2;
545 }
546 continue;
547 case 'y':
548 *warnp = IN_ALL;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700549 pt = _yconv(t->tm_year, TM_YEAR_BASE,
550 false, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700551 pt, ptlim, modifier);
552 continue;
553 case 'Y':
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700554 pt = _yconv(t->tm_year, TM_YEAR_BASE,
555 true, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700556 pt, ptlim, modifier);
557 continue;
558 case 'Z':
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800559#ifdef TM_ZONE
Elliott Hughesa9cac4c2015-11-12 16:51:31 -0800560 // BEGIN: Android-changed.
Almaz Mingaleev24bfd8e2022-07-15 11:01:53 +0100561 pt = _add(_safe_tm_zone(t), pt, ptlim, modifier);
Elliott Hughesa9cac4c2015-11-12 16:51:31 -0800562 // END: Android-changed.
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000563#elif HAVE_TZNAME
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700564 if (t->tm_isdst >= 0)
565 pt = _add(tzname[t->tm_isdst != 0],
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700566 pt, ptlim);
567#endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700568 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000569 ** C99 and later say that %Z must be
570 ** replaced by the empty string if the
571 ** time zone abbreviation is not
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700572 ** determinable.
573 */
574 continue;
575 case 'z':
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000576#if defined TM_GMTOFF || USG_COMPAT || ALTZONE
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700577 {
Elliott Hughese0d0b152013-09-27 00:04:30 -0700578 long diff;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700579 char const * sign;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000580 bool negative;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800581
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000582# ifdef TM_GMTOFF
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700583 diff = t->TM_GMTOFF;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000584# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700585 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000586 ** C99 and later say that the UT offset must
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700587 ** be computed by looking only at
588 ** tm_isdst. This requirement is
589 ** incorrect, since it means the code
590 ** must rely on magic (in this case
591 ** altzone and timezone), and the
592 ** magic might not have the correct
593 ** offset. Doing things correctly is
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000594 ** tricky and requires disobeying the standard;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700595 ** see GNU C strftime for details.
596 ** For now, punt and conform to the
597 ** standard, even though it's incorrect.
598 **
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000599 ** C99 and later say that %z must be replaced by
600 ** the empty string if the time zone is not
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700601 ** determinable, so output nothing if the
602 ** appropriate variables are not available.
603 */
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000604 if (t->tm_isdst < 0)
605 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700606 if (t->tm_isdst == 0)
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000607# if USG_COMPAT
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700608 diff = -timezone;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000609# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700610 continue;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000611# endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700612 else
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000613# if ALTZONE
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700614 diff = -altzone;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000615# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700616 continue;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000617# endif
618# endif
619 negative = diff < 0;
620 if (diff == 0) {
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100621# ifdef TM_ZONE
Almaz Mingaleev24bfd8e2022-07-15 11:01:53 +0100622 // Android-changed: do not use TM_ZONE as it is as it may be null.
623 {
624 const char* zone = _safe_tm_zone(t);
625 negative = zone[0] == '-';
626 }
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100627# else
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000628 negative = t->tm_isdst < 0;
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100629# if HAVE_TZNAME
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000630 if (tzname[t->tm_isdst != 0][0] == '-')
631 negative = true;
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100632# endif
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000633# endif
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000634 }
635 if (negative) {
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700636 sign = "-";
637 diff = -diff;
638 } else sign = "+";
639 pt = _add(sign, pt, ptlim, modifier);
640 diff /= SECSPERMIN;
641 diff = (diff / MINSPERHOUR) * 100 +
642 (diff % MINSPERHOUR);
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800643 pt = _conv(diff, getformat(modifier, "04", " 4", " ", "04"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700644 }
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000645#endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700646 continue;
647 case '+':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700648 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
649 warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700650 continue;
651 case '%':
652 /*
653 ** X311J/88-090 (4.12.3.5): if conversion char is
654 ** undefined, behavior is undefined. Print out the
655 ** character itself as printf(3) also does.
656 */
657 default:
658 break;
659 }
660 }
661 if (pt == ptlim)
662 break;
663 *pt++ = *format;
664 }
665 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800666}
667
668static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700669_conv(int n, const char *format, char *pt, const char *ptlim)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800670{
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800671 // The original implementation used snprintf(3) here, but rolling our own is
672 // about 5x faster. Seems like a good trade-off for so little code, especially
673 // for users like logcat that have a habit of formatting 10k times all at
674 // once...
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800675
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800676 // Format is '0' or ' ' for the fill character, followed by a single-digit
677 // width or ' ' for "whatever".
678 // %d -> " "
679 // %2d -> " 2"
680 // %02d -> "02"
681 char fill = format[0];
682 int width = format[1] == ' ' ? 0 : format[1] - '0';
683
684 char buf[32] __attribute__((__uninitialized__));
685
686 // Terminate first, so we can walk backwards from the least-significant digit
687 // without having to later reverse the result.
688 char* p = &buf[31];
689 *--p = '\0';
690 char* end = p;
691
692 // Output digits backwards, from least-significant to most.
693 while (n >= 10) {
694 *--p = '0' + (n % 10);
695 n /= 10;
696 }
697 *--p = '0' + n;
698
699 // Fill if more digits are required by the format.
700 while ((end - p) < width) {
701 *--p = fill;
702 }
703
704 return _add(p, pt, ptlim, 0);
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800705}
706
707static char *
Elliott Hughes39d903a2014-07-25 15:50:31 -0700708_add(const char *str, char *pt, const char *const ptlim, int modifier)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800709{
710 int c;
711
712 switch (modifier) {
713 case FORCE_LOWER_CASE:
714 while (pt < ptlim && (*pt = tolower(*str++)) != '\0') {
715 ++pt;
716 }
717 break;
718
719 case '^':
720 while (pt < ptlim && (*pt = toupper(*str++)) != '\0') {
721 ++pt;
722 }
723 break;
724
725 case '#':
726 while (pt < ptlim && (c = *str++) != '\0') {
727 if (isupper(c)) {
728 c = tolower(c);
729 } else if (islower(c)) {
730 c = toupper(c);
731 }
732 *pt = c;
733 ++pt;
734 }
735
736 break;
737
738 default:
739 while (pt < ptlim && (*pt = *str++) != '\0') {
740 ++pt;
741 }
742 }
743
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700744 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800745}
746
747/*
748** POSIX and the C Standard are unclear or inconsistent about
749** what %C and %y do if the year is negative or exceeds 9999.
750** Use the convention that %C concatenated with %y yields the
751** same output as %Y, and that %Y contains at least 4 bytes,
752** with more only if necessary.
753*/
754
755static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700756_yconv(int a, int b, bool convert_top, bool convert_yy,
757 char *pt, const char *ptlim, int modifier)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800758{
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700759 register int lead;
760 register int trail;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800761
Almaz Mingaleevd86a3ab2023-04-27 11:24:48 +0100762 int DIVISOR = 100;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700763 trail = a % DIVISOR + b % DIVISOR;
764 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
765 trail %= DIVISOR;
766 if (trail < 0 && lead > 0) {
767 trail += DIVISOR;
768 --lead;
769 } else if (lead < 0 && trail > 0) {
770 trail -= DIVISOR;
771 ++lead;
772 }
773 if (convert_top) {
774 if (lead == 0 && trail < 0)
775 pt = _add("-0", pt, ptlim, modifier);
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800776 else
777 pt = _conv(lead, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700778 }
779 if (convert_yy)
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800780 pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "02", " 2", " ", "02"), pt,
781 ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700782 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800783}