qr_reader-native 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE-mruby.txt +22 -0
- data/LICENSE-spinel.txt +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/ext/qr_reader_native/SPINEL_VERSION +1 -0
- data/ext/qr_reader_native/extconf.rb +8 -0
- data/ext/qr_reader_native/mruby/error.h +13 -0
- data/ext/qr_reader_native/mruby/internal.h +145 -0
- data/ext/qr_reader_native/mruby/string.h +22 -0
- data/ext/qr_reader_native/mruby.h +93 -0
- data/ext/qr_reader_native/mruby_shim.h +118 -0
- data/ext/qr_reader_native/qr_reader_native.c +13513 -0
- data/ext/qr_reader_native/qr_reader_native.h +29 -0
- data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
- data/ext/qr_reader_native/re_cased.h +71 -0
- data/ext/qr_reader_native/re_compile.c +5784 -0
- data/ext/qr_reader_native/re_ctype.h +3540 -0
- data/ext/qr_reader_native/re_exec.c +2190 -0
- data/ext/qr_reader_native/re_internal.h +671 -0
- data/ext/qr_reader_native/re_prop.h +4629 -0
- data/ext/qr_reader_native/re_spinel.c +363 -0
- data/ext/qr_reader_native/re_spinel.h +22 -0
- data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
- data/ext/qr_reader_native/re_utf8.c +156 -0
- data/ext/qr_reader_native/sp_alloc.c +1447 -0
- data/ext/qr_reader_native/sp_alloc.h +1196 -0
- data/ext/qr_reader_native/sp_argf.h +33 -0
- data/ext/qr_reader_native/sp_array.c +1052 -0
- data/ext/qr_reader_native/sp_array.h +456 -0
- data/ext/qr_reader_native/sp_bigint.c +5826 -0
- data/ext/qr_reader_native/sp_bigint.h +117 -0
- data/ext/qr_reader_native/sp_cold.c +5259 -0
- data/ext/qr_reader_native/sp_compat.h +402 -0
- data/ext/qr_reader_native/sp_core.c +818 -0
- data/ext/qr_reader_native/sp_core.h +56 -0
- data/ext/qr_reader_native/sp_crypto.c +877 -0
- data/ext/qr_reader_native/sp_crypto.h +169 -0
- data/ext/qr_reader_native/sp_dtoa.c +1533 -0
- data/ext/qr_reader_native/sp_dtoa.h +29 -0
- data/ext/qr_reader_native/sp_encoding_names.h +172 -0
- data/ext/qr_reader_native/sp_enum.h +86 -0
- data/ext/qr_reader_native/sp_exc.c +1185 -0
- data/ext/qr_reader_native/sp_exc.h +186 -0
- data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
- data/ext/qr_reader_native/sp_fiber.c +889 -0
- data/ext/qr_reader_native/sp_fiber.h +162 -0
- data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
- data/ext/qr_reader_native/sp_format.c +695 -0
- data/ext/qr_reader_native/sp_format.h +83 -0
- data/ext/qr_reader_native/sp_gc.c +1793 -0
- data/ext/qr_reader_native/sp_gc.h +762 -0
- data/ext/qr_reader_native/sp_hash.c +131 -0
- data/ext/qr_reader_native/sp_hash.h +131 -0
- data/ext/qr_reader_native/sp_inspect.c +215 -0
- data/ext/qr_reader_native/sp_inspect.h +128 -0
- data/ext/qr_reader_native/sp_io.c +1676 -0
- data/ext/qr_reader_native/sp_io.h +272 -0
- data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
- data/ext/qr_reader_native/sp_iobuffer.h +167 -0
- data/ext/qr_reader_native/sp_marshal.c +487 -0
- data/ext/qr_reader_native/sp_marshal.h +60 -0
- data/ext/qr_reader_native/sp_net.c +1029 -0
- data/ext/qr_reader_native/sp_net.h +205 -0
- data/ext/qr_reader_native/sp_pack.c +1254 -0
- data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
- data/ext/qr_reader_native/sp_proc.c +410 -0
- data/ext/qr_reader_native/sp_proc.h +327 -0
- data/ext/qr_reader_native/sp_process.c +547 -0
- data/ext/qr_reader_native/sp_process_status.c +211 -0
- data/ext/qr_reader_native/sp_process_status.h +66 -0
- data/ext/qr_reader_native/sp_random.c +287 -0
- data/ext/qr_reader_native/sp_random.h +52 -0
- data/ext/qr_reader_native/sp_range.h +144 -0
- data/ext/qr_reader_native/sp_re.c +1379 -0
- data/ext/qr_reader_native/sp_re.h +186 -0
- data/ext/qr_reader_native/sp_re_names.h +59 -0
- data/ext/qr_reader_native/sp_sched.c +4044 -0
- data/ext/qr_reader_native/sp_sched.h +295 -0
- data/ext/qr_reader_native/sp_slab.c +1507 -0
- data/ext/qr_reader_native/sp_str.c +1462 -0
- data/ext/qr_reader_native/sp_str.h +306 -0
- data/ext/qr_reader_native/sp_str_crypt.c +23 -0
- data/ext/qr_reader_native/sp_string.c +92 -0
- data/ext/qr_reader_native/sp_string.h +253 -0
- data/ext/qr_reader_native/sp_system.c +78 -0
- data/ext/qr_reader_native/sp_system.h +18 -0
- data/ext/qr_reader_native/sp_time.c +860 -0
- data/ext/qr_reader_native/sp_time.h +100 -0
- data/ext/qr_reader_native/sp_types.h +292 -0
- data/ext/qr_reader_native/spinel_rt.h +19017 -0
- data/ext/qr_reader_native/unicase.c +399 -0
- data/ext/qr_reader_native/unicase.h +901 -0
- data/lib/qr_reader/native.rb +6 -0
- metadata +153 -0
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
/* sp_time.c -- libc-backed Time implementations.
|
|
2
|
+
*
|
|
3
|
+
* Sibling to sp_bigint.c / sp_crypto.c. The libc value ops (construct,
|
|
4
|
+
* accessors, shifts) carry no runtime dependency; the formatters
|
|
5
|
+
* (strftime / iso8601 / zone / inspect) return GC-heap strings directly
|
|
6
|
+
* via sp_alloc.h, so the generated TU no longer needs buffer-copying
|
|
7
|
+
* trampolines for them.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include <ctype.h>
|
|
11
|
+
#include "sp_core.h"
|
|
12
|
+
#include <math.h>
|
|
13
|
+
#include <stdio.h>
|
|
14
|
+
#include <stdlib.h>
|
|
15
|
+
#include <string.h>
|
|
16
|
+
#include <time.h>
|
|
17
|
+
|
|
18
|
+
#include "sp_time.h"
|
|
19
|
+
#include "sp_alloc.h" /* sp_str_dup_external / sp_str_empty for the GC formatters */
|
|
20
|
+
|
|
21
|
+
/* Time#floor / #ceil / #round, shared by the typed and the boxed receiver so
|
|
22
|
+
the two cannot drift. The no-argument form is ndigits 0: scale is 10^9 and
|
|
23
|
+
every mode lands the value on a whole second. */
|
|
24
|
+
sp_Time sp_time_round_to(sp_Time t, int64_t ndigits, int mode) {
|
|
25
|
+
/* CRuby names no number here, and a supported method's message is part of
|
|
26
|
+
what it means to be a subset of it. */
|
|
27
|
+
if (ndigits < 0) sp_raise_cls("ArgumentError", "negative ndigits given");
|
|
28
|
+
if (ndigits >= 9) return t; /* full nanosecond resolution */
|
|
29
|
+
int64_t scale = 1;
|
|
30
|
+
for (int64_t k = ndigits; k < 9; k++) scale *= 10;
|
|
31
|
+
int64_t ns = t.tv_nsec;
|
|
32
|
+
if (mode == 0) ns = ns / scale * scale;
|
|
33
|
+
else if (mode == 1) { if (ns % scale) ns = (ns / scale + 1) * scale; }
|
|
34
|
+
else ns = (ns + scale / 2) / scale * scale;
|
|
35
|
+
if (ns >= 1000000000) { t.tv_sec += 1; ns -= 1000000000; }
|
|
36
|
+
t.tv_nsec = (int32_t)ns;
|
|
37
|
+
return t;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
sp_Time sp_time_now(void) {
|
|
41
|
+
struct timespec ts;
|
|
42
|
+
clock_gettime(CLOCK_REALTIME, &ts);
|
|
43
|
+
return (sp_Time){ ts.tv_sec, (int32_t)ts.tv_nsec, 0 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* A sub-second argument of a whole unit or more belongs in the seconds
|
|
47
|
+
field, the way CRuby normalises it (#3704). */
|
|
48
|
+
sp_Time sp_time_norm(sp_Time t) {
|
|
49
|
+
int64_t ns = (int64_t)t.tv_nsec;
|
|
50
|
+
if (ns >= 1000000000LL || ns < 0) {
|
|
51
|
+
int64_t carry = ns / 1000000000LL;
|
|
52
|
+
ns -= carry * 1000000000LL;
|
|
53
|
+
if (ns < 0) { ns += 1000000000LL; carry--; }
|
|
54
|
+
t.tv_sec += carry;
|
|
55
|
+
t.tv_nsec = (int32_t)ns;
|
|
56
|
+
}
|
|
57
|
+
return t;
|
|
58
|
+
}
|
|
59
|
+
/* Add a sub-second offset that may exceed a whole second (#3704). */
|
|
60
|
+
sp_Time sp_time_add_nsec(sp_Time t, int64_t ns) {
|
|
61
|
+
t.tv_sec += ns / 1000000000LL;
|
|
62
|
+
t.tv_nsec = (int32_t)((int64_t)t.tv_nsec + ns % 1000000000LL);
|
|
63
|
+
return sp_time_norm(t);
|
|
64
|
+
}
|
|
65
|
+
sp_Time sp_time_at_int(int64_t sec) {
|
|
66
|
+
return (sp_Time){ sec, 0, 0 };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Time.at(Rational): the exact num/den epoch, floored to the nanosecond
|
|
70
|
+
(Time.at(-1r/3) is second -1, nanosecond 666666666). den is positive by
|
|
71
|
+
sp_Rational's normalized-sign invariant. */
|
|
72
|
+
sp_Time sp_time_at_div(int64_t num, int64_t den) {
|
|
73
|
+
if (den == 0) sp_raise_cls("ZeroDivisionError", "divided by 0");
|
|
74
|
+
int64_t sec = num / den;
|
|
75
|
+
int64_t rem = num % den;
|
|
76
|
+
if (rem < 0) { sec -= 1; rem += den; }
|
|
77
|
+
/* rem < den, so rem * 1e9 needs more than 64 bits only when den does:
|
|
78
|
+
a 128-bit product where the compiler has one, long double otherwise
|
|
79
|
+
(a 32-bit build; the quotient is below 1e9 either way) */
|
|
80
|
+
#if SP_HAVE_INT128
|
|
81
|
+
int64_t ns = (int64_t)(((sp_int128)rem * 1000000000) / den);
|
|
82
|
+
#else
|
|
83
|
+
int64_t ns = (int64_t)(((long double)rem * 1000000000.0L) / (long double)den);
|
|
84
|
+
#endif
|
|
85
|
+
return (sp_Time){ sec, (int32_t)ns, 0 };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Exact Float -> (sec, nsec) shift. CRuby converts the Float through to_r,
|
|
89
|
+
so the EXACT binary value of the double decides the nanosecond, floored
|
|
90
|
+
(Time.at(100) - 1.3 has usec 699999, not 700000, because the double
|
|
91
|
+
nearest -1.3 is -1.3000000000000000444...). Reproduce that with integer
|
|
92
|
+
math: decompose the double into mantissa * 2^exp via frexp, widen the
|
|
93
|
+
mantissa-nanoseconds product to 128 bits, and arithmetic-shift (which
|
|
94
|
+
floors negatives) instead of multiplying in double precision. */
|
|
95
|
+
static void sp_time_shift_ns(double secs, int64_t base_sec, int32_t base_ns,
|
|
96
|
+
int64_t *out_sec, int32_t *out_ns) {
|
|
97
|
+
/* CRuby routes the Float through to_r, so a non-finite value raises
|
|
98
|
+
FloatDomainError (Time.at(Float::INFINITY), t + Float::NAN, ...) rather
|
|
99
|
+
than reaching frexp -- where (int64_t)(NaN/Inf * 2^53) would be UB. */
|
|
100
|
+
if (!isfinite(secs))
|
|
101
|
+
sp_raise_cls("FloatDomainError", isnan(secs) ? "NaN" : (secs < 0 ? "-Infinity" : "Infinity"));
|
|
102
|
+
int e;
|
|
103
|
+
double m = frexp(secs, &e);
|
|
104
|
+
int64_t mi = (int64_t)(m * 9007199254740992.0); /* m * 2^53, exact */
|
|
105
|
+
e -= 53;
|
|
106
|
+
#if SP_HAVE_INT128
|
|
107
|
+
sp_int128 ns = (sp_int128)mi * 1000000000;
|
|
108
|
+
if (e > 0) ns = (e > 34) ? (ns < 0 ? INT64_MIN : INT64_MAX) : ns << e;
|
|
109
|
+
/* Floor toward -inf (arithmetic shift), matching CRuby's #nsec/#usec, which
|
|
110
|
+
truncate the exact rational. spinel stores nanosecond resolution, so the
|
|
111
|
+
sub-nanosecond bits CRuby keeps for #to_f round-tripping are lost by
|
|
112
|
+
design (see docs/limitations.md). */
|
|
113
|
+
else if (e < 0) ns = (-e > 126) ? (ns < 0 ? -1 : 0) : ns >> -e;
|
|
114
|
+
sp_int128 total = ((sp_int128)base_sec * 1000000000 + base_ns) + ns;
|
|
115
|
+
int64_t sec = (int64_t)(total / 1000000000);
|
|
116
|
+
int64_t rem = (int64_t)(total % 1000000000);
|
|
117
|
+
#else
|
|
118
|
+
/* no 128-bit integer (a 32-bit build): the same in long double, whose
|
|
119
|
+
64-bit mantissa on x86 holds the nanosecond count of any date a
|
|
120
|
+
32-bit sp_int can express; the floor is explicit */
|
|
121
|
+
long double nsd = ldexpl((long double)mi * 1000000000.0L, e);
|
|
122
|
+
long double totd = ((long double)base_sec * 1000000000.0L + (long double)base_ns) + floorl(nsd);
|
|
123
|
+
int64_t sec = (int64_t)floorl(totd / 1000000000.0L);
|
|
124
|
+
int64_t rem = (int64_t)(totd - (long double)sec * 1000000000.0L);
|
|
125
|
+
#endif
|
|
126
|
+
if (rem < 0) { sec -= 1; rem += 1000000000; }
|
|
127
|
+
*out_sec = sec;
|
|
128
|
+
*out_ns = (int32_t)rem;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* POSIX convention: keep tv_nsec in [0, 1e9). For negative epoch with
|
|
132
|
+
a non-integer fractional part, decrement tv_sec and roll the fraction
|
|
133
|
+
into the positive nsec range -- so Time.at(-0.5).to_i returns -1, not 0. */
|
|
134
|
+
sp_Time sp_time_at_float(double epoch) {
|
|
135
|
+
sp_Time r = { 0, 0, 0 };
|
|
136
|
+
sp_time_shift_ns(epoch, 0, 0, &r.tv_sec, &r.tv_nsec);
|
|
137
|
+
return r;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Time.new(y[,mo[,d[,h[,mi[,s]]]]]) -- local construction. mktime
|
|
141
|
+
interprets the broken-down value in the host local zone and resolves
|
|
142
|
+
DST itself (tm_isdst=-1). The fixed-offset 7-arg form is a separate
|
|
143
|
+
issue. */
|
|
144
|
+
/* CRuby validates each civil component against a fixed range before
|
|
145
|
+
normalizing overflow into neighbouring fields (e.g. Feb 30 -> Mar 1 is
|
|
146
|
+
allowed, but mon 13 / mday 32 / hour 25 raise ArgumentError) (#3099). */
|
|
147
|
+
static void sp_time_check_args(int64_t mo, int64_t d, int64_t h, int64_t mi, int64_t s) {
|
|
148
|
+
if (mo < 1 || mo > 12) sp_raise_cls("ArgumentError", "mon out of range");
|
|
149
|
+
if (d < 1 || d > 31) sp_raise_cls("ArgumentError", "mday out of range");
|
|
150
|
+
if (h < 0 || h > 24) sp_raise_cls("ArgumentError", "hour out of range");
|
|
151
|
+
if (mi < 0 || mi > 59) sp_raise_cls("ArgumentError", "min out of range");
|
|
152
|
+
if (s < 0 || s > 60) sp_raise_cls("ArgumentError", "sec out of range");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static int64_t sp_time_civil_epoch(int64_t y, int64_t mo, int64_t d,
|
|
156
|
+
int64_t h, int64_t mi, int64_t s);
|
|
157
|
+
|
|
158
|
+
/* localtime/gmtime may share a process-wide buffer. Copying their result
|
|
159
|
+
still races with another OS worker; resolve into caller-owned storage.
|
|
160
|
+
Unlike localtime, POSIX localtime_r need not act as if it called tzset. */
|
|
161
|
+
static struct tm *sp_time_local_tm(time_t s, struct tm *bd) {
|
|
162
|
+
#ifndef __wasi__ /* WASI has no time zone database and no tzset */
|
|
163
|
+
tzset();
|
|
164
|
+
#endif
|
|
165
|
+
return localtime_r(&s, bd);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Not mktime(gmtime(s)) - s: macOS mktime answers -1 for any year before 1900. */
|
|
169
|
+
static int32_t sp_time_local_offset(time_t s) {
|
|
170
|
+
struct tm local;
|
|
171
|
+
struct tm *l = sp_time_local_tm(s, &local);
|
|
172
|
+
if (!l) return 0;
|
|
173
|
+
return (int32_t)(sp_time_civil_epoch(l->tm_year + 1900, l->tm_mon + 1, l->tm_mday,
|
|
174
|
+
l->tm_hour, l->tm_min, l->tm_sec) - (int64_t)s);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
sp_Time sp_time_new(int64_t y, int64_t mo, int64_t d,
|
|
178
|
+
int64_t h, int64_t mi, int64_t s) {
|
|
179
|
+
sp_time_check_args(mo, d, h, mi, s);
|
|
180
|
+
struct tm tm;
|
|
181
|
+
memset(&tm, 0, sizeof(tm));
|
|
182
|
+
tm.tm_year = (int)y - 1900;
|
|
183
|
+
tm.tm_mon = (int)mo - 1;
|
|
184
|
+
tm.tm_mday = (int)d;
|
|
185
|
+
tm.tm_hour = (int)h;
|
|
186
|
+
tm.tm_min = (int)mi;
|
|
187
|
+
tm.tm_sec = (int)s;
|
|
188
|
+
tm.tm_isdst = -1;
|
|
189
|
+
tm.tm_wday = -1;
|
|
190
|
+
time_t e = mktime(&tm);
|
|
191
|
+
if (e == (time_t)-1 && tm.tm_wday == -1) {
|
|
192
|
+
/* Not the -1 epoch: mktime refused the year (macOS, before 1900); resolve via localtime, twice to settle an offset change. */
|
|
193
|
+
int64_t guess = sp_time_civil_epoch(y, mo, d, h, mi, s);
|
|
194
|
+
int64_t epoch = guess - sp_time_local_offset((time_t)guess);
|
|
195
|
+
epoch = guess - sp_time_local_offset((time_t)epoch);
|
|
196
|
+
return (sp_Time){ epoch, 0, 0 };
|
|
197
|
+
}
|
|
198
|
+
return (sp_Time){ (int64_t)e, 0, 0 };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* Civil (y, mo, d, h, mi, s) -> UTC epoch seconds. Avoid timegm
|
|
202
|
+
(not portable; absent on MSVCRT). Compute the epoch via Howard
|
|
203
|
+
Hinnant's days_from_civil + a manual hour/minute/second add. */
|
|
204
|
+
static int64_t sp_time_civil_epoch(int64_t y, int64_t mo, int64_t d,
|
|
205
|
+
int64_t h, int64_t mi, int64_t s) {
|
|
206
|
+
int64_t yy = y - (mo <= 2 ? 1 : 0);
|
|
207
|
+
int64_t era = (yy >= 0 ? yy : yy - 399) / 400;
|
|
208
|
+
int64_t yoe = yy - era * 400;
|
|
209
|
+
int64_t doy = (153 * (mo + (mo > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
|
210
|
+
int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
|
211
|
+
int64_t days = era * 146097 + doe - 719468;
|
|
212
|
+
return days * 86400 + h * 3600 + mi * 60 + s;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Time.utc(y, m, d, h, mi, s) -- UTC construction. */
|
|
216
|
+
/* The month argument of the civil constructors accepts an English month name
|
|
217
|
+
as well as a number: Time.utc(2020, "feb", 4). A plain strtoll read those as
|
|
218
|
+
zero and the constructor rejected them (#3703). */
|
|
219
|
+
int64_t sp_time_month_arg(const char *s) {
|
|
220
|
+
static const char *const names[12] = {
|
|
221
|
+
"jan", "feb", "mar", "apr", "may", "jun",
|
|
222
|
+
"jul", "aug", "sep", "oct", "nov", "dec" };
|
|
223
|
+
if (!s) return 0;
|
|
224
|
+
if (strlen(s) == 3) {
|
|
225
|
+
char pre[4];
|
|
226
|
+
for (int i = 0; i < 3; i++) {
|
|
227
|
+
char ch = s[i];
|
|
228
|
+
pre[i] = (ch >= 'A' && ch <= 'Z') ? (char)(ch - 'A' + 'a') : ch;
|
|
229
|
+
}
|
|
230
|
+
pre[3] = '\0';
|
|
231
|
+
for (int m = 0; m < 12; m++)
|
|
232
|
+
if (strcmp(pre, names[m]) == 0) return (int64_t)(m + 1);
|
|
233
|
+
}
|
|
234
|
+
/* anything else is read as an Integer, and rejected the way Integer() would
|
|
235
|
+
be: CRuby takes only the three-letter abbreviations by name */
|
|
236
|
+
return (int64_t)sp_str_to_i_strict(s);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
sp_Time sp_time_new_utc(int64_t y, int64_t mo, int64_t d,
|
|
240
|
+
int64_t h, int64_t mi, int64_t s) {
|
|
241
|
+
sp_time_check_args(mo, d, h, mi, s);
|
|
242
|
+
return (sp_Time){ sp_time_civil_epoch(y, mo, d, h, mi, s), 0, 1 };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* A zone argument (`in:`, Time.new's 7th positional, or localtime /
|
|
246
|
+
getlocal's String): CRuby's utc_offset_arg. "UTC" in any case and "Z" are
|
|
247
|
+
UTC; a military letter is a whole-hour offset ("A".."I" +1..+9, "K".."M"
|
|
248
|
+
+10..+12, "N".."Y" -1..-12, no "J"); otherwise "+HH", "+HHMM", "+HHMMSS",
|
|
249
|
+
"+HH:MM" or "+HH:MM:SS", minutes and seconds below 60, where a negative
|
|
250
|
+
zero ("-00:00") is UTC as well. Any other spelling is CRuby's ArgumentError
|
|
251
|
+
naming the forms, and an offset of a day or more its "utc_offset out of
|
|
252
|
+
range" (#3696, #3697, #3698). */
|
|
253
|
+
int64_t sp_time_zone_arg_off(const char *z, int *is_utc_out) {
|
|
254
|
+
*is_utc_out = 0;
|
|
255
|
+
if (!z) sp_raise_cls("ArgumentError", "invalid time zone");
|
|
256
|
+
size_t len = strlen(z);
|
|
257
|
+
const char *min = NULL, *sec = NULL;
|
|
258
|
+
int64_t n = 0;
|
|
259
|
+
switch (len) {
|
|
260
|
+
case 1:
|
|
261
|
+
if (z[0] == 'Z') { *is_utc_out = 1; return 0; }
|
|
262
|
+
if (z[0] >= 'A' && z[0] <= 'I') return (int64_t)(z[0] - 'A' + 1) * 3600;
|
|
263
|
+
if (z[0] >= 'K' && z[0] <= 'M') return (int64_t)(z[0] - 'A') * 3600;
|
|
264
|
+
if (z[0] >= 'N' && z[0] <= 'Y') return (int64_t)('M' - z[0]) * 3600;
|
|
265
|
+
goto invalid;
|
|
266
|
+
case 3:
|
|
267
|
+
if ((z[0] | 0x20) == 'u' && (z[1] | 0x20) == 't' && (z[2] | 0x20) == 'c') { *is_utc_out = 1; return 0; }
|
|
268
|
+
break; /* "+HH" */
|
|
269
|
+
case 9: if (z[6] != ':') goto invalid; sec = z + 7; /* fall through: "+HH:MM:SS" */
|
|
270
|
+
case 6: if (z[3] != ':') goto invalid; min = z + 4; break; /* "+HH:MM" */
|
|
271
|
+
case 7: sec = z + 5; /* fall through: "+HHMMSS" */
|
|
272
|
+
case 5: min = z + 3; break; /* "+HHMM" */
|
|
273
|
+
default: goto invalid;
|
|
274
|
+
}
|
|
275
|
+
if (sec) {
|
|
276
|
+
if (!isdigit((unsigned char)sec[0]) || !isdigit((unsigned char)sec[1]) || sec[0] > '5') goto invalid;
|
|
277
|
+
n += (sec[0] - '0') * 10 + (sec[1] - '0');
|
|
278
|
+
}
|
|
279
|
+
if (min) {
|
|
280
|
+
if (!isdigit((unsigned char)min[0]) || !isdigit((unsigned char)min[1]) || min[0] > '5') goto invalid;
|
|
281
|
+
n += ((min[0] - '0') * 10 + (min[1] - '0')) * 60;
|
|
282
|
+
}
|
|
283
|
+
if ((z[0] != '+' && z[0] != '-') || !isdigit((unsigned char)z[1]) || !isdigit((unsigned char)z[2])) goto invalid;
|
|
284
|
+
n += (int64_t)((z[1] - '0') * 10 + (z[2] - '0')) * 3600;
|
|
285
|
+
if (z[0] == '-') {
|
|
286
|
+
if (n == 0) { *is_utc_out = 1; return 0; }
|
|
287
|
+
n = -n;
|
|
288
|
+
}
|
|
289
|
+
if (n <= -86400 || n >= 86400) sp_raise_cls("ArgumentError", "utc_offset out of range");
|
|
290
|
+
return n;
|
|
291
|
+
invalid:
|
|
292
|
+
sp_raise_cls("ArgumentError", sp_sprintf("\"+HH:MM\", \"-HH:MM\", \"UTC\" or \"A\"..\"I\",\"K\"..\"Z\" expected for utc_offset: %s", z));
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Re-read a Time in the zone the argument names (#3698). */
|
|
297
|
+
sp_Time sp_time_in_zone_i(sp_Time t, int64_t off) {
|
|
298
|
+
if (off <= -86400 || off >= 86400)
|
|
299
|
+
sp_raise_cls("ArgumentError", "utc_offset out of range");
|
|
300
|
+
t.is_utc = 2; t.utc_off = (int32_t)off;
|
|
301
|
+
return t;
|
|
302
|
+
}
|
|
303
|
+
sp_Time sp_time_in_zone_s(sp_Time t, const char *z) {
|
|
304
|
+
int isu = 0;
|
|
305
|
+
int64_t off = sp_time_zone_arg_off(z, &isu);
|
|
306
|
+
if (isu) { t.is_utc = 1; t.utc_off = 0; }
|
|
307
|
+
else { t.is_utc = 2; t.utc_off = (int32_t)off; }
|
|
308
|
+
return t;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Time.new(y, mo, d, h, mi, s, utc_offset) -- the civil value is read in a
|
|
312
|
+
fixed zone off seconds east of UTC, so the epoch is the UTC epoch of the
|
|
313
|
+
same civil value minus that offset. CRuby bounds the offset to a day. */
|
|
314
|
+
sp_Time sp_time_new_off(int64_t y, int64_t mo, int64_t d,
|
|
315
|
+
int64_t h, int64_t mi, int64_t s, int64_t off) {
|
|
316
|
+
if (off <= -86400 || off >= 86400)
|
|
317
|
+
sp_raise_cls("ArgumentError", "utc_offset out of range");
|
|
318
|
+
sp_time_check_args(mo, d, h, mi, s);
|
|
319
|
+
sp_Time t = { sp_time_civil_epoch(y, mo, d, h, mi, s) - off, 0, 2 };
|
|
320
|
+
t.utc_off = (int32_t)off;
|
|
321
|
+
return t;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Time.utc/local(y, mo, d, h, mi, s, usec) -- the 7th positional argument is
|
|
325
|
+
microseconds of second. */
|
|
326
|
+
sp_Time sp_time_with_usec(sp_Time t, int64_t usec) {
|
|
327
|
+
if (usec < 0 || usec >= 1000000)
|
|
328
|
+
sp_raise_cls("ArgumentError", "subsecx out of range");
|
|
329
|
+
t.tv_nsec = (int32_t)(usec * 1000);
|
|
330
|
+
return t;
|
|
331
|
+
}
|
|
332
|
+
/* a Float microsecond argument carries into the nanosecond field (#3092). */
|
|
333
|
+
sp_Time sp_time_with_usec_f(sp_Time t, double usec) {
|
|
334
|
+
if (usec < 0 || usec >= 1000000)
|
|
335
|
+
sp_raise_cls("ArgumentError", "subsecx out of range");
|
|
336
|
+
t.tv_nsec = (int32_t)(usec * 1000.0);
|
|
337
|
+
return t;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* Time.new(String): the fixed CRuby form "YYYY-MM-DD HH:MM:SS[.frac]" with
|
|
341
|
+
an optional " +HH:MM" / " -HH:MM" / " UTC" zone suffix. A date without a
|
|
342
|
+
time and any other shape raise CRuby's ArgumentError messages; anything
|
|
343
|
+
the grammar does not cover must be loud, never a guessed instant. */
|
|
344
|
+
sp_Time sp_time_parse(const char *s) {SP_GC_ROOT_STR(s);
|
|
345
|
+
const char *sp_sprintf(const char *fmt, ...); /* generated TU */
|
|
346
|
+
int y, mo, d, h, mi, sec, n = 0;
|
|
347
|
+
/* a year alone ("2021", "-44", "+12345") is Time.new(year), as CRuby
|
|
348
|
+
reads it: four digits at least, local midnight of January 1 */
|
|
349
|
+
{ const char *q = s + (*s == '+' || *s == '-');
|
|
350
|
+
size_t nd = 0;
|
|
351
|
+
while (q[nd] >= '0' && q[nd] <= '9') nd++;
|
|
352
|
+
if (nd > 0 && q[nd] == 0) {
|
|
353
|
+
if (nd < 4) sp_raise_cls("ArgumentError", sp_sprintf("year must be 4 or more digits: %s", s));
|
|
354
|
+
int64_t yy = 0;
|
|
355
|
+
for (size_t i = 0; i < nd && i < 18; i++) yy = yy * 10 + (q[i] - '0');
|
|
356
|
+
if (*s == '-') yy = -yy;
|
|
357
|
+
return sp_time_new(yy, 1, 1, 0, 0, 0);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (sscanf(s, "%4d-%2d-%2d%n", &y, &mo, &d, &n) != 3 || n == 0)
|
|
361
|
+
sp_raise_cls("ArgumentError", sp_sprintf("can't parse: \"%s\"", s));
|
|
362
|
+
const char *p = s + n;
|
|
363
|
+
if (*p == 0)
|
|
364
|
+
sp_raise_cls("ArgumentError", "no time information");
|
|
365
|
+
n = 0;
|
|
366
|
+
if (sscanf(p, " %2d:%2d:%2d%n", &h, &mi, &sec, &n) != 3 || n == 0)
|
|
367
|
+
sp_raise_cls("ArgumentError", sp_sprintf("can't parse: \"%s\"", s));
|
|
368
|
+
p += n;
|
|
369
|
+
int32_t nsec = 0;
|
|
370
|
+
if (*p == '.') {
|
|
371
|
+
p++;
|
|
372
|
+
int digits = 0;
|
|
373
|
+
int64_t frac = 0;
|
|
374
|
+
while (*p >= '0' && *p <= '9' && digits < 9) { frac = frac * 10 + (*p - '0'); p++; digits++; }
|
|
375
|
+
if (digits == 0)
|
|
376
|
+
sp_raise_cls("ArgumentError", sp_sprintf("can't parse: \"%s\"", s));
|
|
377
|
+
while (*p >= '0' && *p <= '9') p++; /* sub-ns digits are beyond sp_Time */
|
|
378
|
+
while (digits < 9) { frac *= 10; digits++; }
|
|
379
|
+
nsec = (int32_t)frac;
|
|
380
|
+
}
|
|
381
|
+
while (*p == ' ') p++;
|
|
382
|
+
if (*p == 0) {
|
|
383
|
+
/* no zone: host-local, like the civil Time.new */
|
|
384
|
+
sp_Time t = sp_time_new(y, mo, d, h, mi, sec);
|
|
385
|
+
t.tv_nsec = nsec;
|
|
386
|
+
return t;
|
|
387
|
+
}
|
|
388
|
+
if (strcmp(p, "UTC") == 0 || strcmp(p, "Z") == 0) {
|
|
389
|
+
sp_Time t = sp_time_new_utc(y, mo, d, h, mi, sec);
|
|
390
|
+
t.tv_nsec = nsec;
|
|
391
|
+
return t;
|
|
392
|
+
}
|
|
393
|
+
int oh, om;
|
|
394
|
+
n = 0;
|
|
395
|
+
if ((*p == '+' || *p == '-') && sscanf(p + 1, "%2d:%2d%n", &oh, &om, &n) == 2 &&
|
|
396
|
+
n > 0 && p[1 + n] == 0) {
|
|
397
|
+
int64_t off = (int64_t)oh * 3600 + (int64_t)om * 60;
|
|
398
|
+
if (*p == '-') off = -off;
|
|
399
|
+
sp_Time t = sp_time_new_off(y, mo, d, h, mi, sec, off);
|
|
400
|
+
t.tv_nsec = nsec;
|
|
401
|
+
return t;
|
|
402
|
+
}
|
|
403
|
+
sp_raise_cls("ArgumentError", sp_sprintf("can't parse: \"%s\"", s));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
sp_Time sp_time_utc(sp_Time t) {
|
|
407
|
+
t.is_utc = 1;
|
|
408
|
+
return t;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
sp_Time sp_time_localtime(sp_Time t) {
|
|
412
|
+
t.is_utc = 0;
|
|
413
|
+
return t;
|
|
414
|
+
}
|
|
415
|
+
/* Time#getlocal(off)/#localtime(off): reinterpret the instant in a fixed zone
|
|
416
|
+
`off` seconds east of UTC, without changing the underlying epoch (#3093). */
|
|
417
|
+
sp_Time sp_time_getlocal_off(sp_Time t, int64_t off) {
|
|
418
|
+
if (off <= -86400 || off >= 86400)
|
|
419
|
+
sp_raise_cls("ArgumentError", "utc_offset out of range");
|
|
420
|
+
t.is_utc = 2;
|
|
421
|
+
t.utc_off = (int32_t)off;
|
|
422
|
+
return t;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* is_utc selects gmtime vs localtime, off is UTC offset in seconds,
|
|
426
|
+
zbuf is the timezone abbreviation (8 bytes). The offset is computed
|
|
427
|
+
(sp_time_local_offset), not read from %z: MSVCRT's %z emits the
|
|
428
|
+
timezone name, not ±HHMM. */
|
|
429
|
+
void sp_time_vtm(sp_Time t, struct tm *bd, int32_t *off, char *zbuf) {
|
|
430
|
+
time_t s = (time_t)t.tv_sec;
|
|
431
|
+
if (t.is_utc == 2) {
|
|
432
|
+
/* fixed offset: the civil value is the UTC civil value shifted east */
|
|
433
|
+
time_t sh = s + (time_t)t.utc_off;
|
|
434
|
+
if (!gmtime_r(&sh, bd)) memset(bd, 0, sizeof(*bd));
|
|
435
|
+
if (off) *off = t.utc_off;
|
|
436
|
+
if (zbuf) zbuf[0] = 0;
|
|
437
|
+
}
|
|
438
|
+
else if (t.is_utc) {
|
|
439
|
+
if (!gmtime_r(&s, bd)) memset(bd, 0, sizeof(*bd));
|
|
440
|
+
if (off) *off = 0;
|
|
441
|
+
if (zbuf) { zbuf[0]='U'; zbuf[1]='T'; zbuf[2]='C'; zbuf[3]=0; }
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
if (!sp_time_local_tm(s, bd)) memset(bd, 0, sizeof(*bd));
|
|
445
|
+
if (off) *off = sp_time_local_offset(s);
|
|
446
|
+
if (zbuf) {
|
|
447
|
+
if (strftime(zbuf, 8, "%Z", bd) == 0) zbuf[0] = 0;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
int64_t sp_time_year(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)(b.tm_year+1900);}
|
|
453
|
+
int64_t sp_time_mon(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)(b.tm_mon+1);}
|
|
454
|
+
int64_t sp_time_mday(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)b.tm_mday;}
|
|
455
|
+
int64_t sp_time_hour(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)b.tm_hour;}
|
|
456
|
+
int64_t sp_time_min(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)b.tm_min;}
|
|
457
|
+
int64_t sp_time_sec(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)b.tm_sec;}
|
|
458
|
+
int64_t sp_time_wday(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)b.tm_wday;}
|
|
459
|
+
int64_t sp_time_yday(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)(b.tm_yday+1);}
|
|
460
|
+
int64_t sp_time_isdst(sp_Time t){struct tm b;sp_time_vtm(t,&b,NULL,NULL);return (int64_t)(b.tm_isdst>0?1:0);}
|
|
461
|
+
int64_t sp_time_utc_offset(sp_Time t){int32_t o;struct tm b;sp_time_vtm(t,&b,&o,NULL);return (int64_t)o;}
|
|
462
|
+
|
|
463
|
+
/* Time + Numeric / Time - Numeric. secs may be fractional; the shift is
|
|
464
|
+
exact in the double's binary value (see sp_time_shift_ns). The zone kind
|
|
465
|
+
and offset are inherited from the receiver. */
|
|
466
|
+
sp_Time sp_time_add(sp_Time t, double secs) {
|
|
467
|
+
sp_Time r = t;
|
|
468
|
+
sp_time_shift_ns(secs, t.tv_sec, t.tv_nsec, &r.tv_sec, &r.tv_nsec);
|
|
469
|
+
return r;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/* strftime returns 0 -- never overruns the buffer -- when the formatted
|
|
473
|
+
result would exceed it, which we surface as "". The 4 KB buffer covers
|
|
474
|
+
any realistic format (CRuby's built-ins are ~25 bytes; this leaves room
|
|
475
|
+
for long literal text or wide fields). A pathological field width
|
|
476
|
+
(`"%1000000000F"`, which CRuby rejects with ERANGE) does not fit and
|
|
477
|
+
yields "" -- a graceful empty string rather than a crash. */
|
|
478
|
+
/* The UTC offset (seconds) of a Time, mirroring sp_time_iso8601's manual calc. */
|
|
479
|
+
static long sp_time_offset_sec(sp_Time t) {
|
|
480
|
+
if (t.is_utc == 2) return t.utc_off; /* fixed offset (Time.at in:) */
|
|
481
|
+
if (t.is_utc) return 0;
|
|
482
|
+
return (long)sp_time_local_offset((time_t)t.tv_sec);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/* A year as Ruby writes it: zero-padded to four digits with the sign in
|
|
486
|
+
front ("0012", "-0012", "10000"); a positive `width` is the whole field,
|
|
487
|
+
sign included, as in "%4Y" ("-012"). C's %Y is not portable here: glibc
|
|
488
|
+
does not pad it at all, and macOS pads a negative year inside the sign
|
|
489
|
+
("-012" for "-0012"). Returns the length written, as snprintf does. */
|
|
490
|
+
static int sp_time_year_field(char *buf, size_t cap, long yr, int width) {
|
|
491
|
+
const char *sign = yr < 0 ? "-" : "";
|
|
492
|
+
unsigned long mag = yr < 0 ? 0UL - (unsigned long)yr : (unsigned long)yr;
|
|
493
|
+
int digits = width > 0 ? width - (yr < 0) : 4;
|
|
494
|
+
return snprintf(buf, cap, "%s%0*lu", sign, digits > 0 ? digits : 1, mag);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
static int sp_time_year_str(char *buf, size_t cap, long yr) {
|
|
498
|
+
return sp_time_year_field(buf, cap, yr, -1);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* buf <- the year, then C strftime of `rest` (fields that carry no year).
|
|
502
|
+
Returns the length, or 0 if it did not fit, as strftime does. */
|
|
503
|
+
static size_t sp_time_year_then(char *buf, size_t cap, const struct tm *b, const char *rest) {
|
|
504
|
+
int yn = sp_time_year_str(buf, cap, (long)b->tm_year + 1900);
|
|
505
|
+
if (yn < 0 || (size_t)yn >= cap) return 0;
|
|
506
|
+
if (!*rest) return (size_t)yn;
|
|
507
|
+
size_t r = strftime(buf + yn, cap - (size_t)yn, rest, b);
|
|
508
|
+
return r ? (size_t)yn + r : 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* Ruby-compatible strftime: C strftime handles the standard directives, but
|
|
512
|
+
Ruby adds %L/%N (subsec), %s (epoch, which C's %s would take through a LOCAL
|
|
513
|
+
mktime), %P (lowercase am/pm), the %:z/%::z colon offsets, and width/flag
|
|
514
|
+
modifiers (%3S, %6N, %10Y). Walk the format, compute those directly, and
|
|
515
|
+
pad; delegate a bare standard directive to strftime. (#2635, #2636) */
|
|
516
|
+
const char *sp_time_strftime(sp_Time t, const char *fmt) {SP_GC_ROOT_STR(fmt);
|
|
517
|
+
/* is_utc is a 3-state kind, not a flag: kind 2 is a fixed offset whose
|
|
518
|
+
civil fields are the UTC ones shifted by utc_off. Reading it as a
|
|
519
|
+
boolean sent kind 2 down the gmtime branch, so every field directive
|
|
520
|
+
rendered UTC under a %z that correctly said otherwise. sp_time_vtm
|
|
521
|
+
resolves all three kinds. */
|
|
522
|
+
struct tm tmv;
|
|
523
|
+
sp_time_vtm(t, &tmv, NULL, NULL);
|
|
524
|
+
static char out[8192];
|
|
525
|
+
size_t oi = 0;
|
|
526
|
+
for (const char *p = fmt; *p && oi < sizeof(out) - 128; p++) {
|
|
527
|
+
if (*p != '%') { out[oi++] = *p; continue; }
|
|
528
|
+
const char *tok = p++;
|
|
529
|
+
int upcase = 0, downcase = 0, pad0 = 0, padsp = 0, nopad = 0, colon = 0;
|
|
530
|
+
for (;; p++) {
|
|
531
|
+
if (*p == '^') upcase = 1; else if (*p == '#') downcase = 1;
|
|
532
|
+
else if (*p == '0') pad0 = 1; else if (*p == '_') padsp = 1;
|
|
533
|
+
else if (*p == '-') nopad = 1; else break;
|
|
534
|
+
}
|
|
535
|
+
while (*p == ':') { colon++; p++; }
|
|
536
|
+
int width = -1;
|
|
537
|
+
if (*p >= '0' && *p <= '9') { width = 0; while (*p >= '0' && *p <= '9') width = width * 10 + (*p++ - '0'); }
|
|
538
|
+
/* the E / O locale modifiers select an alternative representation the C
|
|
539
|
+
locale does not have, so the unmodified directive is what they mean (#3705) */
|
|
540
|
+
while ((*p == 'E' || *p == 'O') && p[1]) p++;
|
|
541
|
+
char d = *p;
|
|
542
|
+
/* a format ending in a bare `%` is invalid, not a literal one (#3705) */
|
|
543
|
+
if (!d) sp_raise_cls("ArgumentError", "invalid format");
|
|
544
|
+
char val[128]; val[0] = 0;
|
|
545
|
+
if (d == '%') { val[0] = '%'; val[1] = 0; }
|
|
546
|
+
else if (d == 's') snprintf(val, sizeof val, "%lld", (long long)t.tv_sec);
|
|
547
|
+
else if (d == 'L') {
|
|
548
|
+
/* a width on %L asks for that many fractional digits, not left padding
|
|
549
|
+
of the millisecond count (#3705) */
|
|
550
|
+
int lw = width > 0 ? width : 3;
|
|
551
|
+
char nb[24]; snprintf(nb, sizeof nb, "%09ld", (long)t.tv_nsec);
|
|
552
|
+
if (lw <= 9) { memcpy(val, nb, (size_t)lw); val[lw] = 0; }
|
|
553
|
+
else { strcpy(val, nb); for (int i = 9; i < lw && i < 120; i++) val[i] = '0'; val[lw < 120 ? lw : 120] = 0; }
|
|
554
|
+
width = -1;
|
|
555
|
+
}
|
|
556
|
+
else if (d == 'N') {
|
|
557
|
+
int w = width > 0 ? width : 9;
|
|
558
|
+
char nb[16]; snprintf(nb, sizeof nb, "%09ld", (long)t.tv_nsec);
|
|
559
|
+
if (w <= 9) { memcpy(val, nb, (size_t)w); val[w] = 0; }
|
|
560
|
+
else { strcpy(val, nb); for (int i = 9; i < w && i < 120; i++) val[i] = '0'; val[w < 120 ? w : 120] = 0; }
|
|
561
|
+
width = -1; /* width consumed by the subsecond precision */
|
|
562
|
+
}
|
|
563
|
+
else if (d == 'z') {
|
|
564
|
+
/* bare %z computed from the receiver's own offset, never C strftime's
|
|
565
|
+
tm_gmtoff -- that field is filled from the tm's construction path and
|
|
566
|
+
varies by platform, and a fixed-offset time (is_utc == 2) has no tm
|
|
567
|
+
representation at all (#2635) */
|
|
568
|
+
if (!colon) {
|
|
569
|
+
long off0 = sp_time_offset_sec(t);
|
|
570
|
+
char sign0 = off0 < 0 ? '-' : '+'; long a0 = off0 < 0 ? -off0 : off0;
|
|
571
|
+
snprintf(val, sizeof val, "%c%02d%02d", sign0, (int)(a0 / 3600), (int)((a0 / 60) % 60));
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
long off = sp_time_offset_sec(t);
|
|
575
|
+
char sign = off < 0 ? '-' : '+'; long a = off < 0 ? -off : off;
|
|
576
|
+
int oh = (int)(a / 3600), om = (int)((a / 60) % 60), os = (int)(a % 60);
|
|
577
|
+
/* %:::z collapses to the minimal precision that loses nothing */
|
|
578
|
+
if (colon >= 3) {
|
|
579
|
+
if (os) snprintf(val, sizeof val, "%c%02d:%02d:%02d", sign, oh, om, os);
|
|
580
|
+
else if (om) snprintf(val, sizeof val, "%c%02d:%02d", sign, oh, om);
|
|
581
|
+
else snprintf(val, sizeof val, "%c%02d", sign, oh);
|
|
582
|
+
}
|
|
583
|
+
else if (colon == 2) snprintf(val, sizeof val, "%c%02d:%02d:%02d", sign, oh, om, os);
|
|
584
|
+
else snprintf(val, sizeof val, "%c%02d:%02d", sign, oh, om);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else if (d == 'P') { char b2[16]; strftime(b2, sizeof b2, "%p", &tmv); for (char *q = b2; *q; q++) *q = (char)tolower((unsigned char)*q); strcpy(val, b2); }
|
|
588
|
+
/* CRuby names a UTC time "UTC", not the C locale's "GMT"; a fixed-offset
|
|
589
|
+
time has no zone NAME at all, so %Z is empty there (Time#zone is nil). */
|
|
590
|
+
else if (d == 'Z' && t.is_utc) { if (t.is_utc == 1) strcpy(val, "UTC"); else val[0] = 0; }
|
|
591
|
+
/* Ruby's %Y is zero-padded to four digits; C's is not, so a year below
|
|
592
|
+
1000 came out "1" where CRuby writes "0001". Ruby keeps the sign
|
|
593
|
+
outside the padding, so -1 is "-0001". The directives that contain
|
|
594
|
+
the year (%F, %c, %v) take it from the same place, and the century
|
|
595
|
+
and two-digit year round toward minus infinity, as Integer#div and
|
|
596
|
+
#% do: -12 is century -1, year 88. */
|
|
597
|
+
else if (d == 'Y' || d == 'G') {
|
|
598
|
+
long yr = (long)tmv.tm_year + 1900;
|
|
599
|
+
if (d == 'G') {
|
|
600
|
+
/* the ISO 8601 week-based year: C computes it, Ruby formats it */
|
|
601
|
+
char gb[32];
|
|
602
|
+
if (strftime(gb, sizeof gb, "%G", &tmv)) yr = strtol(gb, NULL, 10);
|
|
603
|
+
}
|
|
604
|
+
/* the bare directive pads to four digits; a width, `_` or `-` takes
|
|
605
|
+
the signed number and the padding below sizes it (the sign counts
|
|
606
|
+
toward the width, and zeros go after it) */
|
|
607
|
+
if (width > 0 || padsp || nopad) {
|
|
608
|
+
snprintf(val, sizeof val, "%ld", yr);
|
|
609
|
+
if (width <= 0 && padsp) width = 4 + (yr < 0);
|
|
610
|
+
}
|
|
611
|
+
else sp_time_year_str(val, sizeof val, yr);
|
|
612
|
+
}
|
|
613
|
+
else if (d == 'F') sp_time_year_then(val, sizeof val, &tmv, "-%m-%d");
|
|
614
|
+
else if (d == 'C' || d == 'y' || d == 'x' || d == 'D') {
|
|
615
|
+
long yr = (long)tmv.tm_year + 1900;
|
|
616
|
+
long cen = yr >= 0 ? yr / 100 : -((-yr + 99) / 100);
|
|
617
|
+
long yy = yr - cen * 100;
|
|
618
|
+
if (d == 'C') snprintf(val, sizeof val, "%02ld", cen);
|
|
619
|
+
else if (d == 'y') snprintf(val, sizeof val, "%02ld", yy);
|
|
620
|
+
else snprintf(val, sizeof val, "%02d/%02d/%02ld", tmv.tm_mon + 1, tmv.tm_mday, yy);
|
|
621
|
+
}
|
|
622
|
+
else if (d == 'c') {
|
|
623
|
+
size_t n = strftime(val, sizeof val, "%a %b %e %H:%M:%S ", &tmv);
|
|
624
|
+
if (n) sp_time_year_str(val + n, sizeof val - n, (long)tmv.tm_year + 1900);
|
|
625
|
+
}
|
|
626
|
+
else if (d == 'v') {
|
|
627
|
+
/* "%e-%^b-%4Y": the year four wide, sign included */
|
|
628
|
+
size_t n = strftime(val, sizeof val, "%e-%b-", &tmv);
|
|
629
|
+
for (size_t k = 0; k < n; k++) val[k] = (char)toupper((unsigned char)val[k]);
|
|
630
|
+
if (n) sp_time_year_field(val + n, sizeof val - n, (long)tmv.tm_year + 1900, 4);
|
|
631
|
+
}
|
|
632
|
+
else if (d == 'g') {
|
|
633
|
+
/* the ISO week-based year's last two digits, rounded as %y is */
|
|
634
|
+
char gb[32];
|
|
635
|
+
long gy = strftime(gb, sizeof gb, "%G", &tmv) ? strtol(gb, NULL, 10) : (long)tmv.tm_year + 1900;
|
|
636
|
+
long gc = gy >= 0 ? gy / 100 : -((-gy + 99) / 100);
|
|
637
|
+
snprintf(val, sizeof val, "%02ld", gy - gc * 100);
|
|
638
|
+
}
|
|
639
|
+
else if (strchr("aAbBcCdDeFgGhHIjklmMnprRSTtuUvVwWxXyYzZ", d)) {
|
|
640
|
+
/* a standard Ruby directive: format the bare `%X` (we redo width/case
|
|
641
|
+
ourselves for portability) */
|
|
642
|
+
char f2[3] = { '%', d, 0 };
|
|
643
|
+
strftime(val, sizeof val, f2, &tmv);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
/* not a Ruby Time#strftime directive (e.g. `%+`): emit the token
|
|
647
|
+
verbatim -- CRuby does, and a platform's C strftime must not interpret
|
|
648
|
+
it (macOS treats `%+` as date(1), glibc leaves it literal). */
|
|
649
|
+
size_t tl = (size_t)(p - tok) + 1;
|
|
650
|
+
if (tl < sizeof val) { memcpy(val, tok, tl); val[tl] = 0; }
|
|
651
|
+
width = -1; upcase = downcase = 0;
|
|
652
|
+
}
|
|
653
|
+
if (upcase) for (char *q = val; *q; q++) *q = (char)toupper((unsigned char)*q);
|
|
654
|
+
/* `%#` changes the field's case as a WHOLE: all-uppercase becomes
|
|
655
|
+
lowercase, anything else becomes uppercase. Per-character swapcase
|
|
656
|
+
instead turned "January" into "jANUARY" where CRuby answers
|
|
657
|
+
"JANUARY" -- only fields that are already uppercase (%p, %Z) agreed. */
|
|
658
|
+
if (downcase) {
|
|
659
|
+
int has_lower = 0;
|
|
660
|
+
for (char *q = val; *q; q++) if (islower((unsigned char)*q)) { has_lower = 1; break; }
|
|
661
|
+
for (char *q = val; *q; q++)
|
|
662
|
+
*q = has_lower ? (char)toupper((unsigned char)*q) : (char)tolower((unsigned char)*q);
|
|
663
|
+
}
|
|
664
|
+
/* the `-` (no-pad) and `_` (space-pad) modifiers rework the default zero
|
|
665
|
+
padding that C strftime already applied to a numeric field (#3090) */
|
|
666
|
+
/* the space-padded fields (%e / %k / %l) take `-` (strip) and `0` (zero
|
|
667
|
+
pad) the same way the zero-padded ones take `-` and `_` (#3705) */
|
|
668
|
+
if ((nopad || pad0) && val[0] == ' ') {
|
|
669
|
+
size_t sp0 = 0; while (val[sp0] == ' ' && val[sp0 + 1] != 0) sp0++;
|
|
670
|
+
if (nopad) memmove(val, val + sp0, strlen(val) - sp0 + 1);
|
|
671
|
+
else for (size_t k = 0; k < sp0; k++) val[k] = '0';
|
|
672
|
+
}
|
|
673
|
+
if ((nopad || padsp) && val[0]) {
|
|
674
|
+
int all_digit = 1;
|
|
675
|
+
for (char *q = val; *q; q++) if (!isdigit((unsigned char)*q)) { all_digit = 0; break; }
|
|
676
|
+
if (all_digit) {
|
|
677
|
+
size_t z = 0; while (val[z] == '0' && val[z + 1] != 0) z++; /* keep the last digit */
|
|
678
|
+
if (nopad) memmove(val, val + z, strlen(val) - z + 1);
|
|
679
|
+
else for (size_t k = 0; k < z; k++) val[k] = ' ';
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
size_t vl = strlen(val), v0 = 0;
|
|
683
|
+
if (width > 0 && !nopad && vl < (size_t)width) {
|
|
684
|
+
/* CRuby pads a width with zeros for a number and with spaces for
|
|
685
|
+
text (%A %a %B %b %h %p %P %Z), for the composites (%c %x %X %D %F
|
|
686
|
+
%T %R %r %v), for the blank-padded numbers (%e %k %l) and for %n
|
|
687
|
+
%t %%; a `0` or `_` flag picks the character either way. Every
|
|
688
|
+
directive took zeros, so "%10A" was "000Tuesday". */
|
|
689
|
+
char pc = padsp ? ' ' : pad0 ? '0' : (strchr("AaBbhpPZcxXDFTRrvektlnt%", d) ? ' ' : '0');
|
|
690
|
+
/* zeros go after a sign, as CRuby pads "%10s" of -5 to "-000000005"
|
|
691
|
+
and "%10z" to "+000000000"; spaces go before it */
|
|
692
|
+
if (pc == '0' && (val[0] == '-' || val[0] == '+') && oi < sizeof(out) - 2) { out[oi++] = val[0]; v0 = 1; }
|
|
693
|
+
for (size_t k = vl; k < (size_t)width && oi < sizeof(out) - 2; k++) out[oi++] = pc;
|
|
694
|
+
}
|
|
695
|
+
(void)tok;
|
|
696
|
+
for (size_t k = v0; k < vl && oi < sizeof(out) - 2; k++) out[oi++] = val[k];
|
|
697
|
+
}
|
|
698
|
+
out[oi] = 0;
|
|
699
|
+
return sp_str_dup_external(out);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/* RFC 3339 zone suffix: "Z" for a UTC time, "+HH:MM" otherwise. `off` is
|
|
703
|
+
the receiver's own offset as sp_time_vtm resolved it, so the fixed-offset
|
|
704
|
+
kind carries its utc_off here instead of being mistaken for UTC. Returns
|
|
705
|
+
the number of bytes appended (0 if they would not fit). */
|
|
706
|
+
static size_t sp_time_iso_zone(char *buf, size_t n, size_t cap, sp_Time t, int32_t off) {
|
|
707
|
+
if (t.is_utc == 1) {
|
|
708
|
+
if (n + 1 >= cap) return 0;
|
|
709
|
+
buf[n] = 'Z'; buf[n + 1] = 0;
|
|
710
|
+
return 1;
|
|
711
|
+
}
|
|
712
|
+
if (n + 6 >= cap) return 0;
|
|
713
|
+
char sign = off >= 0 ? '+' : '-';
|
|
714
|
+
long a = off < 0 ? -(long)off : (long)off;
|
|
715
|
+
int oh = (int)(a / 3600), om = (int)((a / 60) % 60);
|
|
716
|
+
buf[n++] = sign;
|
|
717
|
+
buf[n++] = (char)('0' + (oh / 10));
|
|
718
|
+
buf[n++] = (char)('0' + (oh % 10));
|
|
719
|
+
buf[n++] = ':';
|
|
720
|
+
buf[n++] = (char)('0' + (om / 10));
|
|
721
|
+
buf[n++] = (char)('0' + (om % 10));
|
|
722
|
+
buf[n] = 0;
|
|
723
|
+
return 6;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/* Time#httpdate (RFC 1123, always GMT) and Time#rfc2822, whose UTC time
|
|
727
|
+
is written -0000 as CRuby's time.rb does. */
|
|
728
|
+
const char *sp_time_httpdate(sp_Time t) {
|
|
729
|
+
return sp_time_strftime(sp_time_utc(t), "%a, %d %b %Y %H:%M:%S GMT");
|
|
730
|
+
}
|
|
731
|
+
const char *sp_time_rfc2822(sp_Time t) {
|
|
732
|
+
return sp_time_strftime(t, t.is_utc == 1 ? "%a, %d %b %Y %H:%M:%S -0000" : "%a, %d %b %Y %H:%M:%S %z");
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/* RFC 3339 / iso8601. sp_time_vtm resolves the civil fields and the offset
|
|
736
|
+
for all three zone kinds; the suffix is formatted here because MSVCRT's
|
|
737
|
+
%z renders the timezone name rather than ±HHMM. */
|
|
738
|
+
const char *sp_time_iso8601(sp_Time t) {
|
|
739
|
+
char buf[64];
|
|
740
|
+
size_t cap = sizeof(buf);
|
|
741
|
+
struct tm b;
|
|
742
|
+
int32_t off;
|
|
743
|
+
sp_time_vtm(t, &b, &off, NULL);
|
|
744
|
+
size_t n = sp_time_year_then(buf, cap, &b, "-%m-%dT%H:%M:%S");
|
|
745
|
+
if (n == 0) return sp_str_empty;
|
|
746
|
+
sp_time_iso_zone(buf, n, cap, t, off);
|
|
747
|
+
return sp_str_dup_external(buf);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/* iso8601 / xmlschema with a fraction-digits argument: like sp_time_iso8601
|
|
751
|
+
but inserting `digits` truncated fractional-second places (#3094, #3095). */
|
|
752
|
+
const char *sp_time_iso8601_frac(sp_Time t, int64_t digits) {
|
|
753
|
+
if (digits <= 0) return sp_time_iso8601(t);
|
|
754
|
+
if (digits > 50) digits = 50;
|
|
755
|
+
char buf[128];
|
|
756
|
+
size_t cap = sizeof(buf);
|
|
757
|
+
struct tm b;
|
|
758
|
+
int32_t off;
|
|
759
|
+
sp_time_vtm(t, &b, &off, NULL);
|
|
760
|
+
size_t n = sp_time_year_then(buf, cap, &b, "-%m-%dT%H:%M:%S");
|
|
761
|
+
if (n == 0) return sp_str_empty;
|
|
762
|
+
char fb[16]; snprintf(fb, sizeof fb, "%09ld", (long)t.tv_nsec);
|
|
763
|
+
if (n + 1 + (size_t)digits < cap) {
|
|
764
|
+
buf[n++] = '.';
|
|
765
|
+
for (int64_t i = 0; i < digits; i++) buf[n++] = i < 9 ? fb[i] : '0';
|
|
766
|
+
buf[n] = 0;
|
|
767
|
+
}
|
|
768
|
+
sp_time_iso_zone(buf, n, cap, t, off);
|
|
769
|
+
return sp_str_dup_external(buf);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const char *sp_time_zone(sp_Time t) {
|
|
773
|
+
/* a fixed-offset Time has no zone NAME, and CRuby answers nil for it rather
|
|
774
|
+
than the empty string the broken-down form leaves behind (#3701) */
|
|
775
|
+
if (t.is_utc == 2) return NULL;
|
|
776
|
+
char buf[8];
|
|
777
|
+
struct tm b;
|
|
778
|
+
sp_time_vtm(t, &b, NULL, buf);
|
|
779
|
+
return sp_str_dup_external(buf);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/* Scalar Time inspect. CRuby form: local "YYYY-MM-DD HH:MM:SS +0900",
|
|
783
|
+
UTC "YYYY-MM-DD HH:MM:SS UTC". The poly-box path keeps its own
|
|
784
|
+
sp_Time_inspect; this value-taking variant is for the scalar
|
|
785
|
+
p/puts/to_s codegen path. */
|
|
786
|
+
static const char *sp_time_fmt(sp_Time t, int frac) {
|
|
787
|
+
char buf[64];
|
|
788
|
+
size_t cap = sizeof(buf);
|
|
789
|
+
struct tm b;
|
|
790
|
+
int32_t off;
|
|
791
|
+
sp_time_vtm(t, &b, &off, NULL);
|
|
792
|
+
/* the year by hand (sp_time_year_then): Time.utc(1,1,1).to_s is
|
|
793
|
+
"0001-01-01 ..." in CRuby, and C's %Y wrote "1-01-01 ..." */
|
|
794
|
+
size_t n = sp_time_year_then(buf, cap, &b, "-%m-%d %H:%M:%S");
|
|
795
|
+
if (n == 0) {
|
|
796
|
+
snprintf(buf, cap, "Time(%lld)", (long long)t.tv_sec);
|
|
797
|
+
return sp_str_dup_external(buf);
|
|
798
|
+
}
|
|
799
|
+
if (frac && t.tv_nsec != 0 && n + 11 < cap) {
|
|
800
|
+
/* fractional seconds, trailing zeros trimmed (".123456", ".5") */
|
|
801
|
+
n += (size_t)snprintf(buf + n, cap - n, ".%09d", (int)t.tv_nsec);
|
|
802
|
+
while (buf[n - 1] == '0') buf[--n] = 0;
|
|
803
|
+
}
|
|
804
|
+
if (n + 10 < cap) {
|
|
805
|
+
if (t.is_utc == 1) {
|
|
806
|
+
buf[n++]=' '; buf[n++]='U'; buf[n++]='T'; buf[n++]='C'; buf[n]=0;
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
char sign = off >= 0 ? '+' : '-';
|
|
810
|
+
long a = off < 0 ? -(long)off : (long)off;
|
|
811
|
+
int oh = (int)(a / 3600);
|
|
812
|
+
int om = (int)((a / 60) % 60);
|
|
813
|
+
int os = (int)(a % 60);
|
|
814
|
+
buf[n++]=' '; buf[n++]=sign;
|
|
815
|
+
buf[n++]=(char)('0'+oh/10); buf[n++]=(char)('0'+oh%10);
|
|
816
|
+
buf[n++]=(char)('0'+om/10); buf[n++]=(char)('0'+om%10);
|
|
817
|
+
if (os) { /* CRuby renders a sub-minute offset as +HHMMSS */
|
|
818
|
+
buf[n++]=(char)('0'+os/10); buf[n++]=(char)('0'+os%10);
|
|
819
|
+
}
|
|
820
|
+
buf[n]=0;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return sp_str_dup_external(buf);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/* Time#inspect renders fractional seconds; Time#to_s does not. */
|
|
827
|
+
const char *sp_time_inspect_v(sp_Time t) { return sp_time_fmt(t, 1); }
|
|
828
|
+
const char *sp_time_to_s_v(sp_Time t) { return sp_time_fmt(t, 0); }
|
|
829
|
+
|
|
830
|
+
/* ---- comparison + shifts (moved from spinel_rt.h; cold) ---- */
|
|
831
|
+
int sp_time_cmp(sp_Time a, sp_Time b) {
|
|
832
|
+
if (a.tv_sec < b.tv_sec) return -1;
|
|
833
|
+
if (a.tv_sec > b.tv_sec) return 1;
|
|
834
|
+
if (a.tv_nsec < b.tv_nsec) return -1;
|
|
835
|
+
if (a.tv_nsec > b.tv_nsec) return 1;
|
|
836
|
+
return 0;
|
|
837
|
+
}
|
|
838
|
+
sp_Time sp_time_add_f(sp_Time t, double secs) {
|
|
839
|
+
return sp_time_add(t, secs);
|
|
840
|
+
}
|
|
841
|
+
/* Value hash: equal (tv_sec, tv_nsec) pairs must hash equal regardless of
|
|
842
|
+
zone kind, mirroring Time#== which compares the instant only. */
|
|
843
|
+
int64_t sp_time_hash(sp_Time t) {
|
|
844
|
+
uint64_t h = (uint64_t)t.tv_sec * 1000000007ULL + (uint64_t)(uint32_t)t.tv_nsec;
|
|
845
|
+
h ^= h >> 33;
|
|
846
|
+
return (int64_t)(h & 0x3fffffffffffffffULL);
|
|
847
|
+
}
|
|
848
|
+
sp_Time sp_time_add_i(sp_Time t, int64_t secs) {
|
|
849
|
+
sp_Time r = t;
|
|
850
|
+
r.tv_sec = t.tv_sec + (time_t)secs;
|
|
851
|
+
return r;
|
|
852
|
+
}
|
|
853
|
+
sp_Time sp_time_sub_i(sp_Time t, int64_t secs) {
|
|
854
|
+
sp_Time r = t;
|
|
855
|
+
r.tv_sec = t.tv_sec - (time_t)secs;
|
|
856
|
+
return r;
|
|
857
|
+
}
|
|
858
|
+
double sp_time_sub_t(sp_Time a, sp_Time b) {
|
|
859
|
+
return sp_time_ns_to_f(a.tv_sec - b.tv_sec, (int64_t)a.tv_nsec - b.tv_nsec);
|
|
860
|
+
}
|