@blamejs/core 0.7.18 → 0.7.20
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.
- package/CHANGELOG.md +427 -423
- package/README.md +150 -150
- package/bin/blamejs.js +0 -0
- package/index.js +310 -308
- package/lib/api-key.js +660 -660
- package/lib/api-snapshot.js +338 -338
- package/lib/app-shutdown.js +385 -385
- package/lib/app.js +365 -365
- package/lib/archive.js +250 -250
- package/lib/atomic-file.js +544 -544
- package/lib/audit-chain.js +177 -177
- package/lib/audit-sign.js +344 -344
- package/lib/audit-tools.js +677 -677
- package/lib/audit.js +766 -766
- package/lib/auth/jwt-external.js +365 -0
- package/lib/auth/jwt.js +337 -311
- package/lib/auth/lockout.js +436 -436
- package/lib/auth/oauth.js +721 -721
- package/lib/auth/passkey.js +181 -181
- package/lib/auth/password.js +628 -594
- package/lib/backup/bundle.js +217 -217
- package/lib/backup/crypto.js +176 -176
- package/lib/backup/index.js +515 -515
- package/lib/backup/manifest.js +282 -282
- package/lib/break-glass.js +1338 -1338
- package/lib/bundler.js +441 -441
- package/lib/cache-redis.js +256 -256
- package/lib/cache.js +1206 -1206
- package/lib/canonical-json.js +115 -115
- package/lib/chain-writer.js +234 -234
- package/lib/cli-helpers.js +206 -206
- package/lib/cli.js +2334 -2334
- package/lib/cluster-provider-db.js +317 -317
- package/lib/cluster-storage.js +226 -226
- package/lib/cluster.js +703 -703
- package/lib/config-drift.js +301 -301
- package/lib/consent.js +222 -222
- package/lib/constants.js +191 -191
- package/lib/cookies.js +350 -315
- package/lib/credential-hash.js +322 -322
- package/lib/crypto.js +266 -266
- package/lib/csv.js +275 -275
- package/lib/db-declare-row-policy.js +267 -267
- package/lib/db-declare-view.js +420 -420
- package/lib/db-query.js +406 -406
- package/lib/db-schema.js +319 -319
- package/lib/db.js +1288 -1288
- package/lib/deprecate.js +222 -222
- package/lib/dev.js +335 -335
- package/lib/dual-control.js +473 -473
- package/lib/error-page.js +420 -420
- package/lib/external-db-migrate.js +441 -441
- package/lib/external-db.js +1061 -1061
- package/lib/file-type.js +273 -273
- package/lib/forms.js +422 -422
- package/lib/framework-error.js +293 -293
- package/lib/framework-schema.js +717 -717
- package/lib/handlers.js +350 -350
- package/lib/http-client-cookie-jar.js +508 -508
- package/lib/http-client.js +1195 -1195
- package/lib/i18n.js +878 -878
- package/lib/jobs.js +185 -185
- package/lib/log-stream-cloudwatch.js +369 -369
- package/lib/log-stream-local.js +146 -146
- package/lib/log-stream-otlp-grpc.js +410 -410
- package/lib/log-stream-otlp.js +286 -286
- package/lib/log-stream-syslog.js +302 -302
- package/lib/log-stream-webhook.js +199 -199
- package/lib/log-stream.js +330 -330
- package/lib/log.js +500 -500
- package/lib/mail-bounce.js +528 -528
- package/lib/mail-dkim.js +369 -369
- package/lib/mail.js +981 -981
- package/lib/metrics.js +683 -683
- package/lib/middleware/api-encrypt.js +936 -936
- package/lib/middleware/attach-user.js +157 -157
- package/lib/middleware/bearer-auth.js +152 -0
- package/lib/middleware/body-parser.js +1170 -1170
- package/lib/middleware/bot-guard.js +178 -178
- package/lib/middleware/compression.js +452 -452
- package/lib/middleware/cors.js +314 -314
- package/lib/middleware/csp-nonce.js +348 -348
- package/lib/middleware/csrf-protect.js +399 -316
- package/lib/middleware/db-role-for.js +264 -264
- package/lib/middleware/fetch-metadata.js +129 -0
- package/lib/middleware/health.js +392 -392
- package/lib/middleware/index.js +85 -79
- package/lib/middleware/rate-limit.js +358 -358
- package/lib/middleware/request-id.js +61 -61
- package/lib/middleware/request-log.js +168 -168
- package/lib/middleware/require-auth.js +104 -104
- package/lib/middleware/security-headers.js +121 -116
- package/lib/middleware/sse.js +166 -166
- package/lib/migrations.js +383 -383
- package/lib/mtls-ca.js +518 -518
- package/lib/mtls-engine-default.js +481 -481
- package/lib/network-dns.js +632 -632
- package/lib/network-heartbeat.js +290 -290
- package/lib/network-nts.js +574 -574
- package/lib/network-proxy.js +265 -265
- package/lib/network-tls.js +328 -328
- package/lib/network.js +233 -233
- package/lib/notify.js +612 -612
- package/lib/ntp-check.js +229 -229
- package/lib/numeric-bounds.js +111 -111
- package/lib/object-store/azure-blob-bucket-ops.js +349 -349
- package/lib/object-store/azure-blob.js +488 -488
- package/lib/object-store/gcs-bucket-ops.js +351 -351
- package/lib/object-store/gcs.js +519 -519
- package/lib/object-store/http-put.js +153 -153
- package/lib/object-store/index.js +197 -197
- package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
- package/lib/object-store/sigv4.js +903 -903
- package/lib/observability.js +151 -151
- package/lib/otel-export.js +269 -269
- package/lib/pagination.js +464 -464
- package/lib/parsers/index.js +80 -80
- package/lib/parsers/safe-env.js +642 -642
- package/lib/parsers/safe-ini.js +292 -292
- package/lib/parsers/safe-toml.js +784 -784
- package/lib/parsers/safe-xml.js +390 -390
- package/lib/parsers/safe-yaml.js +1015 -1015
- package/lib/permissions.js +708 -708
- package/lib/pqc-agent.js +87 -87
- package/lib/pqc-gate.js +279 -279
- package/lib/protobuf-encoder.js +190 -190
- package/lib/protocol-dispatcher.js +161 -161
- package/lib/pubsub-redis.js +167 -167
- package/lib/pubsub.js +429 -429
- package/lib/queue-local.js +476 -476
- package/lib/queue-redis.js +745 -745
- package/lib/queue-sqs.js +319 -319
- package/lib/queue.js +695 -695
- package/lib/redis-client.js +519 -519
- package/lib/request-helpers.js +340 -340
- package/lib/restore-bundle.js +237 -237
- package/lib/restore-rollback.js +259 -259
- package/lib/restore.js +409 -409
- package/lib/retry.js +376 -376
- package/lib/router.js +748 -748
- package/lib/safe-async.js +735 -735
- package/lib/safe-buffer.js +237 -237
- package/lib/safe-json.js +541 -541
- package/lib/safe-schema.js +1266 -1266
- package/lib/safe-url.js +159 -159
- package/lib/scheduler.js +706 -706
- package/lib/security-assert.js +373 -373
- package/lib/seeders.js +618 -618
- package/lib/session.js +535 -478
- package/lib/slug.js +269 -269
- package/lib/ssrf-guard.js +401 -401
- package/lib/static.js +7 -5
- package/lib/storage.js +471 -471
- package/lib/subject.js +281 -281
- package/lib/template.js +791 -791
- package/lib/testing.js +798 -798
- package/lib/time.js +310 -310
- package/lib/totp.js +302 -302
- package/lib/tracing.js +494 -494
- package/lib/uuid.js +132 -132
- package/lib/validate-opts.js +340 -340
- package/lib/vault/index.js +308 -308
- package/lib/vault/rotate.js +784 -784
- package/lib/vault/wrap.js +296 -296
- package/lib/vendor/noble-ciphers.cjs +9 -9
- package/lib/webhook.js +595 -595
- package/lib/websocket.js +1048 -1048
- package/package.json +77 -77
- package/sbom.cyclonedx.json +7 -7
package/lib/time.js
CHANGED
|
@@ -1,310 +1,310 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* time — timezone-aware datetime arithmetic + formatting on top of
|
|
4
|
-
* native `Intl.DateTimeFormat`. No TZ-database vendor; operators get
|
|
5
|
-
* the IANA names Node's ICU build supports (full set on every
|
|
6
|
-
* mainstream platform).
|
|
7
|
-
*
|
|
8
|
-
* b.time.toParts(d, { timezone: "America/New_York" })
|
|
9
|
-
* → { year, month, day, hour, minute, second, millisecond,
|
|
10
|
-
* weekday: 1..7, weekdayName: "Mon"..."Sun", dayOfYear }
|
|
11
|
-
*
|
|
12
|
-
* b.time.format(d, { timezone, locale, dateStyle, timeStyle })
|
|
13
|
-
* → operator-readable string
|
|
14
|
-
*
|
|
15
|
-
* b.time.startOfDay(d, { timezone }) → midnight in TZ
|
|
16
|
-
* b.time.endOfDay(d, { timezone }) → 23:59:59.999 in TZ
|
|
17
|
-
* b.time.addDays(d, n, { timezone }) → calendar-day add (DST-safe)
|
|
18
|
-
* b.time.addMonths(d, n, { timezone }) → calendar-month add
|
|
19
|
-
* b.time.diffDays(a, b, { timezone }) → calendar days between
|
|
20
|
-
*
|
|
21
|
-
* b.time.parseISO(s) → Date | throws TimeError
|
|
22
|
-
* b.time.tzOffsetMs(d, timezone) → ms offset (= local - utc)
|
|
23
|
-
*
|
|
24
|
-
* All ops accept Date, ms-epoch number, or ISO 8601 string. `timezone`
|
|
25
|
-
* defaults to UTC. `locale` defaults to "en-US".
|
|
26
|
-
*/
|
|
27
|
-
var C = require("./constants");
|
|
28
|
-
var { defineClass } = require("./framework-error");
|
|
29
|
-
|
|
30
|
-
var TimeError = defineClass("TimeError", { alwaysPermanent: true });
|
|
31
|
-
|
|
32
|
-
var DEFAULT_TIMEZONE = "UTC";
|
|
33
|
-
var DEFAULT_LOCALE = "en-US";
|
|
34
|
-
|
|
35
|
-
var _dtfCache = new Map();
|
|
36
|
-
function _dtf(opts) {
|
|
37
|
-
var key = JSON.stringify(opts);
|
|
38
|
-
if (_dtfCache.has(key)) return _dtfCache.get(key);
|
|
39
|
-
var dtf;
|
|
40
|
-
try { dtf = new Intl.DateTimeFormat(opts.locale || DEFAULT_LOCALE, opts); }
|
|
41
|
-
catch (e) {
|
|
42
|
-
throw new TimeError("time/bad-timezone-or-locale",
|
|
43
|
-
"Intl rejected the timezone/locale: " + ((e && e.message) || String(e)));
|
|
44
|
-
}
|
|
45
|
-
_dtfCache.set(key, dtf);
|
|
46
|
-
return dtf;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function _toDate(v) {
|
|
50
|
-
if (v instanceof Date) {
|
|
51
|
-
if (isNaN(v.getTime())) {
|
|
52
|
-
throw new TimeError("time/invalid-date", "input Date is invalid (NaN)");
|
|
53
|
-
}
|
|
54
|
-
return v;
|
|
55
|
-
}
|
|
56
|
-
if (typeof v === "number") {
|
|
57
|
-
if (!isFinite(v)) {
|
|
58
|
-
throw new TimeError("time/invalid-ms", "input must be a finite number of milliseconds");
|
|
59
|
-
}
|
|
60
|
-
return new Date(v);
|
|
61
|
-
}
|
|
62
|
-
if (typeof v === "string") return parseISO(v);
|
|
63
|
-
throw new TimeError("time/bad-input",
|
|
64
|
-
"expected Date | number | ISO string, got " + typeof v);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
var WEEKDAY_TO_NUM = {
|
|
68
|
-
"Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6, "Sun": 7,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
function toParts(input, opts) {
|
|
72
|
-
opts = opts || {};
|
|
73
|
-
var date = _toDate(input);
|
|
74
|
-
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
75
|
-
var dtf = _dtf({
|
|
76
|
-
timeZone: tz,
|
|
77
|
-
year: "numeric", month: "2-digit", day: "2-digit",
|
|
78
|
-
hour: "2-digit", minute: "2-digit", second: "2-digit",
|
|
79
|
-
weekday: "short",
|
|
80
|
-
hour12: false,
|
|
81
|
-
});
|
|
82
|
-
var parts = dtf.formatToParts(date);
|
|
83
|
-
var out = { millisecond: date.getUTCMilliseconds() };
|
|
84
|
-
for (var i = 0; i < parts.length; i++) {
|
|
85
|
-
var p = parts[i];
|
|
86
|
-
if (p.type === "year") out.year = parseInt(p.value, 10);
|
|
87
|
-
if (p.type === "month") out.month = parseInt(p.value, 10);
|
|
88
|
-
if (p.type === "day") out.day = parseInt(p.value, 10);
|
|
89
|
-
if (p.type === "hour") out.hour = (p.value === "24" ? 0 : parseInt(p.value, 10));
|
|
90
|
-
if (p.type === "minute") out.minute = parseInt(p.value, 10);
|
|
91
|
-
if (p.type === "second") out.second = parseInt(p.value, 10);
|
|
92
|
-
if (p.type === "weekday") {
|
|
93
|
-
out.weekdayName = p.value;
|
|
94
|
-
out.weekday = WEEKDAY_TO_NUM[p.value] || null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// dayOfYear: computed from out.year + out.month + out.day directly,
|
|
98
|
-
// no recursion through toParts. Days-in-month table for non-leap;
|
|
99
|
-
// Feb gets +1 in leap years (Gregorian rule: divisible by 4, not 100,
|
|
100
|
-
// unless 400).
|
|
101
|
-
// Cumulative days-before-each-month-start in a non-leap Gregorian year.
|
|
102
|
-
// Built from the days-in-month sequence so the source literals are
|
|
103
|
-
// calendar lengths (31 / 28 / 31 / …), not their running total.
|
|
104
|
-
var DAYS_IN_MONTH_NONLEAP = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
105
|
-
var DAYS_BEFORE_MONTH = [0];
|
|
106
|
-
for (var _dm = 0; _dm < DAYS_IN_MONTH_NONLEAP.length - 1; _dm++) {
|
|
107
|
-
DAYS_BEFORE_MONTH.push(DAYS_BEFORE_MONTH[_dm] + DAYS_IN_MONTH_NONLEAP[_dm]);
|
|
108
|
-
}
|
|
109
|
-
var leap = (out.year % 4 === 0 && out.year % 100 !== 0) || (out.year % 400 === 0);
|
|
110
|
-
out.dayOfYear = DAYS_BEFORE_MONTH[out.month - 1] + out.day + (leap && out.month > 2 ? 1 : 0);
|
|
111
|
-
return out;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function format(input, opts) {
|
|
115
|
-
opts = opts || {};
|
|
116
|
-
var date = _toDate(input);
|
|
117
|
-
var fmtOpts = {
|
|
118
|
-
timeZone: opts.timezone || DEFAULT_TIMEZONE,
|
|
119
|
-
locale: opts.locale || DEFAULT_LOCALE,
|
|
120
|
-
};
|
|
121
|
-
if (opts.dateStyle) fmtOpts.dateStyle = opts.dateStyle;
|
|
122
|
-
if (opts.timeStyle) fmtOpts.timeStyle = opts.timeStyle;
|
|
123
|
-
var passthroughKeys = [
|
|
124
|
-
"year", "month", "day", "hour", "minute", "second",
|
|
125
|
-
"weekday", "era", "hour12", "fractionalSecondDigits",
|
|
126
|
-
"timeZoneName",
|
|
127
|
-
];
|
|
128
|
-
for (var i = 0; i < passthroughKeys.length; i++) {
|
|
129
|
-
var k = passthroughKeys[i];
|
|
130
|
-
if (opts[k] !== undefined) fmtOpts[k] = opts[k];
|
|
131
|
-
}
|
|
132
|
-
if (!opts.dateStyle && !opts.timeStyle && !passthroughKeys.some(function (k) { return opts[k] !== undefined; })) {
|
|
133
|
-
fmtOpts.dateStyle = "medium";
|
|
134
|
-
fmtOpts.timeStyle = "short";
|
|
135
|
-
}
|
|
136
|
-
return _dtf(fmtOpts).format(date);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function tzOffsetMs(input, timezone) {
|
|
140
|
-
var date = _toDate(input);
|
|
141
|
-
if (!timezone || typeof timezone !== "string") {
|
|
142
|
-
throw new TimeError("time/bad-timezone",
|
|
143
|
-
"tzOffsetMs: timezone must be a non-empty IANA name");
|
|
144
|
-
}
|
|
145
|
-
var dtf = _dtf({
|
|
146
|
-
timeZone: timezone,
|
|
147
|
-
year: "numeric", month: "2-digit", day: "2-digit",
|
|
148
|
-
hour: "2-digit", minute: "2-digit", second: "2-digit",
|
|
149
|
-
hour12: false,
|
|
150
|
-
});
|
|
151
|
-
var parts = {};
|
|
152
|
-
dtf.formatToParts(date).forEach(function (p) { parts[p.type] = p.value; });
|
|
153
|
-
var hour = parts.hour === "24" ? "00" : parts.hour;
|
|
154
|
-
var asUtcMs = Date.UTC(
|
|
155
|
-
parseInt(parts.year, 10),
|
|
156
|
-
parseInt(parts.month, 10) - 1,
|
|
157
|
-
parseInt(parts.day, 10),
|
|
158
|
-
parseInt(hour, 10),
|
|
159
|
-
parseInt(parts.minute, 10),
|
|
160
|
-
parseInt(parts.second, 10)
|
|
161
|
-
);
|
|
162
|
-
var instantSec = C.TIME.seconds(Math.floor(date.getTime() / 1000));
|
|
163
|
-
return asUtcMs - instantSec;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function _fromPartsAtTz(p, timezone) {
|
|
167
|
-
var candidate = Date.UTC(
|
|
168
|
-
p.year,
|
|
169
|
-
(p.month - 1),
|
|
170
|
-
p.day,
|
|
171
|
-
p.hour || 0,
|
|
172
|
-
p.minute || 0,
|
|
173
|
-
p.second || 0,
|
|
174
|
-
p.millisecond || 0
|
|
175
|
-
);
|
|
176
|
-
var offset1 = tzOffsetMs(candidate, timezone);
|
|
177
|
-
var step1 = candidate - offset1;
|
|
178
|
-
var offset2 = tzOffsetMs(step1, timezone);
|
|
179
|
-
return new Date(step1 - (offset2 - offset1));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function startOfDay(input, opts) {
|
|
183
|
-
opts = opts || {};
|
|
184
|
-
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
185
|
-
var p = toParts(input, { timezone: tz });
|
|
186
|
-
return _fromPartsAtTz({
|
|
187
|
-
year: p.year, month: p.month, day: p.day,
|
|
188
|
-
hour: 0, minute: 0, second: 0, millisecond: 0,
|
|
189
|
-
}, tz);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function endOfDay(input, opts) {
|
|
193
|
-
opts = opts || {};
|
|
194
|
-
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
195
|
-
var p = toParts(input, { timezone: tz });
|
|
196
|
-
return _fromPartsAtTz({
|
|
197
|
-
year: p.year, month: p.month, day: p.day,
|
|
198
|
-
hour: 23, minute: 59, second: 59, millisecond: 999,
|
|
199
|
-
}, tz);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function addDays(input, n, opts) {
|
|
203
|
-
opts = opts || {};
|
|
204
|
-
if (typeof n !== "number" || !isFinite(n)) {
|
|
205
|
-
throw new TimeError("time/bad-arg", "addDays: n must be a finite number");
|
|
206
|
-
}
|
|
207
|
-
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
208
|
-
var p = toParts(input, { timezone: tz });
|
|
209
|
-
var asUtc = new Date(Date.UTC(p.year, p.month - 1, p.day + Math.trunc(n),
|
|
210
|
-
p.hour, p.minute, p.second, p.millisecond));
|
|
211
|
-
return _fromPartsAtTz({
|
|
212
|
-
year: asUtc.getUTCFullYear(),
|
|
213
|
-
month: asUtc.getUTCMonth() + 1,
|
|
214
|
-
day: asUtc.getUTCDate(),
|
|
215
|
-
hour: p.hour, minute: p.minute, second: p.second, millisecond: p.millisecond,
|
|
216
|
-
}, tz);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function addMonths(input, n, opts) {
|
|
220
|
-
opts = opts || {};
|
|
221
|
-
if (typeof n !== "number" || !isFinite(n)) {
|
|
222
|
-
throw new TimeError("time/bad-arg", "addMonths: n must be a finite number");
|
|
223
|
-
}
|
|
224
|
-
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
225
|
-
var p = toParts(input, { timezone: tz });
|
|
226
|
-
var newMonth0 = (p.month - 1) + Math.trunc(n);
|
|
227
|
-
var newYear = p.year + Math.floor(newMonth0 / 12);
|
|
228
|
-
newMonth0 = ((newMonth0 % 12) + 12) % 12;
|
|
229
|
-
var daysInNew = new Date(Date.UTC(newYear, newMonth0 + 1, 0)).getUTCDate();
|
|
230
|
-
var newDay = Math.min(p.day, daysInNew);
|
|
231
|
-
return _fromPartsAtTz({
|
|
232
|
-
year: newYear, month: newMonth0 + 1, day: newDay,
|
|
233
|
-
hour: p.hour, minute: p.minute, second: p.second, millisecond: p.millisecond,
|
|
234
|
-
}, tz);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function diffDays(a, b, opts) {
|
|
238
|
-
opts = opts || {};
|
|
239
|
-
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
240
|
-
var aMid = startOfDay(a, { timezone: tz });
|
|
241
|
-
var bMid = startOfDay(b, { timezone: tz });
|
|
242
|
-
return Math.round((bMid.getTime() - aMid.getTime()) / C.TIME.days(1));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
var ISO_RE = /^(\d{4})-(\d{2})-(\d{2})(?:[T\s](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
246
|
-
|
|
247
|
-
function parseISO(s) {
|
|
248
|
-
if (typeof s !== "string" || s.length === 0) {
|
|
249
|
-
throw new TimeError("time/bad-iso", "parseISO: input must be a non-empty string");
|
|
250
|
-
}
|
|
251
|
-
var m = ISO_RE.exec(s);
|
|
252
|
-
if (!m) {
|
|
253
|
-
throw new TimeError("time/bad-iso",
|
|
254
|
-
"parseISO: not an ISO 8601 datetime: " + JSON.stringify(s));
|
|
255
|
-
}
|
|
256
|
-
var year = parseInt(m[1], 10);
|
|
257
|
-
var month = parseInt(m[2], 10);
|
|
258
|
-
var day = parseInt(m[3], 10);
|
|
259
|
-
var hour = m[4] ? parseInt(m[4], 10) : 0;
|
|
260
|
-
var minute = m[5] ? parseInt(m[5], 10) : 0;
|
|
261
|
-
var second = m[6] ? parseInt(m[6], 10) : 0;
|
|
262
|
-
var msStr = m[7] || "";
|
|
263
|
-
var ms = msStr ? parseInt((msStr + "000").slice(0, 3), 10) : 0;
|
|
264
|
-
var tz = m[0x8];
|
|
265
|
-
|
|
266
|
-
if (month < 1 || month > 12 || day < 1 || day > 31 ||
|
|
267
|
-
hour > 23 || minute > 59 || second > 59) {
|
|
268
|
-
throw new TimeError("time/bad-iso",
|
|
269
|
-
"parseISO: out-of-range component in " + JSON.stringify(s));
|
|
270
|
-
}
|
|
271
|
-
var utcMs;
|
|
272
|
-
if (!tz) {
|
|
273
|
-
utcMs = Date.UTC(year, month - 1, day, hour, minute, second, ms);
|
|
274
|
-
} else if (tz === "Z") {
|
|
275
|
-
utcMs = Date.UTC(year, month - 1, day, hour, minute, second, ms);
|
|
276
|
-
} else {
|
|
277
|
-
var sign = tz.charAt(0) === "-" ? -1 : 1;
|
|
278
|
-
var hh = parseInt(tz.slice(1, 3), 10);
|
|
279
|
-
var mm = parseInt(tz.slice(tz.length - 2), 10);
|
|
280
|
-
var offsetMs = sign * C.TIME.seconds(hh * 3600 + mm * 60);
|
|
281
|
-
utcMs = Date.UTC(year, month - 1, day, hour, minute, second, ms) - offsetMs;
|
|
282
|
-
}
|
|
283
|
-
return new Date(utcMs);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// toIso8601NoMs — emit ISO 8601 with the trailing `.\d{3}Z`
|
|
287
|
-
// milliseconds dropped (`2026-05-03T12:34:56Z` instead of
|
|
288
|
-
// `…:56.789Z`). Used by SAS / SigV4 / log filename builders that need
|
|
289
|
-
// a one-second-resolution timestamp string. Single source of truth so
|
|
290
|
-
// the strip pattern lives in one place.
|
|
291
|
-
var ISO_MS_RE = /\.\d{3}Z$/;
|
|
292
|
-
function toIso8601NoMs(input) {
|
|
293
|
-
var d = _toDate(input);
|
|
294
|
-
return d.toISOString().replace(ISO_MS_RE, "Z");
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
module.exports = {
|
|
298
|
-
toParts: toParts,
|
|
299
|
-
format: format,
|
|
300
|
-
tzOffsetMs: tzOffsetMs,
|
|
301
|
-
startOfDay: startOfDay,
|
|
302
|
-
endOfDay: endOfDay,
|
|
303
|
-
addDays: addDays,
|
|
304
|
-
addMonths: addMonths,
|
|
305
|
-
diffDays: diffDays,
|
|
306
|
-
parseISO: parseISO,
|
|
307
|
-
toIso8601NoMs: toIso8601NoMs,
|
|
308
|
-
ISO_MS_RE: ISO_MS_RE,
|
|
309
|
-
TimeError: TimeError,
|
|
310
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* time — timezone-aware datetime arithmetic + formatting on top of
|
|
4
|
+
* native `Intl.DateTimeFormat`. No TZ-database vendor; operators get
|
|
5
|
+
* the IANA names Node's ICU build supports (full set on every
|
|
6
|
+
* mainstream platform).
|
|
7
|
+
*
|
|
8
|
+
* b.time.toParts(d, { timezone: "America/New_York" })
|
|
9
|
+
* → { year, month, day, hour, minute, second, millisecond,
|
|
10
|
+
* weekday: 1..7, weekdayName: "Mon"..."Sun", dayOfYear }
|
|
11
|
+
*
|
|
12
|
+
* b.time.format(d, { timezone, locale, dateStyle, timeStyle })
|
|
13
|
+
* → operator-readable string
|
|
14
|
+
*
|
|
15
|
+
* b.time.startOfDay(d, { timezone }) → midnight in TZ
|
|
16
|
+
* b.time.endOfDay(d, { timezone }) → 23:59:59.999 in TZ
|
|
17
|
+
* b.time.addDays(d, n, { timezone }) → calendar-day add (DST-safe)
|
|
18
|
+
* b.time.addMonths(d, n, { timezone }) → calendar-month add
|
|
19
|
+
* b.time.diffDays(a, b, { timezone }) → calendar days between
|
|
20
|
+
*
|
|
21
|
+
* b.time.parseISO(s) → Date | throws TimeError
|
|
22
|
+
* b.time.tzOffsetMs(d, timezone) → ms offset (= local - utc)
|
|
23
|
+
*
|
|
24
|
+
* All ops accept Date, ms-epoch number, or ISO 8601 string. `timezone`
|
|
25
|
+
* defaults to UTC. `locale` defaults to "en-US".
|
|
26
|
+
*/
|
|
27
|
+
var C = require("./constants");
|
|
28
|
+
var { defineClass } = require("./framework-error");
|
|
29
|
+
|
|
30
|
+
var TimeError = defineClass("TimeError", { alwaysPermanent: true });
|
|
31
|
+
|
|
32
|
+
var DEFAULT_TIMEZONE = "UTC";
|
|
33
|
+
var DEFAULT_LOCALE = "en-US";
|
|
34
|
+
|
|
35
|
+
var _dtfCache = new Map();
|
|
36
|
+
function _dtf(opts) {
|
|
37
|
+
var key = JSON.stringify(opts);
|
|
38
|
+
if (_dtfCache.has(key)) return _dtfCache.get(key);
|
|
39
|
+
var dtf;
|
|
40
|
+
try { dtf = new Intl.DateTimeFormat(opts.locale || DEFAULT_LOCALE, opts); }
|
|
41
|
+
catch (e) {
|
|
42
|
+
throw new TimeError("time/bad-timezone-or-locale",
|
|
43
|
+
"Intl rejected the timezone/locale: " + ((e && e.message) || String(e)));
|
|
44
|
+
}
|
|
45
|
+
_dtfCache.set(key, dtf);
|
|
46
|
+
return dtf;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function _toDate(v) {
|
|
50
|
+
if (v instanceof Date) {
|
|
51
|
+
if (isNaN(v.getTime())) {
|
|
52
|
+
throw new TimeError("time/invalid-date", "input Date is invalid (NaN)");
|
|
53
|
+
}
|
|
54
|
+
return v;
|
|
55
|
+
}
|
|
56
|
+
if (typeof v === "number") {
|
|
57
|
+
if (!isFinite(v)) {
|
|
58
|
+
throw new TimeError("time/invalid-ms", "input must be a finite number of milliseconds");
|
|
59
|
+
}
|
|
60
|
+
return new Date(v);
|
|
61
|
+
}
|
|
62
|
+
if (typeof v === "string") return parseISO(v);
|
|
63
|
+
throw new TimeError("time/bad-input",
|
|
64
|
+
"expected Date | number | ISO string, got " + typeof v);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var WEEKDAY_TO_NUM = {
|
|
68
|
+
"Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6, "Sun": 7,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function toParts(input, opts) {
|
|
72
|
+
opts = opts || {};
|
|
73
|
+
var date = _toDate(input);
|
|
74
|
+
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
75
|
+
var dtf = _dtf({
|
|
76
|
+
timeZone: tz,
|
|
77
|
+
year: "numeric", month: "2-digit", day: "2-digit",
|
|
78
|
+
hour: "2-digit", minute: "2-digit", second: "2-digit",
|
|
79
|
+
weekday: "short",
|
|
80
|
+
hour12: false,
|
|
81
|
+
});
|
|
82
|
+
var parts = dtf.formatToParts(date);
|
|
83
|
+
var out = { millisecond: date.getUTCMilliseconds() };
|
|
84
|
+
for (var i = 0; i < parts.length; i++) {
|
|
85
|
+
var p = parts[i];
|
|
86
|
+
if (p.type === "year") out.year = parseInt(p.value, 10);
|
|
87
|
+
if (p.type === "month") out.month = parseInt(p.value, 10);
|
|
88
|
+
if (p.type === "day") out.day = parseInt(p.value, 10);
|
|
89
|
+
if (p.type === "hour") out.hour = (p.value === "24" ? 0 : parseInt(p.value, 10));
|
|
90
|
+
if (p.type === "minute") out.minute = parseInt(p.value, 10);
|
|
91
|
+
if (p.type === "second") out.second = parseInt(p.value, 10);
|
|
92
|
+
if (p.type === "weekday") {
|
|
93
|
+
out.weekdayName = p.value;
|
|
94
|
+
out.weekday = WEEKDAY_TO_NUM[p.value] || null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// dayOfYear: computed from out.year + out.month + out.day directly,
|
|
98
|
+
// no recursion through toParts. Days-in-month table for non-leap;
|
|
99
|
+
// Feb gets +1 in leap years (Gregorian rule: divisible by 4, not 100,
|
|
100
|
+
// unless 400).
|
|
101
|
+
// Cumulative days-before-each-month-start in a non-leap Gregorian year.
|
|
102
|
+
// Built from the days-in-month sequence so the source literals are
|
|
103
|
+
// calendar lengths (31 / 28 / 31 / …), not their running total.
|
|
104
|
+
var DAYS_IN_MONTH_NONLEAP = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
105
|
+
var DAYS_BEFORE_MONTH = [0];
|
|
106
|
+
for (var _dm = 0; _dm < DAYS_IN_MONTH_NONLEAP.length - 1; _dm++) {
|
|
107
|
+
DAYS_BEFORE_MONTH.push(DAYS_BEFORE_MONTH[_dm] + DAYS_IN_MONTH_NONLEAP[_dm]);
|
|
108
|
+
}
|
|
109
|
+
var leap = (out.year % 4 === 0 && out.year % 100 !== 0) || (out.year % 400 === 0);
|
|
110
|
+
out.dayOfYear = DAYS_BEFORE_MONTH[out.month - 1] + out.day + (leap && out.month > 2 ? 1 : 0);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function format(input, opts) {
|
|
115
|
+
opts = opts || {};
|
|
116
|
+
var date = _toDate(input);
|
|
117
|
+
var fmtOpts = {
|
|
118
|
+
timeZone: opts.timezone || DEFAULT_TIMEZONE,
|
|
119
|
+
locale: opts.locale || DEFAULT_LOCALE,
|
|
120
|
+
};
|
|
121
|
+
if (opts.dateStyle) fmtOpts.dateStyle = opts.dateStyle;
|
|
122
|
+
if (opts.timeStyle) fmtOpts.timeStyle = opts.timeStyle;
|
|
123
|
+
var passthroughKeys = [
|
|
124
|
+
"year", "month", "day", "hour", "minute", "second",
|
|
125
|
+
"weekday", "era", "hour12", "fractionalSecondDigits",
|
|
126
|
+
"timeZoneName",
|
|
127
|
+
];
|
|
128
|
+
for (var i = 0; i < passthroughKeys.length; i++) {
|
|
129
|
+
var k = passthroughKeys[i];
|
|
130
|
+
if (opts[k] !== undefined) fmtOpts[k] = opts[k];
|
|
131
|
+
}
|
|
132
|
+
if (!opts.dateStyle && !opts.timeStyle && !passthroughKeys.some(function (k) { return opts[k] !== undefined; })) {
|
|
133
|
+
fmtOpts.dateStyle = "medium";
|
|
134
|
+
fmtOpts.timeStyle = "short";
|
|
135
|
+
}
|
|
136
|
+
return _dtf(fmtOpts).format(date);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function tzOffsetMs(input, timezone) {
|
|
140
|
+
var date = _toDate(input);
|
|
141
|
+
if (!timezone || typeof timezone !== "string") {
|
|
142
|
+
throw new TimeError("time/bad-timezone",
|
|
143
|
+
"tzOffsetMs: timezone must be a non-empty IANA name");
|
|
144
|
+
}
|
|
145
|
+
var dtf = _dtf({
|
|
146
|
+
timeZone: timezone,
|
|
147
|
+
year: "numeric", month: "2-digit", day: "2-digit",
|
|
148
|
+
hour: "2-digit", minute: "2-digit", second: "2-digit",
|
|
149
|
+
hour12: false,
|
|
150
|
+
});
|
|
151
|
+
var parts = {};
|
|
152
|
+
dtf.formatToParts(date).forEach(function (p) { parts[p.type] = p.value; });
|
|
153
|
+
var hour = parts.hour === "24" ? "00" : parts.hour;
|
|
154
|
+
var asUtcMs = Date.UTC(
|
|
155
|
+
parseInt(parts.year, 10),
|
|
156
|
+
parseInt(parts.month, 10) - 1,
|
|
157
|
+
parseInt(parts.day, 10),
|
|
158
|
+
parseInt(hour, 10),
|
|
159
|
+
parseInt(parts.minute, 10),
|
|
160
|
+
parseInt(parts.second, 10)
|
|
161
|
+
);
|
|
162
|
+
var instantSec = C.TIME.seconds(Math.floor(date.getTime() / 1000));
|
|
163
|
+
return asUtcMs - instantSec;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function _fromPartsAtTz(p, timezone) {
|
|
167
|
+
var candidate = Date.UTC(
|
|
168
|
+
p.year,
|
|
169
|
+
(p.month - 1),
|
|
170
|
+
p.day,
|
|
171
|
+
p.hour || 0,
|
|
172
|
+
p.minute || 0,
|
|
173
|
+
p.second || 0,
|
|
174
|
+
p.millisecond || 0
|
|
175
|
+
);
|
|
176
|
+
var offset1 = tzOffsetMs(candidate, timezone);
|
|
177
|
+
var step1 = candidate - offset1;
|
|
178
|
+
var offset2 = tzOffsetMs(step1, timezone);
|
|
179
|
+
return new Date(step1 - (offset2 - offset1));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function startOfDay(input, opts) {
|
|
183
|
+
opts = opts || {};
|
|
184
|
+
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
185
|
+
var p = toParts(input, { timezone: tz });
|
|
186
|
+
return _fromPartsAtTz({
|
|
187
|
+
year: p.year, month: p.month, day: p.day,
|
|
188
|
+
hour: 0, minute: 0, second: 0, millisecond: 0,
|
|
189
|
+
}, tz);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function endOfDay(input, opts) {
|
|
193
|
+
opts = opts || {};
|
|
194
|
+
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
195
|
+
var p = toParts(input, { timezone: tz });
|
|
196
|
+
return _fromPartsAtTz({
|
|
197
|
+
year: p.year, month: p.month, day: p.day,
|
|
198
|
+
hour: 23, minute: 59, second: 59, millisecond: 999,
|
|
199
|
+
}, tz);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function addDays(input, n, opts) {
|
|
203
|
+
opts = opts || {};
|
|
204
|
+
if (typeof n !== "number" || !isFinite(n)) {
|
|
205
|
+
throw new TimeError("time/bad-arg", "addDays: n must be a finite number");
|
|
206
|
+
}
|
|
207
|
+
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
208
|
+
var p = toParts(input, { timezone: tz });
|
|
209
|
+
var asUtc = new Date(Date.UTC(p.year, p.month - 1, p.day + Math.trunc(n),
|
|
210
|
+
p.hour, p.minute, p.second, p.millisecond));
|
|
211
|
+
return _fromPartsAtTz({
|
|
212
|
+
year: asUtc.getUTCFullYear(),
|
|
213
|
+
month: asUtc.getUTCMonth() + 1,
|
|
214
|
+
day: asUtc.getUTCDate(),
|
|
215
|
+
hour: p.hour, minute: p.minute, second: p.second, millisecond: p.millisecond,
|
|
216
|
+
}, tz);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function addMonths(input, n, opts) {
|
|
220
|
+
opts = opts || {};
|
|
221
|
+
if (typeof n !== "number" || !isFinite(n)) {
|
|
222
|
+
throw new TimeError("time/bad-arg", "addMonths: n must be a finite number");
|
|
223
|
+
}
|
|
224
|
+
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
225
|
+
var p = toParts(input, { timezone: tz });
|
|
226
|
+
var newMonth0 = (p.month - 1) + Math.trunc(n);
|
|
227
|
+
var newYear = p.year + Math.floor(newMonth0 / 12);
|
|
228
|
+
newMonth0 = ((newMonth0 % 12) + 12) % 12;
|
|
229
|
+
var daysInNew = new Date(Date.UTC(newYear, newMonth0 + 1, 0)).getUTCDate();
|
|
230
|
+
var newDay = Math.min(p.day, daysInNew);
|
|
231
|
+
return _fromPartsAtTz({
|
|
232
|
+
year: newYear, month: newMonth0 + 1, day: newDay,
|
|
233
|
+
hour: p.hour, minute: p.minute, second: p.second, millisecond: p.millisecond,
|
|
234
|
+
}, tz);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function diffDays(a, b, opts) {
|
|
238
|
+
opts = opts || {};
|
|
239
|
+
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
240
|
+
var aMid = startOfDay(a, { timezone: tz });
|
|
241
|
+
var bMid = startOfDay(b, { timezone: tz });
|
|
242
|
+
return Math.round((bMid.getTime() - aMid.getTime()) / C.TIME.days(1));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
var ISO_RE = /^(\d{4})-(\d{2})-(\d{2})(?:[T\s](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
246
|
+
|
|
247
|
+
function parseISO(s) {
|
|
248
|
+
if (typeof s !== "string" || s.length === 0) {
|
|
249
|
+
throw new TimeError("time/bad-iso", "parseISO: input must be a non-empty string");
|
|
250
|
+
}
|
|
251
|
+
var m = ISO_RE.exec(s);
|
|
252
|
+
if (!m) {
|
|
253
|
+
throw new TimeError("time/bad-iso",
|
|
254
|
+
"parseISO: not an ISO 8601 datetime: " + JSON.stringify(s));
|
|
255
|
+
}
|
|
256
|
+
var year = parseInt(m[1], 10);
|
|
257
|
+
var month = parseInt(m[2], 10);
|
|
258
|
+
var day = parseInt(m[3], 10);
|
|
259
|
+
var hour = m[4] ? parseInt(m[4], 10) : 0;
|
|
260
|
+
var minute = m[5] ? parseInt(m[5], 10) : 0;
|
|
261
|
+
var second = m[6] ? parseInt(m[6], 10) : 0;
|
|
262
|
+
var msStr = m[7] || "";
|
|
263
|
+
var ms = msStr ? parseInt((msStr + "000").slice(0, 3), 10) : 0;
|
|
264
|
+
var tz = m[0x8];
|
|
265
|
+
|
|
266
|
+
if (month < 1 || month > 12 || day < 1 || day > 31 ||
|
|
267
|
+
hour > 23 || minute > 59 || second > 59) {
|
|
268
|
+
throw new TimeError("time/bad-iso",
|
|
269
|
+
"parseISO: out-of-range component in " + JSON.stringify(s));
|
|
270
|
+
}
|
|
271
|
+
var utcMs;
|
|
272
|
+
if (!tz) {
|
|
273
|
+
utcMs = Date.UTC(year, month - 1, day, hour, minute, second, ms);
|
|
274
|
+
} else if (tz === "Z") {
|
|
275
|
+
utcMs = Date.UTC(year, month - 1, day, hour, minute, second, ms);
|
|
276
|
+
} else {
|
|
277
|
+
var sign = tz.charAt(0) === "-" ? -1 : 1;
|
|
278
|
+
var hh = parseInt(tz.slice(1, 3), 10);
|
|
279
|
+
var mm = parseInt(tz.slice(tz.length - 2), 10);
|
|
280
|
+
var offsetMs = sign * C.TIME.seconds(hh * 3600 + mm * 60);
|
|
281
|
+
utcMs = Date.UTC(year, month - 1, day, hour, minute, second, ms) - offsetMs;
|
|
282
|
+
}
|
|
283
|
+
return new Date(utcMs);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// toIso8601NoMs — emit ISO 8601 with the trailing `.\d{3}Z`
|
|
287
|
+
// milliseconds dropped (`2026-05-03T12:34:56Z` instead of
|
|
288
|
+
// `…:56.789Z`). Used by SAS / SigV4 / log filename builders that need
|
|
289
|
+
// a one-second-resolution timestamp string. Single source of truth so
|
|
290
|
+
// the strip pattern lives in one place.
|
|
291
|
+
var ISO_MS_RE = /\.\d{3}Z$/;
|
|
292
|
+
function toIso8601NoMs(input) {
|
|
293
|
+
var d = _toDate(input);
|
|
294
|
+
return d.toISOString().replace(ISO_MS_RE, "Z");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = {
|
|
298
|
+
toParts: toParts,
|
|
299
|
+
format: format,
|
|
300
|
+
tzOffsetMs: tzOffsetMs,
|
|
301
|
+
startOfDay: startOfDay,
|
|
302
|
+
endOfDay: endOfDay,
|
|
303
|
+
addDays: addDays,
|
|
304
|
+
addMonths: addMonths,
|
|
305
|
+
diffDays: diffDays,
|
|
306
|
+
parseISO: parseISO,
|
|
307
|
+
toIso8601NoMs: toIso8601NoMs,
|
|
308
|
+
ISO_MS_RE: ISO_MS_RE,
|
|
309
|
+
TimeError: TimeError,
|
|
310
|
+
};
|