File Coverage

XS.xs
Criterion Covered Total %
statement 505 534 94.5
branch 340 404 84.1
condition n/a
subroutine n/a
pod n/a
total 845 938 90.0


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             #include
6              
7             #include "tstr_param.h"
8             #include "tstr_format.h"
9             #include "tstr_datetime.h"
10             #include "tstr_time2str.h"
11             #include "tstr_parsed.h"
12             #include "tstr_token_parse.h"
13             #include "tstr_calendar.h"
14             #include "tstr_time.h"
15             #include "tstr_regexp.h"
16             #include "tstr_parse.h"
17             #include "tstr_sv.h"
18             #include "tstr_carp.h"
19              
20             #if IVSIZE >= 8
21             typedef IV I64V;
22             # define SvI64V(sv) (I64V)SvIV(sv)
23             # define newSVi64v(i64) newSViv((IV)i64)
24             # define XSRETURN_I64V(i64) XSRETURN_IV((IV)i64)
25             #else
26             typedef NV I64V;
27             # define SvI64V(sv) (I64V)SvNV(sv)
28             # define newSVi64v(i64) newSVnv((NV)i64)
29             # define XSRETURN_I64V(i64) XSRETURN_NV((NV)i64)
30             #endif
31              
32             #ifndef XSRETURN_BOOL
33             #define XSRETURN_BOOL(v) STMT_START { ST(0) = boolSV(v); XSRETURN(1); } STMT_END
34             #endif
35              
36             #if NVSIZE > 8
37             # define DEFAULT_PRECISION 9
38             #else
39             # define DEFAULT_PRECISION 6
40             #endif
41              
42             #define DEFAULT_PIVOT_YEAR 1950
43             #define NANOS_PER_SECOND TSTR_NANOS_PER_SECOND
44             #define MIN_EPOCH INT64_C(-62135596800)
45             #define MAX_EPOCH INT64_C(253402300799)
46             #define EPOCH_20500101 INT64_C(2524608000)
47              
48             #define MY_CXT_KEY "Time::Str::_cxt" XS_VERSION
49              
50             typedef struct {
51             REGEXP *regexps[TSTR_FORMAT_TYPE_COUNT];
52             tstr_sv_keys_t keys;
53             } my_cxt_t;
54              
55             START_MY_CXT
56              
57             #define SHARE_KEY(s) newSVpvn_share("" s "", sizeof(s) - 1, 0)
58              
59 32           static void init_keys(pTHX_ tstr_sv_keys_t *k) {
60 32           k->k_year = SHARE_KEY("year");
61 32           k->k_month = SHARE_KEY("month");
62 32           k->k_day = SHARE_KEY("day");
63 32           k->k_hour = SHARE_KEY("hour");
64 32           k->k_minute = SHARE_KEY("minute");
65 32           k->k_second = SHARE_KEY("second");
66 32           k->k_nanosecond = SHARE_KEY("nanosecond");
67 32           k->k_tz_offset = SHARE_KEY("tz_offset");
68 32           k->k_tz_utc = SHARE_KEY("tz_utc");
69 32           k->k_tz_abbrev = SHARE_KEY("tz_abbrev");
70 32           k->k_tz_annotation = SHARE_KEY("tz_annotation");
71 32           k->k_fraction = SHARE_KEY("fraction");
72 32           k->k_day_name = SHARE_KEY("day_name");
73 32           k->k_meridiem = SHARE_KEY("meridiem");
74 32           }
75              
76 32           static void load_regexps(pTHX_ my_cxt_t *cxt) {
77             HV *mapping;
78             HE *entry;
79             int i;
80              
81 32           dSP;
82              
83 32           ENTER;
84 32           SAVETMPS;
85              
86 32           load_module(PERL_LOADMOD_NOIMPORT, newSVpvs("Time::Str::Regexp"), NULL);
87              
88 32 50         PUSHMARK(SP);
89 32           call_pv("Time::Str::Regexp::mapping", G_SCALAR);
90 32           SPAGAIN;
91 32           mapping = (HV *)SvRV(POPs);
92 32           PUTBACK;
93              
94 768 100         for (i = 0; i < TSTR_FORMAT_TYPE_COUNT; i++)
95 736           cxt->regexps[i] = NULL;
96              
97 32           hv_iterinit(mapping);
98 736 100         while ((entry = hv_iternext(mapping))) {
99             STRLEN klen;
100 704 50         const char *key = HePV(entry, klen);
101 704           tstr_format_t fmt = tstr_format_from_string(key, klen);
102 704           SV *val = HeVAL(entry);
103             REGEXP *rx;
104              
105 704 50         if (fmt == TSTR_FORMAT_UNKNOWN)
106 0           croak("panic: Time::Str::Regexp::mapping() returned unknown format key '%.*s'",
107             (int)klen, key);
108              
109 704           rx = SvRX(val);
110 704 50         if (!rx)
111 0           croak("panic: Time::Str::Regexp::mapping() value for '%.*s' is not a regexp",
112             (int)klen, key);
113              
114 704           cxt->regexps[fmt] = ReREFCNT_inc(rx);
115             }
116              
117 32 50         FREETMPS;
118 32           LEAVE;
119 32           }
120              
121 14           static bool tstr_is_hashref(pTHX_ SV *sv) {
122 14           SvGETMAGIC(sv);
123 14 100         if (!SvROK(sv))
124 2           return false;
125 12           sv = SvRV(sv);
126 12           SvGETMAGIC(sv);
127 12 50         if (SvOBJECT(sv))
128 0           return false;
129 12           return SvTYPE(sv) == SVt_PVHV;
130             }
131              
132 49           static bool tstr_fetch_method(pTHX_ SV *sv, const char *name, GV **method) {
133 49           SvGETMAGIC(sv);
134 49 100         if (!SvROK(sv))
135 6           return false;
136 43           sv = SvRV(sv);
137 43           SvGETMAGIC(sv);
138 43 100         if (!SvOBJECT(sv))
139 3           return false;
140              
141 40           HV *stash = SvSTASH(sv);
142 40 100         if ((*method = gv_fetchmethod_autoload (stash, name, 0)))
143 37           return true;
144 3           return false;
145             }
146              
147 30           static int64_t tstr_call_offset_method(pTHX_ SV *obj, GV *method, int64_t value) {
148 30           dSP;
149             int count;
150             int64_t offset;
151             SV *ret;
152              
153 30           ENTER;
154 30           SAVETMPS;
155              
156 30 50         PUSHMARK(SP);
157 30 50         EXTEND(SP, 2);
158 30           PUSHs(obj);
159 30           mPUSHs(newSVi64v(value));
160              
161 30           PUTBACK;
162 30           count = call_sv((SV *)GvCV(method), G_SCALAR);
163 30           SPAGAIN;
164              
165 30 50         if (count != 1)
166 0           croak("panic: method returned %d values", count);
167 30           ret = POPs;
168 30           offset = SvIV(ret);
169 30           PUTBACK;
170              
171 30 50         FREETMPS;
172 30           LEAVE;
173              
174 30           return offset;
175             }
176              
177             MODULE = Time::Str PACKAGE = Time::Str
178              
179             PROTOTYPES: DISABLE
180              
181             BOOT:
182             {
183             MY_CXT_INIT;
184 32           init_keys(aTHX_ &MY_CXT.keys);
185 32           load_regexps(aTHX_ &MY_CXT);
186             }
187              
188             #ifdef USE_ITHREADS
189              
190             void
191             CLONE(...)
192             CODE:
193             {
194             MY_CXT_CLONE;
195             init_keys(aTHX_ &MY_CXT.keys);
196             load_regexps(aTHX_ &MY_CXT);
197             PERL_UNUSED_VAR(items);
198             }
199              
200             #endif
201              
202             void
203             time2str(...)
204             PREINIT:
205 152 50         dXSTARG;
206             int64_t epoch;
207 152           int offset = INT_MAX;
208 152           int nanosecond = -1;
209 152           int precision = -1;
210 152           tstr_format_t fmt = TSTR_FORMAT_RFC3339;
211             tstr_datetime_t dt;
212 152           GV *method = NULL;
213 152           SV *timezone = NULL;
214             int i;
215             PPCODE:
216 152 100         if (items < 1 || !(items & 1))
    100          
