@subrotosaha/datekit 1.1.0 → 1.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 CHANGED
@@ -21,15 +21,15 @@
21
21
 
22
22
  DateKit is a modern, lightweight date/time library that combines the best ideas from Moment.js, date-fns, and Day.js with a fresh, developer-friendly API.
23
23
 
24
- | Feature | DateKit | Moment.js | date-fns | Day.js |
25
- |---------|---------|-----------|----------|--------|
26
- | **Immutable** | ✅ | ❌ | ✅ | ✅ |
27
- | **TypeScript-first** | ✅ | Partial | ✅ | Partial |
28
- | **Chainable API** | ✅ | ✅ | ❌ | ✅ |
29
- | **UTC-first** | ✅ | ❌ | ❌ | Optional |
30
- | **IANA Timezone Support** | ✅ Built-in | Plugin | Separate pkg | Plugin |
31
- | **Business Days** | ✅ Built-in | ❌ | ❌ | ❌ |
32
- | **Zero Dependencies** | ✅ | ❌ | ✅ | ✅ |
24
+ | Feature | DateKit | Moment.js | date-fns | Day.js |
25
+ | ------------------------- | ----------- | --------- | ------------ | -------- |
26
+ | **Immutable** | ✅ | ❌ | ✅ | ✅ |
27
+ | **TypeScript-first** | ✅ | Partial | ✅ | Partial |
28
+ | **Chainable API** | ✅ | ✅ | ❌ | ✅ |
29
+ | **UTC-first** | ✅ | ❌ | ❌ | Optional |
30
+ | **IANA Timezone Support** | ✅ Built-in | Plugin | Separate pkg | Plugin |
31
+ | **Business Days** | ✅ Built-in | ❌ | ❌ | ❌ |
32
+ | **Zero Dependencies** | ✅ | ❌ | ✅ | ✅ |
33
33
 
34
34
  ### 🎯 Key Features
35
35
 
@@ -39,7 +39,9 @@ DateKit is a modern, lightweight date/time library that combines the best ideas
39
39
  - 🎨 **Chainable API** — Fluent, readable code
40
40
  - 🌐 **IANA Timezone Support** — Full timezone conversion built-in
41
41
  - 📊 **Business Day Calculations** — Skip weekends and holidays
42
- - 🌍 **i18n Ready** — Extensible locale system
42
+ - 🌍 **i18n Ready** — 13 built-in locales with RTL support (Arabic, Urdu)
43
+ - 🗓️ **DateRange** — Inclusive date ranges with set operations (intersection, union, overlap)
44
+ - 🔍 **Format-String Parsing** — `DateKit.parse(str, format)` for any date format
43
45
  - 📝 **TypeScript Native** — Full type safety and IntelliSense
44
46
 
45
47
  ---
@@ -71,10 +73,7 @@ const date = new DateKit("2024-03-15T14:30:00Z");
71
73
  console.log(date.format("MMMM D, YYYY")); // "March 15, 2024"
72
74
 
73
75
  // Chain operations (all immutable!)
74
- const futureDate = date
75
- .add(2, "week")
76
- .startOf("month")
77
- .setHour(9);
76
+ const futureDate = date.add(2, "week").startOf("month").setHour(9);
78
77
 
79
78
  console.log(futureDate.format("dddd, MMMM D, YYYY [at] h:mm A"));
80
79
  // "Monday, April 1, 2024 at 9:00 AM"
