File Coverage

XS.xs
Criterion Covered Total %
statement 225 238 94.5
branch 119 136 87.5
condition n/a
subroutine n/a
pod n/a
total 344 374 91.9


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5              
6             #include "tstr_param.h"
7             #include "tstr_format.h"
8             #include "tstr_datetime.h"
9             #include "tstr_time2str.h"
10             #include "tstr_parsed.h"
11             #include "tstr_token_parse.h"
12             #include "tstr_calendar.h"
13             #include "tstr_regexp.h"
14             #include "tstr_parse.h"
15             #include "tstr_sv.h"
16             #include "tstr_carp.h"
17              
18             #if NVSIZE > 8
19             # define DEFAULT_PRECISION 9
20             #else
21             # define DEFAULT_PRECISION 6
22             #endif
23              
24             #define DEFAULT_PIVOT_YEAR 1950
25             #define NANOS_PER_SECOND TSTR_NANOS_PER_SECOND
26             #define MIN_EPOCH INT64_C(-62135596800)
27             #define MAX_EPOCH INT64_C(253402300799)
28             #define EPOCH_20500101 INT64_C(2524608000)
29              
30             #define MY_CXT_KEY "Time::Str::_cxt" XS_VERSION
31              
32             typedef struct {
33             REGEXP *regexps[TSTR_FORMAT_TYPE_COUNT];
34             tstr_sv_keys_t keys;
35             } my_cxt_t;
36              
37             START_MY_CXT
38              
39             #define SHARE_KEY(s) newSVpvn_share("" s "", sizeof(s) - 1, 0)
40              
41 24           static void init_keys(pTHX_ tstr_sv_keys_t *k) {
42 24           k->k_year = SHARE_KEY("year");
43 24           k->k_month = SHARE_KEY("month");
44 24           k->k_day = SHARE_KEY("day");
45 24           k->k_hour = SHARE_KEY("hour");
46 24           k->k_minute = SHARE_KEY("minute");
47 24           k->k_second = SHARE_KEY("second");
48 24           k->k_nanosecond = SHARE_KEY("nanosecond");
49 24           k->k_tz_offset = SHARE_KEY("tz_offset");
50 24           k->k_tz_utc = SHARE_KEY("tz_utc");
51 24           k->k_tz_abbrev = SHARE_KEY("tz_abbrev");
52 24           k->k_tz_annotation = SHARE_KEY("tz_annotation");
53 24           k->k_fraction = SHARE_KEY("fraction");
54 24           k->k_day_name = SHARE_KEY("day_name");
55 24           k->k_meridiem = SHARE_KEY("meridiem");
56 24           }
57              
58 24           static void load_regexps(pTHX_ my_cxt_t *cxt) {
59             HV *mapping;
60             HE *entry;
61             int i;
62              
63 24           dSP;
64              
65 24           ENTER;
66 24           SAVETMPS;
67              
68 24           load_module(PERL_LOADMOD_NOIMPORT, newSVpvs("Time::Str::Regexp"), NULL);
69              
70 24 50         PUSHMARK(SP);
71 24           call_pv("Time::Str::Regexp::mapping", G_SCALAR);
72 24           SPAGAIN;
73 24           mapping = (HV *)SvRV(POPs);
74 24           PUTBACK;
75              
76 576 100         for (i = 0; i < TSTR_FORMAT_TYPE_COUNT; i++)
77 552           cxt->regexps[i] = NULL;
78              
79 24           hv_iterinit(mapping);
80 552 100         while ((entry = hv_iternext(mapping))) {
81             STRLEN klen;
82 528 50         const char *key = HePV(entry, klen);
83 528           tstr_format_t fmt = tstr_format_from_string(key, klen);
84 528           SV *val = HeVAL(entry);
85             REGEXP *rx;
86              
87 528 50         if (fmt == TSTR_FORMAT_UNKNOWN)
88 0           croak("panic: Time::Str::Regexp::mapping() returned unknown format key '%.*s'",
89             (int)klen, key);
90              
91 528           rx = SvRX(val);
92 528 50         if (!rx)
93 0           croak("panic: Time::Str::Regexp::mapping() value for '%.*s' is not a regexp",
94             (int)klen, key);
95              
96 528           cxt->regexps[fmt] = ReREFCNT_inc(rx);
97             }
98              
99 24 50         FREETMPS;
100 24           LEAVE;
101 24           }
102              
103              
104             MODULE = Time::Str PACKAGE = Time::Str
105              
106             PROTOTYPES: DISABLE
107              
108             BOOT:
109             {
110             MY_CXT_INIT;
111 24           init_keys(aTHX_ &MY_CXT.keys);
112 24           load_regexps(aTHX_ &MY_CXT);
113             }
114              
115             #ifdef USE_ITHREADS
116              
117             void
118             CLONE(...)
119             CODE:
120             {
121             MY_CXT_CLONE;
122             init_keys(aTHX_ &MY_CXT.keys);
123             load_regexps(aTHX_ &MY_CXT);
124             PERL_UNUSED_VAR(items);
125             }
126              
127             #endif
128              
129             void
130             time2str(...)
131             PREINIT:
132 136 50         dXSTARG;
133             int64_t epoch;
134 136           int offset = 0;
135 136           int nanosecond = -1;
136 136           int precision = -1;
137 136           tstr_format_t fmt = TSTR_FORMAT_RFC3339;
138             tstr_datetime_t dt;
139             int i;
140             PPCODE:
141 136 100         if (items < 1 || !(items & 1))
    100          