217 2           croak("Usage: time2str(time [, format => 'RFC3339' ])");
218              
219 150           epoch = (int64_t)SvNV(ST(0));
220              
221 397 100         for (i = 1; i < items; i += 2) {
222             const char *key;
223             STRLEN klen;
224             SV *val;
225              
226 261           key = SvPV_const(ST(i), klen);
227 261           val = ST(i + 1);
228              
229 261           switch (tstr_param_from_string(key, klen)) {
230 131           case TSTR_PARAM_FORMAT:
231 131           fmt = tstr_sv_format(aTHX_ val);
232 130           break;
233 39           case TSTR_PARAM_OFFSET:
234 39           offset = tstr_sv_offset(aTHX_ val);
235 37 100         if (timezone)
236 1           croak("Parameter 'offset' is mutually exclusive with 'timezone'");
237 36           break;
238 48           case TSTR_PARAM_PRECISION:
239 48           precision = tstr_sv_precision(aTHX_ val);
240 46           break;
241 26           case TSTR_PARAM_NANOSECOND:
242 26           nanosecond = tstr_sv_nanosecond(aTHX_ val);
243 24           break;
244 16           case TSTR_PARAM_TIMEZONE:
245 16 100         if (!tstr_fetch_method(aTHX_ val, "offset_for_utc", &method))
246 4           croak("Parameter 'timezone' is not an object with an 'offset_for_utc' method");
247 12 100         if (offset != INT_MAX)
248 1           croak("Parameter 'timezone' is mutually exclusive with 'offset'");
249 11           timezone = val;
250 11           break;
251 1           default:
252 1           croak("Unrecognised named parameter: '%"SVf"'", ST(i));
253             }
254             }
255              
256 136 100         if (epoch < MIN_EPOCH || epoch > MAX_EPOCH)
    100          
