blob: 7c4be49de78e826a074a859853582a481251d26e [file] [log] [blame]
Elliott Hughes0a610d02016-07-29 14:04:17 -07001/* Convert a broken-down time stamp to a string. */
2
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
The Android Open Source Project1dc9e472009-03-03 19:28:35 -080038#include "tzfile.h"
39#include "fcntl.h"
40#include "locale.h"
Elliott Hughes5f564542014-06-19 13:54:10 -070041
Elliott Hughesa9209d72016-09-16 18:16:47 -070042#if defined(__BIONIC__)
Elliott Hughes905e6d52014-07-25 11:55:59 -070043
Elliott Hughes905e6d52014-07-25 11:55:59 -070044/* LP32 had a 32-bit time_t, so we need to work around that here. */
Elliott Hughes52defb72014-05-05 17:14:02 -070045#if defined(__LP64__)
46#define time64_t time_t
47#define mktime64 mktime
48#else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070049#include <time64.h>
Elliott Hughes52defb72014-05-05 17:14:02 -070050#endif
Elliott Hughes905e6d52014-07-25 11:55:59 -070051
Elliott Hughes5f564542014-06-19 13:54:10 -070052#include <ctype.h>
Elliott Hughes905e6d52014-07-25 11:55:59 -070053
Elliott Hughes39d903a2014-07-25 15:50:31 -070054#endif
Elliott Hughes905e6d52014-07-25 11:55:59 -070055
The Android Open Source Project1dc9e472009-03-03 19:28:35 -080056struct lc_time_T {
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070057 const char * mon[MONSPERYEAR];
58 const char * month[MONSPERYEAR];
59 const char * wday[DAYSPERWEEK];
60 const char * weekday[DAYSPERWEEK];
61 const char * X_fmt;
62 const char * x_fmt;
63 const char * c_fmt;
64 const char * am;
65 const char * pm;
66 const char * date_fmt;
67};
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -070068
69#define Locale (&C_time_locale)
70
71static const struct lc_time_T C_time_locale = {
72 {
73 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
74 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
75 }, {
76 "January", "February", "March", "April", "May", "June",
77 "July", "August", "September", "October", "November", "December"
78 }, {
79 "Sun", "Mon", "Tue", "Wed",
80 "Thu", "Fri", "Sat"
81 }, {
82 "Sunday", "Monday", "Tuesday", "Wednesday",
83 "Thursday", "Friday", "Saturday"
84 },
85
86 /* X_fmt */
87 "%H:%M:%S",
88
89 /*
90 ** x_fmt
91 ** C99 requires this format.
92 ** Using just numbers (as here) makes Quakers happier;
93 ** it's also compatible with SVR4.
94 */
95 "%m/%d/%y",
96
97 /*
98 ** c_fmt
99 ** C99 requires this format.
100 ** Previously this code used "%D %X", but we now conform to C99.
101 ** Note that
102 ** "%a %b %d %H:%M:%S %Y"
103 ** is used by Solaris 2.3.
104 */
105 "%a %b %e %T %Y",
106
107 /* am */
108 "AM",
109
110 /* pm */
111 "PM",
112
113 /* date_fmt */
114 "%a %b %e %H:%M:%S %Z %Y"
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800115};
116
Elliott Hughesce4783c2013-07-12 17:31:11 -0700117static char * _add(const char *, char *, const char *, int);
118static char * _conv(int, const char *, char *, const char *);
119static char * _fmt(const char *, const struct tm *, char *, const char *,
Elliott Hughes39d903a2014-07-25 15:50:31 -0700120 int *);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700121static char * _yconv(int, int, bool, bool, char *, const char *, int);
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800122
Elliott Hughes0a610d02016-07-29 14:04:17 -0700123#if !HAVE_POSIX_DECLS
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700124extern char * tzname[];
Elliott Hughes0a610d02016-07-29 14:04:17 -0700125#endif
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800126
127#ifndef YEAR_2000_NAME
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700128#define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800129#endif /* !defined YEAR_2000_NAME */
130
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700131#define IN_NONE 0
132#define IN_SOME 1
133#define IN_THIS 2
134#define IN_ALL 3
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800135
Elliott Hughes0a610d02016-07-29 14:04:17 -0700136#if HAVE_STRFTIME_L
137size_t
138strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
139 locale_t locale)
140{
141 /* Just call strftime, as only the C locale is supported. */
142 return strftime(s, maxsize, format, t);
143}
144#endif
145
146#define FORCE_LOWER_CASE 0x100 /* Android extension. */
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800147
148size_t
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700149strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700150{
151 char * p;
152 int warn;
153
154 tzset();
155 warn = IN_NONE;
Elliott Hughes39d903a2014-07-25 15:50:31 -0700156 p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn);
Elliott Hughes9a5a3e82014-05-05 20:28:28 -0700157#ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700158 if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700159 fprintf(stderr, "\n");
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700160 if (format == NULL)
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700161 fprintf(stderr, "NULL strftime format ");
162 else fprintf(stderr, "strftime format \"%s\" ",
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700163 format);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700164 fprintf(stderr, "yields only two digits of years in ");
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700165 if (warn == IN_SOME)
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700166 fprintf(stderr, "some locales");
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700167 else if (warn == IN_THIS)
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700168 fprintf(stderr, "the current locale");
169 else fprintf(stderr, "all locales");
170 fprintf(stderr, "\n");
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700171 }
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800172#endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700173 if (p == s + maxsize)
174 return 0;
175 *p = '\0';
176 return p - s;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800177}
178
179static char *getformat(int modifier, char *normal, char *underscore,
180 char *dash, char *zero) {
181 switch (modifier) {
182 case '_':
183 return underscore;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800184 case '-':
185 return dash;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800186 case '0':
187 return zero;
188 }
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800189 return normal;
190}
191
192static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700193_fmt(const char *format, const struct tm *t, char *pt,
194 const char *ptlim, int *warnp)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800195{
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700196 for ( ; *format; ++format) {
197 if (*format == '%') {
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800198 int modifier = 0;
199label:
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700200 switch (*++format) {
201 case '\0':
202 --format;
203 break;
204 case 'A':
205 pt = _add((t->tm_wday < 0 ||
206 t->tm_wday >= DAYSPERWEEK) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700207 "?" : Locale->weekday[t->tm_wday],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700208 pt, ptlim, modifier);
209 continue;
210 case 'a':
211 pt = _add((t->tm_wday < 0 ||
212 t->tm_wday >= DAYSPERWEEK) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700213 "?" : Locale->wday[t->tm_wday],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700214 pt, ptlim, modifier);
215 continue;
216 case 'B':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700217 pt = _add((t->tm_mon < 0 ||
Eric Fischera48fa7f2009-05-15 13:33:20 -0700218 t->tm_mon >= MONSPERYEAR) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700219 "?" : Locale->month[t->tm_mon],
Eric Fischera48fa7f2009-05-15 13:33:20 -0700220 pt, ptlim, modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700221 continue;
222 case 'b':
223 case 'h':
224 pt = _add((t->tm_mon < 0 ||
225 t->tm_mon >= MONSPERYEAR) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700226 "?" : Locale->mon[t->tm_mon],
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700227 pt, ptlim, modifier);
228 continue;
229 case 'C':
230 /*
231 ** %C used to do a...
232 ** _fmt("%a %b %e %X %Y", t);
233 ** ...whereas now POSIX 1003.2 calls for
234 ** something completely different.
235 ** (ado, 1993-05-24)
236 */
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700237 pt = _yconv(t->tm_year, TM_YEAR_BASE,
238 true, false, pt, ptlim, modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700239 continue;
240 case 'c':
241 {
242 int warn2 = IN_SOME;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800243
Elliott Hughes39d903a2014-07-25 15:50:31 -0700244 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700245 if (warn2 == IN_ALL)
246 warn2 = IN_THIS;
247 if (warn2 > *warnp)
248 *warnp = warn2;
249 }
250 continue;
251 case 'D':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700252 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700253 continue;
254 case 'd':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800255 pt = _conv(t->tm_mday, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
256 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700257 case 'E':
258 case 'O':
259 /*
260 ** C99 locale modifiers.
261 ** The sequences
262 ** %Ec %EC %Ex %EX %Ey %EY
263 ** %Od %oe %OH %OI %Om %OM
264 ** %OS %Ou %OU %OV %Ow %OW %Oy
265 ** are supposed to provide alternate
266 ** representations.
267 */
268 goto label;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800269 case '_':
270 case '-':
271 case '0':
272 case '^':
273 case '#':
274 modifier = *format;
275 goto label;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700276 case 'e':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800277 pt = _conv(t->tm_mday, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
278 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700279 case 'F':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700280 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700281 continue;
282 case 'H':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800283 pt = _conv(t->tm_hour, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
284 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700285 case 'I':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800286 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
287 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
288 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700289 case 'j':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800290 pt = _conv(t->tm_yday + 1, getformat(modifier, "03", " 3", " ", "03"), pt, ptlim);
291 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700292 case 'k':
293 /*
294 ** This used to be...
295 ** _conv(t->tm_hour % 12 ?
296 ** t->tm_hour % 12 : 12, 2, ' ');
297 ** ...and has been changed to the below to
298 ** match SunOS 4.1.1 and Arnold Robbins'
299 ** strftime version 3.0. That is, "%k" and
300 ** "%l" have been swapped.
301 ** (ado, 1993-05-24)
302 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800303 pt = _conv(t->tm_hour, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700304 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800305#ifdef KITCHEN_SINK
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700306 case 'K':
307 /*
308 ** After all this time, still unclaimed!
309 */
Elliott Hughes39d903a2014-07-25 15:50:31 -0700310 pt = _add("kitchen sink", pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700311 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800312#endif /* defined KITCHEN_SINK */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700313 case 'l':
314 /*
315 ** This used to be...
316 ** _conv(t->tm_hour, 2, ' ');
317 ** ...and has been changed to the below to
318 ** match SunOS 4.1.1 and Arnold Robbin's
319 ** strftime version 3.0. That is, "%k" and
320 ** "%l" have been swapped.
321 ** (ado, 1993-05-24)
322 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800323 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
324 getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700325 continue;
326 case 'M':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800327 pt = _conv(t->tm_min, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
328 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700329 case 'm':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800330 pt = _conv(t->tm_mon + 1, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
331 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700332 case 'n':
333 pt = _add("\n", pt, ptlim, modifier);
334 continue;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700335 case 'P':
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700336 case 'p':
337 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
Elliott Hughes39d903a2014-07-25 15:50:31 -0700338 Locale->pm :
339 Locale->am,
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700340 pt, ptlim, (*format == 'P') ? FORCE_LOWER_CASE : modifier);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700341 continue;
342 case 'R':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700343 pt = _fmt("%H:%M", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700344 continue;
345 case 'r':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700346 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700347 continue;
348 case 'S':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800349 pt = _conv(t->tm_sec, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
350 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700351 case 's':
352 {
353 struct tm tm;
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800354 char buf[INT_STRLEN_MAXIMUM(time64_t) + 1] __attribute__((__uninitialized__));
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700355 time64_t mkt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800356
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700357 tm = *t;
358 mkt = mktime64(&tm);
359 if (TYPE_SIGNED(time64_t))
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700360 snprintf(buf, sizeof(buf), "%"PRIdMAX,
361 (intmax_t) mkt);
362 else snprintf(buf, sizeof(buf), "%"PRIuMAX,
363 (uintmax_t) mkt);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700364 pt = _add(buf, pt, ptlim, modifier);
365 }
366 continue;
367 case 'T':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700368 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700369 continue;
370 case 't':
371 pt = _add("\t", pt, ptlim, modifier);
372 continue;
373 case 'U':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800374 pt = _conv((t->tm_yday + DAYSPERWEEK - t->tm_wday) / DAYSPERWEEK,
375 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
376 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700377 case 'u':
378 /*
379 ** From Arnold Robbins' strftime version 3.0:
380 ** "ISO 8601: Weekday as a decimal number
381 ** [1 (Monday) - 7]"
382 ** (ado, 1993-05-24)
383 */
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800384 pt = _conv((t->tm_wday == 0) ? DAYSPERWEEK : t->tm_wday, " ", pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700385 continue;
386 case 'V': /* ISO 8601 week number */
387 case 'G': /* ISO 8601 year (four digits) */
388 case 'g': /* ISO 8601 year (two digits) */
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800389/*
390** From Arnold Robbins' strftime version 3.0: "the week number of the
391** year (the first Monday as the first day of week 1) as a decimal number
392** (01-53)."
393** (ado, 1993-05-24)
394**
Elliott Hughes39d903a2014-07-25 15:50:31 -0700395** From <http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html> by Markus Kuhn:
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800396** "Week 01 of a year is per definition the first week which has the
397** Thursday in this year, which is equivalent to the week which contains
398** the fourth day of January. In other words, the first week of a new year
399** is the week which has the majority of its days in the new year. Week 01
400** might also contain days from the previous year and the week before week
401** 01 of a year is the last week (52 or 53) of the previous year even if
402** it contains days from the new year. A week starts with Monday (day 1)
403** and ends with Sunday (day 7). For example, the first week of the year
404** 1997 lasts from 1996-12-30 to 1997-01-05..."
405** (ado, 1996-01-02)
406*/
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700407 {
408 int year;
409 int base;
410 int yday;
411 int wday;
412 int w;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800413
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700414 year = t->tm_year;
415 base = TM_YEAR_BASE;
416 yday = t->tm_yday;
417 wday = t->tm_wday;
418 for ( ; ; ) {
419 int len;
420 int bot;
421 int top;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800422
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700423 len = isleap_sum(year, base) ?
424 DAYSPERLYEAR :
425 DAYSPERNYEAR;
426 /*
427 ** What yday (-3 ... 3) does
428 ** the ISO year begin on?
429 */
430 bot = ((yday + 11 - wday) %
431 DAYSPERWEEK) - 3;
432 /*
433 ** What yday does the NEXT
434 ** ISO year begin on?
435 */
436 top = bot -
437 (len % DAYSPERWEEK);
438 if (top < -3)
439 top += DAYSPERWEEK;
440 top += len;
441 if (yday >= top) {
442 ++base;
443 w = 1;
444 break;
445 }
446 if (yday >= bot) {
447 w = 1 + ((yday - bot) /
448 DAYSPERWEEK);
449 break;
450 }
451 --base;
452 yday += isleap_sum(year, base) ?
453 DAYSPERLYEAR :
454 DAYSPERNYEAR;
455 }
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800456#ifdef XPG4_1994_04_09
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700457 if ((w == 52 &&
458 t->tm_mon == TM_JANUARY) ||
459 (w == 1 &&
460 t->tm_mon == TM_DECEMBER))
461 w = 53;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800462#endif /* defined XPG4_1994_04_09 */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700463 if (*format == 'V')
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800464 pt = _conv(w, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700465 else if (*format == 'g') {
466 *warnp = IN_ALL;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700467 pt = _yconv(year, base,
468 false, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700469 pt, ptlim, modifier);
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700470 } else pt = _yconv(year, base,
471 true, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700472 pt, ptlim, modifier);
473 }
474 continue;
475 case 'v':
476 /*
477 ** From Arnold Robbins' strftime version 3.0:
478 ** "date as dd-bbb-YYYY"
479 ** (ado, 1993-05-24)
480 */
Elliott Hughes39d903a2014-07-25 15:50:31 -0700481 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700482 continue;
483 case 'W':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800484 pt = _conv(
485 (t->tm_yday + DAYSPERWEEK - (t->tm_wday ? (t->tm_wday - 1) : (DAYSPERWEEK - 1))) /
486 DAYSPERWEEK,
487 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
488 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700489 case 'w':
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800490 pt = _conv(t->tm_wday, " ", pt, ptlim);
491 continue;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700492 case 'X':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700493 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700494 continue;
495 case 'x':
496 {
497 int warn2 = IN_SOME;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800498
Elliott Hughes39d903a2014-07-25 15:50:31 -0700499 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700500 if (warn2 == IN_ALL)
501 warn2 = IN_THIS;
502 if (warn2 > *warnp)
503 *warnp = warn2;
504 }
505 continue;
506 case 'y':
507 *warnp = IN_ALL;
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700508 pt = _yconv(t->tm_year, TM_YEAR_BASE,
509 false, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700510 pt, ptlim, modifier);
511 continue;
512 case 'Y':
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700513 pt = _yconv(t->tm_year, TM_YEAR_BASE,
514 true, true,
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700515 pt, ptlim, modifier);
516 continue;
517 case 'Z':
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800518#ifdef TM_ZONE
Elliott Hughesa9cac4c2015-11-12 16:51:31 -0800519 // BEGIN: Android-changed.
520 {
521 const char* zone = t->TM_ZONE;
522 if (!zone || !*zone) {
523 // "The value of tm_isdst shall be positive if Daylight Savings Time is
524 // in effect, 0 if Daylight Savings Time is not in effect, and negative
525 // if the information is not available."
526 if (t->tm_isdst == 0) zone = tzname[0];
527 else if (t->tm_isdst > 0) zone = tzname[1];
528
529 // "Replaced by the timezone name or abbreviation, or by no bytes if no
530 // timezone information exists."
531 if (!zone || !*zone) zone = "";
532 }
533 pt = _add(zone, pt, ptlim, modifier);
534 }
535 // END: Android-changed.
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700536#else
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700537 if (t->tm_isdst >= 0)
538 pt = _add(tzname[t->tm_isdst != 0],
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700539 pt, ptlim);
540#endif
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700541 /*
542 ** C99 says that %Z must be replaced by the
543 ** empty string if the time zone is not
544 ** determinable.
545 */
546 continue;
547 case 'z':
548 {
Elliott Hughese0d0b152013-09-27 00:04:30 -0700549 long diff;
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700550 char const * sign;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800551
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700552 if (t->tm_isdst < 0)
553 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800554#ifdef TM_GMTOFF
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700555 diff = t->TM_GMTOFF;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800556#else /* !defined TM_GMTOFF */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700557 /*
Elliott Hughese0d0b152013-09-27 00:04:30 -0700558 ** C99 says that the UT offset must
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700559 ** be computed by looking only at
560 ** tm_isdst. This requirement is
561 ** incorrect, since it means the code
562 ** must rely on magic (in this case
563 ** altzone and timezone), and the
564 ** magic might not have the correct
565 ** offset. Doing things correctly is
566 ** tricky and requires disobeying C99;
567 ** see GNU C strftime for details.
568 ** For now, punt and conform to the
569 ** standard, even though it's incorrect.
570 **
571 ** C99 says that %z must be replaced by the
572 ** empty string if the time zone is not
573 ** determinable, so output nothing if the
574 ** appropriate variables are not available.
575 */
576 if (t->tm_isdst == 0)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800577#ifdef USG_COMPAT
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700578 diff = -timezone;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800579#else /* !defined USG_COMPAT */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700580 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800581#endif /* !defined USG_COMPAT */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700582 else
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800583#ifdef ALTZONE
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700584 diff = -altzone;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800585#else /* !defined ALTZONE */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700586 continue;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800587#endif /* !defined ALTZONE */
588#endif /* !defined TM_GMTOFF */
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700589 if (diff < 0) {
590 sign = "-";
591 diff = -diff;
592 } else sign = "+";
593 pt = _add(sign, pt, ptlim, modifier);
594 diff /= SECSPERMIN;
595 diff = (diff / MINSPERHOUR) * 100 +
596 (diff % MINSPERHOUR);
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800597 pt = _conv(diff, getformat(modifier, "04", " 4", " ", "04"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700598 }
599 continue;
600 case '+':
Elliott Hughes39d903a2014-07-25 15:50:31 -0700601 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
602 warnp);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700603 continue;
604 case '%':
605 /*
606 ** X311J/88-090 (4.12.3.5): if conversion char is
607 ** undefined, behavior is undefined. Print out the
608 ** character itself as printf(3) also does.
609 */
610 default:
611 break;
612 }
613 }
614 if (pt == ptlim)
615 break;
616 *pt++ = *format;
617 }
618 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800619}
620
621static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700622_conv(int n, const char *format, char *pt, const char *ptlim)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800623{
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800624 // The original implementation used snprintf(3) here, but rolling our own is
625 // about 5x faster. Seems like a good trade-off for so little code, especially
626 // for users like logcat that have a habit of formatting 10k times all at
627 // once...
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800628
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800629 // Format is '0' or ' ' for the fill character, followed by a single-digit
630 // width or ' ' for "whatever".
631 // %d -> " "
632 // %2d -> " 2"
633 // %02d -> "02"
634 char fill = format[0];
635 int width = format[1] == ' ' ? 0 : format[1] - '0';
636
637 char buf[32] __attribute__((__uninitialized__));
638
639 // Terminate first, so we can walk backwards from the least-significant digit
640 // without having to later reverse the result.
641 char* p = &buf[31];
642 *--p = '\0';
643 char* end = p;
644
645 // Output digits backwards, from least-significant to most.
646 while (n >= 10) {
647 *--p = '0' + (n % 10);
648 n /= 10;
649 }
650 *--p = '0' + n;
651
652 // Fill if more digits are required by the format.
653 while ((end - p) < width) {
654 *--p = fill;
655 }
656
657 return _add(p, pt, ptlim, 0);
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800658}
659
660static char *
Elliott Hughes39d903a2014-07-25 15:50:31 -0700661_add(const char *str, char *pt, const char *const ptlim, int modifier)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800662{
663 int c;
664
665 switch (modifier) {
666 case FORCE_LOWER_CASE:
667 while (pt < ptlim && (*pt = tolower(*str++)) != '\0') {
668 ++pt;
669 }
670 break;
671
672 case '^':
673 while (pt < ptlim && (*pt = toupper(*str++)) != '\0') {
674 ++pt;
675 }
676 break;
677
678 case '#':
679 while (pt < ptlim && (c = *str++) != '\0') {
680 if (isupper(c)) {
681 c = tolower(c);
682 } else if (islower(c)) {
683 c = toupper(c);
684 }
685 *pt = c;
686 ++pt;
687 }
688
689 break;
690
691 default:
692 while (pt < ptlim && (*pt = *str++) != '\0') {
693 ++pt;
694 }
695 }
696
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700697 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800698}
699
700/*
701** POSIX and the C Standard are unclear or inconsistent about
702** what %C and %y do if the year is negative or exceeds 9999.
703** Use the convention that %C concatenated with %y yields the
704** same output as %Y, and that %Y contains at least 4 bytes,
705** with more only if necessary.
706*/
707
708static char *
Elliott Hughes9fb22a32015-10-07 17:13:40 -0700709_yconv(int a, int b, bool convert_top, bool convert_yy,
710 char *pt, const char *ptlim, int modifier)
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800711{
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700712 register int lead;
713 register int trail;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800714
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700715#define DIVISOR 100
716 trail = a % DIVISOR + b % DIVISOR;
717 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
718 trail %= DIVISOR;
719 if (trail < 0 && lead > 0) {
720 trail += DIVISOR;
721 --lead;
722 } else if (lead < 0 && trail > 0) {
723 trail -= DIVISOR;
724 ++lead;
725 }
726 if (convert_top) {
727 if (lead == 0 && trail < 0)
728 pt = _add("-0", pt, ptlim, modifier);
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800729 else
730 pt = _conv(lead, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700731 }
732 if (convert_yy)
Elliott Hughese4d5efe2021-11-18 17:57:38 -0800733 pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "02", " 2", " ", "02"), pt,
734 ptlim);
The Android Open Source Projectedbe7fc2009-03-18 22:20:24 -0700735 return pt;
The Android Open Source Project1dc9e472009-03-03 19:28:35 -0800736}