@arkv/temporal 0.2.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/README.md +492 -0
- package/dist/cjs/constants.js +7 -0
- package/dist/cjs/dayjs.js +319 -0
- package/dist/cjs/diff.js +60 -0
- package/dist/cjs/format.js +84 -0
- package/dist/cjs/index.js +49 -0
- package/dist/cjs/locale.js +73 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/parse.js +46 -0
- package/dist/cjs/start-end.js +111 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/units.js +56 -0
- package/dist/esm/constants.js +4 -0
- package/dist/esm/dayjs.js +315 -0
- package/dist/esm/diff.js +57 -0
- package/dist/esm/format.js +81 -0
- package/dist/esm/index.js +45 -0
- package/dist/esm/locale.js +66 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/parse.js +43 -0
- package/dist/esm/start-end.js +107 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/units.js +52 -0
- package/dist/types/constants.d.ts +3 -0
- package/dist/types/dayjs.d.ts +61 -0
- package/dist/types/diff.d.ts +2 -0
- package/dist/types/format.d.ts +2 -0
- package/dist/types/index.d.ts +16 -0
- package/dist/types/locale.d.ts +15 -0
- package/dist/types/parse.d.ts +2 -0
- package/dist/types/start-end.d.ts +4 -0
- package/dist/types/types.d.ts +12 -0
- package/dist/types/units.d.ts +4 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# @arkv/temporal
|
|
2
|
+
|
|
3
|
+
> The zero-timezone-bug drop-in replacement for Day.js.
|
|
4
|
+
> Keep your exact same chainable API. Swap the engine to the modern, native JavaScript **Temporal** API.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## The Problem
|
|
9
|
+
|
|
10
|
+
JavaScript's `Date` object is broken by design — and most of the popular date libraries (Day.js, Moment.js) are built on top of it. That means they inherit all the same fundamental bugs:
|
|
11
|
+
|
|
12
|
+
### What `Date` gets wrong
|
|
13
|
+
|
|
14
|
+
**1. Timezone traps**
|
|
15
|
+
`Date` only understands two timezones: UTC and "whatever the machine is set to". There is no way to work with an arbitrary named timezone without a third-party dataset and manual offset arithmetic.
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
// Day.js — no timezone support in core
|
|
19
|
+
dayjs('2024-03-10').add(1, 'day').format('YYYY-MM-DD')
|
|
20
|
+
// Returns '2024-03-11' ✓ — but only because it avoids the DST edge
|
|
21
|
+
// On the night of a DST "spring forward", adding 1 "day" adds 23 hours.
|
|
22
|
+
// "23 hours later" is not "tomorrow".
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**2. Mutable state leaking through**
|
|
26
|
+
`Date` methods like `setMonth()` mutate in place. Libraries paper over this with cloning, but bugs still slip through.
|
|
27
|
+
|
|
28
|
+
**3. Month indexing insanity**
|
|
29
|
+
`new Date(2024, 0, 1)` is January 1st. January is month `0`. December is month `11`. This is a design decision from 1995 that has confused every JavaScript developer since.
|
|
30
|
+
|
|
31
|
+
**4. String parsing is implementation-defined**
|
|
32
|
+
`new Date('2024-03-07')` returns midnight UTC on some runtimes and midnight local time on others.
|
|
33
|
+
|
|
34
|
+
**5. No sub-millisecond precision**
|
|
35
|
+
`Date` is capped at millisecond precision. Modern systems need microsecond and nanosecond timestamps.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## The Solution: Temporal
|
|
40
|
+
|
|
41
|
+
The **TC39 Temporal proposal** is a complete, ground-up redesign of date and time in JavaScript. It solves every one of the problems above:
|
|
42
|
+
|
|
43
|
+
- ✅ **Named timezone support** built in — `Temporal.ZonedDateTime` always carries its timezone
|
|
44
|
+
- ✅ **Immutable by design** — every operation returns a new object
|
|
45
|
+
- ✅ **Calendar-aware arithmetic** — adding 1 month to January 31st gives February 28th, not March 3rd
|
|
46
|
+
- ✅ **DST-safe** — adding "1 day" moves the wall clock by exactly 1 day, even across DST boundaries
|
|
47
|
+
- ✅ **Nanosecond precision** — `epochNanoseconds` returns a `BigInt`
|
|
48
|
+
- ✅ **Explicit timezone handling** — you cannot accidentally ignore a timezone
|
|
49
|
+
|
|
50
|
+
### But Temporal is verbose
|
|
51
|
+
|
|
52
|
+
The Temporal API is incredibly powerful. It is also incredibly verbose:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// Temporal — correct but exhausting
|
|
56
|
+
Temporal.Now.plainDateISO().toString()
|
|
57
|
+
// vs
|
|
58
|
+
dayjs().format('YYYY-MM-DD')
|
|
59
|
+
|
|
60
|
+
// Temporal — adding 1 month
|
|
61
|
+
Temporal.Now.zonedDateTimeISO().add({ months: 1 }).toPlainDate().toString()
|
|
62
|
+
// vs
|
|
63
|
+
dayjs().add(1, 'month').format('YYYY-MM-DD')
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Nobody wants to rewrite 10,000 lines of clean `dayjs().format('YYYY-MM-DD')` code into Temporal's multi-step API.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## What `@arkv/temporal` Does
|
|
71
|
+
|
|
72
|
+
`@arkv/temporal` is an **adapter**. It exposes the **exact same chainable API as Day.js**, but maintains a `Temporal.ZonedDateTime` internally instead of a `Date`. You get:
|
|
73
|
+
|
|
74
|
+
- The **DX of Day.js** — same methods, same chaining, same format tokens
|
|
75
|
+
- The **correctness of Temporal** — timezone safety, DST-aware arithmetic, nanosecond timestamps
|
|
76
|
+
- **Zero code changes** for the vast majority of use cases
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
// Before — Day.js with Date underneath
|
|
80
|
+
import dayjs from 'dayjs'
|
|
81
|
+
dayjs('2026-03-07').add(1, 'month').format('YYYY-MM-DD')
|
|
82
|
+
|
|
83
|
+
// After — @arkv/temporal with Temporal underneath
|
|
84
|
+
import tdayjs from '@arkv/temporal'
|
|
85
|
+
tdayjs('2026-03-07').add(1, 'month').format('YYYY-MM-DD')
|
|
86
|
+
// API is identical. The engine is not.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
npm install @arkv/temporal
|
|
95
|
+
# or
|
|
96
|
+
bun add @arkv/temporal
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> **Note:** The Temporal API is not yet available natively in all runtimes (Bun, older Node.js). This package automatically installs and imports [`temporal-polyfill`](https://github.com/fullcalendar/temporal-polyfill) to fill the gap. No configuration required.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Quick Start
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import tdayjs from '@arkv/temporal'
|
|
107
|
+
|
|
108
|
+
// Current time
|
|
109
|
+
tdayjs()
|
|
110
|
+
|
|
111
|
+
// Parse a date string
|
|
112
|
+
tdayjs('2026-03-07')
|
|
113
|
+
tdayjs('2026-03-07T10:30:00')
|
|
114
|
+
tdayjs('2026-03-07T10:30:00+05:00')
|
|
115
|
+
tdayjs('2026-03-07T10:30:00[America/New_York]')
|
|
116
|
+
|
|
117
|
+
// Parse a unix timestamp (milliseconds)
|
|
118
|
+
tdayjs(1741305600000)
|
|
119
|
+
|
|
120
|
+
// Parse a native Date
|
|
121
|
+
tdayjs(new Date())
|
|
122
|
+
|
|
123
|
+
// Clone another instance
|
|
124
|
+
tdayjs(tdayjs())
|
|
125
|
+
|
|
126
|
+
// Unix seconds
|
|
127
|
+
tdayjs.unix(1741305600)
|
|
128
|
+
|
|
129
|
+
// Invalid date (dayjs-compatible)
|
|
130
|
+
tdayjs(null).isValid() // false
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## API Reference
|
|
136
|
+
|
|
137
|
+
All methods are identical to Day.js unless explicitly noted. For detailed documentation, refer to the [Day.js docs](https://day.js.org/docs/en/parse/parse).
|
|
138
|
+
|
|
139
|
+
### Parsing
|
|
140
|
+
|
|
141
|
+
| Input | Behavior |
|
|
142
|
+
|-------|----------|
|
|
143
|
+
| `undefined` / no argument | Current local time |
|
|
144
|
+
| `null` | Invalid date |
|
|
145
|
+
| `number` | Unix timestamp in **milliseconds** |
|
|
146
|
+
| `string` (date only) | e.g. `'2026-03-07'` — midnight local time |
|
|
147
|
+
| `string` (datetime) | e.g. `'2026-03-07T10:30:00'` — local time |
|
|
148
|
+
| `string` (with offset) | e.g. `'2026-03-07T10:30:00+05:00'` — instant, displayed in local tz |
|
|
149
|
+
| `string` (with annotation) | e.g. `'2026-03-07T10:30:00[Europe/London]'` — full ZonedDateTime |
|
|
150
|
+
| `Date` | Native JS Date |
|
|
151
|
+
| `TDayjs` | Clones the instance |
|
|
152
|
+
|
|
153
|
+
### Display / Validity
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
tdayjs().isValid() // true
|
|
157
|
+
tdayjs(null).isValid() // false
|
|
158
|
+
tdayjs().clone() // new identical instance
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Getters
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const d = tdayjs('2026-03-07T10:30:45.123')
|
|
165
|
+
|
|
166
|
+
d.year() // 2026
|
|
167
|
+
d.month() // 2 ← 0-indexed (0 = January, 11 = December)
|
|
168
|
+
d.date() // 7 ← day of month (1–31)
|
|
169
|
+
d.day() // 6 ← day of week (0 = Sunday, 6 = Saturday)
|
|
170
|
+
d.hour() // 10
|
|
171
|
+
d.minute() // 30
|
|
172
|
+
d.second() // 45
|
|
173
|
+
d.millisecond() // 123
|
|
174
|
+
|
|
175
|
+
// Generic getter
|
|
176
|
+
d.get('year') // 2026
|
|
177
|
+
d.get('month') // 2
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
> **Note on `month()`:** Like Day.js, months are **0-indexed**. January = `0`, December = `11`. Internally, Temporal uses 1-indexed months — the conversion is handled automatically.
|
|
181
|
+
|
|
182
|
+
### Setters
|
|
183
|
+
|
|
184
|
+
All setters return a **new instance**. The original is never modified.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
tdayjs('2026-03-07').year(2030) // → 2030-03-07
|
|
188
|
+
tdayjs('2026-03-07').month(0) // → 2026-01-07 (January)
|
|
189
|
+
tdayjs('2026-03-07').month(11) // → 2026-12-07 (December)
|
|
190
|
+
tdayjs('2026-03-07').date(15) // → 2026-03-15
|
|
191
|
+
tdayjs('2026-03-07').hour(9) // → 2026-03-07T09:xx:xx
|
|
192
|
+
tdayjs('2026-03-07').minute(0)
|
|
193
|
+
tdayjs('2026-03-07').second(0)
|
|
194
|
+
tdayjs('2026-03-07').millisecond(0)
|
|
195
|
+
|
|
196
|
+
// Generic setter
|
|
197
|
+
tdayjs('2026-03-07').set('year', 2030)
|
|
198
|
+
tdayjs('2026-03-07').set('month', 5) // June
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Add / Subtract
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// Supported units (case-insensitive, singular/plural/short all work)
|
|
205
|
+
// 'year' | 'years' | 'y'
|
|
206
|
+
// 'month' | 'months' | 'M'
|
|
207
|
+
// 'quarter' | 'quarters' | 'Q' ← native, no plugin needed
|
|
208
|
+
// 'week' | 'weeks' | 'w'
|
|
209
|
+
// 'day' | 'days' | 'd'
|
|
210
|
+
// 'hour' | 'hours' | 'h'
|
|
211
|
+
// 'minute' | 'minutes' | 'm'
|
|
212
|
+
// 'second' | 'seconds' | 's'
|
|
213
|
+
// 'millisecond' | 'milliseconds' | 'ms'
|
|
214
|
+
|
|
215
|
+
tdayjs('2026-03-07').add(1, 'day') // 2026-03-08
|
|
216
|
+
tdayjs('2026-01-31').add(1, 'month') // 2026-02-28 (clamped, not March 3)
|
|
217
|
+
tdayjs('2026-03-07').add(1, 'year') // 2027-03-07
|
|
218
|
+
tdayjs('2026-03-07').add(1, 'quarter') // 2026-06-07
|
|
219
|
+
tdayjs('2026-03-07').subtract(1, 'week') // 2026-02-28
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
> **Why `add(1, 'month')` is different here:**
|
|
223
|
+
> Day.js (using `Date`) can return `March 3` for `January 31 + 1 month` depending on the engine.
|
|
224
|
+
> `@arkv/temporal` uses Temporal's calendar-aware arithmetic which correctly **clamps** to the last day of the target month.
|
|
225
|
+
|
|
226
|
+
### Start Of / End Of
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
const d = tdayjs('2026-03-15T10:30:45')
|
|
230
|
+
|
|
231
|
+
// Start of unit
|
|
232
|
+
d.startOf('year') // 2026-01-01T00:00:00
|
|
233
|
+
d.startOf('month') // 2026-03-01T00:00:00
|
|
234
|
+
d.startOf('week') // 2026-03-09T00:00:00 (Sunday)
|
|
235
|
+
d.startOf('day') // 2026-03-15T00:00:00
|
|
236
|
+
d.startOf('hour') // 2026-03-15T10:00:00
|
|
237
|
+
d.startOf('minute') // 2026-03-15T10:30:00
|
|
238
|
+
d.startOf('second') // 2026-03-15T10:30:45.000
|
|
239
|
+
|
|
240
|
+
// End of unit
|
|
241
|
+
d.endOf('year') // 2026-12-31T23:59:59.999
|
|
242
|
+
d.endOf('month') // 2026-03-31T23:59:59.999
|
|
243
|
+
d.endOf('day') // 2026-03-15T23:59:59.999
|
|
244
|
+
d.endOf('hour') // 2026-03-15T10:59:59.999
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Format
|
|
248
|
+
|
|
249
|
+
Uses the same token syntax as Day.js:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
const d = tdayjs('2026-03-07T09:05:03')
|
|
253
|
+
|
|
254
|
+
d.format() // '2026-03-07T09:05:03+HH:mm' (default)
|
|
255
|
+
d.format('YYYY-MM-DD') // '2026-03-07'
|
|
256
|
+
d.format('DD/MM/YYYY') // '07/03/2026'
|
|
257
|
+
d.format('MMM D, YYYY') // 'Mar 7, 2026'
|
|
258
|
+
d.format('MMMM Do, YYYY') // 'March 7, 2026' (no ordinal plugin needed)
|
|
259
|
+
d.format('HH:mm:ss') // '09:05:03'
|
|
260
|
+
d.format('h:mm A') // '9:05 AM'
|
|
261
|
+
d.format('h:mm a') // '9:05 am'
|
|
262
|
+
d.format('[Today is] dddd') // 'Today is Saturday'
|
|
263
|
+
d.format('ddd, MMM D') // 'Sat, Mar 7'
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
| Token | Output | Description |
|
|
267
|
+
|-------|--------|-------------|
|
|
268
|
+
| `YYYY` | `2026` | 4-digit year |
|
|
269
|
+
| `YY` | `26` | 2-digit year |
|
|
270
|
+
| `M` | `3` | Month (1–12) |
|
|
271
|
+
| `MM` | `03` | Month, zero-padded |
|
|
272
|
+
| `MMM` | `Mar` | Abbreviated month name |
|
|
273
|
+
| `MMMM` | `March` | Full month name |
|
|
274
|
+
| `D` | `7` | Day of month (1–31) |
|
|
275
|
+
| `DD` | `07` | Day of month, zero-padded |
|
|
276
|
+
| `d` | `6` | Day of week (0=Sun, 6=Sat) |
|
|
277
|
+
| `dd` | `Sa` | Min weekday name |
|
|
278
|
+
| `ddd` | `Sat` | Short weekday name |
|
|
279
|
+
| `dddd` | `Saturday` | Full weekday name |
|
|
280
|
+
| `H` | `9` | Hour, 24h (0–23) |
|
|
281
|
+
| `HH` | `09` | Hour, 24h, zero-padded |
|
|
282
|
+
| `h` | `9` | Hour, 12h (1–12) |
|
|
283
|
+
| `hh` | `09` | Hour, 12h, zero-padded |
|
|
284
|
+
| `A` | `AM` | Meridiem, uppercase |
|
|
285
|
+
| `a` | `am` | Meridiem, lowercase |
|
|
286
|
+
| `m` | `5` | Minute (0–59) |
|
|
287
|
+
| `mm` | `05` | Minute, zero-padded |
|
|
288
|
+
| `s` | `3` | Second (0–59) |
|
|
289
|
+
| `ss` | `03` | Second, zero-padded |
|
|
290
|
+
| `SSS` | `000` | Milliseconds |
|
|
291
|
+
| `Z` | `+05:00` | UTC offset with colon |
|
|
292
|
+
| `ZZ` | `+0500` | UTC offset without colon |
|
|
293
|
+
| `[text]` | `text` | Escaped literal text |
|
|
294
|
+
|
|
295
|
+
### Difference
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const a = tdayjs('2026-03-07')
|
|
299
|
+
const b = tdayjs('2025-01-01')
|
|
300
|
+
|
|
301
|
+
a.diff(b, 'year') // 1
|
|
302
|
+
a.diff(b, 'month') // 14
|
|
303
|
+
a.diff(b, 'day') // 430
|
|
304
|
+
a.diff(b, 'hour') // 10320
|
|
305
|
+
a.diff(b) // milliseconds (default)
|
|
306
|
+
|
|
307
|
+
// Float for fractional result
|
|
308
|
+
a.diff(b, 'year', true) // 1.17...
|
|
309
|
+
|
|
310
|
+
// Negative when self is before the argument
|
|
311
|
+
tdayjs('2025-01-01').diff(tdayjs('2026-03-07'), 'year') // -1
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Comparison
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const past = tdayjs('2025-01-01')
|
|
318
|
+
const now = tdayjs('2026-03-07')
|
|
319
|
+
const future = tdayjs('2027-12-31')
|
|
320
|
+
|
|
321
|
+
now.isBefore(future) // true
|
|
322
|
+
now.isAfter(past) // true
|
|
323
|
+
now.isSame(tdayjs('2026-03-07')) // true (millisecond precision)
|
|
324
|
+
|
|
325
|
+
// With unit — compares within the granularity of the unit
|
|
326
|
+
const a = tdayjs('2026-03-07T10:00:00')
|
|
327
|
+
const b = tdayjs('2026-03-07T22:00:00')
|
|
328
|
+
|
|
329
|
+
a.isSame(b, 'day') // true — same day
|
|
330
|
+
a.isSame(b, 'hour') // false — different hour
|
|
331
|
+
a.isBefore(b, 'day') // false — same day, not before
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Conversion
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
const d = tdayjs('2026-03-07T10:30:00')
|
|
338
|
+
|
|
339
|
+
d.valueOf() // 1741343400000 — ms since Unix epoch
|
|
340
|
+
d.unix() // 1741343400 — seconds since Unix epoch
|
|
341
|
+
d.daysInMonth() // 31 — days in March
|
|
342
|
+
d.utcOffset() // e.g. 60 — offset in minutes
|
|
343
|
+
d.toDate() // native Date object
|
|
344
|
+
d.toISOString() // '2026-03-07T10:30:00Z'
|
|
345
|
+
d.toJSON() // '2026-03-07T10:30:00Z' (null if invalid)
|
|
346
|
+
d.toString() // 'Sat, 07 Mar 2026 10:30:00 GMT'
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Locale
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
import tdayjs from '@arkv/temporal'
|
|
353
|
+
import type { ILocale } from '@arkv/temporal'
|
|
354
|
+
|
|
355
|
+
// Get current global locale
|
|
356
|
+
tdayjs.locale() // 'en'
|
|
357
|
+
|
|
358
|
+
// Set global locale (register your own)
|
|
359
|
+
const fr: ILocale = {
|
|
360
|
+
name: 'fr',
|
|
361
|
+
months: ['Janvier', 'Février', /* ... */ 'Décembre'],
|
|
362
|
+
monthsShort: ['Jan', 'Fév', /* ... */ 'Déc'],
|
|
363
|
+
weekdays: ['Dimanche', 'Lundi', /* ... */ 'Samedi'],
|
|
364
|
+
weekdaysShort: ['Dim', 'Lun', /* ... */ 'Sam'],
|
|
365
|
+
weekdaysMin: ['Di', 'Lu', /* ... */ 'Sa'],
|
|
366
|
+
weekStart: 1, // Monday
|
|
367
|
+
}
|
|
368
|
+
tdayjs.locale(fr)
|
|
369
|
+
|
|
370
|
+
// Per-instance locale (does not affect global)
|
|
371
|
+
tdayjs('2026-03-07').locale('en').format('MMMM') // 'March'
|
|
372
|
+
tdayjs('2026-03-07').locale(fr).format('MMMM') // 'Mars'
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Plugin System
|
|
376
|
+
|
|
377
|
+
The plugin interface is compatible with Day.js plugins so that existing plugin code can be adapted with minimal effort:
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
import tdayjs, { TDayjs } from '@arkv/temporal'
|
|
381
|
+
|
|
382
|
+
const myPlugin = (option, Cls, factory) => {
|
|
383
|
+
Cls.prototype.yesterday = function () {
|
|
384
|
+
return this.subtract(1, 'day')
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
tdayjs.extend(myPlugin)
|
|
389
|
+
tdayjs().yesterday().format('YYYY-MM-DD')
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
> **Note:** Day.js plugins that directly access `this.$d` (the internal native `Date`) will not work because `@arkv/temporal` uses `Temporal.ZonedDateTime` internally (`this.$zdt`). Most formatting and arithmetic plugins can be rewritten to use the public API.
|
|
393
|
+
|
|
394
|
+
### Static Methods
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
tdayjs.isDayjs(tdayjs()) // true
|
|
398
|
+
tdayjs.isDayjs(new Date()) // false
|
|
399
|
+
tdayjs.unix(1741305600) // same as tdayjs(1741305600 * 1000)
|
|
400
|
+
tdayjs.locale() // get global locale name
|
|
401
|
+
tdayjs.locale('en') // set global locale
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Key Differences from Day.js
|
|
407
|
+
|
|
408
|
+
| Feature | Day.js | @arkv/temporal |
|
|
409
|
+
|---------|--------|----------------|
|
|
410
|
+
| Internal engine | `Date` object | `Temporal.ZonedDateTime` |
|
|
411
|
+
| Month indexing | 0-indexed (Jan=0) | **Same** — 0-indexed for compatibility |
|
|
412
|
+
| Timezone support | Plugin required | Built-in (always zone-aware) |
|
|
413
|
+
| DST handling | Inaccurate (millisecond math) | Correct (calendar arithmetic) |
|
|
414
|
+
| Month overflow | Platform-dependent | Clamped to month end |
|
|
415
|
+
| Quarter support | Plugin required | **Native** — no plugin needed |
|
|
416
|
+
| Nanosecond precision | No | Yes (via Temporal) |
|
|
417
|
+
| Immutability | Via cloning | Fully immutable |
|
|
418
|
+
| `this.$d` (internal Date) | ✓ | ✗ — use `this.$zdt` instead |
|
|
419
|
+
| Plugin compatibility | Full | Partial (public API only) |
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Why Named Timezone Safety Matters
|
|
424
|
+
|
|
425
|
+
Consider this code:
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
// Setting a recurring alarm for 8:00 AM
|
|
429
|
+
const alarm = tdayjs('2026-11-01T08:00:00[America/New_York]')
|
|
430
|
+
|
|
431
|
+
// Adding 1 day (crosses the DST "fall back" on Nov 1, 2026)
|
|
432
|
+
alarm.add(1, 'day').format('HH:mm')
|
|
433
|
+
// Day.js with `Date`: '07:00' — wrong! (23 hours, not 24)
|
|
434
|
+
// @arkv/temporal: '08:00' — correct! (wall-clock 8AM next day)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
`Temporal` adds "1 calendar day" which means "tomorrow at the same wall-clock time", which is what users expect. `Date`-based libraries add "86,400,000 milliseconds" which is only correct when there's no DST transition.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Compatibility Matrix
|
|
442
|
+
|
|
443
|
+
| Method | Status | Notes |
|
|
444
|
+
|--------|--------|-------|
|
|
445
|
+
| `dayjs()` / `tdayjs()` | ✅ Full | All input types supported |
|
|
446
|
+
| `.format()` | ✅ Full | All standard tokens |
|
|
447
|
+
| `.add()` / `.subtract()` | ✅ Full | Including quarters (no plugin needed) |
|
|
448
|
+
| `.startOf()` / `.endOf()` | ✅ Full | All time units |
|
|
449
|
+
| `.diff()` | ✅ Full | Including float mode |
|
|
450
|
+
| `.isBefore()` / `.isAfter()` / `.isSame()` | ✅ Full | With and without unit granularity |
|
|
451
|
+
| `.year()` / `.month()` / ... | ✅ Full | All getters and setters |
|
|
452
|
+
| `.get()` / `.set()` | ✅ Full | |
|
|
453
|
+
| `.locale()` | ✅ Full | Custom locale registration |
|
|
454
|
+
| `.utc()` / `.local()` | 🔜 Planned | UTC mode plugin |
|
|
455
|
+
| `.tz()` | 🔜 Planned | Named timezone switching |
|
|
456
|
+
| `.fromNow()` / `.to()` | 🔜 Planned | Relative time plugin |
|
|
457
|
+
| `.isLeapYear()` | 🔜 Planned | Via `$zdt.inLeapYear` |
|
|
458
|
+
| `.duration()` | 🔜 Planned | Via `Temporal.Duration` |
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## TypeScript
|
|
463
|
+
|
|
464
|
+
All types are exported and match the Day.js type surface:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
import tdayjs, {
|
|
468
|
+
type TDayjs,
|
|
469
|
+
type ConfigType,
|
|
470
|
+
type UnitType,
|
|
471
|
+
type OpUnitType,
|
|
472
|
+
type QUnitType,
|
|
473
|
+
type ManipulateType,
|
|
474
|
+
type ILocale,
|
|
475
|
+
} from '@arkv/temporal'
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Runtime Requirements
|
|
481
|
+
|
|
482
|
+
- **Node.js**: 22+ (with Temporal polyfill included)
|
|
483
|
+
- **Bun**: Any version (with Temporal polyfill included)
|
|
484
|
+
- **Browsers**: Evergreen (with Temporal polyfill included)
|
|
485
|
+
|
|
486
|
+
The `temporal-polyfill` dependency is automatically included — you don't need to configure anything. Once native Temporal lands in all runtimes, the polyfill will become a no-op.
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## License
|
|
491
|
+
|
|
492
|
+
MIT © [Petar Zarkov](https://github.com/petarzarkov)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REGEX_FORMAT = exports.INVALID_DATE = exports.FORMAT_DEFAULT = void 0;
|
|
4
|
+
exports.FORMAT_DEFAULT = 'YYYY-MM-DDTHH:mm:ssZ';
|
|
5
|
+
exports.INVALID_DATE = 'Invalid Date';
|
|
6
|
+
// Matches format tokens and [escaped] text
|
|
7
|
+
exports.REGEX_FORMAT = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g;
|