257 2           croak("Parameter 'time' is out of range");
258              
259 134 100         if (nanosecond < 0 && SvNOK(ST(0))) {
    50          
260 110           NV t = SvNV(ST(0));
261 110           NV sec = Perl_floor(t);
262 110           NV fr = t - sec;
263 110 100         int scale_exp = (precision >= 0) ? precision : DEFAULT_PRECISION;
264 110           NV scale = Perl_pow(10.0, (NV)scale_exp);
265              
266 110           fr = Perl_floor(fr * scale + 0.5) / scale;
267 110           nanosecond = (int)Perl_floor(fr * NANOS_PER_SECOND + 0.5);
268 110           epoch = (int64_t)sec;
269              
270 110 100         if (nanosecond >= NANOS_PER_SECOND) {
271 4           nanosecond -= NANOS_PER_SECOND;
272 4           epoch++;
273             }
274             }
275              
276 134 50         if (nanosecond < 0)
277 0           nanosecond = 0;
278              
279 134 100         if (timezone)
280 10           offset = tstr_call_offset_method(aTHX_ timezone, method, epoch) / 60;
281 124 100         else if (offset == INT_MAX)
282 89           offset = 0;
283              
284 134 100         if (offset) {
285 45           int64_t local = epoch + (int64_t)offset * 60;
286 45 100         if (local < MIN_EPOCH || local > MAX_EPOCH)
    100          
287 2           croak("Parameter 'time' is out of range for the given offset");
288             }
289              
290 132 100         if (fmt == TSTR_FORMAT_RFC5280) {
291 4 100         fmt = (epoch < EPOCH_20500101) ? TSTR_FORMAT_ASN1UT : TSTR_FORMAT_ASN1GT;
292 4           nanosecond = 0;
293 4           offset = 0;
294 4           precision = -1;
295             }
296              
297 132           tstr_datetime_from_epoch(&dt, epoch, offset, nanosecond);
298              
299 132 100         (void)SvUPGRADE(TARG, SVt_PV);
300 132 100         (void)SvGROW(TARG, 30);
    100          
301 132           SvCUR_set(TARG, 0);
302 132           SvPOK_only(TARG);
303              
304 132 50         if (!tstr_time2str(aTHX_ TARG, &dt, precision, fmt))
305 0           croak("Parameter 'format' does not support time2str");
306 132 50         PUSHTARG;
307              
308             void
309             str2time(...)
310             PREINIT:
311             dMY_CXT;
312 270           tstr_format_t fmt = TSTR_FORMAT_RFC3339;
313 270           int pivot_year = -1;
314 270           int precision = -1;
315             GV *method;
316 270           SV *timezone = NULL;
317 270           HV *timezone_map = NULL;
318             tstr_parsed_t parsed;
319             int i;
320             PPCODE:
321 270 100         if (items < 1 || !(items & 1))
    100          
322 2           croak("Usage: str2time(string [, format => 'RFC3339' ])");
323              
324 583 100         for (i = 1; i < items; i += 2) {
325             const char *key;
326             STRLEN klen;
327             SV *val;
328              
329 324           key = SvPV_const(ST(i), klen);
330 324           val = ST(i + 1);
331              
332 324           switch (tstr_param_from_string(key, klen)) {
333 223           case TSTR_PARAM_FORMAT:
334 223           fmt = tstr_sv_format(aTHX_ val);
335 223           break;
336 0           case TSTR_PARAM_PIVOT_YEAR:
337 0           pivot_year = tstr_sv_pivot_year(aTHX_ val);
338 0           break;
339 62           case TSTR_PARAM_PRECISION:
340 62           precision = tstr_sv_precision(aTHX_ val);
341 60           break;
342 25           case TSTR_PARAM_TIMEZONE:
343 25 100         if (!tstr_fetch_method(aTHX_ val, "offset_for_local", &method))
344 4           croak("Parameter 'timezone' is not an object with an 'offset_for_local' method");
345 21           timezone = val;
346 21           break;
347 14           case TSTR_PARAM_TIMEZONE_MAP:
348 14 100         if (!tstr_is_hashref(aTHX_ val))
349 3           croak("Parameter 'timezone_map' is not a HASH reference");
350 11           timezone_map = (HV *)SvRV(val);
351 11           break;
352 0           default:
353 0           croak("Unrecognised named parameter: '%"SVf"'", ST(i));
354             }
355             }
356              
357 259           tstr_parse(aTHX_ ST(0), fmt, pivot_year,
358             MY_CXT.regexps, &MY_CXT.keys, &parsed);
359              
360 259 100         if (parsed.flags & TSTR_PARSED_HAS_TZ_ABBREV) {
361 11           SV **svp = NULL;
362 11 100         if (timezone_map)
363 10           svp = hv_fetch(timezone_map, parsed.tz_abbrev, (I32)parsed.tz_abbrev_len, 0);
364 11 100         if (timezone_map && svp) {
    100          
365 8           SV *obj = *svp;
366 8 100         if (!tstr_fetch_method(aTHX_ obj, "offset_for_local", &method))
367 4           tstr_croakf("timezone_map value for '%.*s' is not an object with an 'offset_for_local' method",
368             (int)parsed.tz_abbrev_len, parsed.tz_abbrev);
369 4           timezone = obj;
370             }
371             else {
372 3           tstr_croakf("Unable to convert: cannot resolve abbreviated timezone '%.*s'",
373             (int)parsed.tz_abbrev_len, parsed.tz_abbrev);
374             }
375             }
376              
377 252 100         if (!(parsed.flags & TSTR_PARSED_HAS_OFFSET) && !timezone)
    100          
