@nextera.one/tps-standard 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -0
- package/README.md +273 -50
- package/dist/index.d.ts +394 -4
- package/dist/index.js +859 -22
- package/dist/src/index.js +681 -0
- package/dist/test/src/index.js +963 -0
- package/dist/test/test/persian-calendar.test.js +488 -0
- package/dist/test/test/tps-uid.test.js +295 -0
- package/dist/test/tps-uid.test.js +240 -0
- package/package.json +4 -3
- package/src/index.ts +1197 -24
package/LICENSE
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
Copyright 2026 TPS Standards Working Group
|
|
181
|
+
|
|
182
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
183
|
+
you may not use this file except in compliance with the License.
|
|
184
|
+
You may obtain a copy of the License at
|
|
185
|
+
|
|
186
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
187
|
+
|
|
188
|
+
Unless required by applicable law or agreed to in writing, software
|
|
189
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
190
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
191
|
+
See the License for the specific language governing permissions and
|
|
192
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# TPS — Temporal Positioning Standard
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
> **A universal coordinate system for Space, Time, and Context.**
|
|
4
6
|
|
|
5
7
|
TPS defines a deterministic way to represent **when** something happened, **where** it happened, and **under which calendar**, using a single, machine-readable identifier.
|
|
@@ -7,7 +9,7 @@ TPS defines a deterministic way to represent **when** something happened, **wher
|
|
|
7
9
|
## 📦 Installation
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
|
-
npm
|
|
12
|
+
npm i @nextera.one/tps-standard
|
|
11
13
|
```
|
|
12
14
|
|
|
13
15
|
## 🚀 Quick Start
|
|
@@ -15,27 +17,27 @@ npm install tps-standard
|
|
|
15
17
|
### Basic Example
|
|
16
18
|
|
|
17
19
|
```ts
|
|
18
|
-
import { TPS } from
|
|
20
|
+
import { TPS } from "@nextera.one/tps-standard";
|
|
19
21
|
|
|
20
22
|
// Create a TPS time string from current date
|
|
21
|
-
const nowTime = TPS.fromDate(new Date(),
|
|
23
|
+
const nowTime = TPS.fromDate(new Date(), "greg");
|
|
22
24
|
console.log(nowTime);
|
|
23
25
|
// Output: "T:greg.m3.c1.y26.M01.d07.h13.n20.s45"
|
|
24
26
|
|
|
25
27
|
// Parse a full TPS URI with location and extensions
|
|
26
|
-
const uri =
|
|
28
|
+
const uri = "tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d07.h13.n20;f4.r7";
|
|
27
29
|
const parsed = TPS.parse(uri);
|
|
28
30
|
console.log(parsed);
|
|
29
31
|
// { latitude: 31.95, longitude: 35.91, altitude: 800, calendar: 'greg', year: 26, ... }
|
|
30
32
|
|
|
31
33
|
// Convert back to URI
|
|
32
34
|
const components = {
|
|
33
|
-
calendar:
|
|
35
|
+
calendar: "greg",
|
|
34
36
|
year: 26,
|
|
35
37
|
month: 1,
|
|
36
38
|
day: 7,
|
|
37
39
|
latitude: 31.95,
|
|
38
|
-
longitude: 35.91
|
|
40
|
+
longitude: 35.91,
|
|
39
41
|
};
|
|
40
42
|
const uriString = TPS.toURI(components);
|
|
41
43
|
console.log(uriString);
|
|
@@ -52,34 +54,37 @@ TPS represents time as a coordinate using this hierarchy:
|
|
|
52
54
|
T:greg.m3.c1.y26.M01.d07.h13.n20.s45
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
| Component | Meaning
|
|
56
|
-
|
|
57
|
-
| `greg`
|
|
58
|
-
| `m3`
|
|
59
|
-
| `c1`
|
|
60
|
-
| `y26`
|
|
61
|
-
| `M01`
|
|
62
|
-
| `d07`
|
|
63
|
-
| `h13`
|
|
64
|
-
| `n20`
|
|
65
|
-
| `s45`
|
|
57
|
+
| Component | Meaning |
|
|
58
|
+
| --------- | ------------------------- |
|
|
59
|
+
| `greg` | Calendar code (gregorian) |
|
|
60
|
+
| `m3` | Millennium 3 (2000-2999) |
|
|
61
|
+
| `c1` | Century 1 (2000-2099) |
|
|
62
|
+
| `y26` | Year 26 (2026) |
|
|
63
|
+
| `M01` | Month 01 (January) |
|
|
64
|
+
| `d07` | Day 07 |
|
|
65
|
+
| `h13` | Hour 13 (1:00 PM) |
|
|
66
|
+
| `n20` | Minute 20 |
|
|
67
|
+
| `s45` | Second 45 |
|
|
66
68
|
|
|
67
69
|
Partial coordinates represent **time volumes** (entire year, century, etc.).
|
|
68
70
|
|
|
69
71
|
### TPS URI Format
|
|
70
72
|
|
|
71
73
|
**Canonical form:**
|
|
74
|
+
|
|
72
75
|
```
|
|
73
76
|
tps://[SPACE]@[TIME][;EXTENSIONS]
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
#### Components
|
|
77
80
|
|
|
78
|
-
| Component
|
|
79
|
-
|
|
80
|
-
| `SPACE`
|
|
81
|
-
| `TIME`
|
|
82
|
-
| `EXTENSIONS` | Optional context
|
|
81
|
+
| Component | Description |
|
|
82
|
+
| ------------ | --------------------------------------------------------- |
|
|
83
|
+
| `SPACE` | `lat,lon[,alt]m` (WGS84) or `unknown`/`hidden`/`redacted` |
|
|
84
|
+
| `TIME` | TPS Time format (`T:calendar.hierarchy`) |
|
|
85
|
+
| `EXTENSIONS` | Optional context after `;`, pairs separated by `.` |
|
|
86
|
+
|
|
87
|
+
Extensions are encoded as compact pairs with no `=` (e.g. `;f4.r7` means `{ f: "4", r: "7" }`).
|
|
83
88
|
|
|
84
89
|
#### Location Privacy
|
|
85
90
|
|
|
@@ -92,57 +97,156 @@ tps://[SPACE]@[TIME][;EXTENSIONS]
|
|
|
92
97
|
|
|
93
98
|
- `greg` — Gregorian calendar (default)
|
|
94
99
|
- `unix` — Unix epoch seconds
|
|
95
|
-
- `hij` — Hijri (Islamic) —
|
|
96
|
-
- `jul` — Julian —
|
|
97
|
-
- `holo` — Holocene —
|
|
100
|
+
- `hij` — Hijri (Islamic) — _requires driver_
|
|
101
|
+
- `jul` — Julian — _requires driver_
|
|
102
|
+
- `holo` — Holocene — _requires driver_
|
|
98
103
|
|
|
99
104
|
## 🔌 Plugin Architecture
|
|
100
105
|
|
|
101
|
-
TPS supports custom calendar drivers for non-Gregorian systems.
|
|
106
|
+
TPS supports custom calendar drivers for non-Gregorian systems. Drivers can wrap external date libraries (like `moment-hijri`, `@js-joda/extra`, etc.).
|
|
102
107
|
|
|
103
108
|
### CalendarDriver Interface
|
|
104
109
|
|
|
105
110
|
```ts
|
|
106
111
|
export interface CalendarDriver {
|
|
107
112
|
readonly code: CalendarCode;
|
|
113
|
+
readonly name?: string; // Optional human-readable name
|
|
114
|
+
|
|
115
|
+
// Required methods
|
|
108
116
|
fromGregorian(date: Date): Partial<TPSComponents>;
|
|
109
117
|
toGregorian(components: Partial<TPSComponents>): Date;
|
|
110
118
|
fromDate(date: Date): string;
|
|
119
|
+
|
|
120
|
+
// Optional enhanced methods
|
|
121
|
+
parseDate?(input: string, format?: string): Partial<TPSComponents>;
|
|
122
|
+
format?(components: Partial<TPSComponents>, format?: string): string;
|
|
123
|
+
validate?(input: string | Partial<TPSComponents>): boolean;
|
|
124
|
+
getMetadata?(): CalendarMetadata;
|
|
111
125
|
}
|
|
112
126
|
```
|
|
113
127
|
|
|
114
128
|
### Register a Custom Driver
|
|
115
129
|
|
|
116
130
|
```ts
|
|
117
|
-
import { TPS, CalendarDriver, TPSComponents } from
|
|
131
|
+
import { TPS, CalendarDriver, TPSComponents } from "@nextera.one/tps-standard";
|
|
118
132
|
|
|
119
133
|
class HijriDriver implements CalendarDriver {
|
|
120
|
-
readonly code =
|
|
134
|
+
readonly code = "hij";
|
|
135
|
+
readonly name = "Hijri (Islamic)";
|
|
136
|
+
|
|
137
|
+
// Parse a Hijri date string like '1447-07-21'
|
|
138
|
+
parseDate(input: string): Partial<TPSComponents> {
|
|
139
|
+
const [year, month, day] = input.split("-").map(Number);
|
|
140
|
+
return { calendar: "hij", year, month, day };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Format components to Hijri date string
|
|
144
|
+
format(comp: Partial<TPSComponents>): string {
|
|
145
|
+
return `${comp.year}-${String(comp.month).padStart(2, "0")}-${String(
|
|
146
|
+
comp.day
|
|
147
|
+
).padStart(2, "0")}`;
|
|
148
|
+
}
|
|
121
149
|
|
|
122
150
|
fromGregorian(date: Date): Partial<TPSComponents> {
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
151
|
+
// Use external library for accurate conversion
|
|
152
|
+
// Example with moment-hijri:
|
|
153
|
+
// const m = moment(date);
|
|
154
|
+
// return { year: m.iYear(), month: m.iMonth() + 1, day: m.iDate() };
|
|
155
|
+
return { year: 1447, month: 7, day: 21 };
|
|
126
156
|
}
|
|
127
157
|
|
|
128
158
|
toGregorian(components: Partial<TPSComponents>): Date {
|
|
129
|
-
//
|
|
130
|
-
// ... conversion logic ...
|
|
159
|
+
// Reverse conversion using external library
|
|
131
160
|
return new Date();
|
|
132
161
|
}
|
|
133
162
|
|
|
134
163
|
fromDate(date: Date): string {
|
|
135
164
|
const comp = this.fromGregorian(date);
|
|
136
|
-
|
|
165
|
+
const pad = (n?: number) => String(n || 0).padStart(2, "0");
|
|
166
|
+
return `T:hij.y${comp.year}.M${pad(comp.month)}.d${pad(comp.day)}`;
|
|
137
167
|
}
|
|
138
168
|
}
|
|
139
169
|
|
|
140
170
|
// Register the driver
|
|
141
171
|
TPS.registerDriver(new HijriDriver());
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Using Calendar Drivers
|
|
142
175
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
176
|
+
```ts
|
|
177
|
+
// Parse a Hijri date string directly
|
|
178
|
+
const components = TPS.parseCalendarDate("hij", "1447-07-21");
|
|
179
|
+
// { calendar: 'hij', year: 1447, month: 7, day: 21 }
|
|
180
|
+
|
|
181
|
+
// Convert to TPS URI with location
|
|
182
|
+
const uri = TPS.fromCalendarDate("hij", "1447-07-21", {
|
|
183
|
+
latitude: 31.95,
|
|
184
|
+
longitude: 35.91,
|
|
185
|
+
});
|
|
186
|
+
// "tps://31.95,35.91@T:hij.y1447.M07.d21"
|
|
187
|
+
|
|
188
|
+
// Format TPS components back to calendar-native string
|
|
189
|
+
const parsed = TPS.parse("tps://unknown@T:hij.y1447.M07.d21");
|
|
190
|
+
const formatted = TPS.formatCalendarDate("hij", parsed);
|
|
191
|
+
// "1447-07-21"
|
|
192
|
+
|
|
193
|
+
// Using the driver directly
|
|
194
|
+
const driver = TPS.getDriver("hij");
|
|
195
|
+
if (driver?.parseDate) {
|
|
196
|
+
const comp = driver.parseDate("1447-07-21");
|
|
197
|
+
const gregDate = driver.toGregorian(comp);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Wrapping External Libraries
|
|
202
|
+
|
|
203
|
+
Example with `moment-hijri`:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import moment from "moment-hijri";
|
|
207
|
+
|
|
208
|
+
class MomentHijriDriver implements CalendarDriver {
|
|
209
|
+
readonly code = "hij";
|
|
210
|
+
|
|
211
|
+
parseDate(input: string, format = "iYYYY-iMM-iDD"): Partial<TPSComponents> {
|
|
212
|
+
const m = moment(input, format);
|
|
213
|
+
return {
|
|
214
|
+
calendar: "hij",
|
|
215
|
+
year: m.iYear(),
|
|
216
|
+
month: m.iMonth() + 1,
|
|
217
|
+
day: m.iDate(),
|
|
218
|
+
hour: m.hour(),
|
|
219
|
+
minute: m.minute(),
|
|
220
|
+
second: m.second(),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fromGregorian(date: Date): Partial<TPSComponents> {
|
|
225
|
+
const m = moment(date);
|
|
226
|
+
return {
|
|
227
|
+
calendar: "hij",
|
|
228
|
+
year: m.iYear(),
|
|
229
|
+
month: m.iMonth() + 1,
|
|
230
|
+
day: m.iDate(),
|
|
231
|
+
hour: m.hour(),
|
|
232
|
+
minute: m.minute(),
|
|
233
|
+
second: m.second(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
toGregorian(comp: Partial<TPSComponents>): Date {
|
|
238
|
+
const m = moment(`${comp.year}-${comp.month}-${comp.day}`, "iYYYY-iM-iD");
|
|
239
|
+
return m.toDate();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fromDate(date: Date): string {
|
|
243
|
+
const c = this.fromGregorian(date);
|
|
244
|
+
const p = (n?: number) => String(n || 0).padStart(2, "0");
|
|
245
|
+
return `T:hij.y${c.year}.M${p(c.month)}.d${p(c.day)}.h${p(c.hour)}.n${p(
|
|
246
|
+
c.minute
|
|
247
|
+
)}.s${p(c.second)}`;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
146
250
|
```
|
|
147
251
|
|
|
148
252
|
## 📚 API Reference
|
|
@@ -152,8 +256,8 @@ console.log(hijriTime);
|
|
|
152
256
|
Validates whether a string is properly formatted TPS.
|
|
153
257
|
|
|
154
258
|
```ts
|
|
155
|
-
TPS.validate(
|
|
156
|
-
TPS.validate(
|
|
259
|
+
TPS.validate("tps://31.95,35.91@T:greg.m3.c1.y26"); // true
|
|
260
|
+
TPS.validate("invalid"); // false
|
|
157
261
|
```
|
|
158
262
|
|
|
159
263
|
### `TPS.parse(input: string): TPSComponents | null`
|
|
@@ -161,7 +265,9 @@ TPS.validate('invalid'); // false
|
|
|
161
265
|
Parses a TPS string into components. Returns `null` if invalid.
|
|
162
266
|
|
|
163
267
|
```ts
|
|
164
|
-
const parsed = TPS.parse(
|
|
268
|
+
const parsed = TPS.parse(
|
|
269
|
+
"tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d07.h13.n20;f4.r7"
|
|
270
|
+
);
|
|
165
271
|
// {
|
|
166
272
|
// latitude: 31.95,
|
|
167
273
|
// longitude: 35.91,
|
|
@@ -184,14 +290,14 @@ Converts a components object into a canonical TPS URI string.
|
|
|
184
290
|
|
|
185
291
|
```ts
|
|
186
292
|
const components = {
|
|
187
|
-
calendar:
|
|
293
|
+
calendar: "greg",
|
|
188
294
|
year: 26,
|
|
189
295
|
month: 1,
|
|
190
296
|
day: 7,
|
|
191
297
|
latitude: 31.95,
|
|
192
298
|
longitude: 35.91,
|
|
193
299
|
altitude: 800,
|
|
194
|
-
extensions: { f: "4", r: "7" }
|
|
300
|
+
extensions: { f: "4", r: "7" },
|
|
195
301
|
};
|
|
196
302
|
const uri = TPS.toURI(components);
|
|
197
303
|
// "tps://31.95,35.91,800m@T:greg.y26.M01.d07;f4.r7"
|
|
@@ -202,10 +308,10 @@ const uri = TPS.toURI(components);
|
|
|
202
308
|
Generates a TPS time string from a JavaScript Date. Supports registered drivers.
|
|
203
309
|
|
|
204
310
|
```ts
|
|
205
|
-
const timeString = TPS.fromDate(new Date(),
|
|
311
|
+
const timeString = TPS.fromDate(new Date(), "greg");
|
|
206
312
|
// "T:greg.m3.c1.y26.M01.d07.h13.n20.s45"
|
|
207
313
|
|
|
208
|
-
const unixTime = TPS.fromDate(new Date(),
|
|
314
|
+
const unixTime = TPS.fromDate(new Date(), "unix");
|
|
209
315
|
// "T:unix.s1704729645.123"
|
|
210
316
|
```
|
|
211
317
|
|
|
@@ -214,7 +320,7 @@ const unixTime = TPS.fromDate(new Date(), 'unix');
|
|
|
214
320
|
Converts a TPS string back to a JavaScript Date object.
|
|
215
321
|
|
|
216
322
|
```ts
|
|
217
|
-
const date = TPS.toDate(
|
|
323
|
+
const date = TPS.toDate("T:greg.m3.c1.y26.M01.d07.h13.n20.s45");
|
|
218
324
|
console.log(date); // Date object for 2026-01-07 13:20:45 UTC
|
|
219
325
|
```
|
|
220
326
|
|
|
@@ -223,8 +329,8 @@ console.log(date); // Date object for 2026-01-07 13:20:45 UTC
|
|
|
223
329
|
Converts a TPS string from one calendar to another using registered drivers.
|
|
224
330
|
|
|
225
331
|
```ts
|
|
226
|
-
const gregTime =
|
|
227
|
-
const hijriTime = TPS.to(
|
|
332
|
+
const gregTime = "T:greg.m3.c1.y26.M01.d07";
|
|
333
|
+
const hijriTime = TPS.to("hij", gregTime);
|
|
228
334
|
// Requires registered Hijri driver
|
|
229
335
|
```
|
|
230
336
|
|
|
@@ -242,7 +348,7 @@ TPS.registerDriver(hijriDriver);
|
|
|
242
348
|
Retrieves a registered calendar driver.
|
|
243
349
|
|
|
244
350
|
```ts
|
|
245
|
-
const driver = TPS.getDriver(
|
|
351
|
+
const driver = TPS.getDriver("hij");
|
|
246
352
|
if (driver) {
|
|
247
353
|
const components = driver.fromGregorian(new Date());
|
|
248
354
|
}
|
|
@@ -284,7 +390,124 @@ interface TPSComponents {
|
|
|
284
390
|
### `CalendarCode`
|
|
285
391
|
|
|
286
392
|
```ts
|
|
287
|
-
type CalendarCode =
|
|
393
|
+
type CalendarCode = "greg" | "hij" | "jul" | "holo" | "unix";
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## 🆔 TPS-UID — Temporal Positioning Identifier
|
|
397
|
+
|
|
398
|
+
TPS-UID is a **time-first, reversible identifier** format that binds an event to a TPS coordinate. Unlike UUIDs which identify records, TPS-UID identifies **events in spacetime** and allows exact reconstruction of the original TPS string.
|
|
399
|
+
|
|
400
|
+
### Why TPS-UID?
|
|
401
|
+
|
|
402
|
+
| Feature | UUID v4/v7 | TPS-UID |
|
|
403
|
+
| ----------------- | ---------------- | --------------- |
|
|
404
|
+
| **Purpose** | Identify objects | Identify events |
|
|
405
|
+
| **Time** | Optional/weak | Mandatory |
|
|
406
|
+
| **Reversible** | ❌ No | ✅ Yes |
|
|
407
|
+
| **Time-sortable** | v7 only | ✅ Always |
|
|
408
|
+
| **Audit-grade** | ❌ No | ✅ Yes |
|
|
409
|
+
|
|
410
|
+
### Binary Schema
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
MAGIC 4 bytes "TPU7"
|
|
414
|
+
VER 1 byte 0x01
|
|
415
|
+
FLAGS 1 byte bit0 = compression
|
|
416
|
+
TIME 6 bytes 48-bit epoch ms
|
|
417
|
+
NONCE 4 bytes collision guard
|
|
418
|
+
LEN varint payload length
|
|
419
|
+
TPS bytes UTF-8 TPS string
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Quick Start
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import { TPSUID7RB } from "@nextera.one/tps-standard";
|
|
426
|
+
|
|
427
|
+
// Create TPS-UID from a TPS string
|
|
428
|
+
const tps = "tps://31.95,35.91@T:greg.m3.c1.y26.M01.d09.h14.n30.s25";
|
|
429
|
+
const id = TPSUID7RB.encodeBinaryB64(tps);
|
|
430
|
+
// → "tpsuid7rb_VFBVNwEAAZujKmvo..."
|
|
431
|
+
|
|
432
|
+
// Decode back to original TPS (exact reconstruction)
|
|
433
|
+
const decoded = TPSUID7RB.decodeBinaryB64(id);
|
|
434
|
+
console.log(decoded.tps); // exact original TPS
|
|
435
|
+
console.log(decoded.epochMs); // 1767969025000
|
|
436
|
+
|
|
437
|
+
// Generate from current time
|
|
438
|
+
const generated = TPSUID7RB.generate({
|
|
439
|
+
latitude: 32.0,
|
|
440
|
+
longitude: 35.0,
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### API Reference
|
|
445
|
+
|
|
446
|
+
#### `TPSUID7RB.encodeBinary(tps, opts?): Uint8Array`
|
|
447
|
+
|
|
448
|
+
Encode TPS string to binary bytes (canonical form).
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
const bytes = TPSUID7RB.encodeBinary(tps, { compress: true });
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
#### `TPSUID7RB.decodeBinary(bytes): TPSUID7RBDecodeResult`
|
|
455
|
+
|
|
456
|
+
Decode binary bytes back to original TPS.
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
const decoded = TPSUID7RB.decodeBinary(bytes);
|
|
460
|
+
// { version: 'tpsuid7rb', epochMs, compressed, nonce, tps }
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### `TPSUID7RB.encodeBinaryB64(tps, opts?): string`
|
|
464
|
+
|
|
465
|
+
Encode to base64url string with prefix (transport form).
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
const id = TPSUID7RB.encodeBinaryB64(tps, { compress: true });
|
|
469
|
+
// "tpsuid7rb_..."
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### `TPSUID7RB.decodeBinaryB64(id): TPSUID7RBDecodeResult`
|
|
473
|
+
|
|
474
|
+
Decode base64url string back to original TPS.
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
const decoded = TPSUID7RB.decodeBinaryB64(id);
|
|
478
|
+
console.log(decoded.tps); // exact original
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
#### `TPSUID7RB.validateBinaryB64(id): boolean`
|
|
482
|
+
|
|
483
|
+
Validate base64url encoded TPS-UID format.
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
TPSUID7RB.validateBinaryB64("tpsuid7rb_VFB..."); // true
|
|
487
|
+
TPSUID7RB.validateBinaryB64("invalid"); // false
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### `TPSUID7RB.generate(opts?): string`
|
|
491
|
+
|
|
492
|
+
Generate TPS-UID from current time.
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
const id = TPSUID7RB.generate({
|
|
496
|
+
latitude: 32.0,
|
|
497
|
+
longitude: 35.0,
|
|
498
|
+
compress: true,
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Database Schema (Recommended)
|
|
503
|
+
|
|
504
|
+
```sql
|
|
505
|
+
CREATE TABLE events (
|
|
506
|
+
epoch_ms BIGINT NOT NULL,
|
|
507
|
+
tps_uid VARBINARY(96) NOT NULL,
|
|
508
|
+
tps TEXT NOT NULL,
|
|
509
|
+
PRIMARY KEY (epoch_ms, tps_uid)
|
|
510
|
+
);
|
|
288
511
|
```
|
|
289
512
|
|
|
290
513
|
## 🎯 Use Cases
|
|
@@ -306,4 +529,4 @@ type CalendarCode = 'greg' | 'hij' | 'jul' | 'holo' | 'unix';
|
|
|
306
529
|
|
|
307
530
|
## 📄 License
|
|
308
531
|
|
|
309
|
-
|
|
532
|
+
Apache-2.0 — see [LICENSE](LICENSE).
|