142 2           croak("Usage: time2str(time [, format => 'RFC3339' ])");
143              
144 134           epoch = (int64_t)SvNV(ST(0));
145              
146 360 100         for (i = 1; i < items; i += 2) {
147             const char *key;
148             STRLEN klen;
149             SV *val;
150              
151 234           key = SvPV_const(ST(i), klen);
152 234           val = ST(i + 1);
153              
154 234           switch (tstr_param_from_string(key, klen)) {
155 123           case TSTR_PARAM_FORMAT:
156 123           fmt = tstr_sv_format(aTHX_ val);
157 122           break;
158 37           case TSTR_PARAM_OFFSET:
159 37           offset = tstr_sv_offset(aTHX_ val);
160 35           break;
161 47           case TSTR_PARAM_PRECISION:
162 47           precision = tstr_sv_precision(aTHX_ val);
163 45           break;
164 26           case TSTR_PARAM_NANOSECOND:
165 26           nanosecond = tstr_sv_nanosecond(aTHX_ val);
166 24           break;
167 1           default:
168 1           croak("Unrecognised named parameter: '%"SVf"'", ST(i));
169             }
170             }
171              
172 126 100         if (epoch < MIN_EPOCH || epoch > MAX_EPOCH)
    100          
173 2           croak("Parameter 'time' is out of range");
174              
175 124 100         if (nanosecond < 0 && SvNOK(ST(0))) {
    50          
176 100           NV t = SvNV(ST(0));
177 100           NV sec = Perl_floor(t);
178 100           NV fr = t - sec;
179 100 100         int scale_exp = (precision >= 0) ? precision : DEFAULT_PRECISION;
180 100           NV scale = Perl_pow(10.0, (NV)scale_exp);
181              
182 100           fr = Perl_floor(fr * scale + 0.5) / scale;
183 100           nanosecond = (int)Perl_floor(fr * NANOS_PER_SECOND + 0.5);
184 100           epoch = (int64_t)sec;
185              
186 100 100         if (nanosecond >= NANOS_PER_SECOND) {
187 4           nanosecond -= NANOS_PER_SECOND;
188 4           epoch++;
189             }
190             }
191              
192 124 50         if (nanosecond < 0)
193 0           nanosecond = 0;
194              
195 124 100         if (offset) {
196 35           int64_t local = epoch + (int64_t)offset * 60;
197 35 100         if (local < MIN_EPOCH || local > MAX_EPOCH)
    100          
198 2           croak("Parameter 'time' is out of range for the given offset");
199             }
200              
201 122 100         if (fmt == TSTR_FORMAT_RFC5280) {
202 4 100         fmt = (epoch < EPOCH_20500101) ? TSTR_FORMAT_ASN1UT : TSTR_FORMAT_ASN1GT;
203 4           nanosecond = 0;
204 4           offset = 0;
205 4           precision = -1;
206             }
207              
208 122           tstr_datetime_from_epoch(&dt, epoch, offset, nanosecond);
209              
210 122 100         (void)SvUPGRADE(TARG, SVt_PV);
211 122 100         (void)SvGROW(TARG, 30);
    100          