378 6           tstr_croak("Unable to convert: timestamp string without a UTC designator or numeric offset");
379              
380             {
381 246           int month = parsed.month;
382 246           int day = parsed.day;
383 246           int hour = parsed.hour;
384             int leap_second;
385             int second;
386            
387 246 100         if (!(parsed.flags & TSTR_PARSED_HAS_MONTH))
388 2           month = 1;
389 246 100         if (!(parsed.flags & TSTR_PARSED_HAS_DAY))
390 5           day = 1;
391              
392 246 50         if (parsed.flags & TSTR_PARSED_HAS_MERIDIEM)
393 0           hour = hour % 12 + parsed.meridiem;
394              
395             /* A leap second (23:59:60 UTC) cannot be represented as a POSIX
396             * time, so fold it onto the preceding 23:59:59 and validate that
397             * it lands on a real leap-second slot. The error messages are
398             * worded to hold equally if a Time::LeapSecond table lookup is
399             * used instead. */
400 246           leap_second = (parsed.second == 60);
401 246           second = parsed.second - leap_second;
402              
403 246           uint32_t rdn = tstr_calendar_ymd_to_rdn(parsed.year, month, day);
404 246           int64_t sod = ((int64_t)hour * 60 + parsed.minute) * 60 + second;
405 246           int64_t epoch = ((int64_t)rdn - TSTR_CALENDAR_RDN_UNIX_EPOCH) * 86400 + sod;
406              
407             /* A string offset is in minutes; a timezone object resolves the
408             * local time and returns its UTC offset in seconds. */
409 246 100         if (parsed.flags & TSTR_PARSED_HAS_OFFSET)
410 226           epoch -= (int64_t)parsed.offset * 60;
411             else
412 20           epoch -= tstr_call_offset_method(aTHX_ timezone, method, epoch);
413              
414 246 100         if (leap_second) {
415 18           switch (tstr_time_leap_check(epoch)) {
416 3           case 1:
417 3           tstr_croak("Unable to convert: a leap second must occur at 23:59:60 UTC");
418 0           break;
419 3           case 2:
420 3           tstr_croak("Unable to convert: no leap second on this UTC date");
421 0           break;
422             }
423             }
424              
425 240 100         if (parsed.flags & TSTR_PARSED_HAS_NANOSECOND) {
426 63 100         int scale_exp = (precision >= 0) ? precision : DEFAULT_PRECISION;
427 63           NV scale = Perl_pow(10.0, (NV)scale_exp);
428 63           NV fraction = Perl_floor((NV)parsed.nanosecond * scale / NANOS_PER_SECOND) / scale;
429 63           mPUSHn((NV)epoch + fraction);
430             } else {
431             #if IVSIZE == 4
432             mPUSHn((NV)epoch);
433             #else
434 177           mPUSHi(epoch);
435             #endif
436             }
437             }
438              
439             void
440             str2date(...)
441             PREINIT:
442             dMY_CXT;
443 785           tstr_format_t fmt = TSTR_FORMAT_RFC3339;
444 785           int pivot_year = -1;
445             tstr_parsed_t parsed;
446             HV *result;
447             int i;
448             PPCODE:
449 785 100         if (items < 1 || !(items & 1))
    100          
