@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Rethink Health
3
+ Copyright (c) 2023 Rethink Health, SUARL
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @glion/util-timestamp
2
2
 
3
- HL7v2 timestamp parser, formatter, and converter with precision tracking.
3
+ HL7v2 DT, TM, and DTM parser and formatter with precision and calendar checks.
4
4
 
5
5
  ## What it does
6
6
 
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.
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
- ```typescript
18
- import { Timestamp, Precision } from "@glion/util-timestamp";
17
+ ```ts
18
+ import { formatDTM, parseDTM } from "@glion/util-timestamp";
19
19
 
20
- // Parse an HL7v2 timestamp string
21
- const ts = Timestamp.parse("20260307143045-0500");
22
- ts.precision; // "second"
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
- // Create from a JavaScript Date
27
- const ts2 = Timestamp.from(new Date(), { precision: "day" });
28
- ts2.toString(); // "20260307"
24
+ if (result.success) {
25
+ formatDTM(result.data); // "20260307143045-0500"
26
+ }
29
27
 
30
- // Current time
31
- const now = Timestamp.now({ precision: "second", timezone: true });
32
- now.toString(); // "20260307143045+0000"
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
- ### `Timestamp.parse(value: string): Timestamp`
38
-
39
- Parses an HL7v2 timestamp string into a `Timestamp` instance.
35
+ ### `parseDT(value: string): ParseResult<DT>`
40
36
 
41
- Accepts the full HL7v2 TS format: `YYYY[MM[DD[HH[MM[SS[.S[S[S[S]]]]]]]]][+/-ZZZZ]`.
37
+ Parses a DT value, `YYYY[MM[DD]]`.
42
38
 
43
- ```typescript
44
- Timestamp.parse("2026"); // year precision
45
- Timestamp.parse("202603"); // month precision
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
- Throws `TypeError` if the string is not a valid HL7v2 timestamp.
56
-
57
- ### `Timestamp.from(date: Date, options?: TimestampOptions): Timestamp`
44
+ ### `parseTM(value: string): ParseResult<TM>`
58
45
 
59
- Creates a `Timestamp` from a JavaScript `Date` object.
46
+ Parses a TM value, `HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]`.
60
47
 
61
- ```typescript
62
- const date = new Date(2026, 2, 7, 14, 30, 45, 123);
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
- Throws `TypeError` if the `Date` is invalid (`NaN`).
71
-
72
- ### `Timestamp.now(options?: TimestampOptions): Timestamp`
53
+ ### `parseDTM(value: string): ParseResult<DTM>`
73
54
 
74
- Creates a `Timestamp` for the current time.
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
- ```typescript
77
- Timestamp.now().toString(); // "20260307143045"
78
- Timestamp.now({ precision: "day" }).toString(); // "20260307"
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
- ### Instance properties
62
+ ### `formatDT(date: DT): string`, `formatTM(time: TM): string`, `formatDTM(dateTime: DTM): string`
84
63
 
85
- #### `precision: Precision`
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
- The precision level of the timestamp. One of: `"year"`, `"month"`, `"day"`, `"hour"`, `"minute"`, `"second"`, `"millisecond"`.
88
-
89
- ### Instance methods
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
- #### `toDate(): Date`
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
- ### `TimestampOptions`
74
+ The parsed value. A field is present only when the value gives it.
104
75
 
105
- ```typescript
106
- interface TimestampOptions {
107
- /** Precision level for formatting. Default: "second" */
108
- precision?: Precision;
109
-
110
- /** Include local timezone offset. Only applies at "hour" precision or finer. Default: false */
111
- timezone?: boolean;
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
- ```typescript
118
- import { Precision } from "@glion/util-timestamp";
119
-
120
- Precision.Year; // "year" → YYYY
121
- Precision.Month; // "month" → YYYYMM
122
- Precision.Day; // "day" → YYYYMMDD
123
- Precision.Hour; // "hour" → YYYYMMDDHH
124
- Precision.Minute; // "minute" → YYYYMMDDHHmm
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
- Precision is a first-class concept. The timestamp remembers exactly how much detail was provided, so formatting never inflates or truncates:
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
- ```typescript
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
- // Fractional seconds are preserved exactly (even 4-digit)
150
- Timestamp.parse("20260307143045.1234").toString(); // "20260307143045.1234"
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
- ### Precision gate on timezone
200
-
201
- Timezone is only included at `"hour"` precision or finer. Coarser precisions (`"year"`, `"month"`, `"day"`) never carry a timezone suffix:
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
- ```typescript
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. See the [Glion README] for the full package catalog and architecture.
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
@@ -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 { Timestamp } from "./timestamp";
3
- export type { TimestampOptions } from "./timestamp";
4
+ export type { DT, DTM, ParseResult, TimestampIssue, TM } from "./types";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
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"}