@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.
Files changed (2) hide show
  1. package/README.md +209 -167
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,25 +1,48 @@
1
- # Date TZ
1
+ # Date TZ ⏰✨
2
2
 
3
- Powerful, dependency-free timezone utilities for JavaScript and TypeScript. `DateTz` keeps minute-precision timestamps aligned with IANA timezones, detects daylight-saving transitions automatically, and pairs a tiny API with comprehensive TypeScript definitions.
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
- ## TL;DR
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 arithmetic** – timestamps are truncated to minutes to avoid millisecond drift.
36
- - **DST aware** – offsets come from the bundled `timezones` map and are verified via `Intl.DateTimeFormat` when available.
37
- - **Rich formatting/parsing** – reuse familiar tokens (`YYYY`, `MM`, `hh`, `AA`, `tz`, `LM` for locale month names, and more).
38
- - **Lean footprint** – no runtime dependencies, CommonJS output plus type declarations.
39
- - **Ergonomic conversions** – move or clone dates across timezones in one call.
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
- ## API Surface at a Glance
69
+ ---
70
+
71
+ ## API Surface Overview
42
72
 
43
73
  | Member | Description |
44
74
  | ------ | ----------- |
45
- | `new DateTz(value, tz?)` | Accepts a timestamp or an `IDateTz` compliant object plus an optional timezone id. |
46
- | `DateTz.now(tz?)` | Returns the current moment in the given timezone (default `UTC`). |
47
- | `DateTz.parse(string, pattern?, tz?)` | Creates a date from a formatted string. |
48
- | `DateTz.defaultFormat` | Global default pattern used by `toString()` with no args. |
49
- | Instance getters | `year`, `month`, `day`, `hour`, `minute`, `dayOfWeek`, `isDst`, `timezoneOffset`. |
50
- | Instance methods | `toString(pattern?, locale?)`, `compare(other)`, `isComparable(other)`, `add(value, unit)`, `set(value, unit)`, `convertToTimezone(tz)`, `cloneToTimezone(tz)`. |
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
- ## Pattern Tokens
85
+ ## Formatting Tokens
53
86
 
54
87
  | Token | Meaning | Example |
55
88
  | ----- | ------- | ------- |
56
- | `YYYY`, `yyyy` | Four digit year | `2025` |
57
- | `YY`, `yy` | Two digit year | `25` |
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` | `am`/`pm` marker | `am` |
66
- | `AA` | `AM`/`PM` marker | `PM` |
98
+ | `aa` | Lowercase am/pm marker | `pm` |
99
+ | `AA` | Uppercase AM/PM marker | `PM` |
67
100
  | `tz` | Timezone identifier | `Europe/Rome` |
68
101
 
69
- > Need literal text? Keep it inside square brackets. Example: `YYYY-MM-DD[ @ ]HH:mm` → `2025-06-15 @ 09:30`. Characters inside brackets remain unchanged.
102
+ > Literal text? Wrap it in square brackets: `YYYY-MM-DD[ @ ]HH:mm` → `2025-06-15 @ 09:30`.
70
103
 
71
- ## Creating Dates
104
+ ---
72
105
 
73
- ```ts
74
- import { DateTz, IDateTz } from '@lbd-sh/date-tz';
106
+ ## Core Concepts & Recipes
75
107
 
76
- // From a unix timestamp (milliseconds)
77
- const utcMeeting = new DateTz(Date.UTC(2025, 0, 1, 12, 0), 'UTC');
108
+ ### Creating Dates
78
109
 
79
- // From another DateTz-like object
80
- const seed: IDateTz = { timestamp: Date.now(), timezone: 'Asia/Tokyo' };
81
- const tokyo = new DateTz(seed);
110
+ ```ts
111
+ import { DateTz, IDateTz } from '@lbd-sh/date-tz';
82
112
 
83
- // Using the helper
84
- const laNow = DateTz.now('America/Los_Angeles');
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
- ### Working With Plain Date Objects
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
- ## Formatting Showcases
126
+ ### Formatting Showcases
98
127
 
99
128
  ```ts
100
- const order = new DateTz(Date.UTC(2025, 10, 5, 16, 45), 'Europe/Paris');
129
+ const invoice = new DateTz(Date.UTC(2025, 10, 5, 16, 45), 'Europe/Paris');
101
130
 
102
- order.toString(); // "2025-11-05 17:45:00"
103
- order.toString('DD/MM/YYYY HH:mm'); // "05/11/2025 17:45"
104
- order.toString('LM DD, YYYY hh:mm aa', 'fr'); // "Novembre 05, 2025 05:45 pm"
105
- order.toString('[Order timezone:] tz'); // "Order timezone: Europe/Paris"
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
- ### Locale-sensitive Month Names
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
- // Standard 24h format
116
- const release = DateTz.parse('2025-09-01 02:30', 'YYYY-MM-DD HH:mm', 'Asia/Singapore');
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 when the timezone id is missing or invalid, or when pattern/token combos are incompatible (for example `hh` without `aa`/`AA`).
145
+ Parsing throws on invalid zones or incompatible patterns (e.g. `hh` without `aa`/`AA`).
126
146
 