450 2           croak("Usage: str2date(string [, format => 'RFC3339' ])");
451              
452 1558 100         for (i = 1; i < items; i += 2) {
453             const char *key;
454             STRLEN klen;
455             SV *val;
456              
457 779           key = SvPV_const(ST(i), klen);
458 779           val = ST(i + 1);
459              
460 779           switch (tstr_param_from_string(key, klen)) {
461 770           case TSTR_PARAM_FORMAT:
462 770           fmt = tstr_sv_format(aTHX_ val);
463 769           break;
464 8           case TSTR_PARAM_PIVOT_YEAR:
465 8           pivot_year = tstr_sv_pivot_year(aTHX_ val);
466 6           break;
467 1           default:
468 1           croak("Unrecognised named parameter: '%"SVf"'", ST(i));
469             }
470             }
471              
472 779           tstr_parse(aTHX_ ST(0), fmt, pivot_year,
473             MY_CXT.regexps, &MY_CXT.keys, &parsed);
474              
475 764 100         if (GIMME_V == G_ARRAY) {
476             int n;
477 4 50         EXTEND(SP, tstr_parsed_field_count(&parsed) * 2);
    50          
478 4           n = tstr_sv_parsed_to_stack(aTHX_ &parsed, &MY_CXT.keys, SP);
479 4           SP += n;
480             } else {
481 760           HV *result = tstr_sv_parsed_to_hv(aTHX_ &parsed, &MY_CXT.keys);
482 760           mPUSHs(newRV_noinc((SV *)result));
483             }
484              
485             MODULE = Time::Str PACKAGE = Time::Str::Token
486              
487             PROTOTYPES: DISABLE
488              
489             void
490             parse_day(...)
491             PREINIT:
492             const char *src;
493             STRLEN len;
494             int value;
495             PPCODE:
496 82 100         if (items != 1)
497 1           croak("Usage: parse_day(string)");
498 81           src = SvPV_const(ST(0), len);
499 81 100         if (!tstr_token_parse_day(src, len, &value))
500 6           tstr_token_croakf("Unable to parse: day is invalid");
501 75           mPUSHi(value);
502              
503             void
504             parse_day_name(...)
505             PREINIT:
506             const char *src;
507             STRLEN len;
508             int value;
509             PPCODE:
510 26 100         if (items != 1)
511 1           croak("Usage: parse_day_name(string)");
512 25           src = SvPV_const(ST(0), len);
513 25 100         if (!tstr_token_parse_day_name(src, len, &value))
514 4           tstr_token_croakf("Unable to parse: day name is invalid");
515 21           mPUSHi(value);
516              
517             void
518             parse_month(...)
519             PREINIT:
520             const char *src;
521             STRLEN len;
522             int value;
523             PPCODE:
524 82 100         if (items != 1)
525 1           croak("Usage: parse_month(string)");
526 81           src = SvPV_const(ST(0), len);
527 81 100         if (!tstr_token_parse_month(src, len, &value))
528 6           tstr_token_croakf("Unable to parse: month is invalid");
529 75           mPUSHi(value);
530              
531             void
532             parse_meridiem(...)
533             PREINIT:
534             const char *src;
535             STRLEN len;
536             int value;
537             PPCODE:
538 15 100         if (items != 1)
539 1           croak("Usage: parse_meridiem(string)");
540 14           src = SvPV_const(ST(0), len);
541 14 100         if (!tstr_token_parse_meridiem(src, len, &value))
542 4           tstr_token_croakf("Unable to parse: meridiem is invalid");
543 10           mPUSHi(value);
544              
545             void
546             parse_tz_offset(...)
547             PREINIT:
548             const char *src;
549             STRLEN len;
550             int value;
551             PPCODE:
552 45 100         if (items != 1)
553 1           croak("Usage: parse_tz_offset(string)");
554 44           src = SvPV_const(ST(0), len);
555 44 100         if (!tstr_token_parse_tz_offset(src, len, &value))
556 12           tstr_token_croakf("Unable to parse: timezone offset is invalid");
557 32           mPUSHi(value);
558              
559              
560             MODULE = Time::Str PACKAGE = Time::Str::Calendar
561              
562             PROTOTYPES: DISABLE
563              
564             void
565             leap_year(...)
566             PPCODE:
567 26 100         if (items != 1)
568 1           croak("Usage: leap_year(year)");
569 25 100         if (tstr_calendar_leap_year((int)SvIV(ST(0))))
570 14           XSRETURN_YES;
571 11           XSRETURN_NO;
572              
573             void
574             month_days(...)
575             PREINIT:
576             int y, m;
577             PPCODE:
578 18 100         if (items != 2)
579 1           croak("Usage: month_days(year, month)");
580 17           y = (int)SvIV(ST(0));
581 17           m = tstr_sv_month(aTHX_ ST(1));
582 15           mPUSHi(tstr_calendar_month_days(y, m));
583              
584             void
585             valid_ymd(...)
586             PPCODE:
587 44 100         if (items != 3)
588 1           croak("Usage: valid_ymd(year, month, day)");
589 43 100         if (tstr_calendar_valid_ymd((int)SvIV(ST(0)), (int)SvIV(ST(1)), (int)SvIV(ST(2))))
590 23           XSRETURN_YES;
591 20           XSRETURN_NO;
592              
593             void
594             ymd_to_rdn(...)
595             PREINIT:
596             int y, m, d;
597             PPCODE:
598 75 100         if (items != 3)
599 1           croak("Usage: ymd_to_rdn(year, month, day)");
600 74           tstr_sv_ymd(aTHX_ ST(0), ST(1), ST(2), &y, &m, &d);
601 68           mPUSHi((IV)tstr_calendar_ymd_to_rdn(y, m, d));
602              
603             void
604             ymd_to_doy(...)
605             PREINIT:
606             int y, m, d;
607             PPCODE:
608 31 100         if (items != 3)
609 1           croak("Usage: ymd_to_doy(year, month, day)");
610 30           tstr_sv_ymd(aTHX_ ST(0), ST(1), ST(2), &y, &m, &d);
611 24           mPUSHi(tstr_calendar_ymd_to_doy(y, m, d));
612              
613             void
614             yd_to_md(...)
615             PREINIT:
616             int y, doy, m, d;
617             PPCODE:
618 53 100         if (items != 2)
619 1           croak("Usage: yd_to_md(year, day)");
620 52           tstr_sv_yd(aTHX_ ST(0), ST(1), &y, &doy);
621 48           tstr_calendar_yd_to_md(y, doy, &m, &d);
622 48 50         EXTEND(SP, 2);
623 48           mPUSHi(m);
624 48           mPUSHi(d);
625              
626             void
627             rdn_to_ymd(...)
628             PREINIT:
629             IV rdn;
630             int y, m, d;
631             PPCODE:
632 18 100         if (items != 1)
633 1           croak("Usage: rdn_to_ymd(rdn)");
634 17           rdn = SvIV(ST(0));
635 17 100         if (rdn < TSTR_CALENDAR_RDN_MIN || rdn > TSTR_CALENDAR_RDN_MAX)
    100          
