@lbd-sh/date-tz 1.0.6 → 1.0.8
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 +209 -167
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,48 @@
|
|
|
1
|
-
# Date TZ
|
|
1
|
+
# Date TZ ⏰✨
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`DateTz` is the timezone swiss‑army knife for modern JavaScript and TypeScript projects. It keeps minute-precision timestamps aligned with IANA zones, gracefully glides across daylight-saving transitions, and exposes a lightweight API that feels familiar yet powerful. Whether you are building dashboards, schedulers, or automation pipelines, DateTz keeps your time math honest. 🌍
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Quick Start](#quick-start)
|
|
10
|
+
2. [Installation](#installation)
|
|
11
|
+
3. [Why DateTz?](#why-datetz)
|
|
12
|
+
4. [API Surface Overview](#api-surface-overview)
|
|
13
|
+
5. [Formatting Tokens](#formatting-tokens)
|
|
14
|
+
6. [Core Concepts & Recipes](#core-concepts--recipes)
|
|
15
|
+
7. [Daylight Saving Time Deep Dive](#daylight-saving-time-deep-dive)
|
|
16
|
+
8. [Real-World Playbook](#real-world-playbook)
|
|
17
|
+
9. [Build & Distribution](#build--distribution)
|
|
18
|
+
10. [TypeScript & Tooling](#typescript--tooling)
|
|
19
|
+
11. [Performance Tips](#performance-tips)
|
|
20
|
+
12. [FAQ & Troubleshooting](#faq--troubleshooting)
|
|
21
|
+
13. [Contributing](#contributing)
|
|
22
|
+
14. [License](#license)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
6
27
|
|
|
7
28
|
```ts
|
|
8
29
|
import { DateTz } from '@lbd-sh/date-tz';
|
|
9
30
|
|
|
31
|
+
// Create a meeting in Rome and preview it elsewhere
|
|
10
32
|
const rome = new DateTz(Date.UTC(2025, 5, 15, 7, 30), 'Europe/Rome');
|
|
33
|
+
const nyc = rome.cloneToTimezone('America/New_York');
|
|
11
34
|
|
|
12
35
|
rome.toString(); // "2025-06-15 09:30:00"
|
|
13
36
|
rome.toString('DD LM YYYY HH:mm tz', 'en'); // "15 June 2025 09:30 Europe/Rome"
|
|
14
|
-
rome.isDst; // true
|
|
15
|
-
|
|
16
|
-
const nyc = rome.cloneToTimezone('America/New_York');
|
|
17
37
|
nyc.toString('YYYY-MM-DD HH:mm tz'); // "2025-06-15 03:30 America/New_York"
|
|
18
38
|
|
|
19
|
-
rome.add(2, 'day').set(11, 'hour');
|
|
20
|
-
rome.toString('YYYY-MM-DD HH:mm'); // "2025-06-17 11:30"
|
|
39
|
+
rome.add(2, 'day').set(11, 'hour'); // Mutating, still DST-safe
|
|
21
40
|
```
|
|
22
41
|
|
|
42
|
+
Need a full workflow? Jump to the [Real-World Playbook](#real-world-playbook). 👇
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
23
46
|
## Installation
|
|
24
47
|
|
|
25
48
|
```bash
|
|
@@ -30,31 +53,41 @@ yarn add @lbd-sh/date-tz
|
|
|
30
53
|
pnpm add @lbd-sh/date-tz
|
|
31
54
|
```
|
|
32
55
|
|
|
56
|
+
DateTz ships as CommonJS with TypeScript declarations. No runtime dependencies, no polyfills.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
33
60
|
## Why DateTz?
|
|
34
61
|
|
|
35
|
-
- **Predictable
|
|
36
|
-
- **DST aware** – offsets
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
62
|
+
- **Predictable math** 🧮 – timestamps are truncated to minutes so cron-like workflows never drift by milliseconds.
|
|
63
|
+
- **DST aware** 🌓 – offsets are sourced from the bundled `timezones` map and double-checked via `Intl.DateTimeFormat`.
|
|
64
|
+
- **Expressive formatting** 🖨️ – familiar tokens (`YYYY`, `MM`, `hh`, `AA`, `tz`, `LM`, etc.) with locale-aware month names.
|
|
65
|
+
- **Simple conversions** 🔁 – `convertToTimezone` and `cloneToTimezone` make cross-zone comparisons painless.
|
|
66
|
+
- **TypeScript-first** 📘 – strong typings, `IDateTz` contract, and declaration files baked in.
|
|
67
|
+
- **Zero dependencies** 🪶 – keep bundles lean while gaining runtime confidence.
|
|
40
68
|
|
|
41
|
-
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## API Surface Overview
|
|
42
72
|
|
|
43
73
|
| Member | Description |
|
|
44
74
|
| ------ | ----------- |
|
|
45
|
-
| `new DateTz(value, tz?)` |
|
|
46
|
-
| `DateTz.now(tz?)` |
|
|
47
|
-
| `DateTz.parse(
|
|
48
|
-
| `DateTz.defaultFormat` |
|
|
49
|
-
|
|
|
50
|
-
|
|
|
75
|
+
| `new DateTz(value, tz?)` | Build from a timestamp or an `IDateTz`-compatible object (timezone defaults to `UTC`). |
|
|
76
|
+
| `DateTz.now(tz?)` | Current moment in the requested timezone. |
|
|
77
|
+
| `DateTz.parse(str, pattern?, tz?)` | Parse formatted strings into `DateTz` instances. |
|
|
78
|
+
| `DateTz.defaultFormat` | Default pattern used by `toString()` when no arguments are provided. |
|
|
79
|
+
| Getters | `year`, `month`, `day`, `hour`, `minute`, `dayOfWeek`, `isDst`, `timezoneOffset`. |
|
|
80
|
+
| Mutators | `add(value, unit)`, `set(value, unit)`, `convertToTimezone(tz)` (mutating), `cloneToTimezone(tz)` (immutable). |
|
|
81
|
+
| Comparison | `compare(other)`, `isComparable(other)` guard against cross-zone mistakes. |
|
|
82
|
+
|
|
83
|
+
---
|
|
51
84
|
|
|
52
|
-
##
|
|
85
|
+
## Formatting Tokens
|
|
53
86
|
|
|
54
87
|
| Token | Meaning | Example |
|
|
55
88
|
| ----- | ------- | ------- |
|
|
56
|
-
| `YYYY`, `yyyy` | Four
|
|
57
|
-
| `YY`, `yy` | Two
|
|
89
|
+
| `YYYY`, `yyyy` | Four-digit year | `2025` |
|
|
90
|
+
| `YY`, `yy` | Two-digit year | `25` |
|
|
58
91
|
| `MM` | Month (01–12) | `06` |
|
|
59
92
|
| `LM` | Locale month name (capitalised) | `June` |
|
|
60
93
|
| `DD` | Day of month (01–31) | `15` |
|
|
@@ -62,251 +95,260 @@ pnpm add @lbd-sh/date-tz
|
|
|
62
95
|
| `hh` | Hour (01–12) | `03` |
|
|
63
96
|
| `mm` | Minute (00–59) | `30` |
|
|
64
97
|
| `ss` | Second (00–59) | `00` |
|
|
65
|
-
| `aa` |
|
|
66
|
-
| `AA` |
|
|
98
|
+
| `aa` | Lowercase am/pm marker | `pm` |
|
|
99
|
+
| `AA` | Uppercase AM/PM marker | `PM` |
|
|
67
100
|
| `tz` | Timezone identifier | `Europe/Rome` |
|
|
68
101
|
|
|
69
|
-
>
|
|
102
|
+
> Literal text? Wrap it in square brackets: `YYYY-MM-DD[ @ ]HH:mm` → `2025-06-15 @ 09:30`.
|
|
70
103
|
|
|
71
|
-
|
|
104
|
+
---
|
|
72
105
|
|
|
73
|
-
|
|
74
|
-
import { DateTz, IDateTz } from '@lbd-sh/date-tz';
|
|
106
|
+
## Core Concepts & Recipes
|
|
75
107
|
|
|
76
|
-
|
|
77
|
-
const utcMeeting = new DateTz(Date.UTC(2025, 0, 1, 12, 0), 'UTC');
|
|
108
|
+
### Creating Dates
|
|
78
109
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const tokyo = new DateTz(seed);
|
|
110
|
+
```ts
|
|
111
|
+
import { DateTz, IDateTz } from '@lbd-sh/date-tz';
|
|
82
112
|
|
|
83
|
-
|
|
84
|
-
const
|
|
113
|
+
const utc = new DateTz(Date.now(), 'UTC');
|
|
114
|
+
const tokyo = new DateTz({ timestamp: Date.now(), timezone: 'Asia/Tokyo' } satisfies IDateTz);
|
|
115
|
+
const la = DateTz.now('America/Los_Angeles');
|
|
85
116
|
```
|
|
86
117
|
|
|
87
|
-
###
|
|
118
|
+
### From Native Date
|
|
88
119
|
|
|
89
120
|
```ts
|
|
90
121
|
const native = new Date();
|
|
91
122
|
const madrid = new DateTz(native.getTime(), 'Europe/Madrid');
|
|
92
|
-
|
|
93
|
-
// Alternatively, keep everything UTC and convert when needed
|
|
94
|
-
const fromUtc = new DateTz(native.getTime(), 'UTC').cloneToTimezone('Europe/Madrid');
|
|
123
|
+
const alwaysUtc = new DateTz(native.getTime(), 'UTC').cloneToTimezone('Europe/Madrid');
|
|
95
124
|
```
|
|
96
125
|
|
|
97
|
-
|
|
126
|
+
### Formatting Showcases
|
|
98
127
|
|
|
99
128
|
```ts
|
|
100
|
-
const
|
|
129
|
+
const invoice = new DateTz(Date.UTC(2025, 10, 5, 16, 45), 'Europe/Paris');
|
|
101
130
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
131
|
+
invoice.toString(); // "2025-11-05 17:45:00"
|
|
132
|
+
invoice.toString('DD/MM/YYYY HH:mm'); // "05/11/2025 17:45"
|
|
133
|
+
invoice.toString('LM DD, YYYY hh:mm aa', 'fr'); // "Novembre 05, 2025 05:45 pm"
|
|
134
|
+
invoice.toString('[Order timezone:] tz'); // "Order timezone: Europe/Paris"
|
|
106
135
|
```
|
|
107
136
|
|
|
108
|
-
###
|
|
109
|
-
|
|
110
|
-
`LM` maps to `new Date(year, month, 3).toLocaleString(locale, { month: 'long' })` ensuring accurate localisation without full `Intl` formatting.
|
|
111
|
-
|
|
112
|
-
## Parsing Scenarios
|
|
137
|
+
### Parsing Scenarios
|
|
113
138
|
|
|
114
139
|
```ts
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// 12h format (requires AM/PM marker)
|
|
119
|
-
const dinner = DateTz.parse('03-18-2025 07:15 PM', 'MM-DD-YYYY hh:mm AA', 'America/New_York');
|
|
120
|
-
|
|
121
|
-
// Custom tokens with literal text
|
|
122
|
-
const promo = DateTz.parse('Sale closes 2025/03/31 @ 23:59', 'Sale closes YYYY/MM/DD [@] HH:mm', 'UTC');
|
|
140
|
+
DateTz.parse('2025-09-01 02:30', 'YYYY-MM-DD HH:mm', 'Asia/Singapore'); // 24h format
|
|
141
|
+
DateTz.parse('03-18-2025 07:15 PM', 'MM-DD-YYYY hh:mm AA', 'America/New_York'); // 12h
|
|
142
|
+
DateTz.parse('Sale closes 2025/03/31 @ 23:59', 'Sale closes YYYY/MM/DD [@] HH:mm', 'UTC'); // Literals
|
|
123
143
|
```
|
|
124
144
|
|
|
125
|
-
Parsing throws
|
|
145
|
+
Parsing throws on invalid zones or incompatible patterns (e.g. `hh` without `aa`/`AA`).
|
|
126
146
|
|
|
127
|
-
|
|
147
|
+
### Arithmetic Cookbook
|
|
128
148
|
|
|
129
149
|
```ts
|
|
130
150
|
const sprint = new DateTz(Date.UTC(2025, 1, 1, 9, 0), 'Europe/Amsterdam');
|
|
131
151
|
|
|
132
|
-
sprint.add(
|
|
133
|
-
//
|
|
134
|
-
sprint.add(
|
|
135
|
-
|
|
136
|
-
// Move to first business day of next month
|
|
137
|
-
sprint.set(sprint.month + 1, 'month');
|
|
138
|
-
sprint.set(1, 'day');
|
|
139
|
-
while ([0, 6].includes(sprint.dayOfWeek)) {
|
|
140
|
-
sprint.add(1, 'day');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Shift to 10:00 local time
|
|
144
|
-
sprint.set(10, 'hour').set(0, 'minute');
|
|
152
|
+
sprint.add(14, 'day'); // Compose to simulate weeks
|
|
153
|
+
sprint.set(sprint.month + 1, 'month').set(1, 'day'); // First day of next month
|
|
154
|
+
while ([0, 6].includes(sprint.dayOfWeek)) sprint.add(1, 'day'); // Skip weekend
|
|
155
|
+
sprint.set(10, 'hour').set(0, 'minute'); // Move to 10:00
|
|
145
156
|
```
|
|
146
157
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
### Immutable Patterns
|
|
150
|
-
|
|
151
|
-
`add`, `set`, and `convertToTimezone` mutate the instance. Use `cloneToTimezone` or spread semantics when immutability is preferred:
|
|
158
|
+
### Immutability Pattern
|
|
152
159
|
|
|
153
160
|
```ts
|
|
154
161
|
const base = DateTz.now('UTC');
|
|
155
|
-
const
|
|
156
|
-
iteration.add(1, 'day');
|
|
162
|
+
const nextRun = new DateTz(base).add(1, 'day');
|
|
157
163
|
```
|
|
158
164
|
|
|
159
|
-
|
|
165
|
+
Mutators change the instance; use cloning when you need persistence.
|
|
166
|
+
|
|
167
|
+
### Comparing & Sorting
|
|
160
168
|
|
|
161
169
|
```ts
|
|
162
170
|
const slots = [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
DateTz.parse('2025-06-15 08:00', 'YYYY-MM-DD HH:mm', 'Europe/Rome'),
|
|
172
|
+
DateTz.parse('2025-06-15 09:00', 'YYYY-MM-DD HH:mm', 'Europe/Rome'),
|
|
173
|
+
DateTz.parse('2025-06-14 18:00', 'YYYY-MM-DD HH:mm', 'Europe/Rome'),
|
|
166
174
|
];
|
|
167
175
|
|
|
168
176
|
slots.sort((a, b) => a.compare(b));
|
|
169
177
|
```
|
|
170
178
|
|
|
171
|
-
|
|
179
|
+
Always guard cross-zone comparisons:
|
|
172
180
|
|
|
173
181
|
```ts
|
|
174
182
|
const rome = DateTz.now('Europe/Rome');
|
|
175
183
|
const ny = DateTz.now('America/New_York');
|
|
176
184
|
|
|
177
|
-
if (!rome.isComparable(ny))
|
|
178
|
-
|
|
179
|
-
|
|
185
|
+
if (!rome.isComparable(ny)) ny.convertToTimezone(rome.timezone);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Daylight Saving Time Deep Dive
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const dstEdge = new DateTz(Date.UTC(2025, 2, 30, 0, 30), 'Europe/Rome'); // DST start night
|
|
180
194
|
|
|
181
|
-
|
|
195
|
+
dstEdge.toString(); // "2025-03-30 01:30:00"
|
|
196
|
+
dstEdge.add(1, 'hour');
|
|
197
|
+
dstEdge.toString(); // "2025-03-30 03:30:00" (skips 02:30)
|
|
198
|
+
dstEdge.isDst; // true
|
|
182
199
|
```
|
|
183
200
|
|
|
184
|
-
|
|
201
|
+
Under the hood:
|
|
202
|
+
|
|
203
|
+
- Offsets are cached per timestamp (`offsetCache`) for speed.
|
|
204
|
+
- If `Intl.DateTimeFormat` is available, DateTz validates the actual offset at runtime.
|
|
205
|
+
- Without `Intl`, DateTz falls back to the static `timezones` map, so you can safely run on serverless or sandboxed environments.
|
|
206
|
+
|
|
207
|
+
### Timezone Conversion
|
|
185
208
|
|
|
186
209
|
```ts
|
|
187
210
|
const flight = new DateTz(Date.UTC(2025, 3, 28, 20, 0), 'Europe/London');
|
|
188
211
|
|
|
189
|
-
const takeoff = flight.cloneToTimezone('America/Los_Angeles');
|
|
212
|
+
const takeoff = flight.cloneToTimezone('America/Los_Angeles'); // immutable
|
|
190
213
|
const landing = flight.cloneToTimezone('Asia/Tokyo');
|
|
191
|
-
|
|
192
|
-
takeoff.isDst; // false (London DST might not have started yet)
|
|
193
|
-
landing.isDst; // true or false depending on Tokyo rules
|
|
194
214
|
```
|
|
195
215
|
|
|
196
|
-
|
|
197
|
-
- `cloneToTimezone` returns a new instance.
|
|
198
|
-
- Both refresh the cached offset and leverage the `Intl` API to detect real-world DST offsets (falling back to the static map).
|
|
216
|
+
Use `convertToTimezone` if you want to mutate the instance in-place.
|
|
199
217
|
|
|
200
|
-
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Real-World Playbook
|
|
221
|
+
|
|
222
|
+
### Scheduling Emails Across Offices 📧
|
|
201
223
|
|
|
202
224
|
```ts
|
|
203
|
-
const
|
|
225
|
+
const offices = [
|
|
226
|
+
{ tz: 'Europe/Rome', hour: 9 },
|
|
227
|
+
{ tz: 'America/New_York', hour: 9 },
|
|
228
|
+
{ tz: 'Asia/Tokyo', hour: 9 },
|
|
229
|
+
];
|
|
204
230
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
231
|
+
const base = DateTz.now('UTC').set(0, 'minute');
|
|
232
|
+
|
|
233
|
+
const sends = offices.map(({ tz, hour }) => {
|
|
234
|
+
const local = new DateTz(base).convertToTimezone(tz);
|
|
235
|
+
local.set(hour, 'hour');
|
|
236
|
+
if (local.compare(base) < 0) local.add(1, 'day');
|
|
237
|
+
return local;
|
|
238
|
+
});
|
|
209
239
|
```
|
|
210
240
|
|
|
211
|
-
|
|
241
|
+
### React Component Integration ⚛️
|
|
212
242
|
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
DateTz.parse('2025-06-15 10:00', 'YYYY-MM-DD HH:mm', 'Europe/Rome'),
|
|
217
|
-
DateTz.parse('2025-06-15 09:45', 'YYYY-MM-DD HH:mm', 'Europe/Rome'),
|
|
218
|
-
];
|
|
243
|
+
```tsx
|
|
244
|
+
import { useMemo } from 'react';
|
|
245
|
+
import { DateTz } from '@lbd-sh/date-tz';
|
|
219
246
|
|
|
220
|
-
|
|
247
|
+
export function Countdown({ timestamp, tz }: { timestamp: number; tz: string }) {
|
|
248
|
+
const target = useMemo(() => new DateTz(timestamp, tz), [timestamp, tz]);
|
|
249
|
+
return (
|
|
250
|
+
<span title={target.toString('YYYY-MM-DD HH:mm tz')}>
|
|
251
|
+
{target.toString('DD MMM YYYY, HH:mm', navigator.language)}
|
|
252
|
+
</span>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
221
256
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
257
|
+
### Express Middleware 🛤️
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
app.use((req, _res, next) => {
|
|
261
|
+
const headerTz = req.header('x-user-tz') ?? 'UTC';
|
|
262
|
+
req.context = {
|
|
263
|
+
now: () => DateTz.now(headerTz),
|
|
264
|
+
};
|
|
265
|
+
next();
|
|
266
|
+
});
|
|
228
267
|
```
|
|
229
268
|
|
|
230
|
-
|
|
269
|
+
### Testing Automation with Jest 🧪
|
|
231
270
|
|
|
232
271
|
```ts
|
|
233
|
-
|
|
234
|
-
createdAt: DateTz.now('UTC').toString(),
|
|
235
|
-
timestamp: Date.now(),
|
|
236
|
-
};
|
|
272
|
+
import { DateTz } from '@lbd-sh/date-tz';
|
|
237
273
|
|
|
238
|
-
|
|
239
|
-
|
|
274
|
+
describe('billing cutoff', () => {
|
|
275
|
+
it('moves to next business day when on weekend', () => {
|
|
276
|
+
const friday = DateTz.parse('2025-06-13 17:00', 'YYYY-MM-DD HH:mm', 'Europe/Rome');
|
|
277
|
+
friday.add(1, 'day');
|
|
278
|
+
expect(friday.dayOfWeek).toBe(6);
|
|
279
|
+
friday.add(2, 'day');
|
|
280
|
+
expect(friday.dayOfWeek).toBe(1);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
240
283
|
```
|
|
241
284
|
|
|
242
|
-
|
|
285
|
+
---
|
|
243
286
|
|
|
244
|
-
##
|
|
287
|
+
## Build & Distribution
|
|
245
288
|
|
|
246
|
-
|
|
247
|
-
|
|
289
|
+
- The compiled CommonJS bundle plus declarations live in `dist/` (`index.js`, `index.d.ts`, maps, and helpers).
|
|
290
|
+
- `package.json` already points `main` and `types` at the compiled output, so consumers never need the TypeScript sources.
|
|
291
|
+
- Rebuild locally before opening a PR:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
npm ci
|
|
295
|
+
npm run build
|
|
296
|
+
```
|
|
248
297
|
|
|
249
|
-
|
|
298
|
+
- Publishing to npm is handled by the maintainers. Contributors can stop at the build step and submit a pull request.
|
|
250
299
|
|
|
251
|
-
|
|
252
|
-
```
|
|
300
|
+
---
|
|
253
301
|
|
|
254
|
-
|
|
302
|
+
## TypeScript & Tooling
|
|
255
303
|
|
|
256
|
-
|
|
304
|
+
- `IDateTz` describes constructor-friendly shapes.
|
|
305
|
+
- `timezones` exports the full offset map (`Record<string, { sdt: number; dst: number }>`).
|
|
306
|
+
- Compatible with bundlers (Webpack, Vite, TurboPack). For ESM projects, use transpilation or `dynamic import`.
|
|
257
307
|
|
|
258
308
|
```ts
|
|
259
309
|
import type { IDateTz } from '@lbd-sh/date-tz';
|
|
260
310
|
|
|
261
|
-
function normalise(
|
|
262
|
-
|
|
263
|
-
return instance.cloneToTimezone('UTC');
|
|
311
|
+
function normalise(input: IDateTz): IDateTz {
|
|
312
|
+
return new DateTz(input).cloneToTimezone('UTC');
|
|
264
313
|
}
|
|
265
314
|
```
|
|
266
315
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
## Interoperability Patterns
|
|
316
|
+
---
|
|
270
317
|
|
|
271
|
-
|
|
318
|
+
## Performance Tips
|
|
272
319
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
320
|
+
1. **Reuse instances** when adjusting a datetime in a tight loop (mutating via `set`/`add` avoids allocations).
|
|
321
|
+
2. **Cache conversions**: if you often convert the same timestamp across timezones, keep the clones.
|
|
322
|
+
3. **Disable locale formatting** (`toString(pattern)` without the `locale` argument) for the fastest path.
|
|
323
|
+
4. **Tree-shake**: import only what you need (`import { DateTz }` instead of wildcard).
|
|
276
324
|
|
|
277
|
-
|
|
278
|
-
```
|
|
325
|
+
---
|
|
279
326
|
|
|
280
|
-
|
|
327
|
+
## FAQ & Troubleshooting
|
|
281
328
|
|
|
282
|
-
|
|
283
|
-
|
|
329
|
+
**Q: Can I format with seconds or milliseconds?**
|
|
330
|
+
A: Seconds (`ss`) are supported. Milliseconds are intentionally dropped to keep arithmetic deterministic. Store them separately if needed.
|
|
284
331
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
```
|
|
332
|
+
**Q: Why can’t I compare two dates with different timezones?**
|
|
333
|
+
A: `compare` guards against mistakes. Convert one date (`cloneToTimezone`) before comparing.
|
|
289
334
|
|
|
290
|
-
|
|
335
|
+
**Q: How do I add weeks?**
|
|
336
|
+
A: Compose calls (`add(7, 'day')`). Weeks are not a native unit to keep the API small and explicit.
|
|
291
337
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
- Build locally with:
|
|
338
|
+
**Q: Do I need to ship the entire `timezones` map?**
|
|
339
|
+
A: The map is ~40 KB minified. For extreme optimisation you can pre-prune zones before bundling.
|
|
295
340
|
|
|
296
|
-
|
|
297
|
-
npm install
|
|
298
|
-
npm run build
|
|
299
|
-
```
|
|
341
|
+
---
|
|
300
342
|
|
|
301
|
-
|
|
343
|
+
## Contributing
|
|
302
344
|
|
|
303
|
-
|
|
345
|
+
- 💡 Open feature ideas or bug reports at [github.com/lbdsh/date-tz/issues](https://github.com/lbdsh/date-tz/issues).
|
|
346
|
+
- 🔁 Submit pull requests following the existing linting/build steps (`npm ci && npm run build`).
|
|
347
|
+
- 📦 Releases are automated through the GitHub workflow in `.github/workflows/production.yaml`.
|
|
304
348
|
|
|
305
|
-
|
|
306
|
-
- Ship a companion utilities layer (diffs, ranges, calendars).
|
|
307
|
-
- Expand token set with `W` (ISO week) and `ddd` (weekday short names).
|
|
349
|
+
Community feedback keeps DateTz sharp—thanks for being part of it! 🙌
|
|
308
350
|
|
|
309
|
-
|
|
351
|
+
---
|
|
310
352
|
|
|
311
353
|
## License
|
|
312
354
|
|