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