636 2           croak("Parameter 'rdn' is out of range");
637 15           tstr_calendar_rdn_to_ymd((uint32_t)rdn, &y, &m, &d);
638 15 50         EXTEND(SP, 3);
639 15           mPUSHi(y);
640 15           mPUSHi(m);
641 15           mPUSHi(d);
642              
643             void
644             rdn_to_dow(...)
645             PREINIT:
646             IV rdn;
647             PPCODE:
648 33 100         if (items != 1)
649 1           croak("Usage: rdn_to_dow(rdn)");
650 32           rdn = SvIV(ST(0));
651 32 100         if (rdn < TSTR_CALENDAR_RDN_MIN || rdn > TSTR_CALENDAR_RDN_MAX)
    100          
652 2           croak("Parameter 'rdn' is out of range");
653 30           mPUSHi(tstr_calendar_rdn_to_dow((uint32_t)rdn));
654              
655             void
656             ymd_to_dow(...)
657             PREINIT:
658             int y, m, d;
659             PPCODE:
660 59 100         if (items != 3)
661 1           croak("Usage: ymd_to_dow(year, month, day)");
662 58           tstr_sv_ymd(aTHX_ ST(0), ST(1), ST(2), &y, &m, &d);
663 52           mPUSHi(tstr_calendar_ymd_to_dow(y, m, d));
664              
665             void
666             nth_dow_in_month(...)
667             PREINIT:
668             int y, m, ord, dow, day;
669             PPCODE:
670 256 100         if (items != 4)
671 1           croak("Usage: nth_dow_in_month(year, month, ord, dow)");
672 255           y = (int)SvIV(ST(0));
673 255           m = (int)SvIV(ST(1));
674 255           ord = (int)SvIV(ST(2));
675 255           dow = (int)SvIV(ST(3));
676 255 100         if (y < 1 || y > 9999)
    100          
677 2           croak("Parameter 'year' is out of range [1, 9999]");
678 253 100         if (m < 1 || m > 12)
    100          
679 2           croak("Parameter 'month' is out of range [1, 12]");
680 251 100         if (ord < -4 || ord > 4 || ord == 0)
    100          
    100          
681 3           croak("Parameter 'ord' is out of range [-4, -1] or [1, 4]");
682 248 100         if (dow < 1 || dow > 7)
    100          
683 2           croak("Parameter 'dow' is out of range [1, 7]");
684 246           mPUSHi(tstr_calendar_nth_dow_in_month(y, m, ord, dow));
685              
686             void
687             resolve_century(...)
688             PREINIT:
689             int year, pivot_year;
690             PPCODE:
691 22 100         if (items != 2)
692 1           croak("Usage: resolve_century(year, pivot_year)");
693 21           year = (int)SvIV(ST(0));
694 21 100         if (year < 0 || year > 99)
    100          
695 2           croak("Parameter 'year' is out of range [0, 99]");
696 19           pivot_year = tstr_sv_pivot_year(aTHX_ ST(1));
697 17           mPUSHi(tstr_calendar_resolve_century(year, pivot_year));
698              
699             MODULE = Time::Str PACKAGE = Time::Str::Time
700              
701             PROTOTYPES: DISABLE
702              
703             void
704             valid_hms(...)
705             PPCODE:
706 12 100         if (items != 3)
707 1           croak("Usage: valid_hms(hour, minute, second)");
708 11 100         if (tstr_time_valid_hms((int)SvIV(ST(0)), (int)SvIV(ST(1)), (int)SvIV(ST(2))))
709 5           XSRETURN_YES;
710 6           XSRETURN_NO;
711              
712             void
713             valid_hms60(...)
714             PPCODE:
715 10 100         if (items != 3)
716 1           croak("Usage: valid_hms60(hour, minute, second)");
717 9 100         if (tstr_time_valid_hms60((int)SvIV(ST(0)), (int)SvIV(ST(1)), (int)SvIV(ST(2))))
718 3           XSRETURN_YES;
719 6           XSRETURN_NO;
720              
721             void
722             timegm_modern(...)
723             PREINIT:
724             int y, m, d, H, M, S;
725             PPCODE:
726 302 100         if (items != 6)
727 1           croak("Usage: timegm_modern(sec, min, hour, mday, mon, year)");
728 301           S = (int)SvIV(ST(0));
729 301           M = (int)SvIV(ST(1));
730 301           H = (int)SvIV(ST(2));
731 301           d = (int)SvIV(ST(3));
732 301           m = (int)SvIV(ST(4));
733 301           y = (int)SvIV(ST(5));
734 301 100         if (y < 1 || y > 9999)
    100          
735 2           croak("Parameter 'year' is out of range [1, 9999]");
736 299 100         if (m < 1 || m > 12)
    100          
737 2           croak("Parameter 'month' is out of range [1, 12]");
738 297 100         if (d < 1 || d > tstr_calendar_month_days(y, m))
    100          
739 3           croak("Parameter 'day' is out of range");
740 294 50         if (H < 0 || H > 23)
    100          
741 1           croak("Parameter 'hour' is out of range [0, 23]");
742 293 50         if (M < 0 || M > 59)
    100          
743 1           croak("Parameter 'minute' is out of range [0, 59]");
744 292 50         if (S < 0 || S > 59)
    100          
