@f-o-t/datetime 0.1.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/CHANGELOG.md +13 -0
- package/README.md +726 -0
- package/biome.json +39 -0
- package/bunup.config.ts +14 -0
- package/examples/plugins-demo.ts +85 -0
- package/fot.config.ts +5 -0
- package/package.json +47 -0
- package/src/core/datetime.test.ts +1498 -0
- package/src/core/datetime.ts +694 -0
- package/src/core/factory.test.ts +167 -0
- package/src/core/factory.ts +32 -0
- package/src/errors.ts +82 -0
- package/src/index.ts +20 -0
- package/src/plugins/business-days/business-days.test.ts +225 -0
- package/src/plugins/business-days/index.ts +126 -0
- package/src/plugins/format/format.test.ts +173 -0
- package/src/plugins/format/index.ts +78 -0
- package/src/plugins/format/tokens.ts +153 -0
- package/src/plugins/index.ts +15 -0
- package/src/plugins/plugin-base.test.ts +211 -0
- package/src/plugins/plugin-base.ts +104 -0
- package/src/plugins/relative-time/index.ts +169 -0
- package/src/plugins/relative-time/relative-time.test.ts +164 -0
- package/src/plugins/timezone/index.ts +152 -0
- package/src/plugins/timezone/timezone.test.ts +135 -0
- package/src/schemas.test.ts +283 -0
- package/src/schemas.ts +104 -0
- package/src/types.ts +122 -0
- package/tsconfig.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
# @f-o-t/datetime
|
|
2
|
+
|
|
3
|
+
Lightweight, Day.js-inspired datetime library with modern fluent API, Zod-first validation, and plugin architecture.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@f-o-t/datetime)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Immutable**: All operations return new instances, preventing accidental mutations
|
|
11
|
+
- **Type Safety**: Full TypeScript support with Zod schema validation
|
|
12
|
+
- **Plugin Architecture**: Extend functionality with timezone, formatting, business days, and relative time plugins
|
|
13
|
+
- **Lightweight**: Minimal dependencies, built on native JavaScript Date
|
|
14
|
+
- **Fluent API**: Chainable methods for elegant datetime operations
|
|
15
|
+
- **Framework Agnostic**: Works with any JavaScript/TypeScript project
|
|
16
|
+
- **UTC-First**: All core operations use UTC to avoid timezone confusion
|
|
17
|
+
- **Rich API**: Comprehensive operations for creation, arithmetic, comparison, and manipulation
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# bun
|
|
23
|
+
bun add @f-o-t/datetime
|
|
24
|
+
|
|
25
|
+
# npm
|
|
26
|
+
npm install @f-o-t/datetime
|
|
27
|
+
|
|
28
|
+
# yarn
|
|
29
|
+
yarn add @f-o-t/datetime
|
|
30
|
+
|
|
31
|
+
# pnpm
|
|
32
|
+
pnpm add @f-o-t/datetime
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { datetime } from "@f-o-t/datetime";
|
|
39
|
+
|
|
40
|
+
// Create datetime instances
|
|
41
|
+
const now = datetime();
|
|
42
|
+
const birthday = datetime("2024-01-15T10:30:00Z");
|
|
43
|
+
|
|
44
|
+
// Perform arithmetic
|
|
45
|
+
const nextWeek = now.addWeeks(1);
|
|
46
|
+
const yesterday = now.subtractDays(1);
|
|
47
|
+
|
|
48
|
+
// Compare dates
|
|
49
|
+
if (birthday.isBefore(now)) {
|
|
50
|
+
console.log("Birthday has passed");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Calculate differences
|
|
54
|
+
const daysUntil = birthday.diff(now, "day");
|
|
55
|
+
console.log(`Days until birthday: ${daysUntil}`);
|
|
56
|
+
|
|
57
|
+
// Format output
|
|
58
|
+
console.log(now.toISO()); // "2024-01-15T10:30:00.000Z"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Core Concepts
|
|
62
|
+
|
|
63
|
+
### DateTime Object
|
|
64
|
+
|
|
65
|
+
The `DateTime` class is the core type representing a moment in time:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { datetime } from "@f-o-t/datetime";
|
|
69
|
+
|
|
70
|
+
const dt = datetime("2024-01-15T10:30:00Z");
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Important**: DateTime objects are immutable. All operations return new instances.
|
|
74
|
+
|
|
75
|
+
### UTC-First Design
|
|
76
|
+
|
|
77
|
+
All core DateTime methods operate in UTC to avoid timezone-related bugs:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const dt = datetime("2024-01-15T10:30:00Z");
|
|
81
|
+
dt.year(); // Returns UTC year
|
|
82
|
+
dt.hour(); // Returns UTC hour
|
|
83
|
+
dt.addDays(1); // Adds days in UTC
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use the timezone plugin for timezone-aware operations.
|
|
87
|
+
|
|
88
|
+
### Validation
|
|
89
|
+
|
|
90
|
+
All inputs are validated using Zod schemas:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { datetime } from "@f-o-t/datetime";
|
|
94
|
+
|
|
95
|
+
// Valid inputs
|
|
96
|
+
datetime(); // Current time
|
|
97
|
+
datetime(new Date()); // Date object
|
|
98
|
+
datetime("2024-01-15T10:30:00Z"); // ISO string
|
|
99
|
+
datetime(1705315800000); // Unix timestamp
|
|
100
|
+
datetime(dt); // Another DateTime
|
|
101
|
+
|
|
102
|
+
// Invalid inputs throw InvalidDateError
|
|
103
|
+
datetime("invalid"); // Throws
|
|
104
|
+
datetime({}); // Throws
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## API Reference
|
|
108
|
+
|
|
109
|
+
### Factory Functions
|
|
110
|
+
|
|
111
|
+
Create DateTime instances:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { datetime, DateTime } from "@f-o-t/datetime";
|
|
115
|
+
|
|
116
|
+
// Factory function (recommended)
|
|
117
|
+
const dt1 = datetime(); // Current time
|
|
118
|
+
const dt2 = datetime("2024-01-15"); // ISO string
|
|
119
|
+
const dt3 = datetime(new Date()); // Date object
|
|
120
|
+
const dt4 = datetime(1705315800000); // Timestamp
|
|
121
|
+
const dt5 = datetime(dt1); // Clone
|
|
122
|
+
|
|
123
|
+
// Constructor (alternative)
|
|
124
|
+
const dt6 = new DateTime("2024-01-15");
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Validation and Conversion
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const dt = datetime("2024-01-15T10:30:00Z");
|
|
131
|
+
|
|
132
|
+
// Check validity
|
|
133
|
+
dt.isValid(); // true
|
|
134
|
+
|
|
135
|
+
// Convert to native types
|
|
136
|
+
dt.toDate(); // Returns native Date object
|
|
137
|
+
dt.toISO(); // "2024-01-15T10:30:00.000Z"
|
|
138
|
+
dt.valueOf(); // 1705315800000 (Unix timestamp)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Arithmetic Operations
|
|
142
|
+
|
|
143
|
+
All arithmetic methods return new DateTime instances:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const dt = datetime("2024-01-15T10:30:00Z");
|
|
147
|
+
|
|
148
|
+
// Add time
|
|
149
|
+
dt.addMilliseconds(500); // Add 500ms
|
|
150
|
+
dt.addSeconds(30); // Add 30 seconds
|
|
151
|
+
dt.addMinutes(15); // Add 15 minutes
|
|
152
|
+
dt.addHours(2); // Add 2 hours
|
|
153
|
+
dt.addDays(7); // Add 7 days
|
|
154
|
+
dt.addWeeks(2); // Add 2 weeks
|
|
155
|
+
dt.addMonths(3); // Add 3 months
|
|
156
|
+
dt.addYears(1); // Add 1 year
|
|
157
|
+
|
|
158
|
+
// Subtract time
|
|
159
|
+
dt.subtractMilliseconds(500);
|
|
160
|
+
dt.subtractSeconds(30);
|
|
161
|
+
dt.subtractMinutes(15);
|
|
162
|
+
dt.subtractHours(2);
|
|
163
|
+
dt.subtractDays(7);
|
|
164
|
+
dt.subtractWeeks(2);
|
|
165
|
+
dt.subtractMonths(3);
|
|
166
|
+
dt.subtractYears(1);
|
|
167
|
+
|
|
168
|
+
// Negative amounts work in reverse
|
|
169
|
+
dt.addDays(-7); // Same as subtractDays(7)
|
|
170
|
+
dt.subtractDays(-7); // Same as addDays(7)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Comparison Operations
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const dt1 = datetime("2024-01-15T10:00:00Z");
|
|
177
|
+
const dt2 = datetime("2024-01-20T10:00:00Z");
|
|
178
|
+
|
|
179
|
+
// Basic comparisons
|
|
180
|
+
dt1.isBefore(dt2); // true
|
|
181
|
+
dt1.isAfter(dt2); // false
|
|
182
|
+
dt1.isSame(dt2); // false
|
|
183
|
+
dt1.isSameOrBefore(dt2); // true
|
|
184
|
+
dt1.isSameOrAfter(dt2); // false
|
|
185
|
+
|
|
186
|
+
// Range checking
|
|
187
|
+
const mid = datetime("2024-01-17T10:00:00Z");
|
|
188
|
+
mid.isBetween(dt1, dt2); // false (exclusive)
|
|
189
|
+
mid.isBetween(dt1, dt2, true); // true (inclusive)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Getter Methods
|
|
193
|
+
|
|
194
|
+
All getters return UTC values:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const dt = datetime("2024-01-15T10:30:45.123Z");
|
|
198
|
+
|
|
199
|
+
dt.year(); // 2024
|
|
200
|
+
dt.month(); // 0 (0-indexed, 0=January)
|
|
201
|
+
dt.date(); // 15 (day of month)
|
|
202
|
+
dt.day(); // 1 (day of week, 0=Sunday)
|
|
203
|
+
dt.hour(); // 10
|
|
204
|
+
dt.minute(); // 30
|
|
205
|
+
dt.second(); // 45
|
|
206
|
+
dt.millisecond(); // 123
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Setter Methods
|
|
210
|
+
|
|
211
|
+
All setters return new DateTime instances:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const dt = datetime("2024-01-15T10:30:00Z");
|
|
215
|
+
|
|
216
|
+
dt.setYear(2025); // Changes year to 2025
|
|
217
|
+
dt.setMonth(5); // Changes month to June (0-indexed)
|
|
218
|
+
dt.setDate(20); // Changes day to 20th
|
|
219
|
+
dt.setHour(14); // Changes hour to 14:00
|
|
220
|
+
dt.setMinute(45); // Changes minute to XX:45
|
|
221
|
+
dt.setSecond(30); // Changes second to XX:XX:30
|
|
222
|
+
dt.setMillisecond(500); // Changes millisecond to XX:XX:XX.500
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Start/End of Unit
|
|
226
|
+
|
|
227
|
+
Get the start or end of a time unit:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const dt = datetime("2024-01-15T10:30:45.123Z");
|
|
231
|
+
|
|
232
|
+
// Start of unit (sets to 00:00:00.000)
|
|
233
|
+
dt.startOfDay(); // 2024-01-15T00:00:00.000Z
|
|
234
|
+
dt.startOfHour(); // 2024-01-15T10:00:00.000Z
|
|
235
|
+
dt.startOfWeek(); // 2024-01-14T00:00:00.000Z (Sunday)
|
|
236
|
+
dt.startOfMonth(); // 2024-01-01T00:00:00.000Z
|
|
237
|
+
dt.startOfYear(); // 2024-01-01T00:00:00.000Z
|
|
238
|
+
|
|
239
|
+
// End of unit (sets to 23:59:59.999 or max value)
|
|
240
|
+
dt.endOfDay(); // 2024-01-15T23:59:59.999Z
|
|
241
|
+
dt.endOfHour(); // 2024-01-15T10:59:59.999Z
|
|
242
|
+
dt.endOfWeek(); // 2024-01-20T23:59:59.999Z (Saturday)
|
|
243
|
+
dt.endOfMonth(); // 2024-01-31T23:59:59.999Z
|
|
244
|
+
dt.endOfYear(); // 2024-12-31T23:59:59.999Z
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Difference Calculation
|
|
248
|
+
|
|
249
|
+
Calculate the difference between two dates:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
const dt1 = datetime("2024-01-15T10:00:00Z");
|
|
253
|
+
const dt2 = datetime("2024-01-20T14:30:00Z");
|
|
254
|
+
|
|
255
|
+
dt2.diff(dt1, "millisecond"); // 456600000
|
|
256
|
+
dt2.diff(dt1, "second"); // 456600
|
|
257
|
+
dt2.diff(dt1, "minute"); // 7610
|
|
258
|
+
dt2.diff(dt1, "hour"); // 126.83...
|
|
259
|
+
dt2.diff(dt1, "day"); // 5.19...
|
|
260
|
+
dt2.diff(dt1, "week"); // 0.74...
|
|
261
|
+
dt2.diff(dt1, "month"); // 0 (calendar months)
|
|
262
|
+
dt2.diff(dt1, "year"); // 0.01...
|
|
263
|
+
|
|
264
|
+
// Difference can be negative
|
|
265
|
+
dt1.diff(dt2, "day"); // -5.19...
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Plugins
|
|
269
|
+
|
|
270
|
+
Extend DateTime functionality with plugins. Each plugin adds new methods to DateTime instances.
|
|
271
|
+
|
|
272
|
+
### Timezone Plugin
|
|
273
|
+
|
|
274
|
+
Handle timezone conversions and operations:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { DateTime } from "@f-o-t/datetime";
|
|
278
|
+
import timezonePlugin from "@f-o-t/datetime/plugins/timezone";
|
|
279
|
+
|
|
280
|
+
// Install plugin
|
|
281
|
+
DateTime.extend(timezonePlugin);
|
|
282
|
+
|
|
283
|
+
const dt = datetime("2024-01-15T10:00:00Z");
|
|
284
|
+
|
|
285
|
+
// Convert to timezone
|
|
286
|
+
const nyTime = dt.tz("America/New_York");
|
|
287
|
+
|
|
288
|
+
// Get timezone offset
|
|
289
|
+
const offset = dt.utcOffset("America/New_York"); // -300 (minutes)
|
|
290
|
+
|
|
291
|
+
// Format in timezone
|
|
292
|
+
const formatted = dt.tzFormat("America/New_York", "YYYY-MM-DD HH:mm");
|
|
293
|
+
|
|
294
|
+
// Get local timezone
|
|
295
|
+
const localTz = DateTime.localTimezone(); // e.g., "America/Los_Angeles"
|
|
296
|
+
|
|
297
|
+
// Validate timezone
|
|
298
|
+
DateTime.isValidTimezone("America/New_York"); // true
|
|
299
|
+
DateTime.isValidTimezone("Invalid/Zone"); // false
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Available Methods:**
|
|
303
|
+
- `tz(timezone)` - Convert to timezone
|
|
304
|
+
- `toTimezone(timezone)` - Alias for tz()
|
|
305
|
+
- `utcOffset(timezone)` - Get UTC offset in minutes
|
|
306
|
+
- `tzFormat(timezone, format)` - Format in timezone
|
|
307
|
+
|
|
308
|
+
**Static Methods:**
|
|
309
|
+
- `DateTime.localTimezone()` - Get system timezone
|
|
310
|
+
- `DateTime.isValidTimezone(tz)` - Validate timezone string
|
|
311
|
+
|
|
312
|
+
### Business Days Plugin
|
|
313
|
+
|
|
314
|
+
Work with business days (Monday-Friday):
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
import { DateTime } from "@f-o-t/datetime";
|
|
318
|
+
import businessDaysPlugin from "@f-o-t/datetime/plugins/business-days";
|
|
319
|
+
|
|
320
|
+
DateTime.extend(businessDaysPlugin);
|
|
321
|
+
|
|
322
|
+
const dt = datetime("2024-01-15T10:00:00Z"); // Monday
|
|
323
|
+
|
|
324
|
+
// Check if weekday/weekend
|
|
325
|
+
dt.isWeekday(); // true
|
|
326
|
+
dt.isWeekend(); // false
|
|
327
|
+
|
|
328
|
+
// Add/subtract business days (skips weekends)
|
|
329
|
+
const nextBizDay = dt.addBusinessDays(1); // Tuesday
|
|
330
|
+
const prevBizDay = dt.subtractBusinessDays(1); // Friday (prev week)
|
|
331
|
+
|
|
332
|
+
// Calculate business days between dates
|
|
333
|
+
const start = datetime("2024-01-15"); // Monday
|
|
334
|
+
const end = datetime("2024-01-22"); // Next Monday
|
|
335
|
+
start.diffBusinessDays(end); // 5 business days
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Note**: Business days plugin only considers weekends. It does not account for holidays.
|
|
339
|
+
|
|
340
|
+
### Format Plugin
|
|
341
|
+
|
|
342
|
+
Format dates with customizable tokens:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { DateTime } from "@f-o-t/datetime";
|
|
346
|
+
import formatPlugin from "@f-o-t/datetime/plugins/format";
|
|
347
|
+
|
|
348
|
+
DateTime.extend(formatPlugin);
|
|
349
|
+
|
|
350
|
+
const dt = datetime("2024-01-15T14:30:45.123Z");
|
|
351
|
+
|
|
352
|
+
// Common formats
|
|
353
|
+
dt.format("YYYY-MM-DD"); // "2024-01-15"
|
|
354
|
+
dt.format("MM/DD/YYYY"); // "01/15/2024"
|
|
355
|
+
dt.format("MMMM D, YYYY"); // "January 15, 2024"
|
|
356
|
+
dt.format("dddd, MMMM D, YYYY"); // "Monday, January 15, 2024"
|
|
357
|
+
dt.format("h:mm A"); // "2:30 PM"
|
|
358
|
+
dt.format("HH:mm:ss"); // "14:30:45"
|
|
359
|
+
dt.format("YYYY-MM-DDTHH:mm:ss.SSSZ"); // ISO format (default)
|
|
360
|
+
|
|
361
|
+
// Escape characters with brackets
|
|
362
|
+
dt.format("[Year:] YYYY"); // "Year: 2024"
|
|
363
|
+
dt.format("[Today is] dddd"); // "Today is Monday"
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Available Tokens:**
|
|
367
|
+
- `YYYY` - 4-digit year (2024)
|
|
368
|
+
- `YY` - 2-digit year (24)
|
|
369
|
+
- `MMMM` - Full month name (January)
|
|
370
|
+
- `MMM` - Abbreviated month (Jan)
|
|
371
|
+
- `MM` - 2-digit month (01)
|
|
372
|
+
- `M` - Month number (1)
|
|
373
|
+
- `DD` - 2-digit day (15)
|
|
374
|
+
- `D` - Day of month (15)
|
|
375
|
+
- `dddd` - Full day name (Monday)
|
|
376
|
+
- `ddd` - Abbreviated day (Mon)
|
|
377
|
+
- `dd` - Min day name (Mo)
|
|
378
|
+
- `d` - Day of week (1)
|
|
379
|
+
- `HH` - 2-digit hour 24h (14)
|
|
380
|
+
- `H` - Hour 24h (14)
|
|
381
|
+
- `hh` - 2-digit hour 12h (02)
|
|
382
|
+
- `h` - Hour 12h (2)
|
|
383
|
+
- `mm` - 2-digit minute (30)
|
|
384
|
+
- `m` - Minute (30)
|
|
385
|
+
- `ss` - 2-digit second (45)
|
|
386
|
+
- `s` - Second (45)
|
|
387
|
+
- `SSS` - Milliseconds (123)
|
|
388
|
+
- `A` - AM/PM (PM)
|
|
389
|
+
- `a` - am/pm (pm)
|
|
390
|
+
- `Z` - Timezone offset (+00:00)
|
|
391
|
+
- `ZZ` - Compact offset (+0000)
|
|
392
|
+
|
|
393
|
+
### Relative Time Plugin
|
|
394
|
+
|
|
395
|
+
Display human-readable relative time:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { DateTime } from "@f-o-t/datetime";
|
|
399
|
+
import relativeTimePlugin from "@f-o-t/datetime/plugins/relative-time";
|
|
400
|
+
|
|
401
|
+
DateTime.extend(relativeTimePlugin);
|
|
402
|
+
|
|
403
|
+
const now = datetime();
|
|
404
|
+
|
|
405
|
+
// Get relative time from now
|
|
406
|
+
datetime(now.valueOf() - 30000).fromNow(); // "a few seconds ago"
|
|
407
|
+
datetime(now.valueOf() - 120000).fromNow(); // "2 minutes ago"
|
|
408
|
+
datetime(now.valueOf() - 7200000).fromNow(); // "2 hours ago"
|
|
409
|
+
datetime(now.valueOf() + 86400000).fromNow(); // "in a day"
|
|
410
|
+
|
|
411
|
+
// Get relative time to now
|
|
412
|
+
datetime(now.valueOf() - 30000).toNow(); // "in a few seconds"
|
|
413
|
+
datetime(now.valueOf() + 120000).toNow(); // "in 2 minutes"
|
|
414
|
+
|
|
415
|
+
// Relative time between two dates
|
|
416
|
+
const dt1 = datetime("2024-01-15");
|
|
417
|
+
const dt2 = datetime("2024-01-20");
|
|
418
|
+
dt1.from(dt2); // "5 days ago"
|
|
419
|
+
dt1.to(dt2); // "in 5 days"
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Time Ranges:**
|
|
423
|
+
- 0-45 seconds: "a few seconds ago"
|
|
424
|
+
- 45-90 seconds: "a minute ago"
|
|
425
|
+
- 2-45 minutes: "X minutes ago"
|
|
426
|
+
- 45 minutes - 21 hours: "X hours ago"
|
|
427
|
+
- 21 hours - 30 days: "X days ago"
|
|
428
|
+
- 30 days - 1 year: "X months ago"
|
|
429
|
+
- 1+ years: "X years ago"
|
|
430
|
+
|
|
431
|
+
## Zod Schemas
|
|
432
|
+
|
|
433
|
+
Validate datetime inputs with Zod:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
import { DateInputSchema } from "@f-o-t/datetime";
|
|
437
|
+
|
|
438
|
+
// Validate user input
|
|
439
|
+
const result = DateInputSchema.safeParse("2024-01-15");
|
|
440
|
+
|
|
441
|
+
if (result.success) {
|
|
442
|
+
const dt = datetime(result.data);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Use in API schemas
|
|
446
|
+
import { z } from "zod";
|
|
447
|
+
|
|
448
|
+
const EventSchema = z.object({
|
|
449
|
+
name: z.string(),
|
|
450
|
+
startDate: DateInputSchema,
|
|
451
|
+
endDate: DateInputSchema,
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
type Event = z.infer<typeof EventSchema>;
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Accepted Inputs:**
|
|
458
|
+
- `Date` objects
|
|
459
|
+
- ISO date strings
|
|
460
|
+
- Unix timestamps (numbers)
|
|
461
|
+
- DateTime instances
|
|
462
|
+
|
|
463
|
+
## Error Handling
|
|
464
|
+
|
|
465
|
+
The library provides specific error types:
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
import { InvalidDateError, PluginError } from "@f-o-t/datetime";
|
|
469
|
+
|
|
470
|
+
// InvalidDateError - thrown on invalid input
|
|
471
|
+
try {
|
|
472
|
+
datetime("invalid-date");
|
|
473
|
+
} catch (error) {
|
|
474
|
+
if (error instanceof InvalidDateError) {
|
|
475
|
+
console.log("Invalid date input:", error.input);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// PluginError - thrown on plugin issues
|
|
480
|
+
try {
|
|
481
|
+
DateTime.extend(samePluginAgain);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
if (error instanceof PluginError) {
|
|
484
|
+
console.log("Plugin error:", error.pluginName);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Check for invalid dates
|
|
489
|
+
const dt = datetime(new Date("invalid"));
|
|
490
|
+
if (!dt.isValid()) {
|
|
491
|
+
console.log("Date is invalid");
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Advanced Usage
|
|
496
|
+
|
|
497
|
+
### Creating Custom Plugins
|
|
498
|
+
|
|
499
|
+
Extend DateTime with your own functionality:
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
import { createPlugin } from "@f-o-t/datetime";
|
|
503
|
+
import type { DateTimeClass } from "@f-o-t/datetime";
|
|
504
|
+
|
|
505
|
+
// Extend DateTime interface
|
|
506
|
+
declare module "@f-o-t/datetime" {
|
|
507
|
+
interface DateTime {
|
|
508
|
+
isLeapYear(): boolean;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Create plugin
|
|
513
|
+
const leapYearPlugin = createPlugin(
|
|
514
|
+
"leapYear",
|
|
515
|
+
(DateTimeClass: DateTimeClass) => {
|
|
516
|
+
DateTimeClass.prototype.isLeapYear = function(): boolean {
|
|
517
|
+
const year = this.year();
|
|
518
|
+
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
// Install plugin
|
|
524
|
+
DateTime.extend(leapYearPlugin);
|
|
525
|
+
|
|
526
|
+
// Use custom method
|
|
527
|
+
const dt = datetime("2024-01-15");
|
|
528
|
+
dt.isLeapYear(); // true
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Plugin Validation
|
|
532
|
+
|
|
533
|
+
Check if plugins are installed:
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import timezonePlugin from "@f-o-t/datetime/plugins/timezone";
|
|
537
|
+
|
|
538
|
+
// Check if plugin installed
|
|
539
|
+
DateTime.hasPlugin("timezone"); // false
|
|
540
|
+
|
|
541
|
+
// Install plugin
|
|
542
|
+
DateTime.extend(timezonePlugin);
|
|
543
|
+
|
|
544
|
+
// Check again
|
|
545
|
+
DateTime.hasPlugin("timezone"); // true
|
|
546
|
+
|
|
547
|
+
// Get plugin instance
|
|
548
|
+
const plugin = DateTime.getPlugin("timezone");
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Method Chaining
|
|
552
|
+
|
|
553
|
+
All operations return DateTime instances, enabling fluent chaining:
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
const result = datetime("2024-01-15T10:00:00Z")
|
|
557
|
+
.addDays(7)
|
|
558
|
+
.startOfDay()
|
|
559
|
+
.addHours(9)
|
|
560
|
+
.setMinute(30);
|
|
561
|
+
|
|
562
|
+
// Result: 2024-01-22T09:30:00.000Z
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Working with Native Date
|
|
566
|
+
|
|
567
|
+
Convert between DateTime and native Date:
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
// DateTime to Date
|
|
571
|
+
const dt = datetime("2024-01-15");
|
|
572
|
+
const nativeDate = dt.toDate();
|
|
573
|
+
|
|
574
|
+
// Date to DateTime
|
|
575
|
+
const dt2 = datetime(nativeDate);
|
|
576
|
+
|
|
577
|
+
// Modify native date (won't affect DateTime due to cloning)
|
|
578
|
+
nativeDate.setFullYear(2025);
|
|
579
|
+
console.log(dt.year()); // Still 2024
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
## Best Practices
|
|
583
|
+
|
|
584
|
+
### 1. Use UTC for Storage and Business Logic
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
// Good - UTC operations
|
|
588
|
+
const dt = datetime("2024-01-15T10:00:00Z");
|
|
589
|
+
const tomorrow = dt.addDays(1);
|
|
590
|
+
|
|
591
|
+
// Store as ISO string
|
|
592
|
+
const stored = dt.toISO(); // "2024-01-15T10:00:00.000Z"
|
|
593
|
+
|
|
594
|
+
// Use timezone plugin for display only
|
|
595
|
+
const displayed = dt.tz("America/New_York");
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### 2. Validate User Input
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
import { DateInputSchema } from "@f-o-t/datetime";
|
|
602
|
+
|
|
603
|
+
function parseUserDate(input: unknown): DateTime | null {
|
|
604
|
+
const result = DateInputSchema.safeParse(input);
|
|
605
|
+
if (!result.success) {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const dt = datetime(result.data);
|
|
610
|
+
return dt.isValid() ? dt : null;
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### 3. Use Immutability to Your Advantage
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
// Safe to pass around without worrying about mutations
|
|
618
|
+
function processDate(dt: DateTime) {
|
|
619
|
+
// This doesn't affect the original
|
|
620
|
+
const modified = dt.addDays(1);
|
|
621
|
+
return modified;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const original = datetime("2024-01-15");
|
|
625
|
+
const result = processDate(original);
|
|
626
|
+
// original is unchanged
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### 4. Leverage TypeScript for Safety
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
import type { DateTime, TimeUnit } from "@f-o-t/datetime";
|
|
633
|
+
|
|
634
|
+
function calculateAge(birthDate: DateTime): number {
|
|
635
|
+
const now = datetime();
|
|
636
|
+
return Math.floor(now.diff(birthDate, "year"));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Type-safe unit parameter
|
|
640
|
+
function addTime(dt: DateTime, amount: number, unit: TimeUnit): DateTime {
|
|
641
|
+
switch (unit) {
|
|
642
|
+
case "day": return dt.addDays(amount);
|
|
643
|
+
case "hour": return dt.addHours(amount);
|
|
644
|
+
// TypeScript ensures all units are handled
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### 5. Install Plugins Once at Startup
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
// app.ts or main.ts
|
|
653
|
+
import { DateTime } from "@f-o-t/datetime";
|
|
654
|
+
import timezonePlugin from "@f-o-t/datetime/plugins/timezone";
|
|
655
|
+
import formatPlugin from "@f-o-t/datetime/plugins/format";
|
|
656
|
+
|
|
657
|
+
// Install plugins once
|
|
658
|
+
DateTime.extend(timezonePlugin);
|
|
659
|
+
DateTime.extend(formatPlugin);
|
|
660
|
+
|
|
661
|
+
// Now available everywhere
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
## TypeScript
|
|
665
|
+
|
|
666
|
+
Full TypeScript support with strict types:
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import type {
|
|
670
|
+
DateTime,
|
|
671
|
+
DateInput,
|
|
672
|
+
DateTimeClass,
|
|
673
|
+
DateTimeConfig,
|
|
674
|
+
DateTimePlugin,
|
|
675
|
+
TimeUnit,
|
|
676
|
+
} from "@f-o-t/datetime";
|
|
677
|
+
|
|
678
|
+
// DateTime is the core instance type
|
|
679
|
+
const dt: DateTime = datetime();
|
|
680
|
+
|
|
681
|
+
// DateInput accepts multiple input types
|
|
682
|
+
const input: DateInput = "2024-01-15";
|
|
683
|
+
const dt2: DateTime = datetime(input);
|
|
684
|
+
|
|
685
|
+
// TimeUnit for diff operations
|
|
686
|
+
const unit: TimeUnit = "day";
|
|
687
|
+
const difference: number = dt.diff(dt2, unit);
|
|
688
|
+
|
|
689
|
+
// DateTimePlugin for custom plugins
|
|
690
|
+
const plugin: DateTimePlugin = {
|
|
691
|
+
name: "custom",
|
|
692
|
+
install: (DateTimeClass: DateTimeClass) => {
|
|
693
|
+
// Add methods
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
## Performance
|
|
699
|
+
|
|
700
|
+
The library is optimized for common datetime operations:
|
|
701
|
+
|
|
702
|
+
- **10,000 creations**: < 100ms
|
|
703
|
+
- **10,000 arithmetic operations**: < 50ms
|
|
704
|
+
- **10,000 comparisons**: < 30ms
|
|
705
|
+
- **10,000 format operations**: < 200ms
|
|
706
|
+
- **10,000 timezone conversions**: < 300ms
|
|
707
|
+
|
|
708
|
+
All benchmarks run with Bun runtime on modern hardware.
|
|
709
|
+
|
|
710
|
+
## Contributing
|
|
711
|
+
|
|
712
|
+
Contributions are welcome! Please check the repository for guidelines.
|
|
713
|
+
|
|
714
|
+
## License
|
|
715
|
+
|
|
716
|
+
MIT License - see [LICENSE](./LICENSE) file for details.
|
|
717
|
+
|
|
718
|
+
## Credits
|
|
719
|
+
|
|
720
|
+
Inspired by Day.js and built by the Finance Tracker team as part of the F-O-T libraries collection.
|
|
721
|
+
|
|
722
|
+
## Links
|
|
723
|
+
|
|
724
|
+
- [GitHub Repository](https://github.com/F-O-T/libraries)
|
|
725
|
+
- [Issue Tracker](https://github.com/F-O-T/libraries/issues)
|
|
726
|
+
- [NPM Package](https://www.npmjs.com/package/@f-o-t/datetime)
|