212 122           SvCUR_set(TARG, 0);
213 122           SvPOK_only(TARG);
214              
215 122 50         if (!tstr_time2str(aTHX_ TARG, &dt, precision, fmt))
216 0           croak("Parameter 'format' does not support time2str");
217 122 50         PUSHTARG;
218              
219             void
220             str2time(...)
221             PREINIT:
222             dMY_CXT;
223 185           tstr_format_t fmt = TSTR_FORMAT_RFC3339;
224 185           int pivot_year = -1;
225 185           int precision = -1;
226             tstr_parsed_t parsed;
227             int i;
228             PPCODE:
229 185 100         if (items < 1 || !(items & 1))
    100          
230 2           croak("Usage: str2time(string [, format => 'RFC3339' ])");
231              
232 424 100         for (i = 1; i < items; i += 2) {
233             const char *key;
234             STRLEN klen;
235             SV *val;
236              
237 243           key = SvPV_const(ST(i), klen);
238 243           val = ST(i + 1);
239              
240 243           switch (tstr_param_from_string(key, klen)) {
241 181           case TSTR_PARAM_FORMAT:
242 181           fmt = tstr_sv_format(aTHX_ val);
243 181           break;
244 0           case TSTR_PARAM_PIVOT_YEAR:
245 0           pivot_year = tstr_sv_pivot_year(aTHX_ val);
246 0           break;
247 62           case TSTR_PARAM_PRECISION:
248 62           precision = tstr_sv_precision(aTHX_ val);
249 60           break;
250 0           default:
251 0           croak("Unrecognised named parameter: '%"SVf"'", ST(i));
252             }
253             }
254              
255 181           tstr_parse(aTHX_ ST(0), fmt, pivot_year,
256             MY_CXT.regexps, &MY_CXT.keys, &parsed);
257              
258 181 100         if (!(parsed.flags & TSTR_PARSED_HAS_OFFSET))
259 2           tstr_croak("Unable to convert: timestamp string without a UTC designator or numeric offset");
260              
261             {
262 179           int hour = parsed.hour;
263              
264 179 50         if (parsed.flags & TSTR_PARSED_HAS_MERIDIEM)
265 0           hour = hour % 12 + parsed.meridiem;
266              
267 179           uint32_t rdn = tstr_calendar_ymd_to_rdn(parsed.year, parsed.month, parsed.day);
268 179           int64_t sod = ((int64_t)hour * 60 + parsed.minute) * 60 + parsed.second;
269 179           int64_t epoch = ((int64_t)rdn - TSTR_CALENDAR_RDN_UNIX_EPOCH) * 86400
270 179           + sod - (int64_t)parsed.offset * 60;
271              
272 179 100         if (parsed.flags & TSTR_PARSED_HAS_NANOSECOND) {
273 61 100         int scale_exp = (precision >= 0) ? precision : DEFAULT_PRECISION;
274 61           NV scale = Perl_pow(10.0, (NV)scale_exp);
275 61           NV fraction = Perl_floor((NV)parsed.nanosecond * scale / NANOS_PER_SECOND) / scale;
276 61           mPUSHn((NV)epoch + fraction);
277             } else {
278             #if IVSIZE == 4
279             mPUSHn((NV)epoch);
280             #else
281 118           mPUSHi(epoch);
282             #endif
283             }
284             }
285              
286             void
287             str2date(...)
288             PREINIT:
289             dMY_CXT;
290 781           tstr_format_t fmt = TSTR_FORMAT_RFC3339;
291 781           int pivot_year = -1;
292             tstr_parsed_t parsed;
293             HV *result;
294             int i;
295             PPCODE:
296 781 100         if (items < 1 || !(items & 1))
    100          
