overhaul internally-public declarations using wrapper headers
[musl] / src / time / strftime.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <langinfo.h>
5 #include <locale.h>
6 #include <time.h>
7 #include <limits.h>
8 #include "locale_impl.h"
9 #include "libc.h"
10 #include "time_impl.h"
11
12 static int is_leap(int y)
13 {
14         /* Avoid overflow */
15         if (y>INT_MAX-1900) y -= 2000;
16         y += 1900;
17         return !(y%4) && ((y%100) || !(y%400));
18 }
19
20 static int week_num(const struct tm *tm)
21 {
22         int val = (tm->tm_yday + 7U - (tm->tm_wday+6U)%7) / 7;
23         /* If 1 Jan is just 1-3 days past Monday,
24          * the previous week is also in this year. */
25         if ((tm->tm_wday + 371U - tm->tm_yday - 2) % 7 <= 2)
26                 val++;
27         if (!val) {
28                 val = 52;
29                 /* If 31 December of prev year a Thursday,
30                  * or Friday of a leap year, then the
31                  * prev year has 53 weeks. */
32                 int dec31 = (tm->tm_wday + 7U - tm->tm_yday - 1) % 7;
33                 if (dec31 == 4 || (dec31 == 5 && is_leap(tm->tm_year%400-1)))
34                         val++;
35         } else if (val == 53) {
36                 /* If 1 January is not a Thursday, and not
37                  * a Wednesday of a leap year, then this
38                  * year has only 52 weeks. */
39                 int jan1 = (tm->tm_wday + 371U - tm->tm_yday) % 7;
40                 if (jan1 != 4 && (jan1 != 3 || !is_leap(tm->tm_year)))
41                         val = 1;
42         }
43         return val;
44 }
45
46 const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *tm, locale_t loc, int pad)
47 {
48         nl_item item;
49         long long val;
50         const char *fmt = "-";
51         int width = 2, def_pad = '0';
52
53         switch (f) {
54         case 'a':
55                 if (tm->tm_wday > 6U) goto string;
56                 item = ABDAY_1 + tm->tm_wday;
57                 goto nl_strcat;
58         case 'A':
59                 if (tm->tm_wday > 6U) goto string;
60                 item = DAY_1 + tm->tm_wday;
61                 goto nl_strcat;
62         case 'h':
63         case 'b':
64                 if (tm->tm_mon > 11U) goto string;
65                 item = ABMON_1 + tm->tm_mon;
66                 goto nl_strcat;
67         case 'B':
68                 if (tm->tm_mon > 11U) goto string;
69                 item = MON_1 + tm->tm_mon;
70                 goto nl_strcat;
71         case 'c':
72                 item = D_T_FMT;
73                 goto nl_strftime;
74         case 'C':
75                 val = (1900LL+tm->tm_year) / 100;
76                 goto number;
77         case 'e':
78                 def_pad = '_';
79         case 'd':
80                 val = tm->tm_mday;
81                 goto number;
82         case 'D':
83                 fmt = "%m/%d/%y";
84                 goto recu_strftime;
85         case 'F':
86                 fmt = "%Y-%m-%d";
87                 goto recu_strftime;
88         case 'g':
89         case 'G':
90                 val = tm->tm_year + 1900LL;
91                 if (tm->tm_yday < 3 && week_num(tm) != 1) val--;
92                 else if (tm->tm_yday > 360 && week_num(tm) == 1) val++;
93                 if (f=='g') val %= 100;
94                 else width = 4;
95                 goto number;
96         case 'H':
97                 val = tm->tm_hour;
98                 goto number;
99         case 'I':
100                 val = tm->tm_hour;
101                 if (!val) val = 12;
102                 else if (val > 12) val -= 12;
103                 goto number;
104         case 'j':
105                 val = tm->tm_yday+1;
106                 width = 3;
107                 goto number;
108         case 'm':
109                 val = tm->tm_mon+1;
110                 goto number;
111         case 'M':
112                 val = tm->tm_min;
113                 goto number;
114         case 'n':
115                 *l = 1;
116                 return "\n";
117         case 'p':
118                 item = tm->tm_hour >= 12 ? PM_STR : AM_STR;
119                 goto nl_strcat;
120         case 'r':
121                 item = T_FMT_AMPM;
122                 goto nl_strftime;
123         case 'R':
124                 fmt = "%H:%M";
125                 goto recu_strftime;
126         case 's':
127                 val = __tm_to_secs(tm) - tm->__tm_gmtoff;
128                 width = 1;
129                 goto number;
130         case 'S':
131                 val = tm->tm_sec;
132                 goto number;
133         case 't':
134                 *l = 1;
135                 return "\t";
136         case 'T':
137                 fmt = "%H:%M:%S";
138                 goto recu_strftime;
139         case 'u':
140                 val = tm->tm_wday ? tm->tm_wday : 7;
141                 width = 1;
142                 goto number;
143         case 'U':
144                 val = (tm->tm_yday + 7U - tm->tm_wday) / 7;
145                 goto number;
146         case 'W':
147                 val = (tm->tm_yday + 7U - (tm->tm_wday+6U)%7) / 7;
148                 goto number;
149         case 'V':
150                 val = week_num(tm);
151                 goto number;
152         case 'w':
153                 val = tm->tm_wday;
154                 width = 1;
155                 goto number;
156         case 'x':
157                 item = D_FMT;
158                 goto nl_strftime;
159         case 'X':
160                 item = T_FMT;
161                 goto nl_strftime;
162         case 'y':
163                 val = (tm->tm_year + 1900LL) % 100;
164                 if (val < 0) val = -val;
165                 goto number;
166         case 'Y':
167                 val = tm->tm_year + 1900LL;
168                 if (val >= 10000) {
169                         *l = snprintf(*s, sizeof *s, "+%lld", val);
170                         return *s;
171                 }
172                 width = 4;
173                 goto number;
174         case 'z':
175                 if (tm->tm_isdst < 0) {
176                         *l = 0;
177                         return "";
178                 }
179                 *l = snprintf(*s, sizeof *s, "%+.4ld",
180                         tm->__tm_gmtoff/3600*100 + tm->__tm_gmtoff%3600/60);
181                 return *s;
182         case 'Z':
183                 if (tm->tm_isdst < 0) {
184                         *l = 0;
185                         return "";
186                 }
187                 fmt = __tm_to_tzname(tm);
188                 goto string;
189         case '%':
190                 *l = 1;
191                 return "%";
192         default:
193                 return 0;
194         }
195 number:
196         switch (pad ? pad : def_pad) {
197         case '-': *l = snprintf(*s, sizeof *s, "%lld", val); break;
198         case '_': *l = snprintf(*s, sizeof *s, "%*lld", width, val); break;
199         case '0':
200         default:  *l = snprintf(*s, sizeof *s, "%0*lld", width, val); break;
201         }
202         return *s;
203 nl_strcat:
204         fmt = __nl_langinfo_l(item, loc);
205 string:
206         *l = strlen(fmt);
207         return fmt;
208 nl_strftime:
209         fmt = __nl_langinfo_l(item, loc);
210 recu_strftime:
211         *l = __strftime_l(*s, sizeof *s, fmt, tm, loc);
212         if (!*l) return 0;
213         return *s;
214 }
215
216 size_t __strftime_l(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm, locale_t loc)
217 {
218         size_t l, k;
219         char buf[100];
220         char *p;
221         const char *t;
222         int pad, plus;
223         unsigned long width;
224         for (l=0; l<n; f++) {
225                 if (!*f) {
226                         s[l] = 0;
227                         return l;
228                 }
229                 if (*f != '%') {
230                         s[l++] = *f;
231                         continue;
232                 }
233                 f++;
234                 pad = 0;
235                 if (*f == '-' || *f == '_' || *f == '0') pad = *f++;
236                 if ((plus = (*f == '+'))) f++;
237                 width = strtoul(f, &p, 10);
238                 if (*p == 'C' || *p == 'F' || *p == 'G' || *p == 'Y') {
239                         if (!width && p!=f) width = 1;
240                 } else {
241                         width = 0;
242                 }
243                 f = p;
244                 if (*f == 'E' || *f == 'O') f++;
245                 t = __strftime_fmt_1(&buf, &k, *f, tm, loc, pad);
246                 if (!t) break;
247                 if (width) {
248                         /* Trim off any sign and leading zeros, then
249                          * count remaining digits to determine behavior
250                          * for the + flag. */
251                         if (*t=='+' || *t=='-') t++, k--;
252                         for (; *t=='0' && t[1]-'0'<10U; t++, k--);
253                         if (width < k) width = k;
254                         size_t d;
255                         for (d=0; t[d]-'0'<10U; d++);
256                         if (tm->tm_year < -1900) {
257                                 s[l++] = '-';
258                                 width--;
259                         } else if (plus && d+(width-k) >= (*p=='C'?3:5)) {
260                                 s[l++] = '+';
261                                 width--;
262                         }
263                         for (; width > k && l < n; width--)
264                                 s[l++] = '0';
265                 }
266                 if (k > n-l) k = n-l;
267                 memcpy(s+l, t, k);
268                 l += k;
269         }
270         if (n) {
271                 if (l==n) l=n-1;
272                 s[l] = 0;
273         }
274         return 0;
275 }
276
277 size_t strftime(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm)
278 {
279         return __strftime_l(s, n, f, tm, CURRENT_LOCALE);
280 }
281
282 weak_alias(__strftime_l, strftime_l);