blob: d04c5ba8d98e2d508dd2bf44ffbd27767fb78c9c [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
74#define Locale (&C_time_locale)
75
76static const struct lc_time_T C_time_locale = {
77 {
78 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
79 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
80 }, {
81 "January", "February", "March", "April", "May", "June",
82 "July", "August", "September", "October", "November", "December"
83 }, {
84 "Sun", "Mon", "Tue", "Wed",
85 "Thu", "Fri", "Sat"
86 }, {
87 "Sunday", "Monday", "Tuesday", "Wednesday",
88 "Thursday", "Friday", "Saturday"
89 },
90
91 /* X_fmt */
92 "%H:%M:%S",
93
94 /*
95 ** x_fmt
Almaz Mingaleev5411aff2022-02-11 12:27:12 +000096 ** C99 and later require this format.
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070097 ** Using just numbers (as here) makes Quakers happier;
98 ** it's also compatible with SVR4.
99 */
100 "%m/%d/%y",
101
102 /*
103 ** c_fmt
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000104 ** C99 and later require this format.
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700105 ** Previously this code used "%D %X", but we now conform to C99.
106 ** Note that
107 ** "%a %b %d %H:%M:%S %Y"
108 ** is used by Solaris 2.3.
109 */
110 "%a %b %e %T %Y",
111
112 /* am */
113 "AM",
114
115 /* pm */
116 "PM",
117
118 /* date_fmt */
119 "%a %b %e %H:%M:%S %Z %Y"
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800120};
121
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000122enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
123
Elliott Hughesce4783c2013-07-12 17:31:11 -0700124static char * _add(const char *, char *, const char *, int);
125static char * _conv(int, const char *, char *, const char *);
126static char * _fmt(const char *, const struct tm *, char *, const char *,
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000127 enum warn *);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700128static char * _yconv(int, int, bool, bool, char *, const char *, int);
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800129
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800130#ifndef YEAR_2000_NAME
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700131#define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800132#endif /* !defined YEAR_2000_NAME */
133
Elliott Hughes0a610d02016-07-29 14:04:17 -0700134#if HAVE_STRFTIME_L
135size_t
136strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
137 locale_t locale)
138{
139 /* Just call strftime, as only the C locale is supported. */
140 return strftime(s, maxsize, format, t);
141}
142#endif
143
144#define FORCE_LOWER_CASE 0x100 /* Android extension. */
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800145
146size_t
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700147strftime(char *s, size_t maxsize, const char *format, const struct tm *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{
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700220 for ( ; *format; ++format) {
221 if (*format == '%') {
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800222 int modifier = 0;
223label:
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700224 switch (*++format) {
225 case '\0':
226 --format;
227 break;
228 case 'A':
229 pt = _add((t->tm_wday < 0 ||
230 t->tm_wday >= DAYSPERWEEK) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700231 "?" : Locale->weekday[t->tm_wday],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700232 pt, ptlim, modifier);
233 continue;
234 case 'a':
235 pt = _add((t->tm_wday < 0 ||
236 t->tm_wday >= DAYSPERWEEK) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700237 "?" : Locale->wday[t->tm_wday],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700238 pt, ptlim, modifier);
239 continue;
240 case 'B':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700241 pt = _add((t->tm_mon < 0 ||
Eric Fischera48fa7f2009-05-15 13:33:20 -0700242 t->tm_mon >= MONSPERYEAR) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700243 "?" : Locale->month[t->tm_mon],
Eric Fischera48fa7f2009-05-15 13:33:20 -0700244 pt, ptlim, modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700245 continue;
246 case 'b':
247 case 'h':
248 pt = _add((t->tm_mon < 0 ||
249 t->tm_mon >= MONSPERYEAR) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700250 "?" : Locale->mon[t->tm_mon],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700251 pt, ptlim, modifier);
252 continue;
253 case 'C':
254 /*
255 ** %C used to do a...
256 ** _fmt("%a %b %e %X %Y", t);
257 ** ...whereas now POSIX 1003.2 calls for
258 ** something completely different.
259 ** (ado, 1993-05-24)
260 */
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700261 pt = _yconv(t->tm_year, TM_YEAR_BASE,
262 true, false, pt, ptlim, modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700263 continue;
264 case 'c':
265 {
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000266 enum warn warn2 = IN_SOME;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800267
Elliott Hughes39d903a2014-07-25 15:50:31 -0700268 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700269 if (warn2 == IN_ALL)
270 warn2 = IN_THIS;
271 if (warn2 > *warnp)
272 *warnp = warn2;
273 }
274 continue;
275 case 'D':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700276 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700277 continue;
278 case 'd':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800279 pt = _conv(t->tm_mday, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
280 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700281 case 'E':
282 case 'O':
283 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000284 ** Locale modifiers of C99 and later.
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700285 ** The sequences
286 ** %Ec %EC %Ex %EX %Ey %EY
287 ** %Od %oe %OH %OI %Om %OM
288 ** %OS %Ou %OU %OV %Ow %OW %Oy
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000289 ** are supposed to provide alternative
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700290 ** representations.
291 */
292 goto label;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800293 case '_':
294 case '-':
295 case '0':
296 case '^':
297 case '#':
298 modifier = *format;
299 goto label;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700300 case 'e':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800301 pt = _conv(t->tm_mday, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
302 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700303 case 'F':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700304 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700305 continue;
306 case 'H':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800307 pt = _conv(t->tm_hour, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
308 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700309 case 'I':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800310 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
311 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
312 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700313 case 'j':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800314 pt = _conv(t->tm_yday + 1, getformat(modifier, "03", " 3", " ", "03"), pt, ptlim);
315 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700316 case 'k':
317 /*
318 ** This used to be...
319 ** _conv(t->tm_hour % 12 ?
320 ** t->tm_hour % 12 : 12, 2, ' ');
321 ** ...and has been changed to the below to
322 ** match SunOS 4.1.1 and Arnold Robbins'
323 ** strftime version 3.0. That is, "%k" and
324 ** "%l" have been swapped.
325 ** (ado, 1993-05-24)
326 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800327 pt = _conv(t->tm_hour, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700328 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800329#ifdef KITCHEN_SINK
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700330 case 'K':
331 /*
332 ** After all this time, still unclaimed!
333 */
Elliott Hughes39d903a2014-07-25 15:50:31 -0700334 pt = _add("kitchen sink", pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700335 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800336#endif /* defined KITCHEN_SINK */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700337 case 'l':
338 /*
339 ** This used to be...
340 ** _conv(t->tm_hour, 2, ' ');
341 ** ...and has been changed to the below to
342 ** match SunOS 4.1.1 and Arnold Robbin's
343 ** strftime version 3.0. That is, "%k" and
344 ** "%l" have been swapped.
345 ** (ado, 1993-05-24)
346 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800347 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
348 getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700349 continue;
350 case 'M':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800351 pt = _conv(t->tm_min, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
352 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700353 case 'm':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800354 pt = _conv(t->tm_mon + 1, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
355 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700356 case 'n':
357 pt = _add("\n", pt, ptlim, modifier);
358 continue;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700359 case 'P':
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700360 case 'p':
361 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700362 Locale->pm :
363 Locale->am,
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700364 pt, ptlim, (*format == 'P') ? FORCE_LOWER_CASE : modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700365 continue;
366 case 'R':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700367 pt = _fmt("%H:%M", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700368 continue;
369 case 'r':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700370 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700371 continue;
372 case 'S':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800373 pt = _conv(t->tm_sec, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
374 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700375 case 's':
376 {
377 struct tm tm;
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800378 char buf[INT_STRLEN_MAXIMUM(time64_t) + 1] __attribute__((__uninitialized__));
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700379 time64_t mkt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800380
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700381 tm = *t;
382 mkt = mktime64(&tm);
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000383 /* There is no portable, definitive
384 test for whether whether mktime
385 succeeded, so treat (time_t) -1 as
386 the success that it might be. */
387 if (TYPE_SIGNED(time64_t)) {
388 intmax_t n = mkt;
389 sprintf(buf, "%"PRIdMAX, n);
390 } else {
391 uintmax_t n = mkt;
392 sprintf(buf, "%"PRIuMAX, n);
393 }
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700394 pt = _add(buf, pt, ptlim, modifier);
395 }
396 continue;
397 case 'T':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700398 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700399 continue;
400 case 't':
401 pt = _add("\t", pt, ptlim, modifier);
402 continue;
403 case 'U':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800404 pt = _conv((t->tm_yday + DAYSPERWEEK - t->tm_wday) / DAYSPERWEEK,
405 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
406 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700407 case 'u':
408 /*
409 ** From Arnold Robbins' strftime version 3.0:
410 ** "ISO 8601: Weekday as a decimal number
411 ** [1 (Monday) - 7]"
412 ** (ado, 1993-05-24)
413 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800414 pt = _conv((t->tm_wday == 0) ? DAYSPERWEEK : t->tm_wday, " ", pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700415 continue;
416 case 'V': /* ISO 8601 week number */
417 case 'G': /* ISO 8601 year (four digits) */
418 case 'g': /* ISO 8601 year (two digits) */
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800419/*
420** From Arnold Robbins' strftime version 3.0: "the week number of the
421** year (the first Monday as the first day of week 1) as a decimal number
422** (01-53)."
423** (ado, 1993-05-24)
424**
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000425** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800426** "Week 01 of a year is per definition the first week which has the
427** Thursday in this year, which is equivalent to the week which contains
428** the fourth day of January. In other words, the first week of a new year
429** is the week which has the majority of its days in the new year. Week 01
430** might also contain days from the previous year and the week before week
431** 01 of a year is the last week (52 or 53) of the previous year even if
432** it contains days from the new year. A week starts with Monday (day 1)
433** and ends with Sunday (day 7). For example, the first week of the year
434** 1997 lasts from 1996-12-30 to 1997-01-05..."
435** (ado, 1996-01-02)
436*/
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700437 {
438 int year;
439 int base;
440 int yday;
441 int wday;
442 int w;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800443
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700444 year = t->tm_year;
445 base = TM_YEAR_BASE;
446 yday = t->tm_yday;
447 wday = t->tm_wday;
448 for ( ; ; ) {
449 int len;
450 int bot;
451 int top;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800452
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700453 len = isleap_sum(year, base) ?
454 DAYSPERLYEAR :
455 DAYSPERNYEAR;
456 /*
457 ** What yday (-3 ... 3) does
458 ** the ISO year begin on?
459 */
460 bot = ((yday + 11 - wday) %
461 DAYSPERWEEK) - 3;
462 /*
463 ** What yday does the NEXT
464 ** ISO year begin on?
465 */
466 top = bot -
467 (len % DAYSPERWEEK);
468 if (top < -3)
469 top += DAYSPERWEEK;
470 top += len;
471 if (yday >= top) {
472 ++base;
473 w = 1;
474 break;
475 }
476 if (yday >= bot) {
477 w = 1 + ((yday - bot) /
478 DAYSPERWEEK);
479 break;
480 }
481 --base;
482 yday += isleap_sum(year, base) ?
483 DAYSPERLYEAR :
484 DAYSPERNYEAR;
485 }
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800486#ifdef XPG4_1994_04_09
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700487 if ((w == 52 &&
488 t->tm_mon == TM_JANUARY) ||
489 (w == 1 &&
490 t->tm_mon == TM_DECEMBER))
491 w = 53;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800492#endif /* defined XPG4_1994_04_09 */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700493 if (*format == 'V')
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800494 pt = _conv(w, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700495 else if (*format == 'g') {
496 *warnp = IN_ALL;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700497 pt = _yconv(year, base,
498 false, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700499 pt, ptlim, modifier);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700500 } else pt = _yconv(year, base,
501 true, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700502 pt, ptlim, modifier);
503 }
504 continue;
505 case 'v':
506 /*
507 ** From Arnold Robbins' strftime version 3.0:
508 ** "date as dd-bbb-YYYY"
509 ** (ado, 1993-05-24)
510 */
Elliott Hughes39d903a2014-07-25 15:50:31 -0700511 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700512 continue;
513 case 'W':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800514 pt = _conv(
515 (t->tm_yday + DAYSPERWEEK - (t->tm_wday ? (t->tm_wday - 1) : (DAYSPERWEEK - 1))) /
516 DAYSPERWEEK,
517 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
518 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700519 case 'w':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800520 pt = _conv(t->tm_wday, " ", pt, ptlim);
521 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700522 case 'X':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700523 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700524 continue;
525 case 'x':
526 {
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000527 enum warn warn2 = IN_SOME;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800528
Elliott Hughes39d903a2014-07-25 15:50:31 -0700529 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700530 if (warn2 == IN_ALL)
531 warn2 = IN_THIS;
532 if (warn2 > *warnp)
533 *warnp = warn2;
534 }
535 continue;
536 case 'y':
537 *warnp = IN_ALL;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700538 pt = _yconv(t->tm_year, TM_YEAR_BASE,
539 false, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700540 pt, ptlim, modifier);
541 continue;
542 case 'Y':
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700543 pt = _yconv(t->tm_year, TM_YEAR_BASE,
544 true, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700545 pt, ptlim, modifier);
546 continue;
547 case 'Z':
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800548#ifdef TM_ZONE
Elliott Hughesa9cac4c2015-11-12 16:51:31 -0800549 // BEGIN: Android-changed.
Almaz Mingaleev24bfd8e2022-07-15 11:01:53 +0100550 pt = _add(_safe_tm_zone(t), pt, ptlim, modifier);
Elliott Hughesa9cac4c2015-11-12 16:51:31 -0800551 // END: Android-changed.
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000552#elif HAVE_TZNAME
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700553 if (t->tm_isdst >= 0)
554 pt = _add(tzname[t->tm_isdst != 0],
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700555 pt, ptlim);
556#endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700557 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000558 ** C99 and later say that %Z must be
559 ** replaced by the empty string if the
560 ** time zone abbreviation is not
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700561 ** determinable.
562 */
563 continue;
564 case 'z':
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000565#if defined TM_GMTOFF || USG_COMPAT || ALTZONE
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700566 {
Elliott Hughese0d0b152013-09-27 00:04:30 -0700567 long diff;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700568 char const * sign;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000569 bool negative;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800570
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000571# ifdef TM_GMTOFF
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700572 diff = t->TM_GMTOFF;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000573# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700574 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000575 ** C99 and later say that the UT offset must
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700576 ** be computed by looking only at
577 ** tm_isdst. This requirement is
578 ** incorrect, since it means the code
579 ** must rely on magic (in this case
580 ** altzone and timezone), and the
581 ** magic might not have the correct
582 ** offset. Doing things correctly is
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000583 ** tricky and requires disobeying the standard;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700584 ** see GNU C strftime for details.
585 ** For now, punt and conform to the
586 ** standard, even though it's incorrect.
587 **
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000588 ** C99 and later say that %z must be replaced by
589 ** the empty string if the time zone is not
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700590 ** determinable, so output nothing if the
591 ** appropriate variables are not available.
592 */
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000593 if (t->tm_isdst < 0)
594 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700595 if (t->tm_isdst == 0)
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000596# if USG_COMPAT
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700597 diff = -timezone;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000598# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700599 continue;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000600# endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700601 else
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000602# if ALTZONE
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700603 diff = -altzone;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000604# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700605 continue;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000606# endif
607# endif
608 negative = diff < 0;
609 if (diff == 0) {
610#ifdef TM_ZONE
Almaz Mingaleev24bfd8e2022-07-15 11:01:53 +0100611 // Android-changed: do not use TM_ZONE as it is as it may be null.
612 {
613 const char* zone = _safe_tm_zone(t);
614 negative = zone[0] == '-';
615 }
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000616#else
617 negative = t->tm_isdst < 0;
618# if HAVE_TZNAME
619 if (tzname[t->tm_isdst != 0][0] == '-')
620 negative = true;
621# endif
622#endif
623 }
624 if (negative) {
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700625 sign = "-";
626 diff = -diff;
627 } else sign = "+";
628 pt = _add(sign, pt, ptlim, modifier);
629 diff /= SECSPERMIN;
630 diff = (diff / MINSPERHOUR) * 100 +
631 (diff % MINSPERHOUR);
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800632 pt = _conv(diff, getformat(modifier, "04", " 4", " ", "04"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700633 }
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000634#endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700635 continue;
636 case '+':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700637 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
638 warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700639 continue;
640 case '%':
641 /*
642 ** X311J/88-090 (4.12.3.5): if conversion char is
643 ** undefined, behavior is undefined. Print out the
644 ** character itself as printf(3) also does.
645 */
646 default:
647 break;
648 }
649 }
650 if (pt == ptlim)
651 break;
652 *pt++ = *format;
653 }
654 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800655}
656
657static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700658_conv(int n, const char *format, char *pt, const char *ptlim)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800659{
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800660 // The original implementation used snprintf(3) here, but rolling our own is
661 // about 5x faster. Seems like a good trade-off for so little code, especially
662 // for users like logcat that have a habit of formatting 10k times all at
663 // once...
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800664
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800665 // Format is '0' or ' ' for the fill character, followed by a single-digit
666 // width or ' ' for "whatever".
667 // %d -> " "
668 // %2d -> " 2"
669 // %02d -> "02"
670 char fill = format[0];
671 int width = format[1] == ' ' ? 0 : format[1] - '0';
672
673 char buf[32] __attribute__((__uninitialized__));
674
675 // Terminate first, so we can walk backwards from the least-significant digit
676 // without having to later reverse the result.
677 char* p = &buf[31];
678 *--p = '\0';
679 char* end = p;
680
681 // Output digits backwards, from least-significant to most.
682 while (n >= 10) {
683 *--p = '0' + (n % 10);
684 n /= 10;
685 }
686 *--p = '0' + n;
687
688 // Fill if more digits are required by the format.
689 while ((end - p) < width) {
690 *--p = fill;
691 }
692
693 return _add(p, pt, ptlim, 0);
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800694}
695
696static char *
Elliott Hughes39d903a2014-07-25 15:50:31 -0700697_add(const char *str, char *pt, const char *const ptlim, int modifier)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800698{
699 int c;
700
701 switch (modifier) {
702 case FORCE_LOWER_CASE:
703 while (pt < ptlim && (*pt = tolower(*str++)) != '\0') {
704 ++pt;
705 }
706 break;
707
708 case '^':
709 while (pt < ptlim && (*pt = toupper(*str++)) != '\0') {
710 ++pt;
711 }
712 break;
713
714 case '#':
715 while (pt < ptlim && (c = *str++) != '\0') {
716 if (isupper(c)) {
717 c = tolower(c);
718 } else if (islower(c)) {
719 c = toupper(c);
720 }
721 *pt = c;
722 ++pt;
723 }
724
725 break;
726
727 default:
728 while (pt < ptlim && (*pt = *str++) != '\0') {
729 ++pt;
730 }
731 }
732
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700733 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800734}
735
736/*
737** POSIX and the C Standard are unclear or inconsistent about
738** what %C and %y do if the year is negative or exceeds 9999.
739** Use the convention that %C concatenated with %y yields the
740** same output as %Y, and that %Y contains at least 4 bytes,
741** with more only if necessary.
742*/
743
744static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700745_yconv(int a, int b, bool convert_top, bool convert_yy,
746 char *pt, const char *ptlim, int modifier)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800747{
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700748 register int lead;
749 register int trail;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800750
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700751#define DIVISOR 100
752 trail = a % DIVISOR + b % DIVISOR;
753 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
754 trail %= DIVISOR;
755 if (trail < 0 && lead > 0) {
756 trail += DIVISOR;
757 --lead;
758 } else if (lead < 0 && trail > 0) {
759 trail -= DIVISOR;
760 ++lead;
761 }
762 if (convert_top) {
763 if (lead == 0 && trail < 0)
764 pt = _add("-0", pt, ptlim, modifier);
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800765 else
766 pt = _conv(lead, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700767 }
768 if (convert_yy)
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800769 pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "02", " 2", " ", "02"), pt,
770 ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700771 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800772}