@glion/util-timestamp 0.25.0 → 0.27.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/LICENSE +1 -1
- package/README.md +72 -162
- package/dist/format.d.ts +31 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +336 -295
- package/dist/index.js.map +1 -1
- package/dist/parse.d.ts +47 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/precision.d.ts +10 -14
- package/dist/precision.d.ts.map +1 -1
- package/dist/scan.d.ts +45 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +8 -4
- package/dist/timestamp.d.ts +0 -98
- package/dist/timestamp.d.ts.map +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @glion/util-timestamp
|
|
2
2
|
|
|
3
|
-
HL7v2
|
|
3
|
+
HL7v2 DT, TM, and DTM parser and formatter with precision and calendar checks.
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Reads an HL7v2 DT (Date), TM (Time), or DTM (Date/Time) value into a plain object holding the fields the value gives, and writes such an object back as HL7v2 text. A value keeps its precision: `202603` is March 2026, with no day. A parse checks the format and the calendar, so `20250229` fails because 2025 is not a leap year. Parsing never throws: it returns the value or the reason it is not valid. The fields are the sender's local time, with the offset when the value gives one. Pure computation, zero runtime dependencies.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -14,203 +14,113 @@ npm install @glion/util-timestamp
|
|
|
14
14
|
|
|
15
15
|
## Use
|
|
16
16
|
|
|
17
|
-
```
|
|
18
|
-
import {
|
|
17
|
+
```ts
|
|
18
|
+
import { formatDTM, parseDTM } from "@glion/util-timestamp";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
ts.toDate(); // Date representing 2026-03-07T19:30:45.000Z
|
|
24
|
-
ts.toString(); // "20260307143045-0500"
|
|
20
|
+
const result = parseDTM("20260307143045-0500");
|
|
21
|
+
// { success: true, data: { year: 2026, month: 3, day: 7, hour: 14,
|
|
22
|
+
// minute: 30, second: 45, offset: -300, precision: "second" } }
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
if (result.success) {
|
|
25
|
+
formatDTM(result.data); // "20260307143045-0500"
|
|
26
|
+
}
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
parseDTM("20250229");
|
|
29
|
+
// { success: false, issue: { code: "day-out-of-range", year: 2025,
|
|
30
|
+
// month: 2, day: 29, daysInMonth: 28 } }
|
|
33
31
|
```
|
|
34
32
|
|
|
35
33
|
## API
|
|
36
34
|
|
|
37
|
-
### `
|
|
38
|
-
|
|
39
|
-
Parses an HL7v2 timestamp string into a `Timestamp` instance.
|
|
35
|
+
### `parseDT(value: string): ParseResult<DT>`
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
Parses a DT value, `YYYY[MM[DD]]`.
|
|
42
38
|
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Timestamp.parse("20260307"); // day precision
|
|
47
|
-
Timestamp.parse("2026030714"); // hour precision
|
|
48
|
-
Timestamp.parse("202603071430"); // minute precision
|
|
49
|
-
Timestamp.parse("20260307143045"); // second precision
|
|
50
|
-
Timestamp.parse("20260307143045.1234"); // millisecond precision (4-digit fractional)
|
|
51
|
-
Timestamp.parse("20260307143045-0500"); // with timezone offset
|
|
52
|
-
Timestamp.parse("20260307143045.12+0000"); // fractional + timezone
|
|
39
|
+
```ts
|
|
40
|
+
parseDT("202603"); // { success: true, data: { year: 2026, month: 3, precision: "month" } }
|
|
41
|
+
parseDT("2026-03-07"); // { success: false, issue: { code: "invalid-format" } }
|
|
53
42
|
```
|
|
54
43
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
### `Timestamp.from(date: Date, options?: TimestampOptions): Timestamp`
|
|
44
|
+
### `parseTM(value: string): ParseResult<TM>`
|
|
58
45
|
|
|
59
|
-
|
|
46
|
+
Parses a TM value, `HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]`.
|
|
60
47
|
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Timestamp.from(date).toString(); // "20260307143045"
|
|
65
|
-
Timestamp.from(date, { precision: "day" }).toString(); // "20260307"
|
|
66
|
-
Timestamp.from(date, { precision: "millisecond" }).toString(); // "20260307143045.123"
|
|
67
|
-
Timestamp.from(date, { timezone: true }).toString(); // "20260307143045+0000"
|
|
48
|
+
```ts
|
|
49
|
+
parseTM("1430-0500"); // { success: true, data: { hour: 14, minute: 30, offset: -300, precision: "minute" } }
|
|
50
|
+
parseTM("2400"); // { success: false, issue: { code: "hour-out-of-range", hour: 24 } }
|
|
68
51
|
```
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
### `Timestamp.now(options?: TimestampOptions): Timestamp`
|
|
53
|
+
### `parseDTM(value: string): ParseResult<DTM>`
|
|
73
54
|
|
|
74
|
-
|
|
55
|
+
Parses a DTM value, `YYYY[MM[DD[HH[MM[SS[.S[S[S[S]]]]]]]]][+/-ZZZZ]`, or the TS.1 of a TS. The date is checked as written, before the offset applies.
|
|
75
56
|
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
Timestamp.now({ precision: "minute" }).toString(); // "202603071430"
|
|
80
|
-
Timestamp.now({ timezone: true }).toString(); // "20260307143045+0000"
|
|
57
|
+
```ts
|
|
58
|
+
parseDTM("20260307143045.12"); // { success: true, data: { ..., second: 45, fraction: "12", precision: "second" } }
|
|
59
|
+
parseDTM("20261307"); // { success: false, issue: { code: "month-out-of-range", month: 13 } }
|
|
81
60
|
```
|
|
82
61
|
|
|
83
|
-
###
|
|
62
|
+
### `formatDT(date: DT): string`, `formatTM(time: TM): string`, `formatDTM(dateTime: DTM): string`
|
|
84
63
|
|
|
85
|
-
|
|
64
|
+
Write a value as HL7v2 text, with the fields it has. For every valid value, `formatDTM(parseDTM(value).data) === value`, and the same for DT and TM, except that an offset of `-0000` is written `+0000`.
|
|
86
65
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
#### `toString(): string`
|
|
92
|
-
|
|
93
|
-
Formats the timestamp as an HL7v2 string, respecting the precision level.
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
Timestamp.parse("20260307").toString(); // "20260307" (not "20260307000000")
|
|
66
|
+
```ts
|
|
67
|
+
formatDT({ year: 2026, month: 3, precision: "month" }); // "202603"
|
|
68
|
+
formatTM({ hour: 9, offset: 345, precision: "hour" }); // "09+0545"
|
|
69
|
+
formatDTM({ year: 99, precision: "year" }); // "0099"
|
|
97
70
|
```
|
|
98
71
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Returns a copy of the underlying `Date` object. Each call returns a new copy to preserve immutability.
|
|
72
|
+
### `DT`, `TM`, `DTM`
|
|
102
73
|
|
|
103
|
-
|
|
74
|
+
The parsed value. A field is present only when the value gives it.
|
|
104
75
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
76
|
+
| Field | Type | In | Description |
|
|
77
|
+
| ----------- | -------- | ----------- | -------------------------------------------------------------------- |
|
|
78
|
+
| `year` | `number` | DT, DTM | The year as written, `0` to `9999` |
|
|
79
|
+
| `month` | `number` | DT, DTM | `1` to `12` |
|
|
80
|
+
| `day` | `number` | DT, DTM | `1` to the number of days in the month |
|
|
81
|
+
| `hour` | `number` | TM, DTM | `0` to `23`; always present in a TM |
|
|
82
|
+
| `minute` | `number` | TM, DTM | `0` to `59` |
|
|
83
|
+
| `second` | `number` | TM, DTM | `0` to `59` |
|
|
84
|
+
| `fraction` | `string` | TM, DTM | The 1 to 4 digits after the second, as written: `"1230"` for `.1230` |
|
|
85
|
+
| `offset` | `number` | TM, DTM | Minutes from UTC, positive east of UTC: `-0500` is `-300` |
|
|
86
|
+
| `precision` | `string` | DT, TM, DTM | The last field the value gives, one of the `Precision` values |
|
|
114
87
|
|
|
115
88
|
### `Precision`
|
|
116
89
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Precision.
|
|
121
|
-
Precision.
|
|
122
|
-
Precision.
|
|
123
|
-
Precision.
|
|
124
|
-
Precision.
|
|
125
|
-
Precision.Second; // "second" → YYYYMMDDHHmmss
|
|
126
|
-
Precision.Millisecond; // "millisecond" → YYYYMMDDHHmmss.SSSS
|
|
127
|
-
```
|
|
128
|
-
|
|
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
|
|
90
|
+
| Constant | Value | DTM written as |
|
|
91
|
+
| ------------------ | ---------- | ---------------- |
|
|
92
|
+
| `Precision.Year` | `"year"` | `YYYY` |
|
|
93
|
+
| `Precision.Month` | `"month"` | `YYYYMM` |
|
|
94
|
+
| `Precision.Day` | `"day"` | `YYYYMMDD` |
|
|
95
|
+
| `Precision.Hour` | `"hour"` | `YYYYMMDDHH` |
|
|
96
|
+
| `Precision.Minute` | `"minute"` | `YYYYMMDDHHMM` |
|
|
97
|
+
| `Precision.Second` | `"second"` | `YYYYMMDDHHMMSS` |
|
|
141
98
|
|
|
142
|
-
|
|
99
|
+
A DT is `year`, `month`, or `day`; a TM is `hour`, `minute`, or `second`. Digits after the second are in `fraction`.
|
|
143
100
|
|
|
144
|
-
|
|
145
|
-
// Parsing preserves precision
|
|
146
|
-
Timestamp.parse("20260307").toString(); // "20260307" — not "20260307000000"
|
|
147
|
-
Timestamp.parse("202603071430").toString(); // "202603071430" — not "20260307143000"
|
|
101
|
+
### `ParseResult<T>` and `TimestampIssue`
|
|
148
102
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// from() uses the requested precision
|
|
153
|
-
Timestamp.from(date, { precision: "day" }).toString(); // "20260307"
|
|
154
|
-
Timestamp.from(date, { precision: "minute" }).toString(); // "202603071430"
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Timezone handling
|
|
158
|
-
|
|
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.
|
|
160
|
-
|
|
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:
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
// 14:30:45 in UTC-5 → Date stores 19:30:45 UTC
|
|
165
|
-
Timestamp.parse("20260307143045-0500").toDate().toISOString();
|
|
166
|
-
// "2026-03-07T19:30:45.000Z"
|
|
167
|
-
|
|
168
|
-
// 14:30:45 in UTC+5:30 → Date stores 09:00:45 UTC
|
|
169
|
-
Timestamp.parse("20260307143045+0530").toDate().toISOString();
|
|
170
|
-
// "2026-03-07T09:00:45.000Z"
|
|
171
|
-
|
|
172
|
-
// Midnight in UTC+5:30 → previous day in UTC
|
|
173
|
-
Timestamp.parse("20260101000000+0530").toDate().toISOString();
|
|
174
|
-
// "2025-12-31T18:30:00.000Z"
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Parsed timestamps with timezone offsets round-trip exactly, regardless of the server's timezone:
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
Timestamp.parse("20260307143045-0500").toString(); // "20260307143045-0500"
|
|
181
|
-
Timestamp.parse("20260307143045+0530").toString(); // "20260307143045+0530"
|
|
182
|
-
Timestamp.parse("20261231235959.999-0800").toString(); // "20261231235959.999-0800"
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
When using `from()` or `now()` with `{ timezone: true }`, the runtime's local timezone offset is captured at creation time:
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
const ts = Timestamp.from(new Date(), { timezone: true });
|
|
189
|
-
ts.toString(); // "20260307143045-0500" (on a UTC-5 server)
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Timestamps without a timezone offset are treated as local time — no UTC conversion is applied:
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
const ts = Timestamp.parse("20260307143045");
|
|
196
|
-
// Date is constructed with new Date(2026, 2, 7, 14, 30, 45) — local time
|
|
103
|
+
```ts
|
|
104
|
+
type ParseResult<T> =
|
|
105
|
+
{ success: true; data: T } | { success: false; issue: TimestampIssue };
|
|
197
106
|
```
|
|
198
107
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
108
|
+
| `issue.code` | Meaning | Other fields |
|
|
109
|
+
| --------------------- | ---------------------------------------------------------- | --------------------------------------- |
|
|
110
|
+
| `invalid-format` | The characters do not follow the datatype's format | |
|
|
111
|
+
| `month-out-of-range` | The month is not `01` to `12` | `month` |
|
|
112
|
+
| `day-out-of-range` | The day is `00`, or past the last day of its month | `year`, `month`, `day`, `daysInMonth` |
|
|
113
|
+
| `hour-out-of-range` | The hour is not `00` to `23` | `hour` |
|
|
114
|
+
| `minute-out-of-range` | The minute is not `00` to `59` | `minute` |
|
|
115
|
+
| `second-out-of-range` | The second is not `00` to `59` | `second` |
|
|
116
|
+
| `offset-out-of-range` | The offset's hours are over `14`, or its minutes over `59` | `offset`, as written, such as `"+1500"` |
|
|
202
117
|
|
|
203
|
-
|
|
204
|
-
Timestamp.from(date, { precision: "day", timezone: true }).toString();
|
|
205
|
-
// "20260307" — no timezone (day precision is too coarse)
|
|
206
|
-
|
|
207
|
-
Timestamp.from(date, { precision: "hour", timezone: true }).toString();
|
|
208
|
-
// "2026030714-0500" — timezone included
|
|
209
|
-
```
|
|
118
|
+
A DTM reports the date first, then the time, then the offset.
|
|
210
119
|
|
|
211
120
|
## Part of Glion
|
|
212
121
|
|
|
213
|
-
`@glion/util-timestamp` is part of **[Glion]**, the application framework for HL7v2.
|
|
122
|
+
`@glion/util-timestamp` is part of **[Glion]**, the application framework for HL7v2.
|
|
123
|
+
See the [Glion README] for the full package catalog and architecture.
|
|
214
124
|
|
|
215
125
|
[Glion]: https://github.com/rethinkhealth/glion#readme
|
|
216
126
|
[Glion README]: https://github.com/rethinkhealth/glion#readme
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DT, DTM, TM } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Writes a DT as HL7v2 text, `YYYY[MM[DD]]`, with the fields `date` has.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* formatDT({ year: 2026, month: 3, precision: "month" }); // "202603"
|
|
8
|
+
* ```;
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatDT(date: DT): string;
|
|
11
|
+
/**
|
|
12
|
+
* Writes a TM as HL7v2 text, `HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]`, with the
|
|
13
|
+
* fields `time` has. An `offset` of `0` is written `+0000`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* formatTM({ hour: 14, minute: 30, offset: -300, precision: "minute" }); // "1430-0500"
|
|
18
|
+
* ```;
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatTM(time: TM): string;
|
|
21
|
+
/**
|
|
22
|
+
* Writes a DTM as HL7v2 text, `YYYY[MM[DD[HH[MM[SS[.S[S[S[S]]]]]]]]][+/-ZZZZ]`,
|
|
23
|
+
* with the fields `dateTime` has. An `offset` of `0` is written `+0000`.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* formatDTM({ year: 2026, month: 3, day: 7, hour: 14, precision: "hour" }); // "2026030714"
|
|
28
|
+
* ```;
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatDTM(dateTime: DTM): string;
|
|
31
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AA+C3C;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAEzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAEzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,CAE/C"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
export { formatDT, formatDTM, formatTM } from "./format";
|
|
2
|
+
export { parseDT, parseDTM, parseTM } from "./parse";
|
|
1
3
|
export { Precision } from "./precision";
|
|
2
|
-
export {
|
|
3
|
-
export type { TimestampOptions } from "./timestamp";
|
|
4
|
+
export type { DT, DTM, ParseResult, TimestampIssue, TM } from "./types";
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC"}
|