@glion/util-timestamp 0.15.1 → 0.15.3
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/README.md +36 -78
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
# @glion/util-timestamp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
HL7v2 timestamp parser, formatter, and converter with precision tracking.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What it does
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Lossless round-trip** - `Timestamp.parse(str).toString() === str` for all valid inputs
|
|
9
|
-
- **High performance** - No regex, no intermediate arrays, pre-computed lookup tables
|
|
10
|
-
- **Type safe** - Full TypeScript support with const enums and union types
|
|
11
|
-
- **Zero dependencies** - Pure computation, no runtime dependencies
|
|
7
|
+
Parses, formats, and converts HL7v2 TS (Time Stamp) values across seven precision levels — year, month, day, hour, minute, second, millisecond — so that `Timestamp.parse(str).toString() === str` for any valid input. Timezone offsets are applied during parsing so `toDate()` returns the correct absolute moment, and reconstructed during formatting so the original local-time representation round-trips exactly regardless of the server's timezone. Pure computation, zero runtime dependencies.
|
|
12
8
|
|
|
13
|
-
##
|
|
9
|
+
## Install
|
|
14
10
|
|
|
15
11
|
```bash
|
|
16
12
|
npm install @glion/util-timestamp
|
|
17
13
|
```
|
|
18
14
|
|
|
19
|
-
##
|
|
15
|
+
## Use
|
|
20
16
|
|
|
21
17
|
```typescript
|
|
22
18
|
import { Timestamp, Precision } from "@glion/util-timestamp";
|
|
@@ -36,13 +32,13 @@ const now = Timestamp.now({ precision: "second", timezone: true });
|
|
|
36
32
|
now.toString(); // "20260307143045+0000"
|
|
37
33
|
```
|
|
38
34
|
|
|
39
|
-
## API
|
|
35
|
+
## API
|
|
40
36
|
|
|
41
37
|
### `Timestamp.parse(value: string): Timestamp`
|
|
42
38
|
|
|
43
39
|
Parses an HL7v2 timestamp string into a `Timestamp` instance.
|
|
44
40
|
|
|
45
|
-
Accepts the full HL7v2 TS format: `YYYY[MM[DD[HH[MM[SS[.S[S[S[S]]]]]]]]][+/-ZZZZ]
|
|
41
|
+
Accepts the full HL7v2 TS format: `YYYY[MM[DD[HH[MM[SS[.S[S[S[S]]]]]]]]][+/-ZZZZ]`.
|
|
46
42
|
|
|
47
43
|
```typescript
|
|
48
44
|
Timestamp.parse("2026"); // year precision
|
|
@@ -56,7 +52,7 @@ Timestamp.parse("20260307143045-0500"); // with timezone offset
|
|
|
56
52
|
Timestamp.parse("20260307143045.12+0000"); // fractional + timezone
|
|
57
53
|
```
|
|
58
54
|
|
|
59
|
-
|
|
55
|
+
Throws `TypeError` if the string is not a valid HL7v2 timestamp.
|
|
60
56
|
|
|
61
57
|
### `Timestamp.from(date: Date, options?: TimestampOptions): Timestamp`
|
|
62
58
|
|
|
@@ -71,7 +67,7 @@ Timestamp.from(date, { precision: "millisecond" }).toString(); // "2026030714304
|
|
|
71
67
|
Timestamp.from(date, { timezone: true }).toString(); // "20260307143045+0000"
|
|
72
68
|
```
|
|
73
69
|
|
|
74
|
-
|
|
70
|
+
Throws `TypeError` if the `Date` is invalid (`NaN`).
|
|
75
71
|
|
|
76
72
|
### `Timestamp.now(options?: TimestampOptions): Timestamp`
|
|
77
73
|
|
|
@@ -84,13 +80,13 @@ Timestamp.now({ precision: "minute" }).toString(); // "202603071430"
|
|
|
84
80
|
Timestamp.now({ timezone: true }).toString(); // "20260307143045+0000"
|
|
85
81
|
```
|
|
86
82
|
|
|
87
|
-
### Instance
|
|
83
|
+
### Instance properties
|
|
88
84
|
|
|
89
85
|
#### `precision: Precision`
|
|
90
86
|
|
|
91
87
|
The precision level of the timestamp. One of: `"year"`, `"month"`, `"day"`, `"hour"`, `"minute"`, `"second"`, `"millisecond"`.
|
|
92
88
|
|
|
93
|
-
### Instance
|
|
89
|
+
### Instance methods
|
|
94
90
|
|
|
95
91
|
#### `toString(): string`
|
|
96
92
|
|
|
@@ -130,7 +126,18 @@ Precision.Second; // "second" → YYYYMMDDHHmmss
|
|
|
130
126
|
Precision.Millisecond; // "millisecond" → YYYYMMDDHHmmss.SSSS
|
|
131
127
|
```
|
|
132
128
|
|
|
133
|
-
|
|
129
|
+
### Error handling
|
|
130
|
+
|
|
131
|
+
All invalid input throws `TypeError` with a descriptive message:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
Timestamp.parse(""); // TypeError: Invalid HL7v2 timestamp: ""
|
|
135
|
+
Timestamp.parse("not-a-timestamp"); // TypeError: Invalid HL7v2 timestamp: "not-a-timestamp"
|
|
136
|
+
Timestamp.parse("20263"); // TypeError: Invalid HL7v2 timestamp: "20263"
|
|
137
|
+
Timestamp.from(new Date("invalid")); // TypeError: Invalid Date provided to Timestamp.from
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Precision model
|
|
134
141
|
|
|
135
142
|
Precision is a first-class concept. The timestamp remembers exactly how much detail was provided, so formatting never inflates or truncates:
|
|
136
143
|
|
|
@@ -147,43 +154,34 @@ Timestamp.from(date, { precision: "day" }).toString(); // "20260307"
|
|
|
147
154
|
Timestamp.from(date, { precision: "minute" }).toString(); // "202603071430"
|
|
148
155
|
```
|
|
149
156
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
Timezone information is embedded directly in the `Date` object — not stored as separate public metadata. This ensures that `toDate()` always returns the correct absolute moment, and `toString()` always reproduces the original timezone for lossless round-tripping.
|
|
157
|
+
### Timezone handling
|
|
153
158
|
|
|
154
|
-
|
|
159
|
+
Timezone information is embedded directly in the `Date` object — not stored as separate public metadata. This ensures `toDate()` always returns the correct absolute moment, and `toString()` always reproduces the original timezone for lossless round-tripping.
|
|
155
160
|
|
|
156
161
|
When an HL7v2 timestamp includes a timezone offset (`+/-ZZZZ`), the offset is applied during parsing to construct a `Date` with the correct UTC instant:
|
|
157
162
|
|
|
158
163
|
```typescript
|
|
159
164
|
// 14:30:45 in UTC-5 → Date stores 19:30:45 UTC
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
Timestamp.parse("20260307143045-0500").toDate().toISOString();
|
|
166
|
+
// "2026-03-07T19:30:45.000Z"
|
|
162
167
|
|
|
163
168
|
// 14:30:45 in UTC+5:30 → Date stores 09:00:45 UTC
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
Timestamp.parse("20260307143045+0530").toDate().toISOString();
|
|
170
|
+
// "2026-03-07T09:00:45.000Z"
|
|
166
171
|
|
|
167
172
|
// Midnight in UTC+5:30 → previous day in UTC
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
Timestamp.parse("20260101000000+0530").toDate().toISOString();
|
|
174
|
+
// "2025-12-31T18:30:00.000Z"
|
|
170
175
|
```
|
|
171
176
|
|
|
172
|
-
### Lossless Round-Trip with Timezone
|
|
173
|
-
|
|
174
177
|
Parsed timestamps with timezone offsets round-trip exactly, regardless of the server's timezone:
|
|
175
178
|
|
|
176
179
|
```typescript
|
|
177
|
-
// On ANY server (UTC, EST, PST, IST):
|
|
178
180
|
Timestamp.parse("20260307143045-0500").toString(); // "20260307143045-0500"
|
|
179
181
|
Timestamp.parse("20260307143045+0530").toString(); // "20260307143045+0530"
|
|
180
182
|
Timestamp.parse("20261231235959.999-0800").toString(); // "20261231235959.999-0800"
|
|
181
183
|
```
|
|
182
184
|
|
|
183
|
-
The timezone offset is stored internally and used by `toString()` to reconstruct the original local time components from UTC. No public `timezoneOffset` property is exposed — this prevents the data integrity risk of consumers calling `toDate()` and forgetting to account for a separate offset.
|
|
184
|
-
|
|
185
|
-
### Creating Timestamps with Timezone
|
|
186
|
-
|
|
187
185
|
When using `from()` or `now()` with `{ timezone: true }`, the runtime's local timezone offset is captured at creation time:
|
|
188
186
|
|
|
189
187
|
```typescript
|
|
@@ -191,8 +189,6 @@ const ts = Timestamp.from(new Date(), { timezone: true });
|
|
|
191
189
|
ts.toString(); // "20260307143045-0500" (on a UTC-5 server)
|
|
192
190
|
```
|
|
193
191
|
|
|
194
|
-
### Without Timezone
|
|
195
|
-
|
|
196
192
|
Timestamps without a timezone offset are treated as local time — no UTC conversion is applied:
|
|
197
193
|
|
|
198
194
|
```typescript
|
|
@@ -200,7 +196,7 @@ const ts = Timestamp.parse("20260307143045");
|
|
|
200
196
|
// Date is constructed with new Date(2026, 2, 7, 14, 30, 45) — local time
|
|
201
197
|
```
|
|
202
198
|
|
|
203
|
-
### Precision
|
|
199
|
+
### Precision gate on timezone
|
|
204
200
|
|
|
205
201
|
Timezone is only included at `"hour"` precision or finer. Coarser precisions (`"year"`, `"month"`, `"day"`) never carry a timezone suffix:
|
|
206
202
|
|
|
@@ -212,47 +208,9 @@ Timestamp.from(date, { precision: "hour", timezone: true }).toString();
|
|
|
212
208
|
// "2026030714-0500" — timezone included
|
|
213
209
|
```
|
|
214
210
|
|
|
215
|
-
##
|
|
216
|
-
|
|
217
|
-
Optimized for high-throughput streaming processors handling 100K+ messages per second:
|
|
218
|
-
|
|
219
|
-
- **No regex** — manual character-based parsing via `codePointAt` arithmetic
|
|
220
|
-
- **No intermediate arrays** — direct string concatenation in `toString()`
|
|
221
|
-
- **Pre-computed lookup table** — zero-padded strings for 0-99
|
|
222
|
-
- **O(1) precision lookups** — `Map` instead of `Array.indexOf()`
|
|
223
|
-
- **Zero allocations in hot paths** — no substring extraction or `Number()` conversions
|
|
224
|
-
|
|
225
|
-
## Error Handling
|
|
226
|
-
|
|
227
|
-
All invalid input throws `TypeError` with a descriptive message:
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
Timestamp.parse(""); // TypeError: Invalid HL7v2 timestamp: ""
|
|
231
|
-
Timestamp.parse("not-a-timestamp"); // TypeError: Invalid HL7v2 timestamp: "not-a-timestamp"
|
|
232
|
-
Timestamp.parse("20263"); // TypeError: Invalid HL7v2 timestamp: "20263"
|
|
233
|
-
Timestamp.from(new Date("invalid")); // TypeError: Invalid Date provided to Timestamp.from
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## Contributing
|
|
237
|
-
|
|
238
|
-
We welcome contributions! Please see our [Contributing Guide][github-contributing] for more details.
|
|
239
|
-
|
|
240
|
-
1. Fork the repository
|
|
241
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
242
|
-
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
243
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
244
|
-
5. Open a Pull Request
|
|
245
|
-
|
|
246
|
-
## Code of Conduct
|
|
247
|
-
|
|
248
|
-
To ensure a welcoming and positive environment, we have a [Code of Conduct][github-code-of-conduct] that all contributors and participants are expected to adhere to.
|
|
249
|
-
|
|
250
|
-
## License
|
|
251
|
-
|
|
252
|
-
Copyright 2025 Rethink Health, SUARL. All rights reserved.
|
|
211
|
+
## Part of Glion
|
|
253
212
|
|
|
254
|
-
|
|
213
|
+
`@glion/util-timestamp` is part of **[Glion]**, the application framework for HL7v2. See the [Glion README] for the full package catalog and architecture.
|
|
255
214
|
|
|
256
|
-
[
|
|
257
|
-
[
|
|
258
|
-
[github-contributing]: https://github.com/rethinkhealth/glion/blob/main/CONTRIBUTING.md
|
|
215
|
+
[Glion]: https://github.com/rethinkhealth/glion#readme
|
|
216
|
+
[Glion README]: https://github.com/rethinkhealth/glion#readme
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glion/util-timestamp",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.3",
|
|
4
4
|
"description": "HL7v2 timestamp parsing, formatting, and conversion with precision tracking",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"health",
|
|
@@ -36,11 +36,13 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^25.5.0",
|
|
38
38
|
"@vitest/coverage-v8": "4.1.2",
|
|
39
|
+
"publint": "0.3.18",
|
|
39
40
|
"tsdown": "0.21.7",
|
|
40
41
|
"typescript": "^5.9.3",
|
|
41
42
|
"vitest": "4.1.2",
|
|
42
|
-
"@glion/
|
|
43
|
-
"@glion/
|
|
43
|
+
"@glion/check-readme": "0.15.3",
|
|
44
|
+
"@glion/testing": "0.15.3",
|
|
45
|
+
"@glion/tsconfig": "0.15.3"
|
|
44
46
|
},
|
|
45
47
|
"engines": {
|
|
46
48
|
"node": ">=18"
|
|
@@ -49,8 +51,10 @@
|
|
|
49
51
|
"scripts": {
|
|
50
52
|
"build": "tsdown && tsc --emitDeclarationOnly",
|
|
51
53
|
"check-types": "tsc --noEmit",
|
|
54
|
+
"check:readme": "glion-check-readme",
|
|
52
55
|
"test": "vitest run",
|
|
53
56
|
"test:coverage": "vitest run --coverage",
|
|
54
|
-
"test:watch": "vitest"
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"publint": "publint --strict"
|
|
55
59
|
}
|
|
56
60
|
}
|