@nextera.one/tps-standard 0.5.33 → 0.6.0
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 +88 -0
- package/README.md +133 -56
- package/dist/date.d.ts +54 -0
- package/dist/date.js +174 -0
- package/dist/date.js.map +1 -0
- package/dist/driver-manager.d.ts +34 -0
- package/dist/driver-manager.js +53 -0
- package/dist/driver-manager.js.map +1 -0
- package/dist/drivers/chinese.d.ts +25 -0
- package/dist/drivers/chinese.js +485 -0
- package/dist/drivers/chinese.js.map +1 -0
- package/dist/drivers/gregorian.d.ts +3 -5
- package/dist/drivers/gregorian.js +26 -19
- package/dist/drivers/gregorian.js.map +1 -1
- package/dist/drivers/hijri.d.ts +1 -16
- package/dist/drivers/hijri.js +9 -102
- package/dist/drivers/hijri.js.map +1 -1
- package/dist/drivers/holocene.d.ts +6 -3
- package/dist/drivers/holocene.js +7 -20
- package/dist/drivers/holocene.js.map +1 -1
- package/dist/drivers/julian.d.ts +3 -10
- package/dist/drivers/julian.js +11 -71
- package/dist/drivers/julian.js.map +1 -1
- package/dist/drivers/persian.d.ts +1 -6
- package/dist/drivers/persian.js +17 -92
- package/dist/drivers/persian.js.map +1 -1
- package/dist/drivers/tps.d.ts +11 -28
- package/dist/drivers/tps.js +8 -58
- package/dist/drivers/tps.js.map +1 -1
- package/dist/drivers/unix.d.ts +5 -6
- package/dist/drivers/unix.js +10 -32
- package/dist/drivers/unix.js.map +1 -1
- package/dist/esm/date.js +170 -0
- package/dist/esm/date.js.map +1 -0
- package/dist/esm/driver-manager.js +49 -0
- package/dist/esm/driver-manager.js.map +1 -0
- package/dist/esm/drivers/chinese.js +481 -0
- package/dist/esm/drivers/chinese.js.map +1 -0
- package/dist/esm/drivers/gregorian.js +160 -0
- package/dist/esm/drivers/gregorian.js.map +1 -0
- package/dist/esm/drivers/hijri.js +184 -0
- package/dist/esm/drivers/hijri.js.map +1 -0
- package/dist/esm/drivers/holocene.js +115 -0
- package/dist/esm/drivers/holocene.js.map +1 -0
- package/dist/esm/drivers/julian.js +161 -0
- package/dist/esm/drivers/julian.js.map +1 -0
- package/dist/esm/drivers/persian.js +190 -0
- package/dist/esm/drivers/persian.js.map +1 -0
- package/dist/esm/drivers/tps.js +181 -0
- package/dist/esm/drivers/tps.js.map +1 -0
- package/dist/esm/drivers/unix.js +50 -0
- package/dist/esm/drivers/unix.js.map +1 -0
- package/dist/esm/index.js +873 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/types.js +28 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/uid.js +221 -0
- package/dist/esm/uid.js.map +1 -0
- package/dist/esm/utils/calendar.js +126 -0
- package/dist/esm/utils/calendar.js.map +1 -0
- package/dist/esm/utils/env.js +76 -0
- package/dist/esm/utils/env.js.map +1 -0
- package/dist/esm/utils/timezone.js +168 -0
- package/dist/esm/utils/timezone.js.map +1 -0
- package/dist/esm/utils/tps-string.js +160 -0
- package/dist/esm/utils/tps-string.js.map +1 -0
- package/dist/index.d.ts +84 -466
- package/dist/index.js +430 -1095
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +103 -0
- package/dist/types.js +31 -0
- package/dist/types.js.map +1 -0
- package/dist/uid.d.ts +48 -0
- package/dist/uid.js +225 -0
- package/dist/uid.js.map +1 -0
- package/dist/utils/calendar.d.ts +55 -0
- package/dist/utils/calendar.js +136 -0
- package/dist/utils/calendar.js.map +1 -0
- package/dist/utils/env.d.ts +12 -0
- package/dist/utils/env.js +79 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/timezone.d.ts +32 -0
- package/dist/utils/timezone.js +173 -0
- package/dist/utils/timezone.js.map +1 -0
- package/dist/utils/tps-string.d.ts +12 -0
- package/dist/utils/tps-string.js +164 -0
- package/dist/utils/tps-string.js.map +1 -0
- package/package.json +20 -5
- package/src/date.ts +243 -0
- package/src/driver-manager.ts +54 -0
- package/src/drivers/chinese.ts +542 -0
- package/src/drivers/gregorian.ts +29 -27
- package/src/drivers/hijri.ts +13 -113
- package/src/drivers/holocene.ts +11 -12
- package/src/drivers/julian.ts +18 -72
- package/src/drivers/persian.ts +25 -92
- package/src/drivers/tps.ts +16 -55
- package/src/drivers/unix.ts +12 -33
- package/src/index.ts +384 -1556
- package/src/types.ts +131 -0
- package/src/uid.ts +308 -0
- package/src/utils/calendar.ts +161 -0
- package/src/utils/env.ts +88 -0
- package/src/utils/timezone.ts +182 -0
- package/src/utils/tps-string.ts +166 -0
package/dist/index.js
CHANGED
|
@@ -3,18 +3,40 @@
|
|
|
3
3
|
* TPS: Temporal Positioning System
|
|
4
4
|
* The Universal Protocol for Space-Time Coordinates.
|
|
5
5
|
* @packageDocumentation
|
|
6
|
-
* @version 0.
|
|
6
|
+
* @version 0.6.0
|
|
7
7
|
* @license Apache-2.0
|
|
8
8
|
* @copyright 2026 TPS Standards Working Group
|
|
9
9
|
*
|
|
10
|
+
* v0.5.35 Changes:
|
|
11
|
+
* - Added TPS.now(), TPS.diff(), TPS.add() convenience methods
|
|
12
|
+
* - Added Chinese Lunisolar (chin) calendar driver
|
|
13
|
+
* - Added DriverManager (driver registry separated from TPS class)
|
|
14
|
+
* - Added timezone utility (src/utils/timezone.ts) with IANA + offset support
|
|
15
|
+
* - TPS.toDate() now respects ;tz= extensions when present
|
|
16
|
+
* - ESM dual-mode exports + browser IIFE bundle
|
|
17
|
+
*
|
|
10
18
|
* v0.5.0 Changes:
|
|
11
19
|
* - Added Actor anchor (A:) for provenance tracking
|
|
12
20
|
* - Added Signature (!) for cryptographic verification
|
|
13
21
|
* - Added structural anchors (bldg, floor, room, zone)
|
|
14
22
|
* - Added geospatial cell systems (S2, H3, Plus Code, what3words)
|
|
15
23
|
*/
|
|
24
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
27
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
28
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
29
|
+
}
|
|
30
|
+
Object.defineProperty(o, k2, desc);
|
|
31
|
+
}) : (function(o, m, k, k2) {
|
|
32
|
+
if (k2 === undefined) k2 = k;
|
|
33
|
+
o[k2] = m[k];
|
|
34
|
+
}));
|
|
35
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
36
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
|
+
};
|
|
16
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
39
|
+
exports.TPS = exports.getOffsetString = exports.localToUtc = exports.utcToLocal = exports.DriverManager = exports.Env = void 0;
|
|
18
40
|
// built-in drivers are registered automatically; importing them here
|
|
19
41
|
// ensures they are included when the library bundler/tree-shaker runs.
|
|
20
42
|
const gregorian_1 = require("./drivers/gregorian");
|
|
@@ -24,35 +46,29 @@ const persian_1 = require("./drivers/persian");
|
|
|
24
46
|
const hijri_1 = require("./drivers/hijri");
|
|
25
47
|
const julian_1 = require("./drivers/julian");
|
|
26
48
|
const holocene_1 = require("./drivers/holocene");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
exports
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
var TimeOrder;
|
|
45
|
-
(function (TimeOrder) {
|
|
46
|
-
TimeOrder["DESC"] = "desc";
|
|
47
|
-
TimeOrder["ASC"] = "asc";
|
|
48
|
-
})(TimeOrder || (exports.TimeOrder = TimeOrder = {}));
|
|
49
|
+
const chinese_1 = require("./drivers/chinese");
|
|
50
|
+
__exportStar(require("./types"), exports);
|
|
51
|
+
__exportStar(require("./uid"), exports);
|
|
52
|
+
__exportStar(require("./date"), exports);
|
|
53
|
+
var env_1 = require("./utils/env");
|
|
54
|
+
Object.defineProperty(exports, "Env", { enumerable: true, get: function () { return env_1.Env; } });
|
|
55
|
+
var driver_manager_1 = require("./driver-manager");
|
|
56
|
+
Object.defineProperty(exports, "DriverManager", { enumerable: true, get: function () { return driver_manager_1.DriverManager; } });
|
|
57
|
+
var timezone_1 = require("./utils/timezone");
|
|
58
|
+
Object.defineProperty(exports, "utcToLocal", { enumerable: true, get: function () { return timezone_1.utcToLocal; } });
|
|
59
|
+
Object.defineProperty(exports, "localToUtc", { enumerable: true, get: function () { return timezone_1.localToUtc; } });
|
|
60
|
+
Object.defineProperty(exports, "getOffsetString", { enumerable: true, get: function () { return timezone_1.getOffsetString; } });
|
|
61
|
+
const driver_manager_2 = require("./driver-manager");
|
|
62
|
+
const tps_string_1 = require("./utils/tps-string");
|
|
63
|
+
const timezone_2 = require("./utils/timezone");
|
|
64
|
+
const types_1 = require("./types");
|
|
49
65
|
class TPS {
|
|
50
66
|
/**
|
|
51
67
|
* Registers a calendar driver plugin.
|
|
52
68
|
* @param driver - The driver instance to register.
|
|
53
69
|
*/
|
|
54
70
|
static registerDriver(driver) {
|
|
55
|
-
this.
|
|
71
|
+
this.driverManager.register(driver);
|
|
56
72
|
}
|
|
57
73
|
/**
|
|
58
74
|
* Gets a registered calendar driver.
|
|
@@ -60,7 +76,7 @@ class TPS {
|
|
|
60
76
|
* @returns The driver or undefined.
|
|
61
77
|
*/
|
|
62
78
|
static getDriver(code) {
|
|
63
|
-
return this.
|
|
79
|
+
return this.driverManager.get(code);
|
|
64
80
|
}
|
|
65
81
|
// --- CORE METHODS ---
|
|
66
82
|
/**
|
|
@@ -76,6 +92,24 @@ class TPS {
|
|
|
76
92
|
let s = input.trim().replace(/\s+/g, "");
|
|
77
93
|
if (!s)
|
|
78
94
|
return s;
|
|
95
|
+
// ── 1.2 Compact scheme normalization (v0.6.0) ──────────────────────────
|
|
96
|
+
// TPS:... → tps://... (generic compact)
|
|
97
|
+
// NIP4:x → tps://net:ip4:x (IPv4 shorthand)
|
|
98
|
+
// NIP6:x → tps://net:ip6:x (IPv6 shorthand)
|
|
99
|
+
// NODE:x → tps://node:x (logical node shorthand)
|
|
100
|
+
if (/^TPS:/i.test(s) && !s.toLowerCase().startsWith("tps://")) {
|
|
101
|
+
// TPS:L:... or TPS:lat,lon... → tps://...
|
|
102
|
+
s = "tps://" + s.slice(4); // strip 'TPS:'
|
|
103
|
+
}
|
|
104
|
+
else if (/^NIP4:/i.test(s)) {
|
|
105
|
+
s = "tps://net:ip4:" + s.slice(5);
|
|
106
|
+
}
|
|
107
|
+
else if (/^NIP6:/i.test(s)) {
|
|
108
|
+
s = "tps://net:ip6:" + s.slice(5);
|
|
109
|
+
}
|
|
110
|
+
else if (/^NODE:/i.test(s)) {
|
|
111
|
+
s = "tps://node:" + s.slice(5);
|
|
112
|
+
}
|
|
79
113
|
// ── 1.5 Convert legacy "/T:" separators to the new canonical "@T:".
|
|
80
114
|
// The input may contain "/T:" from older versions; we normalise early so
|
|
81
115
|
// subsequent logic can assume only the '@' form.
|
|
@@ -208,7 +242,7 @@ class TPS {
|
|
|
208
242
|
timeStr = timeStr.split(/[!;?#]/)[0];
|
|
209
243
|
}
|
|
210
244
|
if (timeStr) {
|
|
211
|
-
const parsed =
|
|
245
|
+
const parsed = (0, tps_string_1.parseTimeString)(timeStr);
|
|
212
246
|
if (!parsed)
|
|
213
247
|
return null;
|
|
214
248
|
Object.assign(comp, parsed.components);
|
|
@@ -231,13 +265,34 @@ class TPS {
|
|
|
231
265
|
signature = sigMatch.groups.sig;
|
|
232
266
|
timeOnly = input.split(/[!;?#]/)[0];
|
|
233
267
|
}
|
|
234
|
-
|
|
268
|
+
else {
|
|
269
|
+
// Strip extension/query/fragment suffix so parseTimeString sees only tokens
|
|
270
|
+
timeOnly = input.split(/[;?#]/)[0];
|
|
271
|
+
}
|
|
272
|
+
const parsed = (0, tps_string_1.parseTimeString)(timeOnly);
|
|
235
273
|
if (!parsed)
|
|
236
274
|
return null;
|
|
237
275
|
const comp = parsed.components;
|
|
238
276
|
if (signature)
|
|
239
277
|
comp.signature = signature;
|
|
240
278
|
comp.order = parsed.order;
|
|
279
|
+
// Route through the same group mapper used by REGEX_URI for consistency
|
|
280
|
+
// (handles extensions ;KEY:val and context #C:key=val)
|
|
281
|
+
const syntheticGroups = {
|
|
282
|
+
calendar: match.groups.calendar ?? "",
|
|
283
|
+
signature: match.groups.signature ?? "",
|
|
284
|
+
extensions: match.groups.extensions ?? "",
|
|
285
|
+
context: match.groups.context ?? "",
|
|
286
|
+
location: "", // no location in time-only string
|
|
287
|
+
actor: "",
|
|
288
|
+
};
|
|
289
|
+
const mappedComp = this._mapGroupsToComponents(syntheticGroups);
|
|
290
|
+
// Merge temporal components from parseTimeString with mapped metadata
|
|
291
|
+
Object.assign(comp, {
|
|
292
|
+
signature: mappedComp.signature || comp.signature,
|
|
293
|
+
extensions: mappedComp.extensions || comp.extensions,
|
|
294
|
+
context: mappedComp.context,
|
|
295
|
+
});
|
|
241
296
|
return comp;
|
|
242
297
|
}
|
|
243
298
|
/**
|
|
@@ -246,64 +301,99 @@ class TPS {
|
|
|
246
301
|
* @returns Full URI string (e.g. "tps://...").
|
|
247
302
|
*/
|
|
248
303
|
static toURI(comp) {
|
|
249
|
-
// 1.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
spacePart = "L:~";
|
|
304
|
+
// ── 1. Location layers (v0.6.0) ──────────────────────────────────────────
|
|
305
|
+
// Build an ordered list of location layer strings, then join with ";"
|
|
306
|
+
const layers = [];
|
|
307
|
+
// Privacy shorthand takes priority
|
|
308
|
+
if (comp.isHiddenLocation) {
|
|
309
|
+
layers.push("L:~");
|
|
256
310
|
}
|
|
257
311
|
else if (comp.isRedactedLocation) {
|
|
258
|
-
|
|
312
|
+
layers.push("L:redacted");
|
|
259
313
|
}
|
|
260
314
|
else if (comp.isUnknownLocation) {
|
|
261
|
-
|
|
315
|
+
layers.push("L:-");
|
|
316
|
+
}
|
|
317
|
+
else if (comp.spaceAnchor) {
|
|
318
|
+
// Generic / legacy anchor (adm:, planet:, etc.)
|
|
319
|
+
layers.push(comp.spaceAnchor);
|
|
320
|
+
}
|
|
321
|
+
else if (comp.ipv4) {
|
|
322
|
+
layers.push(`net:ip4:${comp.ipv4}`);
|
|
323
|
+
}
|
|
324
|
+
else if (comp.ipv6) {
|
|
325
|
+
layers.push(`net:ip6:${comp.ipv6}`);
|
|
326
|
+
}
|
|
327
|
+
else if (comp.nodeName) {
|
|
328
|
+
layers.push(`node:${comp.nodeName}`);
|
|
262
329
|
}
|
|
263
330
|
else if (comp.s2Cell) {
|
|
264
|
-
|
|
331
|
+
layers.push(`S2:${comp.s2Cell}`);
|
|
265
332
|
}
|
|
266
333
|
else if (comp.h3Cell) {
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
else if (comp.plusCode) {
|
|
270
|
-
spacePart = `L:plus=${comp.plusCode}`;
|
|
334
|
+
layers.push(`H3:${comp.h3Cell}`);
|
|
271
335
|
}
|
|
272
336
|
else if (comp.what3words) {
|
|
273
|
-
|
|
337
|
+
layers.push(`3W:${comp.what3words}`);
|
|
338
|
+
}
|
|
339
|
+
else if (comp.plusCode) {
|
|
340
|
+
layers.push(`plus:${comp.plusCode}`);
|
|
274
341
|
}
|
|
275
342
|
else if (comp.building) {
|
|
276
|
-
|
|
343
|
+
layers.push(`bldg:${comp.building}`);
|
|
277
344
|
if (comp.floor)
|
|
278
|
-
|
|
345
|
+
layers.push(`floor:${comp.floor}`);
|
|
279
346
|
if (comp.room)
|
|
280
|
-
|
|
347
|
+
layers.push(`room:${comp.room}`);
|
|
348
|
+
if (comp.door)
|
|
349
|
+
layers.push(`door:${comp.door}`);
|
|
281
350
|
if (comp.zone)
|
|
282
|
-
|
|
351
|
+
layers.push(`zone:${comp.zone}`);
|
|
283
352
|
}
|
|
284
353
|
else if (comp.latitude !== undefined && comp.longitude !== undefined) {
|
|
285
|
-
|
|
286
|
-
if (comp.altitude !== undefined)
|
|
287
|
-
|
|
288
|
-
|
|
354
|
+
let gps = `L:${comp.latitude},${comp.longitude}`;
|
|
355
|
+
if (comp.altitude !== undefined)
|
|
356
|
+
gps += `,${comp.altitude}m`;
|
|
357
|
+
layers.push(gps);
|
|
289
358
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
359
|
+
else {
|
|
360
|
+
layers.push("L:-"); // unknown fallback
|
|
361
|
+
}
|
|
362
|
+
// Place layer (P:) — appended after primary location
|
|
363
|
+
if (comp.placeCountryCode || comp.placeCountryName ||
|
|
364
|
+
comp.placeCityCode || comp.placeCityName) {
|
|
365
|
+
const pParts = [];
|
|
366
|
+
if (comp.placeCountryCode)
|
|
367
|
+
pParts.push(`cc=${comp.placeCountryCode}`);
|
|
368
|
+
if (comp.placeCountryName)
|
|
369
|
+
pParts.push(`cn=${comp.placeCountryName}`);
|
|
370
|
+
if (comp.placeCityCode)
|
|
371
|
+
pParts.push(`ci=${comp.placeCityCode}`);
|
|
372
|
+
if (comp.placeCityName)
|
|
373
|
+
pParts.push(`ct=${comp.placeCityName}`);
|
|
374
|
+
layers.push(`P:${pParts.join(",")}`);
|
|
375
|
+
}
|
|
376
|
+
const locationStr = layers.join(";");
|
|
377
|
+
// ── 2. Actor (/A:...) ─────────────────────────────────────────────────────
|
|
378
|
+
const actorPart = comp.actor ? `/A:${comp.actor}` : "";
|
|
379
|
+
// ── 3. Time (mandatory 9 tokens) ─────────────────────────────────────────
|
|
380
|
+
const timePart = (0, tps_string_1.buildTimePart)(comp);
|
|
381
|
+
// ── 4. Extensions (;KEY:val;...) ─────────────────────────────────────────
|
|
298
382
|
let extPart = "";
|
|
299
383
|
if (comp.extensions && Object.keys(comp.extensions).length > 0) {
|
|
300
|
-
const extStrings = Object.entries(comp.extensions).map(([k, v]) =>
|
|
301
|
-
|
|
384
|
+
const extStrings = Object.entries(comp.extensions).map(([k, v]) => {
|
|
385
|
+
// Emit as KEY:val (preferred v0.6.0 style)
|
|
386
|
+
return `${k.toUpperCase()}:${v}`;
|
|
387
|
+
});
|
|
388
|
+
extPart = `;${extStrings.join(";")}`;
|
|
389
|
+
}
|
|
390
|
+
// ── 5. Context (#C:key=val;...) ──────────────────────────────────────────
|
|
391
|
+
let contextPart = "";
|
|
392
|
+
if (comp.context && Object.keys(comp.context).length > 0) {
|
|
393
|
+
const ctxStrings = Object.entries(comp.context).map(([k, v]) => `${k}=${v}`);
|
|
394
|
+
contextPart = `#C:${ctxStrings.join(";")}`;
|
|
302
395
|
}
|
|
303
|
-
|
|
304
|
-
// instead of '/', so we interpolate it accordingly. Actor anchor (if
|
|
305
|
-
// present) still uses a leading slash.
|
|
306
|
-
return `tps://${spacePart}${actorPart}@${timePart}${extPart}`;
|
|
396
|
+
return `tps://${locationStr}${actorPart}@${timePart}${extPart}${contextPart}`;
|
|
307
397
|
}
|
|
308
398
|
/**
|
|
309
399
|
* CONVERTER: Creates a TPS Time Object string from a JavaScript Date.
|
|
@@ -314,9 +404,9 @@ class TPS {
|
|
|
314
404
|
* supported key is `order` which may be `'ascending'` or `'descending'`.
|
|
315
405
|
* @returns Canonical string (e.g., "T:tps.m3.c1.y26...").
|
|
316
406
|
*/
|
|
317
|
-
static fromDate(date = new Date(), calendar =
|
|
407
|
+
static fromDate(date = new Date(), calendar = types_1.DefaultCalendars.TPS, opts) {
|
|
318
408
|
const normalizedCalendar = calendar.toLowerCase();
|
|
319
|
-
const driver = this.
|
|
409
|
+
const driver = this.driverManager.get(normalizedCalendar);
|
|
320
410
|
if (driver) {
|
|
321
411
|
// when caller requested an explicit order we can bypass the driver's
|
|
322
412
|
// `fromDate` helper and instead generate components ourselves so that
|
|
@@ -326,21 +416,21 @@ class TPS {
|
|
|
326
416
|
const comp = driver.getComponentsFromDate(date);
|
|
327
417
|
comp.calendar = normalizedCalendar;
|
|
328
418
|
comp.order = opts.order;
|
|
329
|
-
return
|
|
419
|
+
return (0, tps_string_1.buildTimePart)(comp);
|
|
330
420
|
}
|
|
331
421
|
return driver.getFromDate(date);
|
|
332
422
|
}
|
|
333
423
|
// Fallback for old built-in calendars (shouldn't happen once drivers are
|
|
334
424
|
// registered, but kept for backwards compatibility).
|
|
335
425
|
const comp = { calendar: normalizedCalendar };
|
|
336
|
-
if (normalizedCalendar ===
|
|
426
|
+
if (normalizedCalendar === types_1.DefaultCalendars.UNIX) {
|
|
337
427
|
const s = (date.getTime() / 1000).toFixed(3);
|
|
338
428
|
comp.unixSeconds = parseFloat(s);
|
|
339
429
|
if (opts?.order)
|
|
340
430
|
comp.order = opts.order;
|
|
341
|
-
return
|
|
431
|
+
return (0, tps_string_1.buildTimePart)(comp);
|
|
342
432
|
}
|
|
343
|
-
if (normalizedCalendar ===
|
|
433
|
+
if (normalizedCalendar === types_1.DefaultCalendars.GREG) {
|
|
344
434
|
const fullYear = date.getUTCFullYear();
|
|
345
435
|
comp.millennium = Math.floor(fullYear / 1000) + 1;
|
|
346
436
|
comp.century = Math.floor((fullYear % 1000) / 100) + 1;
|
|
@@ -353,7 +443,7 @@ class TPS {
|
|
|
353
443
|
comp.millisecond = date.getUTCMilliseconds();
|
|
354
444
|
if (opts?.order)
|
|
355
445
|
comp.order = opts.order;
|
|
356
|
-
return
|
|
446
|
+
return (0, tps_string_1.buildTimePart)(comp);
|
|
357
447
|
}
|
|
358
448
|
throw new Error(`Calendar driver '${normalizedCalendar}' not implemented. Register a driver.`);
|
|
359
449
|
}
|
|
@@ -382,13 +472,22 @@ class TPS {
|
|
|
382
472
|
const parsed = this.parse(tpsString);
|
|
383
473
|
if (!parsed)
|
|
384
474
|
return null;
|
|
385
|
-
const cal = parsed.calendar ||
|
|
386
|
-
const driver = this.
|
|
475
|
+
const cal = parsed.calendar || types_1.DefaultCalendars.TPS;
|
|
476
|
+
const driver = this.driverManager.get(cal);
|
|
387
477
|
if (!driver) {
|
|
388
478
|
console.error(`Calendar driver '${cal}' not registered.`);
|
|
389
479
|
return null;
|
|
390
480
|
}
|
|
391
|
-
|
|
481
|
+
const date = driver.getDateFromComponents(parsed);
|
|
482
|
+
// If the URI has a ;tz= extension, the calendar date was expressed in local
|
|
483
|
+
// time. Convert from local → UTC using the timezone utility.
|
|
484
|
+
const tz = parsed.extensions?.["tz"];
|
|
485
|
+
if (tz && date) {
|
|
486
|
+
const localMs = date.getTime();
|
|
487
|
+
const utcMs = (0, timezone_2.localToUtc)(localMs, tz);
|
|
488
|
+
return new Date(utcMs);
|
|
489
|
+
}
|
|
490
|
+
return date;
|
|
392
491
|
}
|
|
393
492
|
// --- DRIVER CONVENIENCE METHODS ---
|
|
394
493
|
/**
|
|
@@ -410,7 +509,7 @@ class TPS {
|
|
|
410
509
|
* ```
|
|
411
510
|
*/
|
|
412
511
|
static parseCalendarDate(calendar, dateString, format) {
|
|
413
|
-
const driver = this.
|
|
512
|
+
const driver = this.driverManager.get(calendar);
|
|
414
513
|
if (!driver) {
|
|
415
514
|
throw new Error(`Calendar driver '${calendar}' not found. Register a driver first.`);
|
|
416
515
|
}
|
|
@@ -471,926 +570,319 @@ class TPS {
|
|
|
471
570
|
* ```
|
|
472
571
|
*/
|
|
473
572
|
static formatCalendarDate(calendar, components, format) {
|
|
474
|
-
const driver = this.
|
|
573
|
+
const driver = this.driverManager.get(calendar);
|
|
475
574
|
if (!driver) {
|
|
476
575
|
throw new Error(`Calendar driver '${calendar}' not found.`);
|
|
477
576
|
}
|
|
478
577
|
// format is guaranteed by the interface, so we can call it directly.
|
|
479
578
|
return driver.format(components, format);
|
|
480
579
|
}
|
|
481
|
-
// ---
|
|
580
|
+
// --- CONVENIENCE METHODS ---
|
|
482
581
|
/**
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
* ascending or descending hierarchy; if undefined the default
|
|
486
|
-
* `'descending'` orientation is used.
|
|
582
|
+
* Returns a TPS time string for the current moment.
|
|
583
|
+
* Shorthand for `TPS.fromDate(new Date(), calendar, opts)`.
|
|
487
584
|
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
585
|
+
* @param calendar - Calendar code. Defaults to 'greg'.
|
|
586
|
+
* @param opts - Optional `order` (ASC/DESC) parameter.
|
|
587
|
+
* @returns TPS time string.
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* ```ts
|
|
591
|
+
* TPS.now(); // "T:greg.m3.c1.y26.m3.d4.h06.m30.s00.m0"
|
|
592
|
+
* TPS.now('hij'); // "T:hij.y1447.m09.d05.h06.m30.s00"
|
|
593
|
+
* ```
|
|
490
594
|
*/
|
|
491
|
-
static
|
|
492
|
-
|
|
493
|
-
if (!/^[a-z]{3,4}$/.test(calendar)) {
|
|
494
|
-
throw new Error(`Invalid calendar code '${comp.calendar}'. Calendar code width must be 3–4 lowercase letters.`);
|
|
495
|
-
}
|
|
496
|
-
let time = `T:${calendar}`;
|
|
497
|
-
if (calendar === exports.DefaultCalendars.UNIX) {
|
|
498
|
-
if (comp.unixSeconds !== undefined) {
|
|
499
|
-
time += `.s${comp.unixSeconds}`;
|
|
500
|
-
}
|
|
501
|
-
return time;
|
|
502
|
-
}
|
|
503
|
-
// sequence of [prefix, value, rank]
|
|
504
|
-
// All four of millennium / month / minute / millisecond share the prefix 'm'.
|
|
505
|
-
// Position within the ordered sequence disambiguates them during parsing.
|
|
506
|
-
const tokens = [
|
|
507
|
-
["m", comp.millennium, 8], // m-token rank 8 → millennium
|
|
508
|
-
["c", comp.century, 7],
|
|
509
|
-
["y", comp.year, 6],
|
|
510
|
-
["m", comp.month, 5], // m-token rank 5 → month
|
|
511
|
-
["d", comp.day, 4],
|
|
512
|
-
["h", comp.hour, 3],
|
|
513
|
-
["m", comp.minute, 2], // m-token rank 2 → minute
|
|
514
|
-
["s", comp.second, 1],
|
|
515
|
-
["m", comp.millisecond, 0], // m-token rank 0 → millisecond
|
|
516
|
-
];
|
|
517
|
-
const order = comp.order || TimeOrder.DESC;
|
|
518
|
-
if (order === TimeOrder.ASC)
|
|
519
|
-
tokens.reverse();
|
|
520
|
-
for (const [pref, val] of tokens) {
|
|
521
|
-
if (val !== undefined) {
|
|
522
|
-
// seconds may be fractional
|
|
523
|
-
time += `.${pref}${val}`;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
if (comp.signature) {
|
|
527
|
-
time += `!${comp.signature}`;
|
|
528
|
-
}
|
|
529
|
-
return time;
|
|
595
|
+
static now(calendar = types_1.DefaultCalendars.GREG, opts) {
|
|
596
|
+
return this.fromDate(new Date(), calendar, opts);
|
|
530
597
|
}
|
|
531
598
|
/**
|
|
532
|
-
*
|
|
533
|
-
* `
|
|
534
|
-
* accepts tokens in **any** sequence and will return an `order` value of
|
|
535
|
-
* `'ascending'` or `'descending'`.
|
|
599
|
+
* Returns the difference in milliseconds between two TPS strings.
|
|
600
|
+
* The result is `t2 - t1`; negative if t1 is after t2.
|
|
536
601
|
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
602
|
+
* @param t1 - First TPS string (subtracted from t2).
|
|
603
|
+
* @param t2 - Second TPS string.
|
|
604
|
+
* @returns Milliseconds between the two moments, or NaN on parse failure.
|
|
540
605
|
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
606
|
+
* @example
|
|
607
|
+
* ```ts
|
|
608
|
+
* const ms = TPS.diff('T:greg.m3.c1.y26.m1.d1.h0.m0.s0.m0',
|
|
609
|
+
* 'T:greg.m3.c1.y26.m1.d2.h0.m0.s0.m0');
|
|
610
|
+
* // 86_400_000 (one day)
|
|
611
|
+
* ```
|
|
612
|
+
*/
|
|
613
|
+
static diff(t1, t2) {
|
|
614
|
+
const d1 = this.toDate(t1);
|
|
615
|
+
const d2 = this.toDate(t2);
|
|
616
|
+
if (!d1 || !d2)
|
|
617
|
+
return NaN;
|
|
618
|
+
return d2.getTime() - d1.getTime();
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Returns a new TPS string shifted by the given duration.
|
|
622
|
+
* The result is in the same calendar as the original string.
|
|
546
623
|
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
* recently processed token (m or non-m).
|
|
551
|
-
* 3. When an `m` token is encountered, derive its rank from `lastAssignedRank`
|
|
552
|
-
* and the detected order:
|
|
553
|
-
* - **DESC** null → 8 (mill) | rank > 5 → 5 (month) | rank > 2 → 2 (min) | else → 0 (ms)
|
|
554
|
-
* - **ASC** null → 0 (ms) | rank < 2 → 2 (min) | rank < 5 → 5 (month) | else → 8 (mill)
|
|
624
|
+
* @param tpsStr - Source TPS string.
|
|
625
|
+
* @param duration - Object with optional `days`, `hours`, `minutes`, `seconds`, `milliseconds`.
|
|
626
|
+
* @returns Shifted TPS string, or null if the input is invalid.
|
|
555
627
|
*
|
|
556
|
-
* @
|
|
628
|
+
* @example
|
|
629
|
+
* ```ts
|
|
630
|
+
* const t = 'T:greg.m3.c1.y26.m1.d9.h14.m30.s25.m0';
|
|
631
|
+
* TPS.add(t, { days: 7 }); // one week later
|
|
632
|
+
* TPS.add(t, { hours: -2 }); // two hours earlier
|
|
633
|
+
* ```
|
|
557
634
|
*/
|
|
558
|
-
static
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
s = s.split(/[!;?#]/)[0];
|
|
562
|
-
if (s.startsWith("T:"))
|
|
563
|
-
s = s.slice(2);
|
|
564
|
-
const parts = s.split(".");
|
|
565
|
-
if (parts.length === 0)
|
|
635
|
+
static add(tpsStr, duration) {
|
|
636
|
+
const date = this.toDate(tpsStr);
|
|
637
|
+
if (!date)
|
|
566
638
|
return null;
|
|
567
|
-
const
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
};
|
|
577
|
-
// ── Step 1: pre-scan non-m tokens to estimate order ─────────────────────
|
|
578
|
-
// This is only needed to handle the first 'm' token when lastAssignedRank
|
|
579
|
-
// is still null (nothing has been seen yet).
|
|
580
|
-
let initialOrder = TimeOrder.DESC;
|
|
581
|
-
if (calendar !== exports.DefaultCalendars.UNIX) {
|
|
582
|
-
const nonMRanks = [];
|
|
583
|
-
for (let i = 1; i < parts.length; i++) {
|
|
584
|
-
const pr = parts[i]?.charAt(0);
|
|
585
|
-
if (pr && pr in fixedRankMap)
|
|
586
|
-
nonMRanks.push(fixedRankMap[pr]);
|
|
587
|
-
}
|
|
588
|
-
if (nonMRanks.length >= 2) {
|
|
589
|
-
const isAsc = nonMRanks.every((v, i, a) => i === 0 || a[i - 1] <= v);
|
|
590
|
-
if (isAsc)
|
|
591
|
-
initialOrder = TimeOrder.ASC;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
// ── Step 2: resolve the semantic rank of an 'm' token ───────────────────
|
|
595
|
-
const assignMRank = (lastRank, ord) => {
|
|
596
|
-
if (ord === TimeOrder.DESC) {
|
|
597
|
-
if (lastRank === null)
|
|
598
|
-
return 8; // first token → millennium
|
|
599
|
-
if (lastRank > 5)
|
|
600
|
-
return 5; // after century / year → month
|
|
601
|
-
if (lastRank > 2)
|
|
602
|
-
return 2; // after day / hour → minute
|
|
603
|
-
return 0; // after second → millisecond
|
|
604
|
-
}
|
|
605
|
-
else {
|
|
606
|
-
if (lastRank === null)
|
|
607
|
-
return 0; // first token → millisecond
|
|
608
|
-
if (lastRank < 2)
|
|
609
|
-
return 2; // after millisecond / second → minute
|
|
610
|
-
if (lastRank < 5)
|
|
611
|
-
return 5; // after minute / hour / day → month
|
|
612
|
-
return 8; // after month / year / cent → millennium
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
// ── Step 3: iterate and build components ────────────────────────────────
|
|
616
|
-
const ranks = [];
|
|
617
|
-
let lastAssignedRank = null;
|
|
618
|
-
for (let i = 1; i < parts.length; i++) {
|
|
619
|
-
const token = parts[i];
|
|
620
|
-
if (!token)
|
|
621
|
-
continue;
|
|
622
|
-
const prefix = token.charAt(0);
|
|
623
|
-
const value = token.slice(1);
|
|
624
|
-
// UNIX calendar: single 's' token carries the full unix timestamp
|
|
625
|
-
if (calendar === exports.DefaultCalendars.UNIX && prefix === "s") {
|
|
626
|
-
comp.unixSeconds = parseFloat(value);
|
|
627
|
-
ranks.push(9);
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
if (prefix === "m") {
|
|
631
|
-
const rank = assignMRank(lastAssignedRank, initialOrder);
|
|
632
|
-
switch (rank) {
|
|
633
|
-
case 8:
|
|
634
|
-
comp.millennium = parseInt(value, 10);
|
|
635
|
-
break;
|
|
636
|
-
case 5:
|
|
637
|
-
comp.month = parseInt(value, 10);
|
|
638
|
-
break;
|
|
639
|
-
case 2:
|
|
640
|
-
comp.minute = parseInt(value, 10);
|
|
641
|
-
break;
|
|
642
|
-
case 0:
|
|
643
|
-
comp.millisecond = parseInt(value, 10);
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
ranks.push(rank);
|
|
647
|
-
lastAssignedRank = rank;
|
|
648
|
-
}
|
|
649
|
-
else {
|
|
650
|
-
switch (prefix) {
|
|
651
|
-
case "c":
|
|
652
|
-
comp.century = parseInt(value, 10);
|
|
653
|
-
ranks.push(7);
|
|
654
|
-
lastAssignedRank = 7;
|
|
655
|
-
break;
|
|
656
|
-
case "y":
|
|
657
|
-
comp.year = parseInt(value, 10);
|
|
658
|
-
ranks.push(6);
|
|
659
|
-
lastAssignedRank = 6;
|
|
660
|
-
break;
|
|
661
|
-
case "d":
|
|
662
|
-
comp.day = parseInt(value, 10);
|
|
663
|
-
ranks.push(4);
|
|
664
|
-
lastAssignedRank = 4;
|
|
665
|
-
break;
|
|
666
|
-
case "h":
|
|
667
|
-
comp.hour = parseInt(value, 10);
|
|
668
|
-
ranks.push(3);
|
|
669
|
-
lastAssignedRank = 3;
|
|
670
|
-
break;
|
|
671
|
-
case "s":
|
|
672
|
-
comp.second = parseFloat(value);
|
|
673
|
-
ranks.push(1);
|
|
674
|
-
lastAssignedRank = 1;
|
|
675
|
-
break;
|
|
676
|
-
default:
|
|
677
|
-
// unknown prefix – ignore
|
|
678
|
-
break;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
// ── Step 4: confirm order from the complete rank sequence ────────────────
|
|
683
|
-
let order = TimeOrder.DESC;
|
|
684
|
-
if (ranks.length > 1) {
|
|
685
|
-
const isAsc = ranks.every((v, i, a) => i === 0 || a[i - 1] <= v);
|
|
686
|
-
const isDesc = ranks.every((v, i, a) => i === 0 || a[i - 1] >= v);
|
|
687
|
-
if (isAsc && !isDesc)
|
|
688
|
-
order = TimeOrder.ASC;
|
|
689
|
-
// mixed / single direction → defaults to DESC
|
|
690
|
-
}
|
|
691
|
-
return { components: comp, order };
|
|
639
|
+
const parsed = this.parse(tpsStr);
|
|
640
|
+
const calendar = parsed?.calendar ?? types_1.DefaultCalendars.GREG;
|
|
641
|
+
const order = parsed?.order;
|
|
642
|
+
const deltaMs = (duration.days ?? 0) * 86400000 +
|
|
643
|
+
(duration.hours ?? 0) * 3600000 +
|
|
644
|
+
(duration.minutes ?? 0) * 60000 +
|
|
645
|
+
(duration.seconds ?? 0) * 1000 +
|
|
646
|
+
(duration.milliseconds ?? 0);
|
|
647
|
+
const shifted = new Date(date.getTime() + deltaMs);
|
|
648
|
+
return this.fromDate(shifted, calendar, order ? { order } : undefined);
|
|
692
649
|
}
|
|
650
|
+
// --- INTERNAL HELPERS ---
|
|
693
651
|
static _mapGroupsToComponents(g) {
|
|
694
652
|
const components = {};
|
|
695
653
|
components.calendar = g.calendar;
|
|
696
|
-
// Signature
|
|
654
|
+
// ── Signature ────────────────────────────────────────────────────────────
|
|
697
655
|
if (g.signature) {
|
|
698
656
|
components.signature = g.signature;
|
|
699
657
|
}
|
|
700
|
-
// Actor
|
|
658
|
+
// ── Actor (/A:...) ────────────────────────────────────────────────────────
|
|
701
659
|
if (g.actor) {
|
|
702
|
-
components.actor = g.actor;
|
|
660
|
+
components.actor = g.actor.trim();
|
|
703
661
|
}
|
|
704
|
-
//
|
|
705
|
-
if (g.
|
|
706
|
-
|
|
707
|
-
if (g.space === "unknown" || g.space === "-") {
|
|
708
|
-
components.isUnknownLocation = true;
|
|
709
|
-
}
|
|
710
|
-
else if (g.space === "redacted") {
|
|
711
|
-
components.isRedactedLocation = true;
|
|
712
|
-
}
|
|
713
|
-
else if (g.space === "hidden" || g.space === "~") {
|
|
714
|
-
components.isHiddenLocation = true;
|
|
715
|
-
}
|
|
716
|
-
// Geospatial cells
|
|
717
|
-
else if (g.s2) {
|
|
718
|
-
components.s2Cell = g.s2;
|
|
719
|
-
}
|
|
720
|
-
else if (g.h3) {
|
|
721
|
-
components.h3Cell = g.h3;
|
|
722
|
-
}
|
|
723
|
-
else if (g.plus) {
|
|
724
|
-
components.plusCode = g.plus;
|
|
725
|
-
}
|
|
726
|
-
else if (g.w3w) {
|
|
727
|
-
components.what3words = g.w3w;
|
|
728
|
-
}
|
|
729
|
-
// Structural anchors
|
|
730
|
-
else if (g.bldg) {
|
|
731
|
-
components.building = g.bldg;
|
|
732
|
-
if (g.floor)
|
|
733
|
-
components.floor = g.floor;
|
|
734
|
-
if (g.room)
|
|
735
|
-
components.room = g.room;
|
|
736
|
-
if (g.zone)
|
|
737
|
-
components.zone = g.zone;
|
|
738
|
-
}
|
|
739
|
-
// Generic pre-@ anchor (adm/node/net/planet/etc)
|
|
740
|
-
else if (g.generic) {
|
|
741
|
-
components.spaceAnchor = g.generic;
|
|
742
|
-
}
|
|
743
|
-
// GPS coordinates
|
|
744
|
-
else {
|
|
745
|
-
if (g.lat)
|
|
746
|
-
components.latitude = parseFloat(g.lat);
|
|
747
|
-
if (g.lon)
|
|
748
|
-
components.longitude = parseFloat(g.lon);
|
|
749
|
-
if (g.alt)
|
|
750
|
-
components.altitude = parseFloat(g.alt);
|
|
751
|
-
}
|
|
662
|
+
// ── Location layers (v0.6.0: multi-layer, ;-separated) ───────────────────
|
|
663
|
+
if (g.location) {
|
|
664
|
+
this._parseLocationLayers(g.location, components);
|
|
752
665
|
}
|
|
753
|
-
// Extensions
|
|
666
|
+
// ── Extensions (;KEY:val or ;key=val after T: tokens) ────────────────────
|
|
754
667
|
if (g.extensions) {
|
|
755
668
|
const extObj = {};
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
669
|
+
g.extensions.split(";").forEach((part) => {
|
|
670
|
+
part = part.trim();
|
|
671
|
+
if (!part)
|
|
672
|
+
return;
|
|
673
|
+
const colonIdx = part.indexOf(":");
|
|
674
|
+
const eqIdx = part.indexOf("=");
|
|
675
|
+
if (colonIdx > 0 && (eqIdx < 0 || colonIdx < eqIdx)) {
|
|
676
|
+
// KEY:val form (e.g. TZ:+03:00)
|
|
677
|
+
const key = part.substring(0, colonIdx).toLowerCase();
|
|
678
|
+
const val = part.substring(colonIdx + 1);
|
|
679
|
+
if (key && val !== undefined)
|
|
763
680
|
extObj[key] = val;
|
|
764
681
|
}
|
|
765
|
-
else {
|
|
766
|
-
//
|
|
767
|
-
const key =
|
|
768
|
-
const val =
|
|
769
|
-
if (key && val)
|
|
682
|
+
else if (eqIdx > 0) {
|
|
683
|
+
// key=val form (e.g. tz=+03:00)
|
|
684
|
+
const key = part.substring(0, eqIdx).toLowerCase();
|
|
685
|
+
const val = part.substring(eqIdx + 1);
|
|
686
|
+
if (key && val !== undefined)
|
|
770
687
|
extObj[key] = val;
|
|
771
688
|
}
|
|
772
689
|
});
|
|
773
|
-
|
|
690
|
+
if (Object.keys(extObj).length > 0)
|
|
691
|
+
components.extensions = extObj;
|
|
692
|
+
}
|
|
693
|
+
// ── Context (#C:key=val;key=val) ─────────────────────────────────────────
|
|
694
|
+
if (g.context) {
|
|
695
|
+
const ctx = {};
|
|
696
|
+
g.context.split(";").forEach((part) => {
|
|
697
|
+
part = part.trim();
|
|
698
|
+
if (!part)
|
|
699
|
+
return;
|
|
700
|
+
const eqIdx = part.indexOf("=");
|
|
701
|
+
if (eqIdx > 0) {
|
|
702
|
+
ctx[part.substring(0, eqIdx)] = part.substring(eqIdx + 1);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
if (Object.keys(ctx).length > 0)
|
|
706
|
+
components.context = ctx;
|
|
774
707
|
}
|
|
775
708
|
return components;
|
|
776
709
|
}
|
|
777
|
-
}
|
|
778
|
-
exports.TPS = TPS;
|
|
779
|
-
// --- PLUGIN REGISTRY ---
|
|
780
|
-
TPS.drivers = new Map();
|
|
781
|
-
// --- REGEX ---
|
|
782
|
-
// Updated for v0.5.0: supports L: anchors, A: actor, ! signature, structural & geospatial anchors
|
|
783
|
-
// Tokens may appear in any order; actual semantic parsing happens in
|
|
784
|
-
// `parseTimeString()` so these patterns are intentionally permissive.
|
|
785
|
-
// regex simply ensures prefix, space, calendar, and token characters;
|
|
786
|
-
// token order is not enforced (parseTimeString handles semantics).
|
|
787
|
-
TPS.REGEX_URI = new RegExp("^tps://" +
|
|
788
|
-
// Location part (preserve named captures for space subfields)
|
|
789
|
-
"(?:L:)?(?<space>" +
|
|
790
|
-
"~|-|unknown|redacted|hidden|" +
|
|
791
|
-
"s2=(?<s2>[a-fA-F0-9]+)|" +
|
|
792
|
-
"h3=(?<h3>[a-fA-F0-9]+)|" +
|
|
793
|
-
"plus=(?<plus>[A-Z0-9+]+)|" +
|
|
794
|
-
"w3w=(?<w3w>[a-z]+\\.[a-z]+\\.[a-z]+)|" +
|
|
795
|
-
"bldg=(?<bldg>[\\w-]+)(?:\\.floor=(?<floor>[\\w-]+))?(?:\\.room=(?<room>[\\w-]+))?(?:\\.zone=(?<zone>[\\w-]+))?|" +
|
|
796
|
-
"(?<lat>-?\\d+(?:\\.\\d+)?),(?<lon>-?\\d+(?:\\.\\d+)?)(?:,(?<alt>-?\\d+(?:\\.\\d+)?)m?)?|" +
|
|
797
|
-
"(?<generic>[^@/?#]+)" +
|
|
798
|
-
")" +
|
|
799
|
-
"(?:/A:(?<actor>[^/@]+))?" +
|
|
800
|
-
"@T:(?<calendar>[a-z]{3,4})" +
|
|
801
|
-
"(?:\\.(?:m-?\\d+|c\\d+|y\\d+|d\\d{1,2}|h\\d{1,2}|s\\d+(?:\\.\\d+)?))*" +
|
|
802
|
-
"(?:![^;?#]+)?" +
|
|
803
|
-
"(?:;(?<extensions>[^?#]+))?" +
|
|
804
|
-
"(?:\\?[^#]+)?" +
|
|
805
|
-
"(?:#.+)?$");
|
|
806
|
-
TPS.REGEX_TIME = new RegExp("^T:(?<calendar>[a-z]{3,4})" +
|
|
807
|
-
"(?:\\.(?:m-?\\d+|c\\d+|y\\d+|d\\d{1,2}|h\\d{1,2}|s\\d+(?:\\.\\d+)?))*" +
|
|
808
|
-
"(?:![^;?#]+)?$");
|
|
809
|
-
// register built-in drivers and set default
|
|
810
|
-
// (tps and gregorian provide canonical conversions before unix)
|
|
811
|
-
TPS.registerDriver(new tps_1.TpsDriver());
|
|
812
|
-
TPS.registerDriver(new gregorian_1.GregorianDriver());
|
|
813
|
-
TPS.registerDriver(new unix_1.UnixDriver());
|
|
814
|
-
TPS.registerDriver(new persian_1.PersianDriver());
|
|
815
|
-
TPS.registerDriver(new hijri_1.HijriDriver());
|
|
816
|
-
TPS.registerDriver(new julian_1.JulianDriver());
|
|
817
|
-
TPS.registerDriver(new holocene_1.HoloceneDriver());
|
|
818
|
-
/**
|
|
819
|
-
* TPS-UID v1 — Temporal Positioning System Identifier (Binary Reversible)
|
|
820
|
-
*
|
|
821
|
-
* A time-first, reversible identifier that binds an event to a TPS coordinate.
|
|
822
|
-
* Unlike UUIDs, TPS-UID identifies events in spacetime and allows exact
|
|
823
|
-
* reconstruction of the original TPS string.
|
|
824
|
-
*
|
|
825
|
-
* Binary Schema (all integers big-endian):
|
|
826
|
-
* ```
|
|
827
|
-
* MAGIC 4 bytes "TPU7"
|
|
828
|
-
* VER 1 byte 0x01
|
|
829
|
-
* FLAGS 1 byte bit0 = compression flag
|
|
830
|
-
* TIME 6 bytes epoch_ms (48-bit unsigned)
|
|
831
|
-
* NONCE 4 bytes 32-bit random
|
|
832
|
-
* LEN varint length of TPS payload
|
|
833
|
-
* TPS bytes UTF-8 TPS string (raw or zlib-compressed)
|
|
834
|
-
* ```
|
|
835
|
-
*
|
|
836
|
-
* @example
|
|
837
|
-
* ```ts
|
|
838
|
-
* const tps = 'tps://31.95,35.91@T:greg.m3.c1.y26.m01.d09';
|
|
839
|
-
*
|
|
840
|
-
* // Encode to binary
|
|
841
|
-
* const bytes = TPSUID7RB.encodeBinary(tps);
|
|
842
|
-
*
|
|
843
|
-
* // Encode to base64url string
|
|
844
|
-
* const id = TPSUID7RB.encodeBinaryB64(tps);
|
|
845
|
-
* // → "tpsuid7rb_AFRQV..."
|
|
846
|
-
*
|
|
847
|
-
* // Decode back to original TPS
|
|
848
|
-
* const decoded = TPSUID7RB.decodeBinaryB64(id);
|
|
849
|
-
* console.log(decoded.tps); // exact original TPS
|
|
850
|
-
* ```
|
|
851
|
-
*/
|
|
852
|
-
class TPSUID7RB {
|
|
853
|
-
// ---------------------------
|
|
854
|
-
// Public API
|
|
855
|
-
// ---------------------------
|
|
856
|
-
/**
|
|
857
|
-
* Encode TPS string to binary bytes (Uint8Array).
|
|
858
|
-
* This is the canonical form for hashing, signing, and storage.
|
|
859
|
-
*
|
|
860
|
-
* @param tps - The TPS string to encode
|
|
861
|
-
* @param opts - Encoding options (compress, epochMs override)
|
|
862
|
-
* @returns Binary TPS-UID as Uint8Array
|
|
863
|
-
*/
|
|
864
|
-
static encodeBinary(tps, opts = {}) {
|
|
865
|
-
const compress = opts.compress ?? false;
|
|
866
|
-
const epochMs = opts.epochMs ?? this.epochMsFromTPSString(tps);
|
|
867
|
-
if (!Number.isInteger(epochMs) || epochMs < 0) {
|
|
868
|
-
throw new Error("epochMs must be a non-negative integer");
|
|
869
|
-
}
|
|
870
|
-
if (epochMs > 0xffffffffffff) {
|
|
871
|
-
throw new Error("epochMs exceeds 48-bit range");
|
|
872
|
-
}
|
|
873
|
-
const flags = compress ? 0x01 : 0x00;
|
|
874
|
-
// Generate 32-bit nonce
|
|
875
|
-
const nonceBuf = this.randomBytes(4);
|
|
876
|
-
const nonce = ((nonceBuf[0] << 24) >>> 0) +
|
|
877
|
-
((nonceBuf[1] << 16) >>> 0) +
|
|
878
|
-
((nonceBuf[2] << 8) >>> 0) +
|
|
879
|
-
nonceBuf[3];
|
|
880
|
-
// Encode TPS to UTF-8
|
|
881
|
-
const tpsUtf8 = new TextEncoder().encode(tps);
|
|
882
|
-
// Optionally compress
|
|
883
|
-
const payload = compress ? this.deflateRaw(tpsUtf8) : tpsUtf8;
|
|
884
|
-
// Encode length as varint
|
|
885
|
-
const lenVar = this.uvarintEncode(payload.length);
|
|
886
|
-
// Construct binary structure
|
|
887
|
-
const out = new Uint8Array(4 + 1 + 1 + 6 + 4 + lenVar.length + payload.length);
|
|
888
|
-
let offset = 0;
|
|
889
|
-
// MAGIC
|
|
890
|
-
out.set(this.MAGIC, offset);
|
|
891
|
-
offset += 4;
|
|
892
|
-
// VER
|
|
893
|
-
out[offset++] = this.VER;
|
|
894
|
-
// FLAGS
|
|
895
|
-
out[offset++] = flags;
|
|
896
|
-
// TIME (48-bit big-endian)
|
|
897
|
-
const timeBytes = this.writeU48(epochMs);
|
|
898
|
-
out.set(timeBytes, offset);
|
|
899
|
-
offset += 6;
|
|
900
|
-
// NONCE (32-bit big-endian)
|
|
901
|
-
out.set(nonceBuf, offset);
|
|
902
|
-
offset += 4;
|
|
903
|
-
// LEN (varint)
|
|
904
|
-
out.set(lenVar, offset);
|
|
905
|
-
offset += lenVar.length;
|
|
906
|
-
// TPS payload
|
|
907
|
-
out.set(payload, offset);
|
|
908
|
-
return out;
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Decode binary bytes back to original TPS string.
|
|
912
|
-
*
|
|
913
|
-
* @param bytes - Binary TPS-UID
|
|
914
|
-
* @returns Decoded result with original TPS string
|
|
915
|
-
*/
|
|
916
|
-
static decodeBinary(bytes) {
|
|
917
|
-
// Header min size: 4+1+1+6+4 + 1 (at least 1 byte varint) = 17
|
|
918
|
-
if (bytes.length < 17) {
|
|
919
|
-
throw new Error("TPSUID7RB: too short");
|
|
920
|
-
}
|
|
921
|
-
// MAGIC
|
|
922
|
-
if (bytes[0] !== 0x54 ||
|
|
923
|
-
bytes[1] !== 0x50 ||
|
|
924
|
-
bytes[2] !== 0x55 ||
|
|
925
|
-
bytes[3] !== 0x37) {
|
|
926
|
-
throw new Error("TPSUID7RB: bad magic");
|
|
927
|
-
}
|
|
928
|
-
// VERSION
|
|
929
|
-
const ver = bytes[4];
|
|
930
|
-
if (ver !== this.VER) {
|
|
931
|
-
throw new Error(`TPSUID7RB: unsupported version ${ver}`);
|
|
932
|
-
}
|
|
933
|
-
// FLAGS
|
|
934
|
-
const flags = bytes[5];
|
|
935
|
-
const compressed = (flags & 0x01) === 0x01;
|
|
936
|
-
// TIME (48-bit big-endian)
|
|
937
|
-
const epochMs = this.readU48(bytes, 6);
|
|
938
|
-
// NONCE (32-bit big-endian)
|
|
939
|
-
const nonce = ((bytes[12] << 24) >>> 0) +
|
|
940
|
-
((bytes[13] << 16) >>> 0) +
|
|
941
|
-
((bytes[14] << 8) >>> 0) +
|
|
942
|
-
bytes[15];
|
|
943
|
-
// LEN (varint at offset 16)
|
|
944
|
-
let offset = 16;
|
|
945
|
-
const { value: tpsLen, bytesRead } = this.uvarintDecode(bytes, offset);
|
|
946
|
-
offset += bytesRead;
|
|
947
|
-
if (offset + tpsLen > bytes.length) {
|
|
948
|
-
throw new Error("TPSUID7RB: length overflow");
|
|
949
|
-
}
|
|
950
|
-
// TPS payload
|
|
951
|
-
const payload = bytes.slice(offset, offset + tpsLen);
|
|
952
|
-
const tpsUtf8 = compressed ? this.inflateRaw(payload) : payload;
|
|
953
|
-
const tps = new TextDecoder().decode(tpsUtf8);
|
|
954
|
-
return { version: "tpsuid7rb", epochMs, compressed, nonce, tps };
|
|
955
|
-
}
|
|
956
710
|
/**
|
|
957
|
-
*
|
|
958
|
-
*
|
|
711
|
+
* Parses a multi-layer location string (before @T:) into component fields.
|
|
712
|
+
* Layers are `;`-separated. Each layer is identified by its prefix token.
|
|
959
713
|
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
714
|
+
* Supported layers:
|
|
715
|
+
* L:lat,lon[,altm] — GPS
|
|
716
|
+
* L:~|L:-|L:redacted — Privacy markers
|
|
717
|
+
* P:cc=JO,ci=AMM,... — Place (country/city codes and names)
|
|
718
|
+
* S2:token — S2 cell
|
|
719
|
+
* H3:token — H3 cell
|
|
720
|
+
* 3W:word.word.word — What3Words
|
|
721
|
+
* plus:token — Plus Code
|
|
722
|
+
* net:ip4:x.x.x.x — IPv4
|
|
723
|
+
* net:ip6:x::x — IPv6
|
|
724
|
+
* node:name — Logical node/host
|
|
725
|
+
* bldg:name — Building
|
|
726
|
+
* floor:x — Floor
|
|
727
|
+
* room:x — Room
|
|
728
|
+
* door:x — Door
|
|
729
|
+
* zone:x — Zone
|
|
963
730
|
*/
|
|
964
|
-
static
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
static decodeBinaryB64(id) {
|
|
975
|
-
const s = id.trim();
|
|
976
|
-
if (!s.startsWith(this.PREFIX)) {
|
|
977
|
-
throw new Error("TPSUID7RB: missing prefix");
|
|
978
|
-
}
|
|
979
|
-
const b64 = s.slice(this.PREFIX.length);
|
|
980
|
-
const bytes = this.base64UrlDecode(b64);
|
|
981
|
-
return this.decodeBinary(bytes);
|
|
982
|
-
}
|
|
983
|
-
/**
|
|
984
|
-
* Validate base64url encoded TPS-UID format.
|
|
985
|
-
* Note: This validates shape only; binary decode is authoritative.
|
|
986
|
-
*
|
|
987
|
-
* @param id - String to validate
|
|
988
|
-
* @returns true if format is valid
|
|
989
|
-
*/
|
|
990
|
-
static validateBinaryB64(id) {
|
|
991
|
-
return this.REGEX.test(id.trim());
|
|
992
|
-
}
|
|
993
|
-
/**
|
|
994
|
-
* Generate a TPS-UID from the current time and optional location.
|
|
995
|
-
*
|
|
996
|
-
* @param opts - Generation options
|
|
997
|
-
* @returns Base64url encoded TPS-UID
|
|
998
|
-
*/
|
|
999
|
-
static generate(opts) {
|
|
1000
|
-
const now = new Date();
|
|
1001
|
-
const time = TPS.fromDate(now, exports.DefaultCalendars.TPS, {
|
|
1002
|
-
order: opts?.order,
|
|
1003
|
-
});
|
|
1004
|
-
let space = "unknown";
|
|
1005
|
-
if (opts?.latitude !== undefined && opts?.longitude !== undefined) {
|
|
1006
|
-
space = `${opts.latitude},${opts.longitude}`;
|
|
1007
|
-
if (opts.altitude !== undefined) {
|
|
1008
|
-
space += `,${opts.altitude}m`;
|
|
731
|
+
static _parseLocationLayers(location, components) {
|
|
732
|
+
const layers = location.trim().split(";");
|
|
733
|
+
for (const layer of layers) {
|
|
734
|
+
const l = layer.trim();
|
|
735
|
+
if (!l)
|
|
736
|
+
continue;
|
|
737
|
+
// Privacy shorthand
|
|
738
|
+
if (l === "L:~" || l === "L:hidden") {
|
|
739
|
+
components.isHiddenLocation = true;
|
|
740
|
+
continue;
|
|
1009
741
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
compress: opts?.compress,
|
|
1014
|
-
epochMs: now.getTime(),
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
// ---------------------------
|
|
1018
|
-
// TPS String Helpers
|
|
1019
|
-
// ---------------------------
|
|
1020
|
-
/**
|
|
1021
|
-
* Generate a TPS string from a Date and optional location.
|
|
1022
|
-
*/
|
|
1023
|
-
/**
|
|
1024
|
-
* Parse epoch milliseconds from a TPS string.
|
|
1025
|
-
* Supports both URI format (tps://...) and time-only format (T:greg...)
|
|
1026
|
-
*/
|
|
1027
|
-
static epochMsFromTPSString(tps) {
|
|
1028
|
-
const date = TPS.toDate(tps);
|
|
1029
|
-
if (date)
|
|
1030
|
-
return date.getTime();
|
|
1031
|
-
// If parse fails due to unsupported/extended extension payloads,
|
|
1032
|
-
// strip extensions/query/fragment and retry. Epoch only depends on time.
|
|
1033
|
-
const stripped = tps.replace(/;[^?#]*/, "").replace(/[?#].*$/, "");
|
|
1034
|
-
const retryDate = TPS.toDate(stripped);
|
|
1035
|
-
if (!retryDate)
|
|
1036
|
-
throw new Error("TPS: unable to parse date for epoch");
|
|
1037
|
-
return retryDate.getTime();
|
|
1038
|
-
}
|
|
1039
|
-
// ---------------------------
|
|
1040
|
-
// Binary Helpers
|
|
1041
|
-
// ---------------------------
|
|
1042
|
-
/** Write 48-bit unsigned integer (big-endian) */
|
|
1043
|
-
static writeU48(epochMs) {
|
|
1044
|
-
const b = new Uint8Array(6);
|
|
1045
|
-
// Use BigInt for proper 48-bit handling
|
|
1046
|
-
const v = BigInt(epochMs);
|
|
1047
|
-
b[0] = Number((v >> 40n) & 0xffn);
|
|
1048
|
-
b[1] = Number((v >> 32n) & 0xffn);
|
|
1049
|
-
b[2] = Number((v >> 24n) & 0xffn);
|
|
1050
|
-
b[3] = Number((v >> 16n) & 0xffn);
|
|
1051
|
-
b[4] = Number((v >> 8n) & 0xffn);
|
|
1052
|
-
b[5] = Number(v & 0xffn);
|
|
1053
|
-
return b;
|
|
1054
|
-
}
|
|
1055
|
-
/** Read 48-bit unsigned integer (big-endian) */
|
|
1056
|
-
static readU48(bytes, offset) {
|
|
1057
|
-
const v = (BigInt(bytes[offset]) << 40n) +
|
|
1058
|
-
(BigInt(bytes[offset + 1]) << 32n) +
|
|
1059
|
-
(BigInt(bytes[offset + 2]) << 24n) +
|
|
1060
|
-
(BigInt(bytes[offset + 3]) << 16n) +
|
|
1061
|
-
(BigInt(bytes[offset + 4]) << 8n) +
|
|
1062
|
-
BigInt(bytes[offset + 5]);
|
|
1063
|
-
const n = Number(v);
|
|
1064
|
-
if (!Number.isSafeInteger(n)) {
|
|
1065
|
-
throw new Error("TPSUID7RB: u48 not safe integer");
|
|
1066
|
-
}
|
|
1067
|
-
return n;
|
|
1068
|
-
}
|
|
1069
|
-
/** Encode unsigned integer as LEB128 varint */
|
|
1070
|
-
static uvarintEncode(n) {
|
|
1071
|
-
if (!Number.isInteger(n) || n < 0) {
|
|
1072
|
-
throw new Error("uvarint must be non-negative int");
|
|
1073
|
-
}
|
|
1074
|
-
const out = [];
|
|
1075
|
-
let x = n >>> 0;
|
|
1076
|
-
while (x >= 0x80) {
|
|
1077
|
-
out.push((x & 0x7f) | 0x80);
|
|
1078
|
-
x >>>= 7;
|
|
1079
|
-
}
|
|
1080
|
-
out.push(x);
|
|
1081
|
-
return new Uint8Array(out);
|
|
1082
|
-
}
|
|
1083
|
-
/** Decode LEB128 varint */
|
|
1084
|
-
static uvarintDecode(bytes, offset) {
|
|
1085
|
-
let x = 0;
|
|
1086
|
-
let s = 0;
|
|
1087
|
-
let i = 0;
|
|
1088
|
-
while (true) {
|
|
1089
|
-
if (offset + i >= bytes.length) {
|
|
1090
|
-
throw new Error("uvarint overflow");
|
|
742
|
+
if (l === "L:-" || l === "L:unknown") {
|
|
743
|
+
components.isUnknownLocation = true;
|
|
744
|
+
continue;
|
|
1091
745
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
746
|
+
if (l === "L:redacted") {
|
|
747
|
+
components.isRedactedLocation = true;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
// P: Place layer — P:cc=JO,ci=AMM,cn=Jordan,ct=Amman
|
|
751
|
+
if (l.startsWith("P:")) {
|
|
752
|
+
l.slice(2).split(",").forEach((pair) => {
|
|
753
|
+
const eq = pair.indexOf("=");
|
|
754
|
+
if (eq < 1)
|
|
755
|
+
return;
|
|
756
|
+
const k = pair.substring(0, eq).toLowerCase();
|
|
757
|
+
const v = pair.substring(eq + 1);
|
|
758
|
+
if (k === "cc")
|
|
759
|
+
components.placeCountryCode = v;
|
|
760
|
+
else if (k === "cn")
|
|
761
|
+
components.placeCountryName = v;
|
|
762
|
+
else if (k === "ci")
|
|
763
|
+
components.placeCityCode = v;
|
|
764
|
+
else if (k === "ct")
|
|
765
|
+
components.placeCityName = v;
|
|
766
|
+
});
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
// GPS coordinates (L:lat,lon[,alt])
|
|
770
|
+
if (l.startsWith("L:")) {
|
|
771
|
+
const coords = l.slice(2);
|
|
772
|
+
const m = coords.match(/^(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)(?:,(-?\d+(?:\.\d+)?)m?)?$/);
|
|
773
|
+
if (m) {
|
|
774
|
+
components.latitude = parseFloat(m[1]);
|
|
775
|
+
components.longitude = parseFloat(m[2]);
|
|
776
|
+
if (m[3])
|
|
777
|
+
components.altitude = parseFloat(m[3]);
|
|
1096
778
|
}
|
|
1097
|
-
|
|
1098
|
-
return { value: x >>> 0, bytesRead: i + 1 };
|
|
779
|
+
continue;
|
|
1099
780
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
throw new Error("uvarint too long");
|
|
781
|
+
// Geospatial cells
|
|
782
|
+
if (/^S2:/i.test(l)) {
|
|
783
|
+
components.s2Cell = l.slice(3);
|
|
784
|
+
continue;
|
|
1105
785
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
// Base64url Helpers
|
|
1110
|
-
// ---------------------------
|
|
1111
|
-
/** Encode bytes to base64url (no padding) */
|
|
1112
|
-
static base64UrlEncode(bytes) {
|
|
1113
|
-
// Node.js environment
|
|
1114
|
-
if (typeof Buffer !== "undefined") {
|
|
1115
|
-
return Buffer.from(bytes)
|
|
1116
|
-
.toString("base64")
|
|
1117
|
-
.replace(/\+/g, "-")
|
|
1118
|
-
.replace(/\//g, "_")
|
|
1119
|
-
.replace(/=+$/g, "");
|
|
1120
|
-
}
|
|
1121
|
-
// Browser environment
|
|
1122
|
-
let binary = "";
|
|
1123
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
1124
|
-
binary += String.fromCharCode(bytes[i]);
|
|
1125
|
-
}
|
|
1126
|
-
return btoa(binary)
|
|
1127
|
-
.replace(/\+/g, "-")
|
|
1128
|
-
.replace(/\//g, "_")
|
|
1129
|
-
.replace(/=+$/g, "");
|
|
1130
|
-
}
|
|
1131
|
-
/** Decode base64url to bytes */
|
|
1132
|
-
static base64UrlDecode(b64url) {
|
|
1133
|
-
// Add padding
|
|
1134
|
-
const padLen = (4 - (b64url.length % 4)) % 4;
|
|
1135
|
-
const b64 = (b64url + "=".repeat(padLen))
|
|
1136
|
-
.replace(/-/g, "+")
|
|
1137
|
-
.replace(/_/g, "/");
|
|
1138
|
-
// Node.js environment
|
|
1139
|
-
if (typeof Buffer !== "undefined") {
|
|
1140
|
-
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
1141
|
-
}
|
|
1142
|
-
// Browser environment
|
|
1143
|
-
const binary = atob(b64);
|
|
1144
|
-
const bytes = new Uint8Array(binary.length);
|
|
1145
|
-
for (let i = 0; i < binary.length; i++) {
|
|
1146
|
-
bytes[i] = binary.charCodeAt(i);
|
|
1147
|
-
}
|
|
1148
|
-
return bytes;
|
|
1149
|
-
}
|
|
1150
|
-
// ---------------------------
|
|
1151
|
-
// Compression Helpers
|
|
1152
|
-
// ---------------------------
|
|
1153
|
-
/** Compress using zlib deflate raw */
|
|
1154
|
-
static deflateRaw(data) {
|
|
1155
|
-
// Node.js environment
|
|
1156
|
-
if (typeof require !== "undefined") {
|
|
1157
|
-
try {
|
|
1158
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1159
|
-
const zlib = require("zlib");
|
|
1160
|
-
return new Uint8Array(zlib.deflateRawSync(Buffer.from(data)));
|
|
786
|
+
if (/^H3:/i.test(l)) {
|
|
787
|
+
components.h3Cell = l.slice(3);
|
|
788
|
+
continue;
|
|
1161
789
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
790
|
+
if (/^3W:/i.test(l)) {
|
|
791
|
+
components.what3words = l.slice(3);
|
|
792
|
+
continue;
|
|
1164
793
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
}
|
|
1169
|
-
/** Decompress using zlib inflate raw */
|
|
1170
|
-
static inflateRaw(data) {
|
|
1171
|
-
// Node.js environment
|
|
1172
|
-
if (typeof require !== "undefined") {
|
|
1173
|
-
try {
|
|
1174
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1175
|
-
const zlib = require("zlib");
|
|
1176
|
-
return new Uint8Array(zlib.inflateRawSync(Buffer.from(data)));
|
|
794
|
+
if (/^plus:/i.test(l)) {
|
|
795
|
+
components.plusCode = l.slice(5);
|
|
796
|
+
continue;
|
|
1177
797
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
798
|
+
// Network
|
|
799
|
+
if (/^net:ip4:/i.test(l)) {
|
|
800
|
+
components.ipv4 = l.slice(8);
|
|
801
|
+
continue;
|
|
1180
802
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1185
|
-
// ---------------------------
|
|
1186
|
-
// Cryptographic Sealing (Ed25519)
|
|
1187
|
-
// ---------------------------
|
|
1188
|
-
/**
|
|
1189
|
-
* Seal (sign) a TPS string to create a cryptographically verifiable TPS-UID.
|
|
1190
|
-
* This appends an Ed25519 signature to the binary form.
|
|
1191
|
-
*
|
|
1192
|
-
* @param tps - The TPS string to seal
|
|
1193
|
-
* @param privateKey - Ed25519 private key (hex or buffer)
|
|
1194
|
-
* @param opts - Encoding options
|
|
1195
|
-
* @returns Sealed binary TPS-UID
|
|
1196
|
-
*/
|
|
1197
|
-
static seal(tps, privateKey, opts) {
|
|
1198
|
-
// 1. Create standard binary (unsealed first)
|
|
1199
|
-
// We force the SEAL flag (bit 1) to be 0 initially for the "content to sign"
|
|
1200
|
-
// But wait, we want the signature to cover the header too.
|
|
1201
|
-
// Strategy: Construct the full binary with SEAL flag OFF, sign it, then set SEAL flag ON and append sig.
|
|
1202
|
-
// Actually, the standard way is:
|
|
1203
|
-
// Content = MAGIC + VER + FLAGS(with seal bit set) + TIME + NONCE + LEN + PAYLOAD
|
|
1204
|
-
// Signature = Sign(Content)
|
|
1205
|
-
// Final = Content + SEAL_TYPE + SIGNATURE
|
|
1206
|
-
const compress = opts?.compress ?? false;
|
|
1207
|
-
const epochMs = opts?.epochMs ?? this.epochMsFromTPSString(tps);
|
|
1208
|
-
// Validate epoch
|
|
1209
|
-
if (!Number.isInteger(epochMs) || epochMs < 0 || epochMs > 0xffffffffffff) {
|
|
1210
|
-
throw new Error("epochMs must be a valid 48-bit non-negative integer");
|
|
1211
|
-
}
|
|
1212
|
-
// Flags: Bit 0 = compress, Bit 1 = sealed
|
|
1213
|
-
const flags = (compress ? 0x01 : 0x00) | 0x02; // Set SEAL bit
|
|
1214
|
-
// Generate Nonce
|
|
1215
|
-
const nonceBuf = this.randomBytes(4);
|
|
1216
|
-
// Encode Payload
|
|
1217
|
-
const tpsUtf8 = new TextEncoder().encode(tps);
|
|
1218
|
-
const payload = compress ? this.deflateRaw(tpsUtf8) : tpsUtf8;
|
|
1219
|
-
const lenVar = this.uvarintEncode(payload.length);
|
|
1220
|
-
// Construct Content (Header + Payload)
|
|
1221
|
-
const contentLen = 4 + 1 + 1 + 6 + 4 + lenVar.length + payload.length;
|
|
1222
|
-
const content = new Uint8Array(contentLen);
|
|
1223
|
-
let offset = 0;
|
|
1224
|
-
content.set(this.MAGIC, offset);
|
|
1225
|
-
offset += 4;
|
|
1226
|
-
content[offset++] = this.VER;
|
|
1227
|
-
content[offset++] = flags;
|
|
1228
|
-
content.set(this.writeU48(epochMs), offset);
|
|
1229
|
-
offset += 6;
|
|
1230
|
-
content.set(nonceBuf, offset);
|
|
1231
|
-
offset += 4;
|
|
1232
|
-
content.set(lenVar, offset);
|
|
1233
|
-
offset += lenVar.length;
|
|
1234
|
-
content.set(payload, offset);
|
|
1235
|
-
// Sign the content
|
|
1236
|
-
const signature = this.signEd25519(content, privateKey);
|
|
1237
|
-
const sealType = 0x01; // Ed25519
|
|
1238
|
-
// Final Output: Content + SealType (1) + Signature (64)
|
|
1239
|
-
const final = new Uint8Array(contentLen + 1 + signature.length);
|
|
1240
|
-
final.set(content, 0);
|
|
1241
|
-
final.set([sealType], contentLen);
|
|
1242
|
-
final.set(signature, contentLen + 1);
|
|
1243
|
-
return final;
|
|
1244
|
-
}
|
|
1245
|
-
/**
|
|
1246
|
-
* Verify a sealed TPS-UID and decode it.
|
|
1247
|
-
* Throws if signature is invalid or not sealed.
|
|
1248
|
-
*
|
|
1249
|
-
* @param sealedBytes - The binary sealed TPS-UID
|
|
1250
|
-
* @param publicKey - Ed25519 public key (hex or buffer) to verify against
|
|
1251
|
-
* @returns Decoded result
|
|
1252
|
-
*/
|
|
1253
|
-
static verifyAndDecode(sealedBytes, publicKey) {
|
|
1254
|
-
if (sealedBytes.length < 18)
|
|
1255
|
-
throw new Error("TPSUID7RB: too short");
|
|
1256
|
-
// Check Magic
|
|
1257
|
-
if (sealedBytes[0] !== 0x54 ||
|
|
1258
|
-
sealedBytes[1] !== 0x50 ||
|
|
1259
|
-
sealedBytes[2] !== 0x55 ||
|
|
1260
|
-
sealedBytes[3] !== 0x37) {
|
|
1261
|
-
throw new Error("TPSUID7RB: bad magic");
|
|
1262
|
-
}
|
|
1263
|
-
// Check Flags for Sealed Bit (bit 1)
|
|
1264
|
-
const flags = sealedBytes[5];
|
|
1265
|
-
if ((flags & 0x02) === 0) {
|
|
1266
|
-
throw new Error("TPSUID7RB: not a sealed UID");
|
|
1267
|
-
}
|
|
1268
|
-
// 1. Parse the structure to find where content ends
|
|
1269
|
-
// We need to parse LEN and Payload to find the split point
|
|
1270
|
-
let offset = 16; // Start of LEN
|
|
1271
|
-
// Decode LEN
|
|
1272
|
-
const { value: tpsLen, bytesRead } = this.uvarintDecode(sealedBytes, offset);
|
|
1273
|
-
offset += bytesRead;
|
|
1274
|
-
const payloadEnd = offset + tpsLen;
|
|
1275
|
-
if (payloadEnd > sealedBytes.length) {
|
|
1276
|
-
throw new Error("TPSUID7RB: length overflow (truncated)");
|
|
1277
|
-
}
|
|
1278
|
-
// The Content to verify matches exactly [0 ... payloadEnd]
|
|
1279
|
-
const content = sealedBytes.slice(0, payloadEnd);
|
|
1280
|
-
// After content: SealType (1 byte) + Signature
|
|
1281
|
-
if (sealedBytes.length <= payloadEnd + 1) {
|
|
1282
|
-
throw new Error("TPSUID7RB: missing signature data");
|
|
1283
|
-
}
|
|
1284
|
-
const sealType = sealedBytes[payloadEnd];
|
|
1285
|
-
if (sealType !== 0x01) {
|
|
1286
|
-
throw new Error(`TPSUID7RB: unsupported seal type 0x${sealType.toString(16)}`);
|
|
1287
|
-
}
|
|
1288
|
-
const signature = sealedBytes.slice(payloadEnd + 1);
|
|
1289
|
-
if (signature.length !== 64) {
|
|
1290
|
-
throw new Error(`TPSUID7RB: invalid Ed25519 signature length ${signature.length}`);
|
|
1291
|
-
}
|
|
1292
|
-
// Verify
|
|
1293
|
-
const isValid = this.verifyEd25519(content, signature, publicKey);
|
|
1294
|
-
if (!isValid) {
|
|
1295
|
-
throw new Error("TPSUID7RB: signature verification failed");
|
|
1296
|
-
}
|
|
1297
|
-
// Decode (reuse standard logic, but ignoring the extra bytes at end is fine?)
|
|
1298
|
-
// Actually standard logic doesn't expect trailing bytes unless we tell it to.
|
|
1299
|
-
// But since we verified, we can just slice the content and decode that as a strict binary
|
|
1300
|
-
// EXCEPT standard decodeBinary checks strict length.
|
|
1301
|
-
// So we manually decode the components here to be safe and efficient.
|
|
1302
|
-
return this.decodeBinary(content); // Reuse strict decoder on the content part
|
|
1303
|
-
}
|
|
1304
|
-
// --- Crypto Implementation (Ed25519) ---
|
|
1305
|
-
static signEd25519(data, privateKey) {
|
|
1306
|
-
if (typeof require !== "undefined") {
|
|
1307
|
-
try {
|
|
1308
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1309
|
-
const crypto = require("crypto");
|
|
1310
|
-
let key;
|
|
1311
|
-
if (typeof privateKey === "string") {
|
|
1312
|
-
if (privateKey.includes("PRIVATE KEY")) {
|
|
1313
|
-
// PEM format — use directly
|
|
1314
|
-
key = privateKey;
|
|
1315
|
-
}
|
|
1316
|
-
else {
|
|
1317
|
-
// Hex-encoded DER/PKCS8
|
|
1318
|
-
key = crypto.createPrivateKey({
|
|
1319
|
-
key: Buffer.from(privateKey, "hex"),
|
|
1320
|
-
format: "der",
|
|
1321
|
-
type: "pkcs8",
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
else if (typeof privateKey === "object" &&
|
|
1326
|
-
privateKey !== null &&
|
|
1327
|
-
"asymmetricKeyType" in privateKey) {
|
|
1328
|
-
// Node.js KeyObject (e.g. from crypto.generateKeyPairSync)
|
|
1329
|
-
key = privateKey;
|
|
1330
|
-
}
|
|
1331
|
-
else {
|
|
1332
|
-
// Buffer or Uint8Array — assume DER/PKCS8 encoded
|
|
1333
|
-
key = crypto.createPrivateKey({
|
|
1334
|
-
key: Buffer.from(privateKey),
|
|
1335
|
-
format: "der",
|
|
1336
|
-
type: "pkcs8",
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
return new Uint8Array(crypto.sign(null, data, key));
|
|
803
|
+
if (/^net:ip6:/i.test(l)) {
|
|
804
|
+
components.ipv6 = l.slice(8);
|
|
805
|
+
continue;
|
|
1340
806
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
807
|
+
if (/^node:/i.test(l)) {
|
|
808
|
+
components.nodeName = l.slice(5);
|
|
809
|
+
continue;
|
|
1343
810
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
if (typeof require !== "undefined") {
|
|
1349
|
-
try {
|
|
1350
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1351
|
-
const crypto = require("crypto");
|
|
1352
|
-
return crypto.verify(null, data, publicKey, signature);
|
|
811
|
+
// Structural
|
|
812
|
+
if (/^bldg:/i.test(l)) {
|
|
813
|
+
components.building = l.slice(5);
|
|
814
|
+
continue;
|
|
1353
815
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
816
|
+
if (/^floor:/i.test(l)) {
|
|
817
|
+
components.floor = l.slice(6);
|
|
818
|
+
continue;
|
|
1356
819
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
// ---------------------------
|
|
1361
|
-
// Random Bytes
|
|
1362
|
-
// ---------------------------
|
|
1363
|
-
/** Generate cryptographically secure random bytes */
|
|
1364
|
-
static randomBytes(length) {
|
|
1365
|
-
// Node.js environment
|
|
1366
|
-
if (typeof require !== "undefined") {
|
|
1367
|
-
try {
|
|
1368
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1369
|
-
const crypto = require("crypto");
|
|
1370
|
-
return new Uint8Array(crypto.randomBytes(length));
|
|
820
|
+
if (/^room:/i.test(l)) {
|
|
821
|
+
components.room = l.slice(5);
|
|
822
|
+
continue;
|
|
1371
823
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
824
|
+
if (/^door:/i.test(l)) {
|
|
825
|
+
components.door = l.slice(5);
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
if (/^zone:/i.test(l)) {
|
|
829
|
+
components.zone = l.slice(5);
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
// Fallback: generic space anchor (adm:, planet:, legacy strings)
|
|
833
|
+
if (l) {
|
|
834
|
+
components.spaceAnchor = components.spaceAnchor
|
|
835
|
+
? components.spaceAnchor + ";" + l
|
|
836
|
+
: l;
|
|
1374
837
|
}
|
|
1375
838
|
}
|
|
1376
|
-
// Browser or fallback
|
|
1377
|
-
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
1378
|
-
const bytes = new Uint8Array(length);
|
|
1379
|
-
crypto.getRandomValues(bytes);
|
|
1380
|
-
return bytes;
|
|
1381
|
-
}
|
|
1382
|
-
throw new Error("TPSUID7RB: no crypto available");
|
|
1383
839
|
}
|
|
1384
840
|
}
|
|
1385
|
-
exports.
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
841
|
+
exports.TPS = TPS;
|
|
842
|
+
// --- PLUGIN REGISTRY ---
|
|
843
|
+
/** Shared DriverManager instance — use TPS.driverManager for direct access. */
|
|
844
|
+
TPS.driverManager = new driver_manager_2.DriverManager();
|
|
845
|
+
// --- REGEX (v0.6.0) ---
|
|
846
|
+
// The URI and time regexes are intentionally permissive in the location &
|
|
847
|
+
// extension sections — detailed semantic parsing happens in
|
|
848
|
+
// _mapGroupsToComponents() and the layer parsers below.
|
|
849
|
+
//
|
|
850
|
+
// Structure:
|
|
851
|
+
// tps://[location]/A:[actor]@T:[cal].[tokens];[ext];...#C:[ctx];...
|
|
852
|
+
//
|
|
853
|
+
// The `;` separator is used consistently:
|
|
854
|
+
// - between location layers (before @T:)
|
|
855
|
+
// - between extensions (after T: tokens, before #)
|
|
856
|
+
// - between context key=val pairs (after #C:)
|
|
857
|
+
TPS.REGEX_URI = new RegExp("^tps://" +
|
|
858
|
+
// Location: everything up to optional /A: actor and then @T:
|
|
859
|
+
"(?<location>[^@]+?)" +
|
|
860
|
+
// Optional actor overlay
|
|
861
|
+
"(?:/A:(?<actor>[^@]+))?" +
|
|
862
|
+
// Time section
|
|
863
|
+
"@T:(?<calendar>[a-z]{3,4})" +
|
|
864
|
+
"(?<tokens>(?:\\.[a-z]-?[\\d.]+)*)" +
|
|
865
|
+
// Optional signature
|
|
866
|
+
"(?:!(?<signature>[^;#]+))?" +
|
|
867
|
+
// Optional extensions (;KEY:val;key=val;...)
|
|
868
|
+
"(?:;(?<extensions>[^#]+))?" +
|
|
869
|
+
// Optional context fragment (#C:key=val;...)
|
|
870
|
+
"(?:#C:(?<context>.+))?$");
|
|
871
|
+
TPS.REGEX_TIME = new RegExp("^T:(?<calendar>[a-z]{3,4})" +
|
|
872
|
+
"(?<tokens>(?:\\.[a-z]-?[\\d.]+)*)" +
|
|
873
|
+
"(?:!(?<signature>[^;#]+))?" +
|
|
874
|
+
"(?:;(?<extensions>[^#]+))?" +
|
|
875
|
+
"(?:#C:(?<context>.+))?$");
|
|
876
|
+
// register built-in drivers and set default
|
|
877
|
+
// (tps and gregorian provide canonical conversions before unix)
|
|
878
|
+
TPS.registerDriver(new tps_1.TpsDriver());
|
|
879
|
+
TPS.registerDriver(new gregorian_1.GregorianDriver());
|
|
880
|
+
TPS.registerDriver(new unix_1.UnixDriver());
|
|
881
|
+
TPS.registerDriver(new persian_1.PersianDriver());
|
|
882
|
+
TPS.registerDriver(new hijri_1.HijriDriver());
|
|
883
|
+
TPS.registerDriver(new julian_1.JulianDriver());
|
|
884
|
+
TPS.registerDriver(new holocene_1.HoloceneDriver());
|
|
885
|
+
TPS.registerDriver(new chinese_1.ChineseDriver());
|
|
1394
886
|
/**
|
|
1395
887
|
* `TpsDate` is a Date-like wrapper with native TPS conversion helpers.
|
|
1396
888
|
*
|
|
@@ -1401,161 +893,4 @@ TPSUID7RB.REGEX = /^tpsuid7rb_[A-Za-z0-9_-]+$/;
|
|
|
1401
893
|
* - `new TpsDate(tpsString)`
|
|
1402
894
|
* - `new TpsDate(year, monthIndex, day?, hour?, minute?, second?, ms?)`
|
|
1403
895
|
*/
|
|
1404
|
-
class TpsDate {
|
|
1405
|
-
getTpsComponents() {
|
|
1406
|
-
const parsed = TPS.parse(this.toTPS(exports.DefaultCalendars.TPS));
|
|
1407
|
-
if (!parsed) {
|
|
1408
|
-
throw new Error("TpsDate: failed to derive TPS components");
|
|
1409
|
-
}
|
|
1410
|
-
return parsed;
|
|
1411
|
-
}
|
|
1412
|
-
getTpsFullYear() {
|
|
1413
|
-
const comp = this.getTpsComponents();
|
|
1414
|
-
return (comp.millennium - 1) * 1000 + (comp.century - 1) * 100 + comp.year;
|
|
1415
|
-
}
|
|
1416
|
-
constructor(...args) {
|
|
1417
|
-
if (args.length === 0) {
|
|
1418
|
-
this.internal = new Date();
|
|
1419
|
-
return;
|
|
1420
|
-
}
|
|
1421
|
-
if (args.length === 1) {
|
|
1422
|
-
const value = args[0];
|
|
1423
|
-
if (value instanceof TpsDate) {
|
|
1424
|
-
this.internal = new Date(value.getTime());
|
|
1425
|
-
return;
|
|
1426
|
-
}
|
|
1427
|
-
if (value instanceof Date) {
|
|
1428
|
-
this.internal = new Date(value.getTime());
|
|
1429
|
-
return;
|
|
1430
|
-
}
|
|
1431
|
-
if (typeof value === "string" && TpsDate.looksLikeTPS(value)) {
|
|
1432
|
-
const parsed = TPS.toDate(value);
|
|
1433
|
-
if (!parsed) {
|
|
1434
|
-
throw new RangeError(`Invalid TPS date string: ${value}`);
|
|
1435
|
-
}
|
|
1436
|
-
this.internal = parsed;
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
this.internal = new Date(value);
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
const [year, monthIndex, day, hours, minutes, seconds, ms] = args;
|
|
1443
|
-
this.internal = new Date(year, monthIndex, day ?? 1, hours ?? 0, minutes ?? 0, seconds ?? 0, ms ?? 0);
|
|
1444
|
-
}
|
|
1445
|
-
static looksLikeTPS(input) {
|
|
1446
|
-
const s = input.trim();
|
|
1447
|
-
return s.startsWith("tps://") || s.startsWith("T:") || s.startsWith("t:");
|
|
1448
|
-
}
|
|
1449
|
-
static now() {
|
|
1450
|
-
return Date.now();
|
|
1451
|
-
}
|
|
1452
|
-
static parse(input) {
|
|
1453
|
-
if (this.looksLikeTPS(input)) {
|
|
1454
|
-
const d = TPS.toDate(input);
|
|
1455
|
-
return d ? d.getTime() : Number.NaN;
|
|
1456
|
-
}
|
|
1457
|
-
return Date.parse(input);
|
|
1458
|
-
}
|
|
1459
|
-
static UTC(year, monthIndex, day, hours, minutes, seconds, ms) {
|
|
1460
|
-
return Date.UTC(year, monthIndex, day ?? 1, hours ?? 0, minutes ?? 0, seconds ?? 0, ms ?? 0);
|
|
1461
|
-
}
|
|
1462
|
-
static fromTPS(tps) {
|
|
1463
|
-
return new TpsDate(tps);
|
|
1464
|
-
}
|
|
1465
|
-
toGregorianDate() {
|
|
1466
|
-
return new Date(this.internal.getTime());
|
|
1467
|
-
}
|
|
1468
|
-
toDate() {
|
|
1469
|
-
return this.toGregorianDate();
|
|
1470
|
-
}
|
|
1471
|
-
toTPS(calendar = exports.DefaultCalendars.TPS, opts) {
|
|
1472
|
-
return TPS.fromDate(this.internal, calendar, opts);
|
|
1473
|
-
}
|
|
1474
|
-
toTPSURI(calendar = exports.DefaultCalendars.TPS, opts) {
|
|
1475
|
-
const time = this.toTPS(calendar, { order: opts?.order });
|
|
1476
|
-
const comp = TPS.parse(time);
|
|
1477
|
-
if (opts?.latitude !== undefined && opts?.longitude !== undefined) {
|
|
1478
|
-
comp.latitude = opts.latitude;
|
|
1479
|
-
comp.longitude = opts.longitude;
|
|
1480
|
-
if (opts.altitude !== undefined)
|
|
1481
|
-
comp.altitude = opts.altitude;
|
|
1482
|
-
}
|
|
1483
|
-
else if (opts?.isHiddenLocation) {
|
|
1484
|
-
comp.isHiddenLocation = true;
|
|
1485
|
-
}
|
|
1486
|
-
else if (opts?.isRedactedLocation) {
|
|
1487
|
-
comp.isRedactedLocation = true;
|
|
1488
|
-
}
|
|
1489
|
-
else {
|
|
1490
|
-
comp.isUnknownLocation = true;
|
|
1491
|
-
}
|
|
1492
|
-
return TPS.toURI(comp);
|
|
1493
|
-
}
|
|
1494
|
-
getTime() {
|
|
1495
|
-
return this.internal.getTime();
|
|
1496
|
-
}
|
|
1497
|
-
valueOf() {
|
|
1498
|
-
return this.internal.valueOf();
|
|
1499
|
-
}
|
|
1500
|
-
toString() {
|
|
1501
|
-
return this.toTPS(exports.DefaultCalendars.TPS);
|
|
1502
|
-
}
|
|
1503
|
-
toISOString() {
|
|
1504
|
-
return this.internal.toISOString();
|
|
1505
|
-
}
|
|
1506
|
-
toUTCString() {
|
|
1507
|
-
return this.internal.toUTCString();
|
|
1508
|
-
}
|
|
1509
|
-
toJSON() {
|
|
1510
|
-
return this.internal.toJSON();
|
|
1511
|
-
}
|
|
1512
|
-
getFullYear() {
|
|
1513
|
-
return this.getTpsFullYear();
|
|
1514
|
-
}
|
|
1515
|
-
getUTCFullYear() {
|
|
1516
|
-
return this.getTpsFullYear();
|
|
1517
|
-
}
|
|
1518
|
-
getMonth() {
|
|
1519
|
-
return this.getTpsComponents().month - 1;
|
|
1520
|
-
}
|
|
1521
|
-
getUTCMonth() {
|
|
1522
|
-
return this.getTpsComponents().month - 1;
|
|
1523
|
-
}
|
|
1524
|
-
getDate() {
|
|
1525
|
-
return this.getTpsComponents().day;
|
|
1526
|
-
}
|
|
1527
|
-
getUTCDate() {
|
|
1528
|
-
return this.getTpsComponents().day;
|
|
1529
|
-
}
|
|
1530
|
-
getHours() {
|
|
1531
|
-
return this.getTpsComponents().hour;
|
|
1532
|
-
}
|
|
1533
|
-
getUTCHours() {
|
|
1534
|
-
return this.getTpsComponents().hour;
|
|
1535
|
-
}
|
|
1536
|
-
getMinutes() {
|
|
1537
|
-
return this.getTpsComponents().minute;
|
|
1538
|
-
}
|
|
1539
|
-
getUTCMinutes() {
|
|
1540
|
-
return this.getTpsComponents().minute;
|
|
1541
|
-
}
|
|
1542
|
-
getSeconds() {
|
|
1543
|
-
return this.getTpsComponents().second;
|
|
1544
|
-
}
|
|
1545
|
-
getUTCSeconds() {
|
|
1546
|
-
return this.getTpsComponents().second;
|
|
1547
|
-
}
|
|
1548
|
-
getMilliseconds() {
|
|
1549
|
-
return this.getTpsComponents().millisecond;
|
|
1550
|
-
}
|
|
1551
|
-
getUTCMilliseconds() {
|
|
1552
|
-
return this.getTpsComponents().millisecond;
|
|
1553
|
-
}
|
|
1554
|
-
[Symbol.toPrimitive](hint) {
|
|
1555
|
-
if (hint === "number")
|
|
1556
|
-
return this.valueOf();
|
|
1557
|
-
return this.toString();
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
exports.TpsDate = TpsDate;
|
|
1561
896
|
//# sourceMappingURL=index.js.map
|