1 #include "moment.h"
2 #include "dt_core.h"
3 #include "dt_accessor.h"
4 
5 typedef enum {
6     PAD_DEFAULT,
7     PAD_NONE,
8     PAD_ZERO,
9     PAD_SPACE,
10 } pad_t;
11 
12 static const char *aDoW[] = {
13     "Mon",
14     "Tue",
15     "Wed",
16     "Thu",
17     "Fri",
18     "Sat",
19     "Sun",
20 };
21 
22 static const char *fDoW[] = {
23     "Monday",
24     "Tuesday",
25     "Wednesday",
26     "Thursday",
27     "Friday",
28     "Saturday",
29     "Sunday",
30 };
31 
32 static const char *aMonth[] = {
33     "Jan",
34     "Feb",
35     "Mar",
36     "Apr",
37     "May",
38     "Jun",
39     "Jul",
40     "Aug",
41     "Sep",
42     "Oct",
43     "Nov",
44     "Dec",
45 };
46 
47 static const char *fMonth[] = {
48     "January",
49     "February",
50     "March",
51     "April",
52     "May",
53     "June",
54     "July",
55     "August",
56     "September",
57     "October",
58     "November",
59     "December",
60 };
61 
62 static const char *Meridiem[] = {
63     "AM",
64     "PM",
65 };
66 
67 /*
68  * The first Sunday of January is the first day of week 1; days in the new
69  * year before this are in week 0.
70  */
71 static int
dt_week_number_sun(dt_t dt)72 dt_week_number_sun(dt_t dt) {
73     int sunday = dt_doy(dt) - dt_dow(dt) % 7;
74     return (sunday + 6) / 7;
75 }
76 
77 /*
78  * The first Monday of January is the first day of week 1; days in the new
79  * year before this are in week 0.
80  */
81 static int
dt_week_number_mon(dt_t dt)82 dt_week_number_mon(dt_t dt) {
83     int monday = dt_doy(dt) - (dt_dow(dt) + 6) % 7;
84     return (monday + 6) / 7;
85 }
86 
87 static int
moment_hour_12(const moment_t * mt)88 moment_hour_12(const moment_t *mt) {
89     int h = moment_hour(mt) % 12;
90     if (h == 0)
91         h = 12;
92     return h;
93 }
94 
95 static const char *
moment_hour_meridiem(const moment_t * mt)96 moment_hour_meridiem(const moment_t *mt) {
97     return Meridiem[moment_hour(mt) / 12];
98 }
99 
100 static bool
supports_padding_flag(const char c)101 supports_padding_flag(const char c) {
102     switch (c) {
103         case 'C':
104         case 'd':
105         case 'e':
106         case 'g':
107         case 'G':
108         case 'H':
109         case 'I':
110         case 'j':
111         case 'k':
112         case 'l':
113         case 'm':
114         case 'M':
115         case 'S':
116         case 'U':
117         case 'V':
118         case 'W':
119         case 'y':
120         case 'Y':
121             return TRUE;
122     }
123     return FALSE;
124 }
125 
126 static void
THX_format_num(pTHX_ SV * dsv,size_t width,pad_t want,pad_t def,unsigned int v)127 THX_format_num(pTHX_ SV *dsv, size_t width, pad_t want, pad_t def, unsigned int v) {
128     char buf[20], *p, *e, *d, c;
129     size_t nlen, plen, dlen;
130 
131     p = e = buf + sizeof(buf);
132     do {
133         *--p = '0' + (v % 10);
134     } while (v /= 10);
135 
136     if (want == PAD_DEFAULT)
137         want = def;
138 
139     if      (want == PAD_ZERO)  c = '0';
140     else if (want == PAD_SPACE) c = ' ';
141     else                        width = 0;
142 
143     nlen = e - p;
144     plen = (width > nlen) ? width - nlen : 0;
145     dlen = nlen + plen;
146     (void)SvGROW(dsv, SvCUR(dsv) + dlen + 1);
147     d = SvPVX(dsv) + SvCUR(dsv);
148     if (plen) {
149         memset(d, c, plen);
150         d += plen;
151     }
152     memcpy(d, p, nlen);
153     SvCUR_set(dsv, SvCUR(dsv) + dlen);
154     *SvEND(dsv) = '\0';
155 }
156 
157 
158 #define CHR(n, d) (char)('0' + ((n) / (d)) % 10)
159 static void
THX_format_f(pTHX_ SV * dsv,const moment_t * mt,int len)160 THX_format_f(pTHX_ SV *dsv, const moment_t *mt, int len) {
161     char buf[9];
162     int ns;
163 
164     if      (len > 9) len = 9;
165     else if (len < 0) len = 0;
166     ns = moment_nanosecond(mt);
167     if (len == 0) {
168         if      ((ns % 1000000) == 0) len = 3;
169         else if ((ns % 1000)    == 0) len = 6;
170         else                          len = 9;
171     }
172     switch (len) {
173         case 9: buf[8] = CHR(ns, 1);
174         case 8: buf[7] = CHR(ns, 10);
175         case 7: buf[6] = CHR(ns, 100);
176         case 6: buf[5] = CHR(ns, 1000);
177         case 5: buf[4] = CHR(ns, 10000);
178         case 4: buf[3] = CHR(ns, 100000);
179         case 3: buf[2] = CHR(ns, 1000000);
180         case 2: buf[1] = CHR(ns, 10000000);
181         case 1: buf[0] = CHR(ns, 100000000);
182     }
183     sv_catpvn(dsv, buf, len);
184 }
185 #undef CHR
186 
187 static void
THX_format_s(pTHX_ SV * dsv,const moment_t * mt)188 THX_format_s(pTHX_ SV *dsv, const moment_t *mt) {
189     char buf[30], *p, *e;
190     int64_t v;
191 
192     v = moment_epoch(mt);
193     p = e = buf + sizeof(buf);
194     if (v < 0) {
195         do {
196             *--p = '0' - (v % 10);
197         } while (v /= 10);
198         *--p = '-';
199     }
200     else {
201         do {
202             *--p = '0' + (v % 10);
203         } while (v /= 10);
204     }
205     sv_catpvn(dsv, p, e - p);
206 }
207 
208 static void
THX_format_z(pTHX_ SV * dsv,const moment_t * mt,int extended)209 THX_format_z(pTHX_ SV *dsv, const moment_t *mt, int extended) {
210     int offset, sign;
211 
212     offset = moment_offset(mt);
213     if (offset < 0)
214         sign = '-', offset = -offset;
215     else
216         sign = '+';
217     if (extended)
218         sv_catpvf(dsv, "%c%02d:%02d", sign, offset / 60, offset % 60);
219     else
220         sv_catpvf(dsv, "%c%04d", sign, (offset / 60) * 100 + (offset % 60));
221 }
222 
223 static void
THX_format_Z(pTHX_ SV * dsv,const moment_t * mt)224 THX_format_Z(pTHX_ SV *dsv, const moment_t *mt) {
225     int offset, sign;
226 
227     offset = moment_offset(mt);
228     if (offset == 0)
229         sv_catpvn(dsv, "Z", 1);
230     else {
231         if (offset < 0)
232             sign = '-', offset = -offset;
233         else
234             sign = '+';
235         sv_catpvf(dsv, "%c%02d:%02d", sign, offset / 60, offset % 60);
236     }
237 }
238 
239 #define format_num(dsv, width, wanted, def, num) \
240     THX_format_num(aTHX_ dsv, width, wanted, def, num)
241 
242 #define format_f(dsv, mt, len) \
243     THX_format_f(aTHX_ dsv, mt, len)
244 
245 #define format_s(dsv, mt) \
246     THX_format_s(aTHX_ dsv, mt)
247 
248 #define format_z(dsv, mt, extended) \
249     THX_format_z(aTHX_ dsv, mt, extended)
250 
251 #define format_Z(dsv, mt) \
252     THX_format_Z(aTHX_ dsv, mt)
253 
254 SV *
THX_moment_strftime(pTHX_ const moment_t * mt,const char * s,STRLEN len)255 THX_moment_strftime(pTHX_ const moment_t *mt, const char *s, STRLEN len) {
256     const char *e, *p;
257     char c;
258     SV *dsv;
259     dt_t dt;
260     pad_t pad;
261     int year, month, day, width, zextd;
262 
263     dsv = sv_2mortal(newSV(16));
264     SvCUR_set(dsv, 0);
265     SvPOK_only(dsv);
266 
267     dt = moment_local_dt(mt);
268     dt_to_ymd(dt, &year, &month, &day);
269 
270     e = s + len;
271     while (s < e) {
272         p = (const char *)memchr(s, '%', e - s);
273         if (p == NULL || p + 1 == e)
274             p = e;
275         sv_catpvn(dsv, s, p - s);
276         if (p == e)
277             break;
278 
279         pad = PAD_DEFAULT;
280         width = -1;
281         zextd = 0;
282         s = p + 1;
283 
284       label:
285         switch (c = *s++) {
286             case 'a': /* locale's abbreviated day of the week name */
287                 sv_catpv(dsv, aDoW[dt_dow(dt) - 1]);
288                 break;
289             case 'A': /* locale's full day of the week name */
290                 sv_catpv(dsv, fDoW[dt_dow(dt) - 1]);
291                 break;
292             case 'b': /* locale's abbreviated month name */
293             case 'h':
294                 sv_catpv(dsv, aMonth[month - 1]);
295                 break;
296             case 'B': /* locale's full month name */
297                 sv_catpv(dsv, fMonth[month - 1]);
298                 break;
299             case 'c': /* locale's date and time (C locale: %a %b %e %H:%M:%S %Y) */
300                 sv_catpvf(dsv, "%s %s %2d %02d:%02d:%02d %04d",
301                          aDoW[dt_dow(dt) - 1],
302                          aMonth[month - 1],
303                          day,
304                          moment_hour(mt),
305                          moment_minute(mt),
306                          moment_second(mt),
307                          year);
308                 break;
309             case 'C':
310                 format_num(dsv, 2, pad, PAD_ZERO, year / 100);
311                 break;
312             case 'd':
313                 format_num(dsv, 2, pad, PAD_ZERO, day);
314                 break;
315             case 'x': /* locale's time representation (C locale: %m/%d/%y) */
316             case 'D':
317                 sv_catpvf(dsv, "%02d/%02d/%02d", month, day, year % 100);
318                 break;
319             case 'e':
320                 format_num(dsv, 2, pad, PAD_SPACE, day);
321                 break;
322             case 'f': /* extended conversion specification */
323                 if (moment_nanosecond(mt)) {
324                     sv_catpvn(dsv, ".", 1);
325                     format_f(dsv, mt, width);
326                 }
327                 break;
328             case 'F':
329                 sv_catpvf(dsv, "%04d-%02d-%02d", year, month, day);
330                 break;
331             case 'g':
332                 format_num(dsv, 2, pad, PAD_ZERO, dt_yow(dt) % 100);
333                 break;
334             case 'G':
335                 format_num(dsv, 4, pad, PAD_ZERO, dt_yow(dt));
336                 break;
337             case 'H':
338                 format_num(dsv, 2, pad, PAD_ZERO, moment_hour(mt));
339                 break;
340             case 'I':
341                 format_num(dsv, 2, pad, PAD_ZERO, moment_hour_12(mt));
342                 break;
343             case 'j':
344                 format_num(dsv, 3, pad, PAD_ZERO, dt_doy(dt));
345                 break;
346             case 'k': /* extended conversion specification */
347                 format_num(dsv, 2, pad, PAD_SPACE, moment_hour(mt));
348                 break;
349             case 'l': /* extended conversion specification */
350                 format_num(dsv, 2, pad, PAD_SPACE, moment_hour_12(mt));
351                 break;
352             case 'm':
353                 format_num(dsv, 2, pad, PAD_ZERO, month);
354                 break;
355             case 'M':
356                 format_num(dsv, 2, pad, PAD_ZERO, moment_minute(mt));
357                 break;
358             case 'n':
359                 sv_catpvn(dsv, "\n", 1);
360                 break;
361             case 'N': /* extended conversion specification */
362                 format_f(dsv, mt, width);
363                 break;
364             case 'p': /* locale's equivalent of either a.m. or p.m (C locale: AM or PM) */
365                 sv_catpv(dsv, moment_hour_meridiem(mt));
366                 break;
367             case 'r': /* locale's time in a.m. and p.m. notation (C locale: %I:%M:%S %p) */
368                 sv_catpvf(dsv, "%02d:%02d:%02d %s",
369                           moment_hour_12(mt),
370                           moment_minute(mt),
371                           moment_second(mt),
372                           moment_hour_meridiem(mt));
373                 break;
374             case 'R':
375                 sv_catpvf(dsv, "%02d:%02d",
376                           moment_hour(mt),
377                           moment_minute(mt));
378                 break;
379             case 's': /* extended conversion specification */
380                 format_s(dsv, mt);
381                 break;
382             case 'S':
383                 format_num(dsv, 2, pad, PAD_ZERO, moment_second(mt));
384                 break;
385             case 't':
386                 sv_catpvn(dsv, "\t", 1);
387                 break;
388             case 'X': /* locale's date representation (C locale: %H:%M:%S) */
389             case 'T':
390                 sv_catpvf(dsv, "%02d:%02d:%02d",
391                           moment_hour(mt),
392                           moment_minute(mt),
393                           moment_second(mt));
394                 break;
395             case 'u':
396                 sv_catpvf(dsv, "%d", dt_dow(dt));
397                 break;
398             case 'U':
399                 format_num(dsv, 2, pad, PAD_ZERO, dt_week_number_sun(dt));
400                 break;
401             case 'V':
402                 format_num(dsv, 2, pad, PAD_ZERO, dt_woy(dt));
403                 break;
404             case 'w':
405                 sv_catpvf(dsv, "%d", dt_dow(dt) % 7);
406                 break;
407             case 'W':
408                 format_num(dsv, 2, pad, PAD_ZERO, dt_week_number_mon(dt));
409                 break;
410             case 'y':
411                 format_num(dsv, 2, pad, PAD_ZERO, year % 100);
412                 break;
413             case 'Y':
414                 format_num(dsv, 4, pad, PAD_ZERO, year);
415                 break;
416             case 'z':
417                 format_z(dsv, mt, zextd);
418                 break;
419             case 'Z':
420                 format_Z(dsv, mt);
421                 break;
422             case '%':
423                 sv_catpvn(dsv, "%", 1);
424                 break;
425             case ':':
426                 if (s < e && *s == 'z') {
427                     zextd = 1;
428                     goto label;
429                 }
430                 goto unknown;
431             case '_':
432                 if (s < e && supports_padding_flag(*s)) {
433                     pad = PAD_SPACE;
434                     goto label;
435                 }
436                 goto unknown;
437             case '-':
438                 if (s < e && supports_padding_flag(*s)) {
439                     pad = PAD_NONE;
440                     goto label;
441                 }
442                 goto unknown;
443             case '0': case '1': case '2': case '3': case '4':
444             case '5': case '6': case '7': case '8': case '9':
445                 if (s < e && (*s == 'f' || *s == 'N')) {
446                     width = c - '0';
447                     goto label;
448                 }
449                 if (s < e && c == '0' && supports_padding_flag(*s)) {
450                     pad = PAD_ZERO;
451                     goto label;
452                 }
453                 /* FALLTROUGH */
454             default:
455             unknown:
456                 sv_catpvn(dsv, p, s - p);
457                 break;
458         }
459     }
460     return dsv;
461 }
462 
463 SV *
THX_moment_to_string(pTHX_ const moment_t * mt,bool reduced)464 THX_moment_to_string(pTHX_ const moment_t *mt, bool reduced) {
465     SV *dsv;
466     dt_t dt;
467     int year, month, day, sec, ns, offset, sign;
468 
469     dsv = sv_2mortal(newSV(16));
470     SvCUR_set(dsv, 0);
471     SvPOK_only(dsv);
472 
473     dt = moment_local_dt(mt);
474     dt_to_ymd(dt, &year, &month, &day);
475 
476     sv_catpvf(dsv, "%04d-%02d-%02dT%02d:%02d",
477         year, month, day, moment_hour(mt), moment_minute(mt));
478 
479     sec = moment_second(mt);
480     ns  = moment_nanosecond(mt);
481     if (!reduced || sec || ns) {
482         sv_catpvf(dsv, ":%02d", sec);
483         if (ns) {
484             if      ((ns % 1000000) == 0) sv_catpvf(dsv, ".%03d", ns / 1000000);
485             else if ((ns % 1000)    == 0) sv_catpvf(dsv, ".%06d", ns / 1000);
486             else                          sv_catpvf(dsv, ".%09d", ns);
487         }
488     }
489 
490     offset = moment_offset(mt);
491     if (offset == 0)
492         sv_catpvn(dsv, "Z", 1);
493     else {
494         if (offset < 0)
495             sign = '-', offset = -offset;
496         else
497             sign = '+';
498 
499         if (reduced && (offset % 60) == 0)
500             sv_catpvf(dsv, "%c%02d", sign, offset / 60);
501         else
502             sv_catpvf(dsv, "%c%02d:%02d", sign, offset / 60, offset % 60);
503     }
504 
505     return dsv;
506 }
507 
508