745 1           croak("Parameter 'second' is out of range [0, 59]");
746             #if IVSIZE >= 8
747 291           mPUSHi((IV)tstr_time_timegm(y, m, d, H, M, S));
748             #else
749             mPUSHn((NV)tstr_time_timegm(y, m, d, H, M, S));
750             #endif
751              
752             void
753             gmtime_modern(...)
754             PREINIT:
755             int y, m, d, H, M, S, wday, yday;
756             int64_t epoch;
757             PPCODE:
758 22 100         if (items != 1)
759 1           croak("Usage: gmtime_modern(time)");
760             #if IVSIZE >= 8
761 21           epoch = (int64_t)SvIV(ST(0));
762             #else
763             epoch = (int64_t)SvNV(ST(0));
764             #endif
765 21 100         if (epoch < TSTR_TIME_EPOCH_MIN || epoch > TSTR_TIME_EPOCH_MAX)
    100          
766 2           croak("Parameter 'time' is out of range");
767 19           tstr_time_gmtime(epoch, &y, &m, &d, &H, &M, &S, &wday, &yday);
768 19 50         EXTEND(SP, 9);
769 19           mPUSHi(S);
770 19           mPUSHi(M);
771 19           mPUSHi(H);
772 19           mPUSHi(d);
773 19           mPUSHi(m);
774 19           mPUSHi(y);
775 19           mPUSHi(wday);
776 19           mPUSHi(yday);
777 19           mPUSHi(0);
778              
779             void
780             gmtime_year(...)
781             PREINIT:
782             int y;
783             int64_t epoch;
784             PPCODE:
785 114 50         if (items != 1)
786 0           croak("Usage: gmtime_modern(time)");
787             #if IVSIZE >= 8
788 114           epoch = (int64_t)SvIV(ST(0));
789             #else
790             epoch = (int64_t)SvNV(ST(0));
791             #endif
792 114 50         if (epoch < TSTR_TIME_EPOCH_MIN || epoch > TSTR_TIME_EPOCH_MAX)
    50          
793 0           croak("Parameter 'time' is out of range");
794 114           tstr_time_gmtime(epoch, &y, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
795 114 50         EXTEND(SP, 1);
796 114           mPUSHi(y);
797              
798             void
799             timegm_posix(...)
800             PREINIT:
801             int y, m, d, H, M, S;
802             PPCODE:
803 10 100         if (items != 6)
804 1           croak("Usage: timegm_posix(sec, min, hour, mday, mon, year)");
805 9           S = (int)SvIV(ST(0));
806 9           M = (int)SvIV(ST(1));
807 9           H = (int)SvIV(ST(2));
808 9           d = (int)SvIV(ST(3));
809 9           m = (int)SvIV(ST(4));
810 9           y = (int)SvIV(ST(5));
811 9 100         if (y < -1899 || y > 8099)
    100          
812 2           croak("Parameter 'year' is out of range [-1899, 8099]");
813 7           y += 1900;
814 7 100         if (m < 0 || m > 11)
    100          
815 2           croak("Parameter 'month' is out of range [0, 11]");
816 5           m += 1;
817 5 50         if (d < 1 || d > tstr_calendar_month_days(y, m))
    50          
818 0           croak("Parameter 'day' is out of range");
819 5 50         if (H < 0 || H > 23)
    50          
820 0           croak("Parameter 'hour' is out of range [0, 23]");
821 5 50         if (M < 0 || M > 59)
    50          
822 0           croak("Parameter 'minute' is out of range [0, 59]");
823 5 50         if (S < 0 || S > 59)
    50          
824 0           croak("Parameter 'second' is out of range [0, 59]");
825             #if IVSIZE >= 8
826 5           mPUSHi((IV)tstr_time_timegm(y, m, d, H, M, S));
827             #else
828             mPUSHn((NV)tstr_time_timegm(y, m, d, H, M, S));
829             #endif
830              
831             MODULE = Time::Str PACKAGE = Time::Str::Util
832              
833             PROTOTYPES: DISABLE
834              
835             void
836             binary_search(...)
837             PREINIT:
838             AV *av;
839             IV len, lo, hi, mid, hi_orig;
840             I64V value;
841 27           bool found = false;
842             PPCODE:
843 27 100         if (items < 2 || items > 4)
    100          
844 3           croak("Usage: binary_search(array, value [, lo [, hi]])");
845 24 100         if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVAV)
    50          