127
- ## Arithmetic Cookbook
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(2, 'week'); // week not supported
133
- // Use compositions instead:
134
- sprint.add(14, 'day');
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
- `add` accepts `minute`, `hour`, `day`, `month`, `year`. Compose multiple calls for complex adjustments. Overflows and leap years are handled automatically.
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 iteration = new DateTz(base);
156
- iteration.add(1, 'day');
162
+ const nextRun = new DateTz(base).add(1, 'day');
157
163
  ```
158
164
 
159
- ## Comparing & Sorting
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
- new DateTz(Date.UTC(2025, 6, 10, 8, 0), 'Europe/Rome'),
164
- new DateTz(Date.UTC(2025, 6, 10, 9, 0), 'Europe/Rome'),
165
- new DateTz(Date.UTC(2025, 6, 9, 18, 0), 'Europe/Rome'),
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
- `compare` throws if timezones differ:
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
- ny.convertToTimezone(rome.timezone);
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
- rome.compare(ny);
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
- ## Timezone Conversion Deep Dive
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
- - `convertToTimezone` mutates the instance.
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
- ### DST Transition Example
218
+ ---
219
+
220
+ ## Real-World Playbook
221
+
222
+ ### Scheduling Emails Across Offices 📧
201
223
 
202
224
  ```ts
203
- const dstEdge = new DateTz(Date.UTC(2025, 2, 30, 0, 30), 'Europe/Rome'); // Night DST starts
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
- dstEdge.toString(); // "2025-03-30 01:30:00"
206
- dstEdge.add(1, 'hour');
207
- dstEdge.toString(); // "2025-03-30 03:30:00" (skips 02:30 automatically)
208
- dstEdge.isDst; // true
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
- ## Working with Collections
241
+ ### React Component Integration ⚛️
212
242
 
213
- ```ts
214
- const timeline = [
215
- DateTz.parse('2025-06-15 09:30', 'YYYY-MM-DD HH:mm', 'Europe/Rome'),
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
- const sorted = timeline.slice().sort((a, b) => a.compare(b));
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
- // Group by day
223
- const byDate = sorted.reduce<Record<string, DateTz[]>>((acc, slot) => {
224
- const key = slot.toString('YYYY-MM-DD');
225
- (acc[key] ||= []).push(slot);
226
- return acc;
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
- ## Serialization Tips
269
+ ### Testing Automation with Jest 🧪
231
270
 
232
271
  ```ts
233
- const payload = {
234
- createdAt: DateTz.now('UTC').toString(),
235
- timestamp: Date.now(),
236
- };
272
+ import { DateTz } from '@lbd-sh/date-tz';
237
273
 
238
- // Later...
239
- const restored = new DateTz(payload.timestamp, 'UTC');
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
- Prefer storing the timestamp (UTC) and timezone id. When deserialising, feed both back into the constructor for deterministic results.
285
+ ---
243
286
 
244
- ## Extending the Timezone Map
287
+ ## Build & Distribution
245
288
 
246
- ```ts
247
- import { timezones } from '@lbd-sh/date-tz';
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
- timezones['Custom/Island'] = { sdt: 32_400, dst: 36_000 }; // Offsets in seconds
298
+ - Publishing to npm is handled by the maintainers. Contributors can stop at the build step and submit a pull request.
250
299
 
251
- const island = new DateTz(Date.now(), 'Custom/Island');
252
- ```
300
+ ---
253
301
 
254
- > Ensure keys follow IANA naming conventions. Offsets are seconds from UTC (negative for west, positive for east).
302
+ ## TypeScript & Tooling
255
303
 
256
- ## TypeScript Excellence
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(date: IDateTz): IDateTz {
262
- const instance = new DateTz(date);
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
- `IDateTz` lets you accept plain objects from APIs while still benefiting from compile-time guarantees.
268
-
269
- ## Interoperability Patterns
316
+ ---
270
317
 
271
- ### With Fetch / APIs
318
+ ## Performance Tips
272
319
 
273
- ```ts
274
- const response = await fetch('/api/events');
275
- const body: { timestamp: number; timezone: string }[] = await response.json();
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
- const events = body.map(({ timestamp, timezone }) => new DateTz(timestamp, timezone));
278
- ```
325
+ ---
279
326
 
280
- ### With Cron-like Scheduling
327
+ ## FAQ & Troubleshooting
281
328
 
282
- ```ts
283
- const job = new DateTz(Date.UTC(2025, 5, 1, 5, 0), 'America/New_York');
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
- while (job.compare(DateTz.now('America/New_York')) < 0) {
286
- job.add(1, 'day');
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
- ## Packaging Notes
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
- - The published CommonJS bundle lives in `dist/index.js`; declarations and maps ship alongside (`index.d.ts`, `*.map`).
293
- - `package.json` exposes `main` and `types` from the compiled output, so consumers do not need to run TypeScript.
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
- ```bash
297
- npm install
298
- npm run build
299
- ```
341
+ ---
300
342
 
301
- - The GitHub Action (`.github/workflows/production.yaml`) produces the build, versions using GitVersion, and publishes to npm as `@lbd-sh/date-tz`.
343
+ ## Contributing
302
344
 
303
- ## Roadmap Ideas
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
- - Add optional ESM build targets.
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
- Contributions and feature requests are welcome — open an issue at [github.com/lbdsh/date-tz/issues](https://github.com/lbdsh/date-tz/issues).
351
+ ---
310
352
 
311
353
  ## License
312
354
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@lbd-sh/date-tz",
3
3
  "displayName": "Date TZ",
4
4
  "engineStrict": false,
5
- "version": "1.0.6",
5
+ "version": "1.0.8",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "scripts": {