| 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 |
|
7
|
|
|
|
|
|
|
#include |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
#include "datetime.h" |
|
10
|
|
|
|
|
|
|
#include "decimal.h" |
|
11
|
|
|
|
|
|
|
#include "scalar_kind.h" |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
static const int days_in_month[] = {31,28,31,30,31,30,31,31,30,31,30,31}; |
|
14
|
|
|
|
|
|
|
|
|
15
|
1990
|
|
|
|
|
|
static int is_leap_year(int year) { |
|
16
|
1990
|
100
|
|
|
|
|
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
} |
|
18
|
|
|
|
|
|
|
|
|
19
|
48
|
|
|
|
|
|
int32_t parse_date_string(pTHX_ const char *s, STRLEN len) { |
|
20
|
48
|
100
|
|
|
|
|
if (!looks_like_date(s, len)) |
|
21
|
2
|
50
|
|
|
|
|
croak("Invalid date string (expected YYYY-MM-DD): %.*s", |
|
22
|
|
|
|
|
|
|
(int)(len > 30 ? 30 : len), s); |
|
23
|
|
|
|
|
|
|
|
|
24
|
46
|
|
|
|
|
|
int year = (s[0]-'0')*1000 + (s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0'); |
|
25
|
46
|
|
|
|
|
|
int month = (s[5]-'0')*10 + (s[6]-'0'); |
|
26
|
46
|
|
|
|
|
|
int day = (s[8]-'0')*10 + (s[9]-'0'); |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
/* looks_like_date only validates the digit shape; reject out-of-range |
|
29
|
|
|
|
|
|
|
* components so the days_in_month loop can't read past the array, and |
|
30
|
|
|
|
|
|
|
* reject impossible day-of-month combinations (e.g. 2024-04-31). */ |
|
31
|
46
|
100
|
|
|
|
|
if (month < 1 || month > 12 || day < 1 || day > 31) |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
32
|
5
|
50
|
|
|
|
|
croak("Invalid date '%.*s': month/day out of range", |
|
33
|
|
|
|
|
|
|
(int)(len > 30 ? 30 : len), s); |
|
34
|
82
|
|
|
|
|
|
int max_day = days_in_month[month-1] |
|
35
|
41
|
100
|
|
|
|
|
+ (month == 2 && is_leap_year(year) ? 1 : 0); |
|
|
|
100
|
|
|
|
|
|
|
36
|
41
|
100
|
|
|
|
|
if (day > max_day) |
|
37
|
3
|
50
|
|
|
|
|
croak("Invalid date '%.*s': day %d exceeds month %d's length (%d)", |
|
38
|
|
|
|
|
|
|
(int)(len > 30 ? 30 : len), s, day, month, max_day); |
|
39
|
|
|
|
|
|
|
|
|
40
|
38
|
|
|
|
|
|
int32_t days = 0; |
|
41
|
|
|
|
|
|
|
int y, m; |
|
42
|
|
|
|
|
|
|
|
|
43
|
1860
|
100
|
|
|
|
|
for (y = 1970; y < year; y++) days += is_leap_year(y) ? 366 : 365; |
|
|
|
100
|
|
|
|
|
|
|
44
|
181
|
100
|
|
|
|
|
for (y = year; y < 1970; y++) days -= is_leap_year(y) ? 366 : 365; |
|
|
|
100
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
|
|
46
|
183
|
100
|
|
|
|
|
for (m = 1; m < month; m++) { |
|
47
|
145
|
|
|
|
|
|
days += days_in_month[m-1]; |
|
48
|
145
|
100
|
|
|
|
|
if (m == 2 && is_leap_year(year)) days++; |
|
|
|
100
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
|
|
51
|
38
|
|
|
|
|
|
days += day - 1; |
|
52
|
38
|
|
|
|
|
|
return days; |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
|
|
55
|
21
|
|
|
|
|
|
static void parse_time_part(pTHX_ const char *s, STRLEN len, |
|
56
|
|
|
|
|
|
|
int *h, int *mi, int *se) { |
|
57
|
21
|
50
|
|
|
|
|
if (len < 19) { *h = *mi = *se = 0; return; } |
|
58
|
|
|
|
|
|
|
#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') |
|
59
|
21
|
100
|
|
|
|
|
if ((s[10] != ' ' && s[10] != 'T') || s[13] != ':' || s[16] != ':' |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
60
|
21
|
50
|
|
|
|
|
|| !IS_DIGIT(s[11]) || !IS_DIGIT(s[12]) |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
61
|
19
|
50
|
|
|
|
|
|| !IS_DIGIT(s[14]) || !IS_DIGIT(s[15]) |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
62
|
19
|
50
|
|
|
|
|
|| !IS_DIGIT(s[17]) || !IS_DIGIT(s[18])) |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
63
|
3
|
50
|
|
|
|
|
croak("Invalid datetime string: %.*s", (int)(len > 30 ? 30 : len), s); |
|
64
|
|
|
|
|
|
|
#undef IS_DIGIT |
|
65
|
18
|
|
|
|
|
|
*h = (s[11]-'0')*10 + (s[12]-'0'); |
|
66
|
18
|
|
|
|
|
|
*mi = (s[14]-'0')*10 + (s[15]-'0'); |
|
67
|
18
|
|
|
|
|
|
*se = (s[17]-'0')*10 + (s[18]-'0'); |
|
68
|
|
|
|
|
|
|
} |
|
69
|
|
|
|
|
|
|
|
|
70
|
5
|
|
|
|
|
|
static int parse_tz_offset(pTHX_ const char *s, STRLEN len, STRLEN pos) { |
|
71
|
5
|
50
|
|
|
|
|
if (pos >= len) return 0; |
|
72
|
5
|
|
|
|
|
|
char c = s[pos]; |
|
73
|
5
|
100
|
|
|
|
|
if (c == 'Z' || c == 'z') return 0; |
|
|
|
50
|
|
|
|
|
|
|
74
|
4
|
100
|
|
|
|
|
if (c != '+' && c != '-') return 0; |
|
|
|
50
|
|
|
|
|
|
|
75
|
4
|
100
|
|
|
|
|
int sign = (c == '-') ? -1 : 1; |
|
76
|
4
|
|
|
|
|
|
pos++; |
|
77
|
4
|
50
|
|
|
|
|
if (pos + 2 > len |
|
78
|
4
|
50
|
|
|
|
|
|| s[pos] < '0' || s[pos] > '9' |
|
|
|
50
|
|
|
|
|
|
|
79
|
4
|
50
|
|
|
|
|
|| s[pos+1] < '0' || s[pos+1] > '9') |
|
|
|
50
|
|
|
|
|
|
|
80
|
0
|
0
|
|
|
|
|
croak("Invalid datetime timezone: %.*s", |
|
81
|
|
|
|
|
|
|
(int)(len > 30 ? 30 : len), s); |
|
82
|
4
|
|
|
|
|
|
int hh = (s[pos]-'0')*10 + (s[pos+1]-'0'); |
|
83
|
4
|
|
|
|
|
|
pos += 2; |
|
84
|
4
|
|
|
|
|
|
int mm = 0; |
|
85
|
4
|
50
|
|
|
|
|
if (pos < len && s[pos] == ':') pos++; |
|
|
|
100
|
|
|
|
|
|
|
86
|
4
|
50
|
|
|
|
|
if (pos + 2 <= len |
|
87
|
4
|
50
|
|
|
|
|
&& s[pos] >= '0' && s[pos] <= '9' |
|
|
|
50
|
|
|
|
|
|
|
88
|
4
|
50
|
|
|
|
|
&& s[pos+1] >= '0' && s[pos+1] <= '9') { |
|
|
|
50
|
|
|
|
|
|
|
89
|
4
|
|
|
|
|
|
mm = (s[pos]-'0')*10 + (s[pos+1]-'0'); |
|
90
|
|
|
|
|
|
|
} |
|
91
|
4
|
|
|
|
|
|
return sign * (hh * 3600 + mm * 60); |
|
92
|
|
|
|
|
|
|
} |
|
93
|
|
|
|
|
|
|
|
|
94
|
14
|
|
|
|
|
|
uint32_t parse_datetime_string(pTHX_ const char *s, STRLEN len) { |
|
95
|
14
|
|
|
|
|
|
int32_t days = parse_date_string(aTHX_ s, len); |
|
96
|
|
|
|
|
|
|
int h, mi, se; |
|
97
|
13
|
|
|
|
|
|
parse_time_part(aTHX_ s, len, &h, &mi, &se); |
|
98
|
11
|
|
|
|
|
|
int64_t epoch = (int64_t)days * 86400 + h * 3600 + mi * 60 + se; |
|
99
|
|
|
|
|
|
|
/* Optional ISO 8601 zone marker at position 19 (after HH:MM:SS). */ |
|
100
|
11
|
100
|
|
|
|
|
if (len > 19) epoch -= parse_tz_offset(aTHX_ s, len, 19); |
|
101
|
|
|
|
|
|
|
/* DateTime is UInt32 on the wire (1970-01-01 .. 2106-02-07). Reject |
|
102
|
|
|
|
|
|
|
* out-of-range epochs explicitly rather than silently wrapping. */ |
|
103
|
11
|
100
|
|
|
|
|
if (epoch < 0 || epoch > 0xFFFFFFFFLL) |
|
|
|
100
|
|
|
|
|
|
|
104
|
2
|
50
|
|
|
|
|
croak("DateTime out of UInt32 range " |
|
105
|
|
|
|
|
|
|
"(1970-01-01 .. 2106-02-07 06:28:15): %.*s", |
|
106
|
|
|
|
|
|
|
(int)(len > 30 ? 30 : len), s); |
|
107
|
9
|
|
|
|
|
|
return (uint32_t)epoch; |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
/* Multiply a Perl double by 10^precision, croak on Inf/NaN/overflow, |
|
111
|
|
|
|
|
|
|
* return the rounded int64. Shared by the SvNOK and string-number |
|
112
|
|
|
|
|
|
|
* branches of the T_DATETIME64 encode path. */ |
|
113
|
7
|
|
|
|
|
|
int64_t dt64_double_to_int64(pTHX_ double v, int precision) { |
|
114
|
7
|
|
|
|
|
|
double d = v * decimal_pow10(precision); |
|
115
|
|
|
|
|
|
|
/* Same bound as the T_DECIMAL64 path: 2^63 in double form. Strict |
|
116
|
|
|
|
|
|
|
* inequality so 2^63 itself (which would overflow int64_t after |
|
117
|
|
|
|
|
|
|
* llround) is rejected. */ |
|
118
|
7
|
100
|
|
|
|
|
if (!(isfinite(d) && d > -9.223372036854776e18 && d < 9.223372036854776e18)) |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
119
|
2
|
|
|
|
|
|
croak("DateTime64 overflow (or non-finite value)"); |
|
120
|
5
|
|
|
|
|
|
return (int64_t)llround(d); |
|
121
|
|
|
|
|
|
|
} |
|
122
|
|
|
|
|
|
|
|
|
123
|
8
|
|
|
|
|
|
int64_t parse_datetime64_string(pTHX_ const char *s, STRLEN len, int precision) { |
|
124
|
8
|
|
|
|
|
|
int32_t days = parse_date_string(aTHX_ s, len); |
|
125
|
|
|
|
|
|
|
int h, mi, se; |
|
126
|
8
|
|
|
|
|
|
parse_time_part(aTHX_ s, len, &h, &mi, &se); |
|
127
|
|
|
|
|
|
|
|
|
128
|
7
|
|
|
|
|
|
int64_t base = (int64_t)days * 86400 + h * 3600 + mi * 60 + se; |
|
129
|
7
|
|
|
|
|
|
int64_t frac = 0; |
|
130
|
7
|
|
|
|
|
|
STRLEN tz_pos = 19; |
|
131
|
|
|
|
|
|
|
|
|
132
|
7
|
50
|
|
|
|
|
if (len > 20 && s[19] == '.') { |
|
|
|
50
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
int i; |
|
134
|
37
|
100
|
|
|
|
|
for (i = 0; i < precision && (20 + i) < (int)len; i++) { |
|
|
|
50
|
|
|
|
|
|
|
135
|
30
|
|
|
|
|
|
char c = s[20 + i]; |
|
136
|
30
|
50
|
|
|
|
|
if (c < '0' || c > '9') break; |
|
|
|
50
|
|
|
|
|
|
|
137
|
30
|
|
|
|
|
|
frac = frac * 10 + (c - '0'); |
|
138
|
|
|
|
|
|
|
} |
|
139
|
7
|
50
|
|
|
|
|
for (; i < precision; i++) frac *= 10; |
|
140
|
|
|
|
|
|
|
/* Skip any extra fractional digits beyond the column's precision. */ |
|
141
|
7
|
|
|
|
|
|
tz_pos = 20; |
|
142
|
37
|
100
|
|
|
|
|
while (tz_pos < len && s[tz_pos] >= '0' && s[tz_pos] <= '9') tz_pos++; |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
|
|
145
|
7
|
100
|
|
|
|
|
if (tz_pos < len) base -= parse_tz_offset(aTHX_ s, len, tz_pos); |
|
146
|
|
|
|
|
|
|
|
|
147
|
7
|
50
|
|
|
|
|
uint64_t scale = (precision >= 0 && precision <= 19) ? pow10_u64[precision] : 1; |
|
|
|
50
|
|
|
|
|
|
|
148
|
7
|
|
|
|
|
|
return base * (int64_t)scale + frac; |
|
149
|
|
|
|
|
|
|
} |