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