846 1           croak("Parameter 'array' must be an array reference");
847 23           av = (AV *)SvRV(ST(0));
848 23           value = SvI64V(ST(1));
849 23           len = av_len(av) + 1;
850             {
851 23 100         lo = (items >= 3) ? SvIV(ST(2)) : 0;
852 23 100         hi = (items >= 4) ? SvIV(ST(3)) : len;
853 23 50         if (lo < 0 || lo > len)
    50          
854 0           croak("Parameter 'lo' is out of range [0, %" IVdf "]", len);
855 23 50         if (hi < 0 || hi > len)
    50          
856 0           croak("Parameter 'hi' is out of range [0, %" IVdf "]", len);
857 23 50         if (lo > hi)
858 0           croak("Parameter 'lo' must not exceed 'hi'");
859             }
860 23           hi_orig = hi;
861 76 100         while (lo < hi) {
862 53           mid = (lo + hi) >> 1;
863             {
864 53           SV **elem = av_fetch(av, mid, 0);
865 53 50         if (elem && SvI64V(*elem) < value)
    100          
866 21           lo = mid + 1;
867             else
868 32           hi = mid;
869             }
870             }
871 23 100         if (lo != hi_orig) {
872 18           SV **elem = av_fetch(av, lo, 0);
873 18 50         found = elem && !(value < SvI64V(*elem));
    100          
874             }
875 23 100         XSRETURN_BOOL(found);
876              
877             void
878             lower_bound(...)
879             PREINIT:
880             AV *av;
881             IV lo, hi, mid;
882             I64V value;
883             PPCODE:
884 28 100         if (items < 2 || items > 4)
    50          
885 1           croak("Usage: lower_bound(array, value [, lo [, hi]])");
886 27 100         if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVAV)
    50          
887 1           croak("Parameter 'array' must be an array reference");
888 26           av = (AV *)SvRV(ST(0));
889 26           value = SvI64V(ST(1));
890             {
891 26           IV len = av_len(av) + 1;
892 26 100         lo = (items >= 3) ? SvIV(ST(2)) : 0;
893 26 100         hi = (items >= 4) ? SvIV(ST(3)) : len;
894 26 50         if (lo < 0 || lo > len)
    50          
895 0           croak("Parameter 'lo' is out of range [0, %" IVdf "]", len);
896 26 50         if (hi < 0 || hi > len)
    50          
897 0           croak("Parameter 'hi' is out of range [0, %" IVdf "]", len);
898 26 50         if (lo > hi)
899 0           croak("Parameter 'lo' must not exceed 'hi'");
900             }
901 87 100         while (lo < hi) {
902 61           mid = (lo + hi) >> 1;
903             {
904 61           SV **elem = av_fetch(av, mid, 0);
905 61 50         if (elem && SvI64V(*elem) < value)
    100          
906 22           lo = mid + 1;
907             else
908 39           hi = mid;
909             }
910             }
911 26           mPUSHi(lo);
912              
913             void
914             range_bounds(...)
915             PREINIT:
916             AV *av;
917             IV lo, hi, mid, len;
918             I64V min_value, max_value;
919             PPCODE:
920 13 100         if (items != 3)
921 1           croak("Usage: range_bounds(array, min_value, max_value)");
922 12 100         if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVAV)
    50          
923 1           croak("Parameter 'array' must be an array reference");
924 11           av = (AV *)SvRV(ST(0));
925 11           min_value = SvI64V(ST(1));
926 11           max_value = SvI64V(ST(2));
927 11 100         if (min_value > max_value)
928 1           croak("Parameter 'min_value' must not exceed 'max_value'");
929 10           len = av_len(av) + 1;
930             // lower_bound for min_value
931 10           lo = 0;
932 10           hi = len;
933 34 100         while (lo < hi) {
934 24           mid = (lo + hi) >> 1;
935             {
936 24           SV **elem = av_fetch(av, mid, 0);
937 24 50         if (elem && SvI64V(*elem) < min_value)
    100          
938 8           lo = mid + 1;
939             else
940 16           hi = mid;
941             }
942             }
943             // linear scan for upper bound
944 10           hi = lo;
945 26 100         while (hi < len) {
946 23           SV **elem = av_fetch(av, hi, 0);
947 23 50         if (elem && SvI64V(*elem) <= max_value)
    100          
948 16           hi++;
949             else
950             break;
951             }
952 10 50         EXTEND(SP, 2);
953 10           mPUSHi(lo);
954 10           mPUSHi(hi);
955              
956             void
957             upper_bound(...)
958             PREINIT:
959             AV *av;
960             IV lo, hi, mid;
961             I64V value;
962             PPCODE:
963 122 100         if (items < 2 || items > 4)
    50          
964 1           croak("Usage: upper_bound(array, value [, lo [, hi ]])");
965 121 100         if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVAV)
    50          
966 1           croak("Parameter 'array' must be an array reference");
967 120           av = (AV *)SvRV(ST(0));
968 120           value = SvI64V(ST(1));
969             {
970 120           IV len = av_len(av) + 1;
971 120 100         lo = (items >= 3) ? SvIV(ST(2)) : 0;
972 120 100         hi = (items >= 4) ? SvIV(ST(3)) : len;
973 120 50         if (lo < 0 || lo > len)
    50          
974 0           croak("Parameter 'lo' is out of range [0, %" IVdf "]", len);
975 120 50         if (hi < 0 || hi > len)
    50          
976 0           croak("Parameter 'hi' is out of range [0, %" IVdf "]", len);
977 120 50         if (lo > hi)
978 0           croak("Parameter 'lo' must not exceed 'hi'");
979             }
980 379 100         while (lo < hi) {
981 259           mid = (lo + hi) >> 1;
982             {
983 259           SV **elem = av_fetch(av, mid, 0);
984 259 50         if (elem && value < SvI64V(*elem))
    100          
985 147           hi = mid;
986             else
987 112           lo = mid + 1;
988             }
989             }
990 120           mPUSHi(lo);
991