daymath 0.2.0 → 0.2.2
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 +46 -58
- package/index.d.ts +10 -4
- package/index.js +25 -9
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,95 +1,83 @@
|
|
|
1
1
|
# daymath
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/daymath)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/daymath)
|
|
6
|
+
|
|
7
|
+
**ISO 8601** calendar day math. **date-fns-shaped** names. **Temporal.PlainDate** under the hood.
|
|
4
8
|
|
|
5
9
|
No `Date`. No time zones. No silent “local now.”
|
|
6
10
|
|
|
11
|
+
[**Play in the browser →**](https://leemr.github.io/daymath/) · [npm](https://www.npmjs.com/package/daymath) · [FUTURE.md](./FUTURE.md)
|
|
12
|
+
|
|
7
13
|
```bash
|
|
8
14
|
npm install daymath
|
|
9
15
|
```
|
|
10
16
|
|
|
11
|
-
## Why
|
|
12
|
-
|
|
13
|
-
`Date` is a timestamp. Calendar work (“add one month”, “days between hire and start”) is not. This package only does plain calendar days.
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
17
|
```js
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
isBefore,
|
|
23
|
-
isSameDay,
|
|
24
|
-
startOfMonth,
|
|
25
|
-
eachDayOfInterval,
|
|
26
|
-
} from 'daymath'
|
|
27
|
-
|
|
28
|
-
addDays('2026-08-06', 1) // '2026-08-07'
|
|
29
|
-
addMonths('2026-01-31', 1) // '2026-02-28' (constrain)
|
|
18
|
+
import { addDays, addMonths, differenceInDays, isSameDay } from 'daymath'
|
|
19
|
+
|
|
20
|
+
addDays('2026-08-06', 1) // '2026-08-07'
|
|
21
|
+
addMonths('2026-01-31', 1) // '2026-02-28'
|
|
30
22
|
differenceInDays('2026-08-06', '2026-08-01') // 5
|
|
31
|
-
|
|
32
|
-
isSameDay('2026-08-06', '2026-08-06') // true (alias of isEqual)
|
|
33
|
-
startOfMonth('2026-08-06') // '2026-08-01'
|
|
34
|
-
eachDayOfInterval({
|
|
35
|
-
start: '2026-08-05',
|
|
36
|
-
end: '2026-08-07',
|
|
37
|
-
}) // ['2026-08-05', '2026-08-06', '2026-08-07']
|
|
23
|
+
isSameDay('2026-08-06', '2026-08-06') // true
|
|
38
24
|
```
|
|
39
25
|
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
## Why
|
|
27
|
+
|
|
28
|
+
`Date` is a timestamp. Hire dates, passport expiry, trip days are **calendar** values. daymath only does plain days as ISO strings.
|
|
29
|
+
|
|
30
|
+
| In | Out |
|
|
31
|
+
|----|-----|
|
|
32
|
+
| `YYYY-MM-DD` or expanded `±YYYYYY-MM-DD` | same forms (Temporal `toString`) |
|
|
33
|
+
| or `Temporal.PlainDate` | string |
|
|
42
34
|
|
|
43
|
-
`Date` throws
|
|
35
|
+
`Date` **throws** (including `isValid`). `isValid('asdf')` → `false`.
|
|
44
36
|
|
|
45
|
-
## date-fns parity
|
|
37
|
+
## date-fns parity (names, not `Date`)
|
|
46
38
|
|
|
47
39
|
| Topic | daymath |
|
|
48
40
|
|-------|---------|
|
|
49
|
-
|
|
|
50
|
-
| `isSameDay` | Alias of `isEqual`
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
|
|
|
55
|
-
|
|
|
41
|
+
| Values | ISO day **strings**, not `Date` |
|
|
42
|
+
| `isSameDay` | Alias of `isEqual` |
|
|
43
|
+
| `isValid` | Valid daymath day; **`Date` throws** |
|
|
44
|
+
| `getMonth` / `setMonth` | **0–11** (0 = January) |
|
|
45
|
+
| `getDay` | **0–6** (0 = Sunday) |
|
|
46
|
+
| `weekStartsOn` | default `0` (Sunday) |
|
|
47
|
+
| Intervals | `{ start, end }` |
|
|
56
48
|
|
|
57
|
-
## API
|
|
49
|
+
## API
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
`parse` · `format` · `isValid`
|
|
51
|
+
**Parse** — `parse` · `format` · `isValid`
|
|
61
52
|
|
|
62
|
-
|
|
63
|
-
`addDays` / `subDays` · `addWeeks` / `subWeeks` · `addMonths` / `subMonths` · `addYears` / `subYears` · `addQuarters` / `subQuarters`
|
|
53
|
+
**Add/sub** — Days · Weeks · Months · Years · Quarters
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
`getYear` · `getMonth` · `getDate` · `getDay` · `getDayOfYear` · `getDaysInMonth` · `getQuarter` · `isLeapYear`
|
|
67
|
-
`setYear` · `setMonth` · `setDate`
|
|
55
|
+
**Get/set** — `getYear` · `getMonth` · `getDate` · `getDay` · `getDayOfYear` · `getDaysInMonth` · `getQuarter` · `isLeapYear` · `setYear` · `setMonth` · `setDate`
|
|
68
56
|
|
|
69
|
-
|
|
70
|
-
`startOfMonth` / `endOfMonth` · `startOfYear` / `endOfYear` · `startOfQuarter` / `endOfQuarter` · `startOfWeek` / `endOfWeek`
|
|
57
|
+
**Bounds** — `startOf`/`endOf` Month · Year · Quarter · Week
|
|
71
58
|
|
|
72
|
-
|
|
73
|
-
`differenceInDays` · `differenceInWeeks` · `differenceInMonths` · `differenceInCalendarMonths` · `differenceInYears` · `differenceInCalendarYears` · `differenceInQuarters` · `differenceInCalendarQuarters`
|
|
59
|
+
**Diffs** — Days · Weeks · Months · CalendarMonths · Years · CalendarYears · Quarters · CalendarQuarters
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
`isBefore` · `isAfter` · `isEqual` · `isSameDay` · `isSameWeek` · `isSameMonth` · `isSameYear` · `isSameQuarter` · `compareAsc` · `compareDesc` · `min` · `max`
|
|
61
|
+
**Compare** — `isBefore` · `isAfter` · `isEqual` · `isSameDay` · `isSameWeek` · Month · Year · Quarter · `compareAsc` · `compareDesc` · `min` · `max`
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
`isSunday`…`isSaturday` · `isWeekend` · `isFirstDayOfMonth` · `isLastDayOfMonth`
|
|
63
|
+
**Weekday** — `isSunday`…`isSaturday` · `isWeekend` · first/last day of month
|
|
80
64
|
|
|
81
|
-
|
|
82
|
-
`eachDayOfInterval` · `eachMonthOfInterval` · `eachYearOfInterval` · `isWithinInterval` · `clamp` · `areIntervalsOverlapping`
|
|
65
|
+
**Intervals** — `eachDayOfInterval` · `eachMonthOfInterval` · `eachYearOfInterval` · `isWithinInterval` · `clamp` · `areIntervalsOverlapping`
|
|
83
66
|
|
|
84
|
-
Amounts
|
|
67
|
+
Amounts are finite integers.
|
|
85
68
|
|
|
86
69
|
## Temporal
|
|
87
70
|
|
|
88
71
|
Uses global `Temporal` when present; otherwise [`temporal-polyfill`](https://www.npmjs.com/package/temporal-polyfill).
|
|
89
72
|
|
|
90
|
-
## Types
|
|
73
|
+
## Types & tests
|
|
91
74
|
|
|
92
|
-
|
|
75
|
+
Plain JS + `index.d.ts` (no compile step).
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm test
|
|
79
|
+
npm run test:coverage # 100% lines on index.js
|
|
80
|
+
```
|
|
93
81
|
|
|
94
82
|
## License
|
|
95
83
|
|
package/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Temporal } from 'temporal-polyfill'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Calendar day input: ISO 8601
|
|
5
|
-
* `
|
|
4
|
+
* Calendar day input: ISO 8601 day string, or a Temporal.PlainDate.
|
|
5
|
+
* - `YYYY-MM-DD` (years 0000–9999)
|
|
6
|
+
* - expanded `±YYYYYY-MM-DD` (e.g. `+010000-01-01`)
|
|
7
|
+
* `Date` is rejected at runtime (TypeError).
|
|
6
8
|
*/
|
|
7
9
|
export type DayInput = string | Temporal.PlainDate
|
|
8
10
|
|
|
@@ -19,12 +21,16 @@ export type WeekOptions = {
|
|
|
19
21
|
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* True for a valid daymath day string / PlainDate.
|
|
26
|
+
* Invalid strings → false. `Date` → throws TypeError (not a quiet false).
|
|
27
|
+
*/
|
|
22
28
|
export function isValid(value: unknown): boolean
|
|
23
29
|
|
|
24
|
-
/** Validate / normalize to ISO 8601 `
|
|
30
|
+
/** Validate / normalize to ISO 8601 day string (Temporal `toString` form). */
|
|
25
31
|
export function parse(date: DayInput): string
|
|
26
32
|
|
|
27
|
-
/** Format as `YYYY-MM-DD`
|
|
33
|
+
/** Format as ISO day (only `yyyy-MM-dd` / `YYYY-MM-DD` patterns supported). */
|
|
28
34
|
export function format(date: DayInput, pattern?: 'yyyy-MM-dd' | 'YYYY-MM-DD'): string
|
|
29
35
|
|
|
30
36
|
export function addDays(date: DayInput, amount: number): string
|
package/index.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
/** daymath — calendar date math (
|
|
1
|
+
/** daymath — calendar date math (ISO 8601 day). date-fns-shaped. No Date / time zones. */
|
|
2
2
|
import { Temporal as TemporalPolyfill } from 'temporal-polyfill'
|
|
3
3
|
|
|
4
4
|
const Temporal = globalThis.Temporal ?? TemporalPolyfill
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* ISO 8601 calendar day string:
|
|
8
|
+
* - `YYYY-MM-DD` (years 0000–9999)
|
|
9
|
+
* - expanded `±YYYYYY-MM-DD` (Temporal form, e.g. `+010000-01-01`)
|
|
10
|
+
*/
|
|
11
|
+
const ISO_DAY =
|
|
12
|
+
/^(?:[+-]\d{6}|\d{4})-\d{2}-\d{2}$/
|
|
13
|
+
|
|
6
14
|
/** @typedef {string | Temporal.PlainDate} DayInput */
|
|
7
15
|
/**
|
|
8
16
|
* @typedef {object} Interval
|
|
@@ -17,7 +25,7 @@ const Temporal = globalThis.Temporal ?? TemporalPolyfill
|
|
|
17
25
|
// ─── core conversion ───────────────────────────────────────────────
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
|
-
* Reject Date and non-calendar values. Accept
|
|
28
|
+
* Reject Date and non-calendar values. Accept ISO day string or PlainDate.
|
|
21
29
|
* @param {unknown} value
|
|
22
30
|
* @param {string} label
|
|
23
31
|
* @returns {Temporal.PlainDate}
|
|
@@ -25,14 +33,13 @@ const Temporal = globalThis.Temporal ?? TemporalPolyfill
|
|
|
25
33
|
function toPlainDate(value, label = 'date') {
|
|
26
34
|
if (value instanceof Date) {
|
|
27
35
|
throw new TypeError(
|
|
28
|
-
`daymath: Date is not allowed for ${label} (pass
|
|
36
|
+
`daymath: Date is not allowed for ${label} (pass ISO 8601 day string)`,
|
|
29
37
|
)
|
|
30
38
|
}
|
|
31
39
|
if (typeof value === 'string') {
|
|
32
|
-
|
|
33
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
40
|
+
if (!ISO_DAY.test(value)) {
|
|
34
41
|
throw new RangeError(
|
|
35
|
-
`daymath: ${label} must be YYYY-MM-DD (got ${JSON.stringify(value)})`,
|
|
42
|
+
`daymath: ${label} must be ISO 8601 day YYYY-MM-DD or ±YYYYYY-MM-DD (got ${JSON.stringify(value)})`,
|
|
36
43
|
)
|
|
37
44
|
}
|
|
38
45
|
try {
|
|
@@ -47,7 +54,7 @@ function toPlainDate(value, label = 'date') {
|
|
|
47
54
|
return value
|
|
48
55
|
}
|
|
49
56
|
throw new TypeError(
|
|
50
|
-
`daymath: ${label} must be
|
|
57
|
+
`daymath: ${label} must be ISO 8601 day string or Temporal.PlainDate`,
|
|
51
58
|
)
|
|
52
59
|
}
|
|
53
60
|
|
|
@@ -105,8 +112,17 @@ function toInterval(interval) {
|
|
|
105
112
|
|
|
106
113
|
// ─── parse / format / valid ────────────────────────────────────────
|
|
107
114
|
|
|
108
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* True if value is a valid daymath day (ISO day string or PlainDate).
|
|
117
|
+
* Invalid strings → false. `Date` → throws (not a quiet false — swap trap).
|
|
118
|
+
* @param {unknown} value
|
|
119
|
+
*/
|
|
109
120
|
export function isValid(value) {
|
|
121
|
+
if (value instanceof Date) {
|
|
122
|
+
throw new TypeError(
|
|
123
|
+
'daymath: Date is not allowed for isValid (pass ISO 8601 day string)',
|
|
124
|
+
)
|
|
125
|
+
}
|
|
110
126
|
try {
|
|
111
127
|
toPlainDate(value)
|
|
112
128
|
return true
|
|
@@ -116,7 +132,7 @@ export function isValid(value) {
|
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
/**
|
|
119
|
-
* Validate / normalize
|
|
135
|
+
* Validate / normalize an ISO 8601 calendar day string.
|
|
120
136
|
* @param {DayInput} date
|
|
121
137
|
* @returns {string}
|
|
122
138
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daymath",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Calendar date math (
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Calendar date math (ISO 8601 day / PlainDate). date-fns-shaped. No time zones.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
7
7
|
"types": "./index.d.ts",
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node --test test.js",
|
|
21
|
-
"
|
|
21
|
+
"test:coverage": "node --test --experimental-test-coverage --test-coverage-include=index.js test.js",
|
|
22
|
+
"prepublishOnly": "npm run test:coverage"
|
|
22
23
|
},
|
|
24
|
+
|
|
23
25
|
"keywords": [
|
|
24
26
|
"date",
|
|
25
27
|
"calendar",
|