blob: 8c1b983243c7437231c15fa1c8fac5f63c6dbc65 [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
193static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700194_fmt(const char *format, const struct tm *t, char *pt,
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000195 const char *ptlim, enum warn *warnp)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800196{
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700197 for ( ; *format; ++format) {
198 if (*format == '%') {
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800199 int modifier = 0;
200label:
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700201 switch (*++format) {
202 case '\0':
203 --format;
204 break;
205 case 'A':
206 pt = _add((t->tm_wday < 0 ||
207 t->tm_wday >= DAYSPERWEEK) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700208 "?" : Locale->weekday[t->tm_wday],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700209 pt, ptlim, modifier);
210 continue;
211 case 'a':
212 pt = _add((t->tm_wday < 0 ||
213 t->tm_wday >= DAYSPERWEEK) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700214 "?" : Locale->wday[t->tm_wday],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700215 pt, ptlim, modifier);
216 continue;
217 case 'B':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700218 pt = _add((t->tm_mon < 0 ||
Eric Fischera48fa7f2009-05-15 13:33:20 -0700219 t->tm_mon >= MONSPERYEAR) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700220 "?" : Locale->month[t->tm_mon],
Eric Fischera48fa7f2009-05-15 13:33:20 -0700221 pt, ptlim, modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700222 continue;
223 case 'b':
224 case 'h':
225 pt = _add((t->tm_mon < 0 ||
226 t->tm_mon >= MONSPERYEAR) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700227 "?" : Locale->mon[t->tm_mon],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700228 pt, ptlim, modifier);
229 continue;
230 case 'C':
231 /*
232 ** %C used to do a...
233 ** _fmt("%a %b %e %X %Y", t);
234 ** ...whereas now POSIX 1003.2 calls for
235 ** something completely different.
236 ** (ado, 1993-05-24)
237 */
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700238 pt = _yconv(t->tm_year, TM_YEAR_BASE,
239 true, false, pt, ptlim, modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700240 continue;
241 case 'c':
242 {
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000243 enum warn warn2 = IN_SOME;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800244
Elliott Hughes39d903a2014-07-25 15:50:31 -0700245 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700246 if (warn2 == IN_ALL)
247 warn2 = IN_THIS;
248 if (warn2 > *warnp)
249 *warnp = warn2;
250 }
251 continue;
252 case 'D':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700253 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700254 continue;
255 case 'd':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800256 pt = _conv(t->tm_mday, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
257 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700258 case 'E':
259 case 'O':
260 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000261 ** Locale modifiers of C99 and later.
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700262 ** The sequences
263 ** %Ec %EC %Ex %EX %Ey %EY
264 ** %Od %oe %OH %OI %Om %OM
265 ** %OS %Ou %OU %OV %Ow %OW %Oy
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000266 ** are supposed to provide alternative
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700267 ** representations.
268 */
269 goto label;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800270 case '_':
271 case '-':
272 case '0':
273 case '^':
274 case '#':
275 modifier = *format;
276 goto label;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700277 case 'e':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800278 pt = _conv(t->tm_mday, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
279 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700280 case 'F':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700281 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700282 continue;
283 case 'H':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800284 pt = _conv(t->tm_hour, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
285 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700286 case 'I':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800287 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
288 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
289 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700290 case 'j':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800291 pt = _conv(t->tm_yday + 1, getformat(modifier, "03", " 3", " ", "03"), pt, ptlim);
292 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700293 case 'k':
294 /*
295 ** This used to be...
296 ** _conv(t->tm_hour % 12 ?
297 ** t->tm_hour % 12 : 12, 2, ' ');
298 ** ...and has been changed to the below to
299 ** match SunOS 4.1.1 and Arnold Robbins'
300 ** strftime version 3.0. That is, "%k" and
301 ** "%l" have been swapped.
302 ** (ado, 1993-05-24)
303 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800304 pt = _conv(t->tm_hour, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700305 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800306#ifdef KITCHEN_SINK
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700307 case 'K':
308 /*
309 ** After all this time, still unclaimed!
310 */
Elliott Hughes39d903a2014-07-25 15:50:31 -0700311 pt = _add("kitchen sink", pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700312 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800313#endif /* defined KITCHEN_SINK */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700314 case 'l':
315 /*
316 ** This used to be...
317 ** _conv(t->tm_hour, 2, ' ');
318 ** ...and has been changed to the below to
319 ** match SunOS 4.1.1 and Arnold Robbin's
320 ** strftime version 3.0. That is, "%k" and
321 ** "%l" have been swapped.
322 ** (ado, 1993-05-24)
323 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800324 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
325 getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700326 continue;
327 case 'M':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800328 pt = _conv(t->tm_min, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
329 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700330 case 'm':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800331 pt = _conv(t->tm_mon + 1, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
332 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700333 case 'n':
334 pt = _add("\n", pt, ptlim, modifier);
335 continue;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700336 case 'P':
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700337 case 'p':
338 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700339 Locale->pm :
340 Locale->am,
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700341 pt, ptlim, (*format == 'P') ? FORCE_LOWER_CASE : modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700342 continue;
343 case 'R':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700344 pt = _fmt("%H:%M", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700345 continue;
346 case 'r':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700347 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700348 continue;
349 case 'S':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800350 pt = _conv(t->tm_sec, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
351 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700352 case 's':
353 {
354 struct tm tm;
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800355 char buf[INT_STRLEN_MAXIMUM(time64_t) + 1] __attribute__((__uninitialized__));
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700356 time64_t mkt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800357
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700358 tm = *t;
359 mkt = mktime64(&tm);
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000360 /* There is no portable, definitive
361 test for whether whether mktime
362 succeeded, so treat (time_t) -1 as
363 the success that it might be. */
364 if (TYPE_SIGNED(time64_t)) {
365 intmax_t n = mkt;
366 sprintf(buf, "%"PRIdMAX, n);
367 } else {
368 uintmax_t n = mkt;
369 sprintf(buf, "%"PRIuMAX, n);
370 }
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700371 pt = _add(buf, pt, ptlim, modifier);
372 }
373 continue;
374 case 'T':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700375 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700376 continue;
377 case 't':
378 pt = _add("\t", pt, ptlim, modifier);
379 continue;
380 case 'U':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800381 pt = _conv((t->tm_yday + DAYSPERWEEK - t->tm_wday) / DAYSPERWEEK,
382 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
383 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700384 case 'u':
385 /*
386 ** From Arnold Robbins' strftime version 3.0:
387 ** "ISO 8601: Weekday as a decimal number
388 ** [1 (Monday) - 7]"
389 ** (ado, 1993-05-24)
390 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800391 pt = _conv((t->tm_wday == 0) ? DAYSPERWEEK : t->tm_wday, " ", pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700392 continue;
393 case 'V': /* ISO 8601 week number */
394 case 'G': /* ISO 8601 year (four digits) */
395 case 'g': /* ISO 8601 year (two digits) */
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800396/*
397** From Arnold Robbins' strftime version 3.0: "the week number of the
398** year (the first Monday as the first day of week 1) as a decimal number
399** (01-53)."
400** (ado, 1993-05-24)
401**
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000402** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800403** "Week 01 of a year is per definition the first week which has the
404** Thursday in this year, which is equivalent to the week which contains
405** the fourth day of January. In other words, the first week of a new year
406** is the week which has the majority of its days in the new year. Week 01
407** might also contain days from the previous year and the week before week
408** 01 of a year is the last week (52 or 53) of the previous year even if
409** it contains days from the new year. A week starts with Monday (day 1)
410** and ends with Sunday (day 7). For example, the first week of the year
411** 1997 lasts from 1996-12-30 to 1997-01-05..."
412** (ado, 1996-01-02)
413*/
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700414 {
415 int year;
416 int base;
417 int yday;
418 int wday;
419 int w;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800420
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700421 year = t->tm_year;
422 base = TM_YEAR_BASE;
423 yday = t->tm_yday;
424 wday = t->tm_wday;
425 for ( ; ; ) {
426 int len;
427 int bot;
428 int top;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800429
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700430 len = isleap_sum(year, base) ?
431 DAYSPERLYEAR :
432 DAYSPERNYEAR;
433 /*
434 ** What yday (-3 ... 3) does
435 ** the ISO year begin on?
436 */
437 bot = ((yday + 11 - wday) %
438 DAYSPERWEEK) - 3;
439 /*
440 ** What yday does the NEXT
441 ** ISO year begin on?
442 */
443 top = bot -
444 (len % DAYSPERWEEK);
445 if (top < -3)
446 top += DAYSPERWEEK;
447 top += len;
448 if (yday >= top) {
449 ++base;
450 w = 1;
451 break;
452 }
453 if (yday >= bot) {
454 w = 1 + ((yday - bot) /
455 DAYSPERWEEK);
456 break;
457 }
458 --base;
459 yday += isleap_sum(year, base) ?
460 DAYSPERLYEAR :
461 DAYSPERNYEAR;
462 }
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800463#ifdef XPG4_1994_04_09
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700464 if ((w == 52 &&
465 t->tm_mon == TM_JANUARY) ||
466 (w == 1 &&
467 t->tm_mon == TM_DECEMBER))
468 w = 53;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800469#endif /* defined XPG4_1994_04_09 */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700470 if (*format == 'V')
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800471 pt = _conv(w, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700472 else if (*format == 'g') {
473 *warnp = IN_ALL;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700474 pt = _yconv(year, base,
475 false, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700476 pt, ptlim, modifier);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700477 } else pt = _yconv(year, base,
478 true, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700479 pt, ptlim, modifier);
480 }
481 continue;
482 case 'v':
483 /*
484 ** From Arnold Robbins' strftime version 3.0:
485 ** "date as dd-bbb-YYYY"
486 ** (ado, 1993-05-24)
487 */
Elliott Hughes39d903a2014-07-25 15:50:31 -0700488 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700489 continue;
490 case 'W':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800491 pt = _conv(
492 (t->tm_yday + DAYSPERWEEK - (t->tm_wday ? (t->tm_wday - 1) : (DAYSPERWEEK - 1))) /
493 DAYSPERWEEK,
494 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
495 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700496 case 'w':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800497 pt = _conv(t->tm_wday, " ", pt, ptlim);
498 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700499 case 'X':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700500 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700501 continue;
502 case 'x':
503 {
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000504 enum warn warn2 = IN_SOME;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800505
Elliott Hughes39d903a2014-07-25 15:50:31 -0700506 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700507 if (warn2 == IN_ALL)
508 warn2 = IN_THIS;
509 if (warn2 > *warnp)
510 *warnp = warn2;
511 }
512 continue;
513 case 'y':
514 *warnp = IN_ALL;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700515 pt = _yconv(t->tm_year, TM_YEAR_BASE,
516 false, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700517 pt, ptlim, modifier);
518 continue;
519 case 'Y':
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700520 pt = _yconv(t->tm_year, TM_YEAR_BASE,
521 true, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700522 pt, ptlim, modifier);
523 continue;
524 case 'Z':
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800525#ifdef TM_ZONE
Elliott Hughesa9cac4c2015-11-12 16:51:31 -0800526 // BEGIN: Android-changed.
527 {
528 const char* zone = t->TM_ZONE;
529 if (!zone || !*zone) {
530 // "The value of tm_isdst shall be positive if Daylight Savings Time is
531 // in effect, 0 if Daylight Savings Time is not in effect, and negative
532 // if the information is not available."
533 if (t->tm_isdst == 0) zone = tzname[0];
534 else if (t->tm_isdst > 0) zone = tzname[1];
535
536 // "Replaced by the timezone name or abbreviation, or by no bytes if no
537 // timezone information exists."
538 if (!zone || !*zone) zone = "";
539 }
540 pt = _add(zone, pt, ptlim, modifier);
541 }
542 // END: Android-changed.
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000543#elif HAVE_TZNAME
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700544 if (t->tm_isdst >= 0)
545 pt = _add(tzname[t->tm_isdst != 0],
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700546 pt, ptlim);
547#endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700548 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000549 ** C99 and later say that %Z must be
550 ** replaced by the empty string if the
551 ** time zone abbreviation is not
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700552 ** determinable.
553 */
554 continue;
555 case 'z':
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000556#if defined TM_GMTOFF || USG_COMPAT || ALTZONE
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700557 {
Elliott Hughese0d0b152013-09-27 00:04:30 -0700558 long diff;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700559 char const * sign;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000560 bool negative;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800561
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000562# ifdef TM_GMTOFF
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700563 diff = t->TM_GMTOFF;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000564# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700565 /*
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000566 ** C99 and later say that the UT offset must
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700567 ** be computed by looking only at
568 ** tm_isdst. This requirement is
569 ** incorrect, since it means the code
570 ** must rely on magic (in this case
571 ** altzone and timezone), and the
572 ** magic might not have the correct
573 ** offset. Doing things correctly is
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000574 ** tricky and requires disobeying the standard;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700575 ** see GNU C strftime for details.
576 ** For now, punt and conform to the
577 ** standard, even though it's incorrect.
578 **
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000579 ** C99 and later say that %z must be replaced by
580 ** the empty string if the time zone is not
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700581 ** determinable, so output nothing if the
582 ** appropriate variables are not available.
583 */
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000584 if (t->tm_isdst < 0)
585 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700586 if (t->tm_isdst == 0)
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000587# if USG_COMPAT
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700588 diff = -timezone;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000589# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700590 continue;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000591# endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700592 else
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000593# if ALTZONE
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700594 diff = -altzone;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000595# else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700596 continue;
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000597# endif
598# endif
599 negative = diff < 0;
600 if (diff == 0) {
601#ifdef TM_ZONE
602 negative = t->TM_ZONE[0] == '-';
603#else
604 negative = t->tm_isdst < 0;
605# if HAVE_TZNAME
606 if (tzname[t->tm_isdst != 0][0] == '-')
607 negative = true;
608# endif
609#endif
610 }
611 if (negative) {
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700612 sign = "-";
613 diff = -diff;
614 } else sign = "+";
615 pt = _add(sign, pt, ptlim, modifier);
616 diff /= SECSPERMIN;
617 diff = (diff / MINSPERHOUR) * 100 +
618 (diff % MINSPERHOUR);
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800619 pt = _conv(diff, getformat(modifier, "04", " 4", " ", "04"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700620 }
Almaz Mingaleev5411aff2022-02-11 12:27:12 +0000621#endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700622 continue;
623 case '+':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700624 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
625 warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700626 continue;
627 case '%':
628 /*
629 ** X311J/88-090 (4.12.3.5): if conversion char is
630 ** undefined, behavior is undefined. Print out the
631 ** character itself as printf(3) also does.
632 */
633 default:
634 break;
635 }
636 }
637 if (pt == ptlim)
638 break;
639 *pt++ = *format;
640 }
641 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800642}
643
644static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700645_conv(int n, const char *format, char *pt, const char *ptlim)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800646{
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800647 // The original implementation used snprintf(3) here, but rolling our own is
648 // about 5x faster. Seems like a good trade-off for so little code, especially
649 // for users like logcat that have a habit of formatting 10k times all at
650 // once...
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800651
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800652 // Format is '0' or ' ' for the fill character, followed by a single-digit
653 // width or ' ' for "whatever".
654 // %d -> " "
655 // %2d -> " 2"
656 // %02d -> "02"
657 char fill = format[0];
658 int width = format[1] == ' ' ? 0 : format[1] - '0';
659
660 char buf[32] __attribute__((__uninitialized__));
661
662 // Terminate first, so we can walk backwards from the least-significant digit
663 // without having to later reverse the result.
664 char* p = &buf[31];
665 *--p = '\0';
666 char* end = p;
667
668 // Output digits backwards, from least-significant to most.
669 while (n >= 10) {
670 *--p = '0' + (n % 10);
671 n /= 10;
672 }
673 *--p = '0' + n;
674
675 // Fill if more digits are required by the format.
676 while ((end - p) < width) {
677 *--p = fill;
678 }
679
680 return _add(p, pt, ptlim, 0);
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800681}
682
683static char *
Elliott Hughes39d903a2014-07-25 15:50:31 -0700684_add(const char *str, char *pt, const char *const ptlim, int modifier)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800685{
686 int c;
687
688 switch (modifier) {
689 case FORCE_LOWER_CASE:
690 while (pt < ptlim && (*pt = tolower(*str++)) != '\0') {
691 ++pt;
692 }
693 break;
694
695 case '^':
696 while (pt < ptlim && (*pt = toupper(*str++)) != '\0') {
697 ++pt;
698 }
699 break;
700
701 case '#':
702 while (pt < ptlim && (c = *str++) != '\0') {
703 if (isupper(c)) {
704 c = tolower(c);
705 } else if (islower(c)) {
706 c = toupper(c);
707 }
708 *pt = c;
709 ++pt;
710 }
711
712 break;
713
714 default:
715 while (pt < ptlim && (*pt = *str++) != '\0') {
716 ++pt;
717 }
718 }
719
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700720 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800721}
722
723/*
724** POSIX and the C Standard are unclear or inconsistent about
725** what %C and %y do if the year is negative or exceeds 9999.
726** Use the convention that %C concatenated with %y yields the
727** same output as %Y, and that %Y contains at least 4 bytes,
728** with more only if necessary.
729*/
730
731static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700732_yconv(int a, int b, bool convert_top, bool convert_yy,
733 char *pt, const char *ptlim, int modifier)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800734{
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700735 register int lead;
736 register int trail;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800737
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700738#define DIVISOR 100
739 trail = a % DIVISOR + b % DIVISOR;
740 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
741 trail %= DIVISOR;
742 if (trail < 0 && lead > 0) {
743 trail += DIVISOR;
744 --lead;
745 } else if (lead < 0 && trail > 0) {
746 trail -= DIVISOR;
747 ++lead;
748 }
749 if (convert_top) {
750 if (lead == 0 && trail < 0)
751 pt = _add("-0", pt, ptlim, modifier);
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800752 else
753 pt = _conv(lead, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700754 }
755 if (convert_yy)
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800756 pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "02", " 2", " ", "02"), pt,
757 ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700758 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800759}