297 2           croak("Usage: str2date(string [, format => 'RFC3339' ])");
298              
299 1554 100         for (i = 1; i < items; i += 2) {
300             const char *key;
301             STRLEN klen;
302             SV *val;
303              
304 779           key = SvPV_const(ST(i), klen);
305 779           val = ST(i + 1);
306              
307 779           switch (tstr_param_from_string(key, klen)) {
308 770           case TSTR_PARAM_FORMAT:
309 770           fmt = tstr_sv_format(aTHX_ val);
310 769           break;
311 8           case TSTR_PARAM_PIVOT_YEAR:
312 8           pivot_year = tstr_sv_pivot_year(aTHX_ val);
313 6           break;
314 1           default:
315 1           croak("Unrecognised named parameter: '%"SVf"'", ST(i));
316             }
317             }
318              
319 775           tstr_parse(aTHX_ ST(0), fmt, pivot_year,
320             MY_CXT.regexps, &MY_CXT.keys, &parsed);
321              
322 760 50         if (GIMME_V == G_ARRAY) {
323             int n;
324 0 0         EXTEND(SP, tstr_parsed_field_count(&parsed) * 2);
    0          
325 0           n = tstr_sv_parsed_to_stack(aTHX_ &parsed, &MY_CXT.keys, SP);
326 0           SP += n;
327             } else {
328 760           HV *result = tstr_sv_parsed_to_hv(aTHX_ &parsed, &MY_CXT.keys);
329 760           mPUSHs(newRV_noinc((SV *)result));
330             }
331              
332             MODULE = Time::Str PACKAGE = Time::Str::Token
333              
334             PROTOTYPES: DISABLE
335              
336             void
337             parse_day(...)
338             PREINIT:
339             const char *src;
340             STRLEN len;
341             int value;
342             PPCODE:
343 82 100         if (items != 1)
344 1           croak("Usage: parse_day(string)");
345 81           src = SvPV_const(ST(0), len);
346 81 100         if (!tstr_token_parse_day(src, len, &value))
347 6           tstr_token_croakf("Unable to parse: day is invalid");
348 75           mPUSHi(value);
349              
350             void
351             parse_day_name(...)
352             PREINIT:
353             const char *src;
354             STRLEN len;
355             int value;
356             PPCODE:
357 26 100         if (items != 1)
358 1           croak("Usage: parse_day_name(string)");
359 25           src = SvPV_const(ST(0), len);
360 25 100         if (!tstr_token_parse_day_name(src, len, &value))
361 4           tstr_token_croakf("Unable to parse: day name is invalid");
362 21           mPUSHi(value);
363              
364             void
365             parse_month(...)
366             PREINIT:
367             const char *src;
368             STRLEN len;
369             int value;
370             PPCODE:
371 73 100         if (items != 1)
372 1           croak("Usage: parse_month(string)");
373 72           src = SvPV_const(ST(0), len);
374 72 100         if (!tstr_token_parse_month(src, len, &value))
375 6           tstr_token_croakf("Unable to parse: month is invalid");
376 66           mPUSHi(value);
377              
378             void
379             parse_meridiem(...)
380             PREINIT:
381             const char *src;
382             STRLEN len;
383             int value;
384             PPCODE:
385 15 100         if (items != 1)
386 1           croak("Usage: parse_meridiem(string)");
387 14           src = SvPV_const(ST(0), len);
388 14 100         if (!tstr_token_parse_meridiem(src, len, &value))
389 4           tstr_token_croakf("Unable to parse: meridiem is invalid");
390 10           mPUSHi(value);
391              
392             void
393             parse_tz_offset(...)
394             PREINIT:
395             const char *src;
396             STRLEN len;
397             int value;
398             PPCODE:
399 45 100         if (items != 1)
400 1           croak("Usage: parse_tz_offset(string)");
401 44           src = SvPV_const(ST(0), len);
402 44 100         if (!tstr_token_parse_tz_offset(src, len, &value))
403 12           tstr_token_croakf("Unable to parse: timezone offset is invalid");
404 32           mPUSHi(value);
405              
406              
407             MODULE = Time::Str PACKAGE = Time::Str::Calendar
408              
409             PROTOTYPES: DISABLE
410              
411             void
412             leap_year(...)
413             PPCODE:
414 18 100         if (items != 1)
415 1           croak("Usage: leap_year(year)");
416 17 100         if (tstr_calendar_leap_year((int)SvIV(ST(0))))
417 8           XSRETURN_YES;
418 9           XSRETURN_NO;
419              
420             void
421             month_days(...)
422             PREINIT:
423             int y, m;
424             PPCODE:
425 18 100         if (items != 2)
426 1           croak("Usage: month_days(year, month)");
427 17           y = (int)SvIV(ST(0));
428 17           m = tstr_sv_month(aTHX_ ST(1));
429 15           mPUSHi(tstr_calendar_month_days(y, m));
430              
431             void
432             valid_ymd(...)
433             PPCODE:
434 44 100         if (items != 3)
435 1           croak("Usage: valid_ymd(year, month, day)");
436 43 100         if (tstr_calendar_valid_ymd((int)SvIV(ST(0)), (int)SvIV(ST(1)), (int)SvIV(ST(2))))
437 23           XSRETURN_YES;
438 20           XSRETURN_NO;
439              
440             void
441             ymd_to_rdn(...)
442             PREINIT:
443             int y, m, d;
444             PPCODE:
445 44 100         if (items != 3)
446 1           croak("Usage: ymd_to_rdn(year, month, day)");
447 43           tstr_sv_ymd(aTHX_ ST(0), ST(1), ST(2), &y, &m, &d);
448 37           mPUSHi((IV)tstr_calendar_ymd_to_rdn(y, m, d));
449              
450             void
451             rdn_to_ymd(...)
452             PREINIT:
453             IV rdn;
454             int y, m, d;
455             PPCODE:
456 18 100         if (items != 1)
457 1           croak("Usage: rdn_to_ymd(rdn)");
458 17           rdn = SvIV(ST(0));
459 17 100         if (rdn < TSTR_CALENDAR_RDN_MIN || rdn > TSTR_CALENDAR_RDN_MAX)
    100          
