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