@@ -110,6 +109,17 @@ console.log(meeting.humanize()); // "an hour"
110
109
  - [Localization](#localization)
111
110
  - [Intervals](#intervals)
112
111
  - [Static Methods](#static-methods)
112
+ - [Parse with Format](#parse-with-format)
113
+
114
+ </details>
115
+
116
+ <details>
117
+ <summary><strong>📅 DateRange Class</strong></summary>
118
+
119
+ - [Creating Date Ranges](#creating-date-ranges)
120
+ - [Predicates](#range-predicates)
121
+ - [Set Operations](#set-operations)
122
+ - [Metrics](#range-metrics)
113
123
 
114
124
  </details>
115
125
 
@@ -159,9 +169,9 @@ const fromUnix = DateKit.unix(1710513000);
159
169
 
160
170
  // With configuration
161
171
  const configured = new DateKit("2024-03-15", {
162
- locale: "es", // Spanish locale
163
- weekStartsOn: 1, // Monday = 1
164
- strictParsing: true // Strict date parsing
172
+ locale: "es", // Spanish locale
173
+ weekStartsOn: 1, // Monday = 1
174
+ strictParsing: true, // Strict date parsing
165
175
  });
166
176
  ```
167
177
 
@@ -175,18 +185,18 @@ Transform dates into human-readable strings:
175
185
  const date = new DateKit("2024-03-15T14:30:45.123Z");
176
186
 
177
187
  // Common formats
178
- date.format("YYYY-MM-DD"); // "2024-03-15"
179
- date.format("DD/MM/YYYY"); // "15/03/2024"
180
- date.format("MMMM D, YYYY"); // "March 15, 2024"
181
- date.format("dddd, MMMM Do YYYY"); // "Friday, March 15th 2024"
188
+ date.format("YYYY-MM-DD"); // "2024-03-15"
189
+ date.format("DD/MM/YYYY"); // "15/03/2024"
190
+ date.format("MMMM D, YYYY"); // "March 15, 2024"
191
+ date.format("dddd, MMMM Do YYYY"); // "Friday, March 15th 2024"
182
192
 
183
193
  // With time
184
- date.format("YYYY-MM-DD HH:mm:ss"); // "2024-03-15 14:30:45"
185
- date.format("h:mm A"); // "2:30 PM"
186
- date.format("HH:mm:ss.SSS"); // "14:30:45.123"
194
+ date.format("YYYY-MM-DD HH:mm:ss"); // "2024-03-15 14:30:45"
195
+ date.format("h:mm A"); // "2:30 PM"
196
+ date.format("HH:mm:ss.SSS"); // "14:30:45.123"
187
197
 
188
198
  // Complex formats
189
- date.format("[Today is] dddd"); // "Today is Friday"
199
+ date.format("[Today is] dddd"); // "Today is Friday"
190
200
  date.format("Qo [quarter of] YYYY"); // "1st quarter of 2024"
191
201
 
192
202
  // With locale
@@ -224,8 +234,8 @@ DateKit.formatInTimezone(utcDate, "Asia/Dhaka", "YYYY-MM-DD HH:mm");
224
234
  // Meeting at 2:30 PM in Dhaka - what time in New York?
225
235
  DateKit.convertTimezone(
226
236
  "2024-12-25T14:30:00",
227
- "Asia/Dhaka", // Source timezone
228
- "America/New_York", // Target timezone
237
+ "Asia/Dhaka", // Source timezone
238
+ "America/New_York", // Target timezone
229
239
  "YYYY-MM-DD HH:mm"
230
240
  );
231
241
  // → "2024-12-25 03:30" (10.5 hour difference)
@@ -250,7 +260,11 @@ tokyoMorning.toISOString();
250
260
  // → "2024-12-25T00:00:00.000Z" (stored as UTC internally)
251
261
 
252
262
  // Display it in another timezone
253
- DateKit.formatInTimezone(tokyoMorning.toDate(), "America/Los_Angeles", "h:mm A");
263
+ DateKit.formatInTimezone(
264
+ tokyoMorning.toDate(),
265
+ "America/Los_Angeles",
266
+ "h:mm A"
267
+ );
254
268
  // → "4:00 PM" (previous day!)
255
269
  ```
256
270
 
@@ -260,7 +274,8 @@ Parse browser-generated date strings and format them **preserving the original t
260
274
 
261
275
  ```typescript
262
276
  // Browser's Date.toString() output with timezone info
263
- const browserDate = "Sun Dec 25 2024 00:00:00 GMT+0600 (Bangladesh Standard Time)";
277
+ const browserDate =
278
+ "Sun Dec 25 2024 00:00:00 GMT+0600 (Bangladesh Standard Time)";
264
279
 
265
280
  // ✅ Preserves the local date (midnight in Bangladesh)
266
281
  DateKit.formatFromTimezoneString(browserDate, "YYYY-MM-DD HH:mm");
@@ -291,9 +306,9 @@ frenchKit.formatZonedDate(dateString, "dddd D MMMM YYYY");
291
306
 
292
307
  ```typescript
293
308
  // Current offset for a timezone
294
- DateKit.getTimezoneOffset("Asia/Kolkata"); // 330 (UTC+5:30)
295
- DateKit.getTimezoneOffset("America/New_York"); // -300 or -240 (depends on DST)
296
- DateKit.getTimezoneOffset("UTC"); // 0
309
+ DateKit.getTimezoneOffset("Asia/Kolkata"); // 330 (UTC+5:30)
310
+ DateKit.getTimezoneOffset("America/New_York"); // -300 or -240 (depends on DST)
311
+ DateKit.getTimezoneOffset("UTC"); // 0
297
312
 
298
313
  // Check offset at a specific date (for DST-aware calculations)
299
314
  const summer = new Date("2024-07-15");
@@ -309,23 +324,23 @@ DateKit.getTimezoneOffset("America/New_York", winter); // -300 (EST)
309
324
 
310
325
  Access individual date/time components (all UTC-based):
311
326
 
312
- | Method | Returns | Example |
313
- |--------|---------|---------|
314
- | `year()` | Full year | `2024` |
315
- | `month()` | Month (0-indexed) | `2` (March) |
316
- | `getDate()` | Day of month | `15` |
317
- | `day()` | Day of week (0=Sun) | `5` (Friday) |
318
- | `hour()` | Hour (0-23) | `14` |
319
- | `minute()` | Minute (0-59) | `30` |
320
- | `second()` | Second (0-59) | `45` |
321
- | `millisecond()` | Millisecond (0-999) | `123` |
322
- | `quarter()` | Quarter (1-4) | `1` |
323
- | `week()` | ISO week number | `11` |
324
- | `isoWeek()` | ISO week number | `11` |
325
- | `weekday()` | Locale-aware weekday | `5` |
326
- | `isoWeekday()` | ISO weekday (Mon=1) | `5` |
327
- | `dayOfYear()` | Day of year (1-366) | `75` |
328
- | `weekYear()` | ISO week year | `2024` |
327
+ | Method | Returns | Example |
328
+ | --------------- | -------------------- | ------------ |
329
+ | `year()` | Full year | `2024` |
330
+ | `month()` | Month (0-indexed) | `2` (March) |
331
+ | `getDate()` | Day of month | `15` |
332
+ | `day()` | Day of week (0=Sun) | `5` (Friday) |
333
+ | `hour()` | Hour (0-23) | `14` |
334
+ | `minute()` | Minute (0-59) | `30` |
335
+ | `second()` | Second (0-59) | `45` |
336
+ | `millisecond()` | Millisecond (0-999) | `123` |
337
+ | `quarter()` | Quarter (1-4) | `1` |
338
+ | `week()` | ISO week number | `11` |
339
+ | `isoWeek()` | ISO week number | `11` |
340
+ | `weekday()` | Locale-aware weekday | `5` |
341
+ | `isoWeekday()` | ISO weekday (Mon=1) | `5` |
342
+ | `dayOfYear()` | Day of year (1-366) | `75` |
343
+ | `weekYear()` | ISO week year | `2024` |
329
344
 
330
345
  ```typescript
331
346
  const date = new DateKit("2024-03-15T14:30:45.123Z");
@@ -352,24 +367,26 @@ All setters return a **new DateKit instance** (immutable):
352
367
  const date = new DateKit("2024-03-15T14:30:00Z");
353
368
 
354
369
  // Individual setters
355
- date.setYear(2025).toISOString(); // "2025-03-15T14:30:00.000Z"
356
- date.setMonth(11).toISOString(); // "2024-12-15T14:30:00.000Z"
357
- date.setDate(1).toISOString(); // "2024-03-01T14:30:00.000Z"
358
- date.setHour(9).toISOString(); // "2024-03-15T09:30:00.000Z"
359
- date.setMinute(0).toISOString(); // "2024-03-15T14:00:00.000Z"
360
- date.setSecond(0).toISOString(); // "2024-03-15T14:30:00.000Z"
361
- date.setMillisecond(500).toISOString(); // "2024-03-15T14:30:00.500Z"
362
- date.setQuarter(3).toISOString(); // "2024-09-15T14:30:00.000Z"
370
+ date.setYear(2025).toISOString(); // "2025-03-15T14:30:00.000Z"
371
+ date.setMonth(11).toISOString(); // "2024-12-15T14:30:00.000Z"
372
+ date.setDate(1).toISOString(); // "2024-03-01T14:30:00.000Z"
373
+ date.setHour(9).toISOString(); // "2024-03-15T09:30:00.000Z"
374
+ date.setMinute(0).toISOString(); // "2024-03-15T14:00:00.000Z"
375
+ date.setSecond(0).toISOString(); // "2024-03-15T14:30:00.000Z"
376
+ date.setMillisecond(500).toISOString(); // "2024-03-15T14:30:00.500Z"
377
+ date.setQuarter(3).toISOString(); // "2024-09-15T14:30:00.000Z"
363
378
 
364
379
  // Set multiple values at once
365
- date.set({
366
- year: 2025,
367
- month: 0, // January
368
- date: 1,
369
- hour: 0,
370
- minute: 0,
371
- second: 0
372
- }).format("YYYY-MM-DD HH:mm:ss");
380
+ date
381
+ .set({
382
+ year: 2025,
383
+ month: 0, // January
384
+ date: 1,
385
+ hour: 0,
386
+ minute: 0,
387
+ second: 0,
388
+ })
389
+ .format("YYYY-MM-DD HH:mm:ss");
373
390
  // → "2025-01-01 00:00:00"
374
391
  ```
375
392
 
@@ -383,15 +400,15 @@ Add or subtract time with chainable operations:
383
400
  const date = new DateKit("2024-01-15T10:00:00Z");
384
401
 
385
402
  // Adding time
386
- date.add(5, "day").format("YYYY-MM-DD"); // "2024-01-20"
387
- date.add(2, "week").format("YYYY-MM-DD"); // "2024-01-29"
388
- date.add(3, "month").format("YYYY-MM-DD"); // "2024-04-15"
389
- date.add(1, "year").format("YYYY-MM-DD"); // "2025-01-15"
390
- date.add(90, "minute").format("HH:mm"); // "11:30"
403
+ date.add(5, "day").format("YYYY-MM-DD"); // "2024-01-20"
404
+ date.add(2, "week").format("YYYY-MM-DD"); // "2024-01-29"
405
+ date.add(3, "month").format("YYYY-MM-DD"); // "2024-04-15"
406
+ date.add(1, "year").format("YYYY-MM-DD"); // "2025-01-15"
407
+ date.add(90, "minute").format("HH:mm"); // "11:30"
391
408
 
392
409
  // Subtracting time
393
410
  date.subtract(1, "month").format("YYYY-MM-DD"); // "2023-12-15"
394
- date.subtract(2, "hour").format("HH:mm"); // "08:00"
411
+ date.subtract(2, "hour").format("HH:mm"); // "08:00"
395
412
 
396
413
  // Chaining (all operations are immutable!)
397
414
  const result = date
@@ -414,18 +431,18 @@ Snap to the boundaries of time units:
414
431
  const date = new DateKit("2024-03-15T14:30:45.123Z");
415
432
 
416
433
  // Start of...
417
- date.startOf("year").format("YYYY-MM-DD HH:mm:ss"); // "2024-01-01 00:00:00"
418
- date.startOf("quarter").format("YYYY-MM-DD"); // "2024-01-01"
419
- date.startOf("month").format("YYYY-MM-DD"); // "2024-03-01"
420
- date.startOf("week").format("YYYY-MM-DD"); // "2024-03-10" (Sunday)
421
- date.startOf("day").format("YYYY-MM-DD HH:mm:ss"); // "2024-03-15 00:00:00"
422
- date.startOf("hour").format("HH:mm:ss"); // "14:00:00"
434
+ date.startOf("year").format("YYYY-MM-DD HH:mm:ss"); // "2024-01-01 00:00:00"
435
+ date.startOf("quarter").format("YYYY-MM-DD"); // "2024-01-01"
436
+ date.startOf("month").format("YYYY-MM-DD"); // "2024-03-01"
437
+ date.startOf("week").format("YYYY-MM-DD"); // "2024-03-10" (Sunday)
438
+ date.startOf("day").format("YYYY-MM-DD HH:mm:ss"); // "2024-03-15 00:00:00"
439
+ date.startOf("hour").format("HH:mm:ss"); // "14:00:00"
423
440
 
424
441
  // End of...
425
- date.endOf("year").format("YYYY-MM-DD HH:mm:ss"); // "2024-12-31 23:59:59"
426
- date.endOf("month").format("YYYY-MM-DD"); // "2024-03-31"
427
- date.endOf("day").format("HH:mm:ss.SSS"); // "23:59:59.999"
428
- date.endOf("hour").format("HH:mm:ss.SSS"); // "14:59:59.999"
442
+ date.endOf("year").format("YYYY-MM-DD HH:mm:ss"); // "2024-12-31 23:59:59"
443
+ date.endOf("month").format("YYYY-MM-DD"); // "2024-03-31"
444
+ date.endOf("day").format("HH:mm:ss.SSS"); // "23:59:59.999"
445
+ date.endOf("hour").format("HH:mm:ss.SSS"); // "14:59:59.999"
429
446
  ```
430
447
 
431
448
  ---
@@ -440,24 +457,24 @@ const jan20 = new DateKit("2024-01-20");
440
457
  const feb15 = new DateKit("2024-02-15");
441
458
 
442
459
  // Basic comparisons
443
- jan15.isBefore(jan20); // true
444
- jan20.isAfter(jan15); // true
445
- jan15.isSame("2024-01-15"); // true
460
+ jan15.isBefore(jan20); // true
461
+ jan20.isAfter(jan15); // true
462
+ jan15.isSame("2024-01-15"); // true
446
463
 
447
464
  // Compare by unit
448
465
  jan15.isSame(jan20, "month"); // true (both January)
449
- jan15.isSame(feb15, "year"); // true (both 2024)
466
+ jan15.isSame(feb15, "year"); // true (both 2024)
450
467
 
451
468
  // Inclusive comparisons
452
- jan15.isSameOrBefore(jan20); // true
453
- jan20.isSameOrAfter(jan15); // true
469
+ jan15.isSameOrBefore(jan20); // true
470
+ jan20.isSameOrAfter(jan15); // true
454
471
 
455
472
  // Range check with inclusivity options
456
473
  const jan17 = new DateKit("2024-01-17");
457
- jan17.isBetween("2024-01-15", "2024-01-20"); // true (exclusive)
458
- jan17.isBetween("2024-01-15", "2024-01-20", undefined, "[]"); // true (inclusive)
459
- jan15.isBetween("2024-01-15", "2024-01-20", undefined, "[)"); // true (start-inclusive)
460
- jan20.isBetween("2024-01-15", "2024-01-20", undefined, "(]"); // true (end-inclusive)
474
+ jan17.isBetween("2024-01-15", "2024-01-20"); // true (exclusive)
475
+ jan17.isBetween("2024-01-15", "2024-01-20", undefined, "[]"); // true (inclusive)
476
+ jan15.isBetween("2024-01-15", "2024-01-20", undefined, "[)"); // true (start-inclusive)
477
+ jan20.isBetween("2024-01-15", "2024-01-20", undefined, "(]"); // true (end-inclusive)
461
478
  ```
462
479
 
463
480
  ---
@@ -472,25 +489,25 @@ const saturday = new DateKit("2024-03-16"); // A Saturday
472
489
  const leapYear = new DateKit("2024-02-29");
473
490
 
474
491
  // Relative checks
475
- today.isToday(); // true
476
- today.add(1, "day").isTomorrow(); // true
492
+ today.isToday(); // true
493
+ today.add(1, "day").isTomorrow(); // true
477
494
  today.subtract(1, "day").isYesterday(); // true
478
495
 
479
496
  // Period checks
480
- today.isThisWeek(); // true
481
- today.isThisMonth(); // true
482
- today.isThisQuarter(); // true
483
- today.isThisYear(); // true
497
+ today.isThisWeek(); // true
498
+ today.isThisMonth(); // true
499
+ today.isThisQuarter(); // true
500
+ today.isThisYear(); // true
484
501
 
485
502
  // Day type checks
486
- saturday.isWeekend(); // true
487
- saturday.isWeekday(); // false
503
+ saturday.isWeekend(); // true
504
+ saturday.isWeekday(); // false
488
505
 
489
506
  // Year checks
490
- leapYear.isLeapYear(); // true (2024 is a leap year)
507
+ leapYear.isLeapYear(); // true (2024 is a leap year)
491
508
 
492
509
  // DST check (environment-dependent)
493
- today.isDST(); // true/false based on current DST status
510
+ today.isDST(); // true/false based on current DST status
494
511
  ```
495
512
 
496
513
  ---
@@ -504,22 +521,22 @@ const start = new DateKit("2024-01-01T00:00:00Z");
504
521
  const end = new DateKit("2024-03-15T14:30:00Z");
505
522
 
506
523
  // Basic differences (returns integers by default)
507
- end.diff(start, "day"); // 74
508
- end.diff(start, "week"); // 10
509
- end.diff(start, "month"); // 2
510
- end.diff(start, "hour"); // 1782
524
+ end.diff(start, "day"); // 74
525
+ end.diff(start, "week"); // 10
526
+ end.diff(start, "month"); // 2
527
+ end.diff(start, "hour"); // 1782
511
528
 
512
529
  // Precise differences (floating point)
513
- end.diff(start, "day", true); // 74.604...
530
+ end.diff(start, "day", true); // 74.604...
514
531
  end.diff(start, "month", true); // 2.467...
515
532
 
516
533
  // Negative differences (when comparing backwards)
517
- start.diff(end, "day"); // -74
534
+ start.diff(end, "day"); // -74
518
535
 
519
536
  // Common use case: age calculation
520
537
  const birthdate = new DateKit("1990-05-15");
521
538
  const today = new DateKit("2024-03-15");
522
- today.diff(birthdate, "year"); // 33
539
+ today.diff(birthdate, "year"); // 33
523
540
  ```
524
541
 
525
542
  ---
@@ -532,23 +549,23 @@ Human-friendly "time ago" / "time from now" strings:
532
549
  const now = DateKit.now();
533
550
 
534
551
  // From now (past)
535
- now.subtract(5, "second").fromNow(); // "a few seconds ago"
536
- now.subtract(3, "minute").fromNow(); // "3 minutes ago"
537
- now.subtract(2, "hour").fromNow(); // "2 hours ago"
538
- now.subtract(1, "day").fromNow(); // "a day ago"
539
- now.subtract(5, "day").fromNow(); // "5 days ago"
540
- now.subtract(1, "month").fromNow(); // "a month ago"
541
- now.subtract(2, "year").fromNow(); // "2 years ago"
552
+ now.subtract(5, "second").fromNow(); // "a few seconds ago"
553
+ now.subtract(3, "minute").fromNow(); // "3 minutes ago"
554
+ now.subtract(2, "hour").fromNow(); // "2 hours ago"
555
+ now.subtract(1, "day").fromNow(); // "a day ago"
556
+ now.subtract(5, "day").fromNow(); // "5 days ago"
557
+ now.subtract(1, "month").fromNow(); // "a month ago"
558
+ now.subtract(2, "year").fromNow(); // "2 years ago"
542
559
 
543
560
  // To now (future)
544
- now.add(10, "minute").toNow(); // "in 10 minutes"
545
- now.add(3, "day").toNow(); // "in 3 days"
561
+ now.add(10, "minute").toNow(); // "in 10 minutes"
562
+ now.add(3, "day").toNow(); // "in 3 days"
546
563
 
547
564
  // Between specific dates
548
565
  const past = new DateKit("2024-01-01");
549
566
  const future = new DateKit("2024-12-31");
550
- past.from(future); // "in 12 months"
551
- future.to(past); // "12 months ago"
567
+ past.from(future); // "in 12 months"
568
+ future.to(past); // "12 months ago"
552
569
 
553
570
  // Without suffix
554
571
  now.subtract(5, "minute").fromNow(true); // "5 minutes"
@@ -563,16 +580,16 @@ Context-aware date descriptions:
563
580
  ```typescript
564
581
  const now = DateKit.now();
565
582
 
566
- now.calendar(); // "Today at 2:30 PM"
567
- now.add(1, "day").calendar(); // "Tomorrow at 2:30 PM"
568
- now.subtract(1, "day").calendar(); // "Yesterday at 2:30 PM"
569
- now.add(3, "day").calendar(); // "Thursday at 2:30 PM"
570
- now.subtract(7, "day").calendar(); // "03/08/2024"
583
+ now.calendar(); // "Today at 2:30 PM"
584
+ now.add(1, "day").calendar(); // "Tomorrow at 2:30 PM"
585
+ now.subtract(1, "day").calendar(); // "Yesterday at 2:30 PM"
586
+ now.add(3, "day").calendar(); // "Thursday at 2:30 PM"
587
+ now.subtract(7, "day").calendar(); // "03/08/2024"
571
588
 
572
589
  // With custom reference date
573
590
  const eventDate = new DateKit("2024-06-15T10:00:00Z");
574
591
  const currentDate = new DateKit("2024-06-14");
575
- eventDate.calendar(currentDate); // "Tomorrow at 10:00 AM"
592
+ eventDate.calendar(currentDate); // "Tomorrow at 10:00 AM"
576
593
  ```
577
594
 
578
595
  ---
@@ -585,18 +602,18 @@ Helpful methods for common operations:
585
602
  const date = new DateKit("2024-02-15");
586
603
 
587
604
  // Days in the current month
588
- date.daysInMonth(); // 29 (February 2024, leap year)
605
+ date.daysInMonth(); // 29 (February 2024, leap year)
589
606
  new DateKit("2023-02-15").daysInMonth(); // 28 (non-leap year)
590
607
  new DateKit("2024-01-15").daysInMonth(); // 31
591
608
 
592
609
  // Weeks in year (ISO)
593
- date.weeksInYear(); // 52 (or 53 for some years)
610
+ date.weeksInYear(); // 52 (or 53 for some years)
594
611
 
595
612
  // Age calculation
596
613
  const birthdate = new DateKit("1990-05-15");
597
- birthdate.age(); // Current age in years
598
- birthdate.age("2024-05-14"); // 33 (day before birthday)
599
- birthdate.age("2024-05-15"); // 34 (on birthday)
614
+ birthdate.age(); // Current age in years
615
+ birthdate.age("2024-05-14"); // 33 (day before birthday)
616
+ birthdate.age("2024-05-15"); // 34 (on birthday)
600
617
 
601
618
  // Clone (independent copy)
602
619
  const original = new DateKit("2024-03-15");
@@ -606,8 +623,8 @@ copy.add(1, "day"); // Doesn't affect original
606
623
  // Duration to another date
607
624
  const start = new DateKit("2024-01-01T08:00:00Z");
608
625
  const end = new DateKit("2024-01-01T17:30:00Z");
609
- start.duration(end).asHours(); // 9.5
610
- start.duration(end).humanize(); // "9 hours"
626
+ start.duration(end).asHours(); // 9.5
627
+ start.duration(end).humanize(); // "9 hours"
611
628
  ```
612
629
 
613
630
  ---
@@ -621,7 +638,7 @@ const friday = new DateKit("2024-03-15"); // Friday
621
638
  const monday = new DateKit("2024-03-18"); // Monday
622
639
 
623
640
  // Check if business day
624
- friday.isBusinessDay(); // true
641
+ friday.isBusinessDay(); // true
625
642
  friday.add(1, "day").isBusinessDay(); // false (Saturday)
626
643
 
627
644
  // Add business days (skips weekends)
@@ -647,8 +664,8 @@ const holidays = [
647
664
  friday.addBusinessDays(1, holidays).format("YYYY-MM-DD dddd");
648
665
  // → "2024-03-19 Tuesday" (skipped the holiday)
649
666
 
650
- friday.isBusinessDay(holidays); // true
651
- monday.isBusinessDay(holidays); // false (it's a holiday)
667
+ friday.isBusinessDay(holidays); // true
668
+ monday.isBusinessDay(holidays); // false (it's a holiday)
652
669
  ```
653
670
 
654
671
  ---
@@ -668,7 +685,7 @@ const spanish = date.locale("es") as DateKit;
668
685
  spanish.format("dddd, D [de] MMMM [de] YYYY"); // "Viernes, 15 de Marzo de 2024"
669
686
 
670
687
  // Get current locale
671
- date.locale(); // "en"
688
+ date.locale(); // "en"
672
689
  spanish.locale(); // "es"
673
690
 
674
691
  // Create with locale
@@ -676,9 +693,45 @@ const esDate = new DateKit("2024-03-15", { locale: "es" });
676
693
  esDate.format("MMMM"); // "Marzo"
677
694
  ```
678
695
 
679
- **Built-in locales:** `en` (English), `es` (Spanish)
696
+ **Built-in locales:**
697
+
698
+ | Code | Language | Direction |
699
+ | ---- | -------------------- | --------- |
700
+ | `en` | English | LTR |
701
+ | `es` | Spanish | LTR |
702
+ | `fr` | French | LTR |
703
+ | `de` | German | LTR |
704
+ | `pt` | Portuguese | LTR |
705
+ | `zh` | Chinese (Simplified) | LTR |
706
+ | `ja` | Japanese | LTR |
707
+ | `ko` | Korean | LTR |
708
+ | `ru` | Russian | LTR |
709
+ | `hi` | Hindi | LTR |
710
+ | `bn` | Bengali | LTR |
711
+ | `ar` | Arabic | **RTL** |
712
+ | `ur` | Urdu | **RTL** |
680
713
 
681
- > 💡 **Tip**: Use `registerLocale()` to add custom locales.
714
+ ```typescript
715
+ import { DateKit, registerLocale } from "@subrotosaha/datekit";
716
+ import { ru } from "@subrotosaha/datekit/locales/ru";
717
+
718
+ // All 13 locales are registered automatically
719
+ new DateKit().locale("ja").fromNow(); // "数秒前"
720
+
721
+ // Text direction is exposed via LocaleConfig.dir
722
+ import { getLocale } from "@subrotosaha/datekit";
723
+ getLocale("ar").dir; // "rtl"
724
+ getLocale("en").dir; // "ltr"
725
+
726
+ // Register a custom locale
727
+ registerLocale({
728
+ name: "my-locale",
729
+ dir: "ltr",
730
+ // ... other fields
731
+ });
732
+ ```
733
+
734
+ > 💡 **Tip**: Use `registerLocale()` to add your own custom locale at runtime.
682
735
 
683
736
  ---
684
737
 
@@ -690,25 +743,25 @@ Generate arrays of dates for iteration:
690
743
  // Each day in a range
691
744
  const days = DateKit.eachDayOfInterval({
692
745
  start: "2024-03-01",
693
- end: "2024-03-07"
746
+ end: "2024-03-07",
694
747
  });
695
- days.map(d => d.format("YYYY-MM-DD"));
748
+ days.map((d) => d.format("YYYY-MM-DD"));
696
749
  // → ["2024-03-01", "2024-03-02", ..., "2024-03-07"]
697
750
 
698
751
  // Each week start
699
752
  const weeks = DateKit.eachWeekOfInterval({
700
753
  start: "2024-03-01",
701
- end: "2024-03-31"
754
+ end: "2024-03-31",
702
755
  });
703
- weeks.map(d => d.format("YYYY-MM-DD"));
756
+ weeks.map((d) => d.format("YYYY-MM-DD"));
704
757
  // → ["2024-02-25", "2024-03-03", "2024-03-10", ...]
705
758
 
706
759
  // Each month start
707
760
  const months = DateKit.eachMonthOfInterval({
708
761
  start: "2024-01-01",
709
- end: "2024-06-30"
762
+ end: "2024-06-30",
710
763
  });
711
- months.map(d => d.format("MMMM YYYY"));
764
+ months.map((d) => d.format("MMMM YYYY"));
712
765
  // → ["January 2024", "February 2024", ..., "June 2024"]
713
766
  ```
714
767
 
@@ -720,16 +773,16 @@ Utility methods without instance creation:
720
773
 
721
774
  ```typescript
722
775
  // Current moment
723
- DateKit.now(); // DateKit for current time
776
+ DateKit.now(); // DateKit for current time
724
777
 
725
778
  // Create from specific inputs
726
- DateKit.utc("2024-03-15"); // Parse as UTC
727
- DateKit.unix(1710513000); // From Unix seconds
779
+ DateKit.utc("2024-03-15"); // Parse as UTC
780
+ DateKit.unix(1710513000); // From Unix seconds
728
781
 
729
782
  // Validation
730
- DateKit.isValid("2024-03-15"); // true
731
- DateKit.isValid("invalid-date"); // false
732
- DateKit.isValid(new Date("invalid")); // false
783
+ DateKit.isValid("2024-03-15"); // true
784
+ DateKit.isValid("invalid-date"); // false
785
+ DateKit.isValid(new Date("invalid")); // false
733
786
 
734
787
  // Find extremes
735
788
  DateKit.max("2024-01-01", "2024-06-15", "2024-03-20").format("YYYY-MM-DD");
@@ -739,12 +792,158 @@ DateKit.min("2024-01-01", "2024-06-15", "2024-03-20").format("YYYY-MM-DD");
739
792
  // → "2024-01-01"
740
793
 
741
794
  // Duration factory
742
- DateKit.duration(2, "hours").asMinutes(); // 120
795
+ DateKit.duration(2, "hours").asMinutes(); // 120
743
796
  DateKit.duration({ days: 1, hours: 12 }).asHours(); // 36
744
797
 
745
798
  // Type guard
746
799
  DateKit.isDuration(new Duration(5, "days")); // true
747
- DateKit.isDuration({}); // false
800
+ DateKit.isDuration({}); // false
801
+ ```
802
+
803
+ ---
804
+
805
+ ### Parse with Format
806
+
807
+ Parse a string using a custom format pattern:
808
+
809
+ ```typescript
810
+ // DateKit.parse(dateStr, formatStr, config?)
811
+ DateKit.parse("25/03/2025", "DD/MM/YYYY").format("YYYY-MM-DD");
812
+ // → "2025-03-25"
813
+
814
+ DateKit.parse("March 25, 2025", "MMMM DD, YYYY").toISOString();
815
+ // → "2025-03-25T00:00:00.000Z"
816
+
817
+ DateKit.parse("2025-03-25 14:30", "YYYY-MM-DD HH:mm").format("h:mm A");
818
+ // → "2:30 PM"
819
+
820
+ // Supports AM/PM, 12-hour clock
821
+ DateKit.parse("03/25/2025 02:30 PM", "MM/DD/YYYY hh:mm A");
822
+
823
+ // Two-digit year (pivots at 2000 + current offset)
824
+ DateKit.parse("25/12/99", "DD/MM/YY").year(); // 1999
825
+
826
+ // Escape literal text with square brackets (same as format())
827
+ DateKit.parse("Today is 2025-03-25", "[Today is] YYYY-MM-DD");
828
+ ```
829
+
830
+ **Supported parse tokens:**
831
+
832
+ | Token | Description |
833
+ | -------- | ----------------------- |
834
+ | `YYYY` | 4-digit year |
835
+ | `YY` | 2-digit year |
836
+ | `MM` | 2-digit month (01-12) |
837
+ | `M` | Month without padding |
838
+ | `DD` | 2-digit day of month |
839
+ | `D` | Day without padding |
840
+ | `HH` | 24-hour hours (00-23) |
841
+ | `H` | 24-hour without padding |
842
+ | `hh` | 12-hour hours (01-12) |
843
+ | `h` | 12-hour without padding |
844
+ | `mm` | Minutes (00-59) |
845
+ | `m` | Minutes without padding |
846
+ | `ss` | Seconds (00-59) |
847
+ | `s` | Seconds without padding |
848
+ | `SSS` | Milliseconds |
849
+ | `A` | AM/PM (uppercase) |
850
+ | `a` | am/pm (lowercase) |
851
+ | `[text]` | Escaped literal text |
852
+
853
+ ---
854
+
855
+ ## 📅 DateRange Class
856
+
857
+ Represent an inclusive date range `[start, end]` with set operations.
858
+
859
+ ### Creating Date Ranges
860
+
861
+ ```typescript
862
+ import { DateRange } from "@subrotosaha/datekit";
863
+
864
+ // From any DateInput (ISO string, Date, timestamp, DateKit)
865
+ const q1 = new DateRange("2025-01-01", "2025-03-31");
866
+ const q2 = new DateRange("2025-04-01", "2025-06-30");
867
+
868
+ // Start must not be after end — throws RangeError otherwise
869
+ const range = new DateRange(
870
+ DateKit.now().startOf("year"),
871
+ DateKit.now().endOf("year")
872
+ );
873
+
874
+ console.log(range.start.format("YYYY-MM-DD")); // "2025-01-01"
875
+ console.log(range.end.format("YYYY-MM-DD")); // "2025-12-31"
876
+ ```
877
+
878
+ ---
879
+
880
+ ### Range Predicates
881
+
882
+ ```typescript
883
+ const range = new DateRange("2025-01-01", "2025-12-31");
884
+
885
+ // Contains — inclusive on both ends
886
+ range.contains("2025-06-15"); // true
887
+ range.contains("2024-12-31"); // false
888
+ range.contains("2026-01-01"); // false
889
+
890
+ // Overlaps — true unless one ends strictly before the other starts
891
+ const a = new DateRange("2025-01-01", "2025-06-30");
892
+ const b = new DateRange("2025-04-01", "2025-12-31");
893
+ const c = new DateRange("2026-01-01", "2026-12-31");
894
+
895
+ a.overlaps(b); // true (April–June overlap)
896
+ a.overlaps(c); // false (no overlap)
897
+ ```
898
+
899
+ ---
900
+
901
+ ### Set Operations
902
+
903
+ ```typescript
904
+ const a = new DateRange("2025-01-01", "2025-06-30");
905
+ const b = new DateRange("2025-04-01", "2025-12-31");
906
+
907
+ // Intersection — overlapping portion, or null
908
+ const overlap = a.intersection(b);
909
+ overlap?.start.format("YYYY-MM-DD"); // "2025-04-01"
910
+ overlap?.end.format("YYYY-MM-DD"); // "2025-06-30"
911
+
912
+ // Non-overlapping ranges
913
+ const p = new DateRange("2025-01-01", "2025-03-31");
914
+ const q = new DateRange("2025-07-01", "2025-12-31");
915
+ p.intersection(q); // null
916
+
917
+ // Union — smallest range covering both
918
+ const all = a.union(b);
919
+ all.start.format("YYYY-MM-DD"); // "2025-01-01"
920
+ all.end.format("YYYY-MM-DD"); // "2025-12-31"
921
+ ```
922
+
923
+ ---
924
+
925
+ ### Range Metrics
926
+
927
+ ```typescript
928
+ const q1 = new DateRange("2025-01-01", "2025-03-31");
929
+
930
+ // Number of whole days
931
+ q1.days(); // 89
932
+
933
+ // Duration object (full precision)
934
+ q1.duration().asHours(); // 2136
935
+ q1.duration().humanize(); // "3 months"
936
+
937
+ // Iterate — returns DateKit[] at a given step
938
+ q1.toArray("week").map((d) => d.format("MMM D"));
939
+ // → ["Jan 1", "Jan 8", "Jan 15", ...]
940
+
941
+ q1.toArray("month").map((d) => d.format("MMMM"));
942
+ // → ["January", "February", "March"]
943
+
944
+ // Serialisation
945
+ q1.toString(); // "[2025-01-01T00:00:00.000Z / 2025-03-31T00:00:00.000Z]"
946
+ q1.toJSON(); // { start: "2025-01-01T00:00:00.000Z", end: "2025-03-31T00:00:00.000Z" }
748
947
  ```
749
948
 
750
949
  ---
@@ -768,7 +967,7 @@ const complex = new Duration({
768
967
  days: 2,
769
968
  hours: 5,
770
969
  minutes: 30,
771
- seconds: 15
970
+ seconds: 15,
772
971
  });
773
972
 
774
973
  // From DateKit factory
@@ -793,15 +992,15 @@ Convert durations to different units:
793
992
  const duration = new Duration(90, "minutes");
794
993
 
795
994
  duration.asMilliseconds(); // 5400000
796
- duration.asSeconds(); // 5400
797
- duration.asMinutes(); // 90
798
- duration.asHours(); // 1.5
799
- duration.asDays(); // 0.0625
800
- duration.asWeeks(); // 0.00893...
995
+ duration.asSeconds(); // 5400
996
+ duration.asMinutes(); // 90
997
+ duration.asHours(); // 1.5
998
+ duration.asDays(); // 0.0625
999
+ duration.asWeeks(); // 0.00893...
801
1000
 
802
1001
  // Approximate conversions
803
- duration.asMonths(); // ~0.00205 (using 30.44 days/month)
804
- duration.asYears(); // ~0.00017 (using 365.25 days/year)
1002
+ duration.asMonths(); // ~0.00205 (using 30.44 days/month)
1003
+ duration.asYears(); // ~0.00017 (using 365.25 days/year)
805
1004
 
806
1005
  // Get structured object
807
1006
  const complex = new Duration({ days: 2, hours: 5, minutes: 30 });
@@ -816,14 +1015,14 @@ complex.toObject();
816
1015
  Human-readable duration strings:
817
1016
 
818
1017
  ```typescript
819
- new Duration(30, "seconds").humanize(); // "a few seconds"
820
- new Duration(1, "minutes").humanize(); // "a minute"
821
- new Duration(45, "minutes").humanize(); // "an hour"
822
- new Duration(5, "hours").humanize(); // "5 hours"
823
- new Duration(24, "hours").humanize(); // "a day"
824
- new Duration(35, "days").humanize(); // "a month"
825
- new Duration(400, "days").humanize(); // "a year"
826
- new Duration(3, "years").humanize(); // "3 years"
1018
+ new Duration(30, "seconds").humanize(); // "a few seconds"
1019
+ new Duration(1, "minutes").humanize(); // "a minute"
1020
+ new Duration(45, "minutes").humanize(); // "an hour"
1021
+ new Duration(5, "hours").humanize(); // "5 hours"
1022
+ new Duration(24, "hours").humanize(); // "a day"
1023
+ new Duration(35, "days").humanize(); // "a month"
1024
+ new Duration(400, "days").humanize(); // "a year"
1025
+ new Duration(3, "years").humanize(); // "3 years"
827
1026
  ```
828
1027
 
829
1028
  ---
@@ -837,7 +1036,7 @@ const hour = new Duration(1, "hours");
837
1036
  const halfHour = new Duration(30, "minutes");
838
1037
 
839
1038
  // Addition
840
- hour.add(halfHour).asMinutes(); // 90
1039
+ hour.add(halfHour).asMinutes(); // 90
841
1040
 
842
1041
  // Subtraction
843
1042
  hour.subtract(halfHour).asMinutes(); // 30
@@ -855,69 +1054,71 @@ new Duration(2, "hours")
855
1054
 
856
1055
  Transform DateKit instances:
857
1056
 
858
- | Method | Returns | Description |
859
- |--------|---------|-------------|
860
- | `toDate()` | `Date` | Native Date object (copy) |
861
- | `toISOString()` | `string` | ISO 8601 format |
862
- | `toUnix()` | `number` | Unix timestamp (seconds) |
863
- | `valueOf()` | `number` | Unix timestamp (milliseconds) |
864
- | `toArray()` | `number[]` | `[year, month, date, hour, min, sec, ms]` |
865
- | `toObject()` | `object` | Structured date components |
866
- | `toJSON()` | `string` | ISO string (for serialization) |
867
- | `toString()` | `string` | Native Date string |
1057
+ | Method | Returns | Description |
1058
+ | --------------- | ---------- | ----------------------------------------- |
1059
+ | `toDate()` | `Date` | Native Date object (copy) |
1060
+ | `toISOString()` | `string` | ISO 8601 format |
1061
+ | `toUnix()` | `number` | Unix timestamp (seconds) |
1062
+ | `valueOf()` | `number` | Unix timestamp (milliseconds) |
1063
+ | `toArray()` | `number[]` | `[year, month, date, hour, min, sec, ms]` |
1064
+ | `toObject()` | `object` | Structured date components |
1065
+ | `toJSON()` | `string` | ISO string (for serialization) |
1066
+ | `toString()` | `string` | Native Date string |
868
1067
 
869
1068
  ```typescript
870
1069
  const date = new DateKit("2024-03-15T14:30:45.123Z");
871
1070
 
872
- date.toDate(); // Date object
873
- date.toISOString(); // "2024-03-15T14:30:45.123Z"
874
- date.toUnix(); // 1710513045
875
- date.valueOf(); // 1710513045123
876
- date.toArray(); // [2024, 2, 15, 14, 30, 45, 123]
877
- date.toObject(); // { year: 2024, month: 2, date: 15, ... }
1071
+ date.toDate(); // Date object
1072
+ date.toISOString(); // "2024-03-15T14:30:45.123Z"
1073
+ date.toUnix(); // 1710513045
1074
+ date.valueOf(); // 1710513045123
1075
+ date.toArray(); // [2024, 2, 15, 14, 30, 45, 123]
1076
+ date.toObject(); // { year: 2024, month: 2, date: 15, ... }
878
1077
  ```
879
1078
 
880
1079
  ---
881
1080
 
882
1081
  ## 📋 Format Tokens Reference
883
1082
 
884
- | Category | Token | Output | Example |
885
- |----------|-------|--------|---------|
886
- | **Year** | `YYYY` | 4-digit year | `2024` |
887
- | | `YY` | 2-digit year | `24` |
888
- | **Quarter** | `Q` | Quarter number | `1` - `4` |
889
- | | `Qo` | Quarter ordinal | `1st`, `2nd` |
890
- | **Month** | `MMMM` | Full name | `March` |
891
- | | `MMM` | Short name | `Mar` |
892
- | | `MM` | 2-digit | `03` |
893
- | | `M` | Number | `3` |
894
- | | `Mo` | Ordinal | `3rd` |
895
- | **Week** | `W` / `WW` | ISO week | `11` / `11` |
896
- | | `Wo` | Week ordinal | `11th` |
897
- | **Day of Year** | `DDD` | Day number | `75` |
898
- | | `DDDD` | Padded | `075` |
899
- | | `DDDo` | Ordinal | `75th` |
900
- | **Day of Month** | `DD` | 2-digit | `15` |
901
- | | `D` | Number | `15` |
902
- | | `Do` | Ordinal | `15th` |
903
- | **Day of Week** | `dddd` | Full name | `Friday` |
904
- | | `ddd` | Short name | `Fri` |
905
- | | `dd` | Min name | `Fr` |
906
- | | `d` | Number (Sun=0) | `5` |
907
- | | `do` | Ordinal | `5th` |
908
- | **Hour** | `HH` / `H` | 24-hour | `14` / `14` |
909
- | | `hh` / `h` | 12-hour | `02` / `2` |
910
- | **Minute** | `mm` / `m` | Minutes | `30` / `30` |
911
- | **Second** | `ss` / `s` | Seconds | `45` / `45` |
912
- | **Millisecond** | `SSS` | 3-digit | `123` |
913
- | | `SS` | 2-digit | `12` |
914
- | | `S` | 1-digit | `1` |
915
- | **AM/PM** | `A` | Uppercase | `PM` |
916
- | | `a` | Lowercase | `pm` |
917
- | **Timezone** | `Z` | With colon | `+00:00` |
918
- | | `ZZ` | Compact | `+0000` |
919
- | **Unix** | `X` | Seconds | `1710513045` |
920
- | | `x` | Milliseconds | `1710513045123` |
1083
+ | Category | Token | Output | Example |
1084
+ | ----------------- | ---------- | ------------------- | --------------- |
1085
+ | **Year** | `YYYY` | 4-digit year | `2024` |
1086
+ | | `YY` | 2-digit year | `24` |
1087
+ | **Quarter** | `Q` | Quarter number | `1` - `4` |
1088
+ | | `Qo` | Quarter ordinal | `1st`, `2nd` |
1089
+ | **Month** | `MMMM` | Full name | `March` |
1090
+ | | `MMM` | Short name | `Mar` |
1091
+ | | `MM` | 2-digit | `03` |
1092
+ | | `M` | Number | `3` |
1093
+ | | `Mo` | Ordinal | `3rd` |
1094
+ | **Week (ISO)** | `W` / `WW` | ISO week number | `11` / `11` |
1095
+ | | `Wo` | ISO week ordinal | `11th` |
1096
+ | **Week (locale)** | `w` / `ww` | Locale week number | `11` / `11` |
1097
+ | | `wo` | Locale week ordinal | `11th` |
1098
+ | **Day of Year** | `DDD` | Day number | `75` |
1099
+ | | `DDDD` | Padded | `075` |
1100
+ | | `DDDo` | Ordinal | `75th` |
1101
+ | **Day of Month** | `DD` | 2-digit | `15` |
1102
+ | | `D` | Number | `15` |
1103
+ | | `Do` | Ordinal | `15th` |
1104
+ | **Day of Week** | `dddd` | Full name | `Friday` |
1105
+ | | `ddd` | Short name | `Fri` |
1106
+ | | `dd` | Min name | `Fr` |
1107
+ | | `d` | Number (Sun=0) | `5` |
1108
+ | | `do` | Ordinal | `5th` |
1109
+ | **Hour** | `HH` / `H` | 24-hour | `14` / `14` |
1110
+ | | `hh` / `h` | 12-hour | `02` / `2` |
1111
+ | **Minute** | `mm` / `m` | Minutes | `30` / `30` |
1112
+ | **Second** | `ss` / `s` | Seconds | `45` / `45` |
1113
+ | **Millisecond** | `SSS` | 3-digit | `123` |
1114
+ | | `SS` | 2-digit | `12` |
1115
+ | | `S` | 1-digit | `1` |
1116
+ | **AM/PM** | `A` | Uppercase | `PM` |
1117
+ | | `a` | Lowercase | `pm` |
1118
+ | **Timezone** | `Z` | With colon | `+00:00` |
1119
+ | | `ZZ` | Compact | `+0000` |
1120
+ | **Unix** | `X` | Seconds | `1710513045` |
1121
+ | | `x` | Milliseconds | `1710513045123` |
921
1122
 
922
1123
  ---
923
1124
 
@@ -937,6 +1138,8 @@ import type {
937
1138
  QuarterNumber,
938
1139
  DayOfWeek,
939
1140
  } from "@subrotosaha/datekit";
1141
+
1142
+ import { DateRange } from "@subrotosaha/datekit";
940
1143
  ```
941
1144
 
942
1145
  <details>
@@ -948,8 +1151,15 @@ type DateInput = Date | string | number;
948
1151
 
949
1152
  // Time manipulation units
950
1153
  type TimeUnit =
951
- | "millisecond" | "second" | "minute" | "hour"
952
- | "day" | "week" | "month" | "quarter" | "year";
1154
+ | "millisecond"
1155
+ | "second"
1156
+ | "minute"
1157
+ | "hour"
1158
+ | "day"
1159
+ | "week"
1160
+ | "month"
1161
+ | "quarter"
1162
+ | "year";
953
1163
 
954
1164
  // Days of the week (0 = Sunday)
955
1165
  type DayOfWeek = 0 | 1 | 2 | 3 | 4 | 5 | 6;
@@ -959,16 +1169,16 @@ type QuarterNumber = 1 | 2 | 3 | 4;
959
1169
 
960
1170
  // Configuration options
961
1171
  interface DateKitConfig {
962
- locale?: string; // Locale code (e.g., "en", "es")
963
- weekStartsOn?: DayOfWeek; // First day of week
964
- timezone?: string; // IANA timezone
965
- strictParsing?: boolean; // Strict date parsing
1172
+ locale?: string; // Locale code (e.g., "en", "es")
1173
+ weekStartsOn?: DayOfWeek; // First day of week
1174
+ timezone?: string; // IANA timezone
1175
+ strictParsing?: boolean; // Strict date parsing
966
1176
  }
967
1177
 
968
1178
  // For set() method
969
1179
  interface SetDateValues {
970
1180
  year?: number;
971
- month?: number; // 0-indexed
1181
+ month?: number; // 0-indexed
972
1182
  date?: number;
973
1183
  hour?: number;
974
1184
  minute?: number;
@@ -997,14 +1207,36 @@ interface DurationObject {
997
1207
  // Locale configuration
998
1208
  interface LocaleConfig {
999
1209
  name: string;
1210
+ dir?: "ltr" | "rtl"; // Text direction (RTL for ar, ur)
1000
1211
  weekdays: string[];
1001
1212
  weekdaysShort: string[];
1002
1213
  weekdaysMin: string[];
1003
1214
  months: string[];
1004
1215
  monthsShort: string[];
1005
1216
  ordinal: (n: number) => string;
1006
- relativeTime: { /* ... */ };
1007
- calendar: { /* ... */ };
1217
+ relativeTime: {
1218
+ future: string; // e.g. "in %s"
1219
+ past: string; // e.g. "%s ago"
1220
+ s: string; // seconds
1221
+ m: string; // a minute
1222
+ mm: string | ((n: number) => string); // N minutes (supports plural functions)
1223
+ h: string; // an hour
1224
+ hh: string | ((n: number) => string); // N hours
1225
+ d: string; // a day
1226
+ dd: string | ((n: number) => string); // N days
1227
+ M: string; // a month
1228
+ MM: string | ((n: number) => string); // N months
1229
+ y: string; // a year
1230
+ yy: string | ((n: number) => string); // N years
1231
+ };
1232
+ calendar: {
1233
+ sameDay: string; // "[Today at] LT"
1234
+ nextDay: string; // "[Tomorrow at] LT"
1235
+ nextWeek: string; // "dddd [at] LT"
1236
+ lastDay: string; // "[Yesterday at] LT"
1237
+ lastWeek: string; // "[Last] dddd [at] LT"
1238
+ sameElse: string; // "L" (fallback format)
1239
+ };
1008
1240
  }
1009
1241
  ```
1010
1242
 
@@ -1029,12 +1261,7 @@ const timezones = [
1029
1261
 
1030
1262
  console.log("Meeting scheduled for 2 PM Dhaka time:");
1031
1263
  timezones.forEach(({ city, tz }) => {
1032
- const time = DateKit.convertTimezone(
1033
- meetingTime,
1034
- "Asia/Dhaka",
1035
- tz,
1036
- "h:mm A"
1037
- );
1264
+ const time = DateKit.convertTimezone(meetingTime, "Asia/Dhaka", tz, "h:mm A");
1038
1265
  console.log(` ${city}: ${time}`);
1039
1266
  });
1040
1267
  ```
@@ -1046,13 +1273,14 @@ function getWeekDates(date: DateKit) {
1046
1273
  const start = date.startOf("week");
1047
1274
  return DateKit.eachDayOfInterval({
1048
1275
  start: start.toISOString(),
1049
- end: start.add(6, "day").toISOString()
1276
+ end: start.add(6, "day").toISOString(),
1050
1277
  });
1051
1278
  }
1052
1279
 
1053
1280
  const thisWeek = getWeekDates(DateKit.now());
1054
- thisWeek.forEach(day => {
1055
- console.log(day.format("ddd, MMM D"),
1281
+ thisWeek.forEach((day) => {
1282
+ console.log(
1283
+ day.format("ddd, MMM D"),
1056
1284
  day.isToday() ? "(today)" : "",
1057
1285
  day.isWeekend() ? "🌴" : ""
1058
1286
  );
@@ -1070,7 +1298,10 @@ function getNextBusinessDay(date: DateKit): DateKit {
1070
1298
  return next;
1071
1299
  }
1072
1300
 
1073
- function calculateDeliveryDate(orderDate: DateKit, businessDays: number): DateKit {
1301
+ function calculateDeliveryDate(
1302
+ orderDate: DateKit,
1303
+ businessDays: number
1304
+ ): DateKit {
1074
1305
  return orderDate.addBusinessDays(businessDays);
1075
1306
  }
1076
1307
 
@@ -1079,7 +1310,9 @@ const delivery = calculateDeliveryDate(order, 5);
1079
1310
 
1080
1311
  console.log(`Ordered: ${order.format("dddd, MMMM D")}`);
1081
1312
  console.log(`Estimated Delivery: ${delivery.format("dddd, MMMM D")}`);
1082
- console.log(`(${order.businessDaysUntil(delivery.toISOString())} business days)`);
1313
+ console.log(
1314
+ `(${order.businessDaysUntil(delivery.toISOString())} business days)`
1315
+ );
1083
1316
  ```
1084
1317
 
1085
1318
  ### Age Calculator
@@ -1089,18 +1322,20 @@ function formatAge(birthdate: DateKit): string {
1089
1322
  const years = birthdate.age();
1090
1323
  const months = DateKit.now().diff(birthdate.add(years, "year"), "month");
1091
1324
  const days = DateKit.now().diff(
1092
- birthdate.add(years, "year").add(months, "month"),
1325
+ birthdate.add(years, "year").add(months, "month"),
1093
1326
  "day"
1094
1327
  );
1095
-
1328
+
1096
1329
  return `${years} years, ${months} months, ${days} days`;
1097
1330
  }
1098
1331
 
1099
1332
  const birthday = new DateKit("1990-05-15");
1100
1333
  console.log(`Age: ${formatAge(birthday)}`);
1101
- console.log(`Days until next birthday: ${
1102
- birthday.setYear(DateKit.now().year() + 1).diff(DateKit.now(), "day")
1103
- }`);
1334
+ console.log(
1335
+ `Days until next birthday: ${birthday
1336
+ .setYear(DateKit.now().year() + 1)
1337
+ .diff(DateKit.now(), "day")}`
1338
+ );
1104
1339
  ```
1105
1340
 
1106
1341
  ---
@@ -1130,8 +1365,8 @@ All "mutating" methods return new instances:
1130
1365
  const original = new DateKit("2024-03-15");
1131
1366
  const modified = original.add(1, "day");
1132
1367
 
1133
- original.format("YYYY-MM-DD"); // "2024-03-15" (unchanged!)
1134
- modified.format("YYYY-MM-DD"); // "2024-03-16"
1368
+ original.format("YYYY-MM-DD"); // "2024-03-15" (unchanged!)
1369
+ modified.format("YYYY-MM-DD"); // "2024-03-16"
1135
1370
  ```
1136
1371
 
1137
1372
  ### Month Overflow