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