460 2           croak("Parameter 'rdn' is out of range");
461 15           tstr_calendar_rdn_to_ymd((uint32_t)rdn, &y, &m, &d);
462 15 50         EXTEND(SP, 3);
463 15           mPUSHi(y);
464 15           mPUSHi(m);
465 15           mPUSHi(d);
466              
467             void
468             rdn_to_dow(...)
469             PREINIT:
470             IV rdn;
471             PPCODE:
472 33 100         if (items != 1)
473 1           croak("Usage: rdn_to_dow(rdn)");
474 32           rdn = SvIV(ST(0));
475 32 100         if (rdn < TSTR_CALENDAR_RDN_MIN || rdn > TSTR_CALENDAR_RDN_MAX)
    100          
476 2           croak("Parameter 'rdn' is out of range");
477 30           mPUSHi(tstr_calendar_rdn_to_dow((uint32_t)rdn));
478              
479             void
480             ymd_to_dow(...)
481             PREINIT:
482             int y, m, d;
483             PPCODE:
484 31 100         if (items != 3)
485 1           croak("Usage: ymd_to_dow(year, month, day)");
486 30           tstr_sv_ymd(aTHX_ ST(0), ST(1), ST(2), &y, &m, &d);
487 24           mPUSHi(tstr_calendar_ymd_to_dow(y, m, d));
488              
489             void
490             resolve_century(...)
491             PREINIT:
492             int year, pivot_year;
493             PPCODE:
494 22 100         if (items != 2)
495 1           croak("Usage: resolve_century(year, pivot_year)");
496 21           year = (int)SvIV(ST(0));
497 21 100         if (year < 0 || year > 99)
    100          
498 2           croak("Parameter 'year' is out of range [0, 99]");
499 19           pivot_year = tstr_sv_pivot_year(aTHX_ ST(1));
500 17           mPUSHi(tstr_calendar_resolve_century(year, pivot_year));
501