daymath 0.1.0 → 0.2.1
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 +61 -22
- package/index.d.ts +98 -4
- package/index.js +559 -23
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# daymath
|
|
2
2
|
|
|
3
|
-
Calendar date math for
|
|
3
|
+
Calendar date math for **ISO 8601** day strings. **date-fns-shaped** names. **Temporal.PlainDate** under the hood.
|
|
4
4
|
|
|
5
5
|
No `Date`. No time zones. No silent “local now.”
|
|
6
6
|
|
|
@@ -20,34 +20,73 @@ import {
|
|
|
20
20
|
addMonths,
|
|
21
21
|
differenceInDays,
|
|
22
22
|
isBefore,
|
|
23
|
-
|
|
23
|
+
isSameDay,
|
|
24
|
+
startOfMonth,
|
|
25
|
+
eachDayOfInterval,
|
|
24
26
|
} from 'daymath'
|
|
25
27
|
|
|
26
|
-
addDays('2026-08-06', 1)
|
|
27
|
-
addMonths('2026-01-31', 1)
|
|
28
|
+
addDays('2026-08-06', 1) // '2026-08-07'
|
|
29
|
+
addMonths('2026-01-31', 1) // '2026-02-28' (constrain)
|
|
28
30
|
differenceInDays('2026-08-06', '2026-08-01') // 5
|
|
29
|
-
isBefore('2026-08-05', '2026-08-06')
|
|
30
|
-
|
|
31
|
+
isBefore('2026-08-05', '2026-08-06') // true
|
|
32
|
+
isSameDay('2026-08-06', '2026-08-06') // true (alias of isEqual)
|
|
33
|
+
startOfMonth('2026-08-06') // '2026-08-01'
|
|
34
|
+
addDays('9999-12-31', 1) // '+010000-01-01' (expanded year)
|
|
35
|
+
eachDayOfInterval({
|
|
36
|
+
start: '2026-08-05',
|
|
37
|
+
end: '2026-08-07',
|
|
38
|
+
}) // ['2026-08-05', '2026-08-06', '2026-08-07']
|
|
31
39
|
```
|
|
32
40
|
|
|
33
|
-
Inputs
|
|
34
|
-
|
|
41
|
+
**Inputs:** ISO 8601 day string or `Temporal.PlainDate`.
|
|
42
|
+
- `YYYY-MM-DD` (years 0000–9999)
|
|
43
|
+
- expanded `±YYYYYY-MM-DD` (e.g. `+010000-01-01`)
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
**Outputs:** Temporal’s ISO day string (same forms).
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
`Date` throws (including `isValid(date)`). Bad strings: math helpers throw; `isValid('asdf')` → `false`. Time-bearing / sloppy forms throw.
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
|
45
|
-
|
|
46
|
-
|
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
49
|
+
See [FUTURE.md](./FUTURE.md) for backlog (bundle size, business days, …).
|
|
50
|
+
|
|
51
|
+
## date-fns parity notes
|
|
52
|
+
|
|
53
|
+
| Topic | daymath |
|
|
54
|
+
|-------|---------|
|
|
55
|
+
| Value type | ISO day string (not `Date`) |
|
|
56
|
+
| `isSameDay` | Alias of `isEqual` (same calendar day) |
|
|
57
|
+
| `isValid` | Our predicate: valid day string / PlainDate. `Date` **throws** (not date-fns’s Date check) |
|
|
58
|
+
| `getMonth` / `setMonth` | **0–11** like Date/date-fns (0 = January) |
|
|
59
|
+
| `getDay` | **0–6** like Date/date-fns (0 = Sunday) |
|
|
60
|
+
| `weekStartsOn` | `0` = Sunday … `6` = Saturday (default `0`) |
|
|
61
|
+
| Intervals | `{ start, end }` inclusive for `each*` / `isWithin` / `clamp` |
|
|
62
|
+
| `areIntervalsOverlapping` | Default `{ inclusive: false }` (date-fns); pass `true` for closed |
|
|
63
|
+
|
|
64
|
+
## API (0.2)
|
|
65
|
+
|
|
66
|
+
### Parse / format
|
|
67
|
+
`parse` · `format` · `isValid`
|
|
68
|
+
|
|
69
|
+
### Add / sub
|
|
70
|
+
`addDays` / `subDays` · `addWeeks` / `subWeeks` · `addMonths` / `subMonths` · `addYears` / `subYears` · `addQuarters` / `subQuarters`
|
|
71
|
+
|
|
72
|
+
### Get / set
|
|
73
|
+
`getYear` · `getMonth` · `getDate` · `getDay` · `getDayOfYear` · `getDaysInMonth` · `getQuarter` · `isLeapYear`
|
|
74
|
+
`setYear` · `setMonth` · `setDate`
|
|
75
|
+
|
|
76
|
+
### Start / end
|
|
77
|
+
`startOfMonth` / `endOfMonth` · `startOfYear` / `endOfYear` · `startOfQuarter` / `endOfQuarter` · `startOfWeek` / `endOfWeek`
|
|
78
|
+
|
|
79
|
+
### Differences
|
|
80
|
+
`differenceInDays` · `differenceInWeeks` · `differenceInMonths` · `differenceInCalendarMonths` · `differenceInYears` · `differenceInCalendarYears` · `differenceInQuarters` · `differenceInCalendarQuarters`
|
|
81
|
+
|
|
82
|
+
### Compare
|
|
83
|
+
`isBefore` · `isAfter` · `isEqual` · `isSameDay` · `isSameWeek` · `isSameMonth` · `isSameYear` · `isSameQuarter` · `compareAsc` · `compareDesc` · `min` · `max`
|
|
84
|
+
|
|
85
|
+
### Weekday / month edges
|
|
86
|
+
`isSunday`…`isSaturday` · `isWeekend` · `isFirstDayOfMonth` · `isLastDayOfMonth`
|
|
87
|
+
|
|
88
|
+
### Intervals
|
|
89
|
+
`eachDayOfInterval` · `eachMonthOfInterval` · `eachYearOfInterval` · `isWithinInterval` · `clamp` · `areIntervalsOverlapping`
|
|
51
90
|
|
|
52
91
|
Amounts must be finite integers.
|
|
53
92
|
|
|
@@ -57,7 +96,7 @@ Uses global `Temporal` when present; otherwise [`temporal-polyfill`](https://www
|
|
|
57
96
|
|
|
58
97
|
## Types
|
|
59
98
|
|
|
60
|
-
Ships `index.d.ts` (no TypeScript compile step
|
|
99
|
+
Ships `index.d.ts` (no TypeScript compile step). Source is plain JS.
|
|
61
100
|
|
|
62
101
|
## License
|
|
63
102
|
|
package/index.d.ts
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import type { Temporal } from 'temporal-polyfill'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Calendar day input:
|
|
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
|
|
|
11
|
+
/** Inclusive calendar-day interval (date-fns shape). */
|
|
12
|
+
export type Interval = {
|
|
13
|
+
start: DayInput
|
|
14
|
+
end: DayInput
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Week options. `weekStartsOn`: 0 = Sunday … 6 = Saturday (date-fns default 0).
|
|
19
|
+
*/
|
|
20
|
+
export type WeekOptions = {
|
|
21
|
+
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* True for a valid daymath day string / PlainDate.
|
|
26
|
+
* Invalid strings → false. `Date` → throws TypeError (not a quiet false).
|
|
27
|
+
*/
|
|
9
28
|
export function isValid(value: unknown): boolean
|
|
10
29
|
|
|
11
|
-
/** Validate / normalize to `
|
|
30
|
+
/** Validate / normalize to ISO 8601 day string (Temporal `toString` form). */
|
|
12
31
|
export function parse(date: DayInput): string
|
|
13
32
|
|
|
14
|
-
/** Format as `YYYY-MM-DD`
|
|
33
|
+
/** Format as ISO day (only `yyyy-MM-dd` / `YYYY-MM-DD` patterns supported). */
|
|
15
34
|
export function format(date: DayInput, pattern?: 'yyyy-MM-dd' | 'YYYY-MM-DD'): string
|
|
16
35
|
|
|
17
36
|
export function addDays(date: DayInput, amount: number): string
|
|
@@ -22,16 +41,91 @@ export function addMonths(date: DayInput, amount: number): string
|
|
|
22
41
|
export function subMonths(date: DayInput, amount: number): string
|
|
23
42
|
export function addYears(date: DayInput, amount: number): string
|
|
24
43
|
export function subYears(date: DayInput, amount: number): string
|
|
44
|
+
export function addQuarters(date: DayInput, amount: number): string
|
|
45
|
+
export function subQuarters(date: DayInput, amount: number): string
|
|
46
|
+
|
|
47
|
+
/** Full year number. */
|
|
48
|
+
export function getYear(date: DayInput): number
|
|
49
|
+
/** Month index like Date/date-fns: 0 = January … 11 = December. */
|
|
50
|
+
export function getMonth(date: DayInput): number
|
|
51
|
+
/** Day of month 1…31. */
|
|
52
|
+
export function getDate(date: DayInput): number
|
|
53
|
+
/** Weekday like Date/date-fns: 0 = Sunday … 6 = Saturday. */
|
|
54
|
+
export function getDay(date: DayInput): number
|
|
55
|
+
export function getDayOfYear(date: DayInput): number
|
|
56
|
+
export function getDaysInMonth(date: DayInput): number
|
|
57
|
+
/** Quarter 1…4. */
|
|
58
|
+
export function getQuarter(date: DayInput): number
|
|
59
|
+
export function isLeapYear(date: DayInput): boolean
|
|
60
|
+
|
|
61
|
+
export function setYear(date: DayInput, year: number): string
|
|
62
|
+
/** `month`: 0 = January … 11 = December (date-fns). */
|
|
63
|
+
export function setMonth(date: DayInput, month: number): string
|
|
64
|
+
export function setDate(date: DayInput, dayOfMonth: number): string
|
|
65
|
+
|
|
66
|
+
export function startOfMonth(date: DayInput): string
|
|
67
|
+
export function endOfMonth(date: DayInput): string
|
|
68
|
+
export function startOfYear(date: DayInput): string
|
|
69
|
+
export function endOfYear(date: DayInput): string
|
|
70
|
+
export function startOfQuarter(date: DayInput): string
|
|
71
|
+
export function endOfQuarter(date: DayInput): string
|
|
72
|
+
export function startOfWeek(date: DayInput, options?: WeekOptions): string
|
|
73
|
+
export function endOfWeek(date: DayInput, options?: WeekOptions): string
|
|
25
74
|
|
|
26
75
|
/** Full days: `dateLeft − dateRight` (date-fns argument order). */
|
|
27
76
|
export function differenceInDays(dateLeft: DayInput, dateRight: DayInput): number
|
|
77
|
+
export function differenceInWeeks(dateLeft: DayInput, dateRight: DayInput): number
|
|
78
|
+
export function differenceInMonths(dateLeft: DayInput, dateRight: DayInput): number
|
|
79
|
+
export function differenceInCalendarMonths(dateLeft: DayInput, dateRight: DayInput): number
|
|
80
|
+
export function differenceInYears(dateLeft: DayInput, dateRight: DayInput): number
|
|
81
|
+
export function differenceInCalendarYears(dateLeft: DayInput, dateRight: DayInput): number
|
|
82
|
+
export function differenceInQuarters(dateLeft: DayInput, dateRight: DayInput): number
|
|
83
|
+
export function differenceInCalendarQuarters(dateLeft: DayInput, dateRight: DayInput): number
|
|
28
84
|
|
|
29
85
|
export function isBefore(date: DayInput, dateToCompare: DayInput): boolean
|
|
30
86
|
export function isAfter(date: DayInput, dateToCompare: DayInput): boolean
|
|
87
|
+
/** Same calendar day. */
|
|
31
88
|
export function isEqual(dateLeft: DayInput, dateRight: DayInput): boolean
|
|
89
|
+
/** Alias of `isEqual` (date-fns name). */
|
|
90
|
+
export const isSameDay: typeof isEqual
|
|
91
|
+
|
|
92
|
+
export function isSameWeek(
|
|
93
|
+
dateLeft: DayInput,
|
|
94
|
+
dateRight: DayInput,
|
|
95
|
+
options?: WeekOptions,
|
|
96
|
+
): boolean
|
|
97
|
+
export function isSameMonth(dateLeft: DayInput, dateRight: DayInput): boolean
|
|
98
|
+
export function isSameYear(dateLeft: DayInput, dateRight: DayInput): boolean
|
|
99
|
+
export function isSameQuarter(dateLeft: DayInput, dateRight: DayInput): boolean
|
|
32
100
|
|
|
33
101
|
export function compareAsc(dateLeft: DayInput, dateRight: DayInput): -1 | 0 | 1
|
|
34
102
|
export function compareDesc(dateLeft: DayInput, dateRight: DayInput): -1 | 0 | 1
|
|
35
103
|
|
|
36
104
|
export function min(dates: DayInput[]): string
|
|
37
105
|
export function max(dates: DayInput[]): string
|
|
106
|
+
|
|
107
|
+
export function isSunday(date: DayInput): boolean
|
|
108
|
+
export function isMonday(date: DayInput): boolean
|
|
109
|
+
export function isTuesday(date: DayInput): boolean
|
|
110
|
+
export function isWednesday(date: DayInput): boolean
|
|
111
|
+
export function isThursday(date: DayInput): boolean
|
|
112
|
+
export function isFriday(date: DayInput): boolean
|
|
113
|
+
export function isSaturday(date: DayInput): boolean
|
|
114
|
+
export function isWeekend(date: DayInput): boolean
|
|
115
|
+
export function isFirstDayOfMonth(date: DayInput): boolean
|
|
116
|
+
export function isLastDayOfMonth(date: DayInput): boolean
|
|
117
|
+
|
|
118
|
+
export function eachDayOfInterval(interval: Interval): string[]
|
|
119
|
+
export function eachMonthOfInterval(interval: Interval): string[]
|
|
120
|
+
export function eachYearOfInterval(interval: Interval): string[]
|
|
121
|
+
export function isWithinInterval(date: DayInput, interval: Interval): boolean
|
|
122
|
+
export function clamp(date: DayInput, interval: Interval): string
|
|
123
|
+
/**
|
|
124
|
+
* date-fns default: `inclusive: false` (touching endpoints only is not overlap).
|
|
125
|
+
* Pass `{ inclusive: true }` for closed intervals.
|
|
126
|
+
*/
|
|
127
|
+
export function areIntervalsOverlapping(
|
|
128
|
+
intervalLeft: Interval,
|
|
129
|
+
intervalRight: Interval,
|
|
130
|
+
options?: { inclusive?: boolean },
|
|
131
|
+
): boolean
|
package/index.js
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
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 */
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} Interval
|
|
17
|
+
* @property {DayInput} start
|
|
18
|
+
* @property {DayInput} end
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} WeekOptions
|
|
22
|
+
* @property {0|1|2|3|4|5|6} [weekStartsOn] 0=Sun … 6=Sat (date-fns default 0)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// ─── core conversion ───────────────────────────────────────────────
|
|
7
26
|
|
|
8
27
|
/**
|
|
9
|
-
* Reject Date and non-calendar values. Accept
|
|
28
|
+
* Reject Date and non-calendar values. Accept ISO day string or PlainDate.
|
|
10
29
|
* @param {unknown} value
|
|
11
30
|
* @param {string} label
|
|
12
31
|
* @returns {Temporal.PlainDate}
|
|
@@ -14,14 +33,13 @@ const Temporal = globalThis.Temporal ?? TemporalPolyfill
|
|
|
14
33
|
function toPlainDate(value, label = 'date') {
|
|
15
34
|
if (value instanceof Date) {
|
|
16
35
|
throw new TypeError(
|
|
17
|
-
`daymath: Date is not allowed for ${label} (pass
|
|
36
|
+
`daymath: Date is not allowed for ${label} (pass ISO 8601 day string)`,
|
|
18
37
|
)
|
|
19
38
|
}
|
|
20
39
|
if (typeof value === 'string') {
|
|
21
|
-
|
|
22
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
40
|
+
if (!ISO_DAY.test(value)) {
|
|
23
41
|
throw new RangeError(
|
|
24
|
-
`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)})`,
|
|
25
43
|
)
|
|
26
44
|
}
|
|
27
45
|
try {
|
|
@@ -36,7 +54,7 @@ function toPlainDate(value, label = 'date') {
|
|
|
36
54
|
return value
|
|
37
55
|
}
|
|
38
56
|
throw new TypeError(
|
|
39
|
-
`daymath: ${label} must be
|
|
57
|
+
`daymath: ${label} must be ISO 8601 day string or Temporal.PlainDate`,
|
|
40
58
|
)
|
|
41
59
|
}
|
|
42
60
|
|
|
@@ -45,8 +63,66 @@ function toDayString(plain) {
|
|
|
45
63
|
return plain.toString()
|
|
46
64
|
}
|
|
47
65
|
|
|
48
|
-
/**
|
|
66
|
+
/** Temporal ISO weekday 1=Mon…7=Sun → JS/date-fns 0=Sun…6=Sat */
|
|
67
|
+
function isoToJsWeekday(isoDayOfWeek) {
|
|
68
|
+
return isoDayOfWeek === 7 ? 0 : isoDayOfWeek
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @param {unknown} n @param {string} label */
|
|
72
|
+
function assertFiniteNumber(n, label) {
|
|
73
|
+
if (typeof n !== 'number' || !Number.isFinite(n)) {
|
|
74
|
+
throw new TypeError(`daymath: ${label} must be a finite number`)
|
|
75
|
+
}
|
|
76
|
+
if (!Number.isInteger(n)) {
|
|
77
|
+
throw new RangeError(`daymath: ${label} must be an integer`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @param {unknown} dates */
|
|
82
|
+
function assertNonEmptyDates(dates) {
|
|
83
|
+
if (!Array.isArray(dates) || dates.length === 0) {
|
|
84
|
+
throw new RangeError('daymath: expected a non-empty array of dates')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {WeekOptions} [options]
|
|
90
|
+
* @returns {0|1|2|3|4|5|6}
|
|
91
|
+
*/
|
|
92
|
+
function weekStartsOnFrom(options) {
|
|
93
|
+
const w = options?.weekStartsOn ?? 0
|
|
94
|
+
if (!Number.isInteger(w) || w < 0 || w > 6) {
|
|
95
|
+
throw new RangeError('daymath: weekStartsOn must be an integer 0…6 (0=Sun)')
|
|
96
|
+
}
|
|
97
|
+
return /** @type {0|1|2|3|4|5|6} */ (w)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {unknown} interval
|
|
102
|
+
* @returns {{ start: Temporal.PlainDate, end: Temporal.PlainDate }}
|
|
103
|
+
*/
|
|
104
|
+
function toInterval(interval) {
|
|
105
|
+
if (interval == null || typeof interval !== 'object') {
|
|
106
|
+
throw new TypeError('daymath: interval must be { start, end }')
|
|
107
|
+
}
|
|
108
|
+
const start = toPlainDate(/** @type {Interval} */ (interval).start, 'start')
|
|
109
|
+
const end = toPlainDate(/** @type {Interval} */ (interval).end, 'end')
|
|
110
|
+
return { start, end }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── parse / format / valid ────────────────────────────────────────
|
|
114
|
+
|
|
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
|
+
*/
|
|
49
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
|
+
}
|
|
50
126
|
try {
|
|
51
127
|
toPlainDate(value)
|
|
52
128
|
return true
|
|
@@ -56,7 +132,7 @@ export function isValid(value) {
|
|
|
56
132
|
}
|
|
57
133
|
|
|
58
134
|
/**
|
|
59
|
-
* Validate / normalize
|
|
135
|
+
* Validate / normalize an ISO 8601 calendar day string.
|
|
60
136
|
* @param {DayInput} date
|
|
61
137
|
* @returns {string}
|
|
62
138
|
*/
|
|
@@ -65,7 +141,7 @@ export function parse(date) {
|
|
|
65
141
|
}
|
|
66
142
|
|
|
67
143
|
/**
|
|
68
|
-
* Format as YYYY-MM-DD (only supported pattern
|
|
144
|
+
* Format as YYYY-MM-DD (only supported pattern).
|
|
69
145
|
* @param {DayInput} date
|
|
70
146
|
* @param {string} [pattern='yyyy-MM-dd']
|
|
71
147
|
* @returns {string}
|
|
@@ -79,6 +155,8 @@ export function format(date, pattern = 'yyyy-MM-dd') {
|
|
|
79
155
|
return toDayString(toPlainDate(date))
|
|
80
156
|
}
|
|
81
157
|
|
|
158
|
+
// ─── add / sub ─────────────────────────────────────────────────────
|
|
159
|
+
|
|
82
160
|
/**
|
|
83
161
|
* @param {DayInput} date
|
|
84
162
|
* @param {number} amount
|
|
@@ -120,7 +198,7 @@ export function subWeeks(date, amount) {
|
|
|
120
198
|
}
|
|
121
199
|
|
|
122
200
|
/**
|
|
123
|
-
* Calendar months (
|
|
201
|
+
* Calendar months (overflow constrain — Jan 31 + 1 month → Feb 28/29).
|
|
124
202
|
* @param {DayInput} date
|
|
125
203
|
* @param {number} amount
|
|
126
204
|
* @returns {string}
|
|
@@ -160,6 +238,174 @@ export function subYears(date, amount) {
|
|
|
160
238
|
return addYears(date, -amount)
|
|
161
239
|
}
|
|
162
240
|
|
|
241
|
+
/**
|
|
242
|
+
* @param {DayInput} date
|
|
243
|
+
* @param {number} amount
|
|
244
|
+
* @returns {string}
|
|
245
|
+
*/
|
|
246
|
+
export function addQuarters(date, amount) {
|
|
247
|
+
assertFiniteNumber(amount, 'amount')
|
|
248
|
+
return addMonths(date, amount * 3)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {DayInput} date
|
|
253
|
+
* @param {number} amount
|
|
254
|
+
* @returns {string}
|
|
255
|
+
*/
|
|
256
|
+
export function subQuarters(date, amount) {
|
|
257
|
+
assertFiniteNumber(amount, 'amount')
|
|
258
|
+
return addQuarters(date, -amount)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ─── getters / setters (date-fns / Date month & weekday indexing) ─
|
|
262
|
+
|
|
263
|
+
/** @param {DayInput} date @returns {number} */
|
|
264
|
+
export function getYear(date) {
|
|
265
|
+
return toPlainDate(date).year
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Month index like Date/date-fns: 0 = January … 11 = December.
|
|
270
|
+
* @param {DayInput} date
|
|
271
|
+
* @returns {number}
|
|
272
|
+
*/
|
|
273
|
+
export function getMonth(date) {
|
|
274
|
+
return toPlainDate(date).month - 1
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Day of month 1…31. @param {DayInput} date @returns {number} */
|
|
278
|
+
export function getDate(date) {
|
|
279
|
+
return toPlainDate(date).day
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Weekday like Date/date-fns: 0 = Sunday … 6 = Saturday.
|
|
284
|
+
* @param {DayInput} date
|
|
285
|
+
* @returns {number}
|
|
286
|
+
*/
|
|
287
|
+
export function getDay(date) {
|
|
288
|
+
return isoToJsWeekday(toPlainDate(date).dayOfWeek)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** @param {DayInput} date @returns {number} */
|
|
292
|
+
export function getDayOfYear(date) {
|
|
293
|
+
return toPlainDate(date).dayOfYear
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** @param {DayInput} date @returns {number} */
|
|
297
|
+
export function getDaysInMonth(date) {
|
|
298
|
+
return toPlainDate(date).daysInMonth
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Quarter 1…4. @param {DayInput} date @returns {number} */
|
|
302
|
+
export function getQuarter(date) {
|
|
303
|
+
return Math.ceil(toPlainDate(date).month / 3)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
307
|
+
export function isLeapYear(date) {
|
|
308
|
+
return toPlainDate(date).inLeapYear
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {DayInput} date
|
|
313
|
+
* @param {number} year
|
|
314
|
+
* @returns {string}
|
|
315
|
+
*/
|
|
316
|
+
export function setYear(date, year) {
|
|
317
|
+
assertFiniteNumber(year, 'year')
|
|
318
|
+
return toDayString(toPlainDate(date).with({ year }))
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @param {DayInput} date
|
|
323
|
+
* @param {number} month 0 = January … 11 = December (date-fns)
|
|
324
|
+
* @returns {string}
|
|
325
|
+
*/
|
|
326
|
+
export function setMonth(date, month) {
|
|
327
|
+
assertFiniteNumber(month, 'month')
|
|
328
|
+
if (month < 0 || month > 11) {
|
|
329
|
+
throw new RangeError('daymath: month must be 0…11 (0=January)')
|
|
330
|
+
}
|
|
331
|
+
return toDayString(toPlainDate(date).with({ month: month + 1 }))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @param {DayInput} date
|
|
336
|
+
* @param {number} dayOfMonth
|
|
337
|
+
* @returns {string}
|
|
338
|
+
*/
|
|
339
|
+
export function setDate(date, dayOfMonth) {
|
|
340
|
+
assertFiniteNumber(dayOfMonth, 'day')
|
|
341
|
+
return toDayString(toPlainDate(date).with({ day: dayOfMonth }))
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ─── start / end of unit ───────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
/** @param {DayInput} date @returns {string} */
|
|
347
|
+
export function startOfMonth(date) {
|
|
348
|
+
const d = toPlainDate(date)
|
|
349
|
+
return toDayString(d.with({ day: 1 }))
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** @param {DayInput} date @returns {string} */
|
|
353
|
+
export function endOfMonth(date) {
|
|
354
|
+
const d = toPlainDate(date)
|
|
355
|
+
return toDayString(d.with({ day: d.daysInMonth }))
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** @param {DayInput} date @returns {string} */
|
|
359
|
+
export function startOfYear(date) {
|
|
360
|
+
const d = toPlainDate(date)
|
|
361
|
+
return toDayString(d.with({ month: 1, day: 1 }))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** @param {DayInput} date @returns {string} */
|
|
365
|
+
export function endOfYear(date) {
|
|
366
|
+
const d = toPlainDate(date)
|
|
367
|
+
return toDayString(d.with({ month: 12, day: 31 }))
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** @param {DayInput} date @returns {string} */
|
|
371
|
+
export function startOfQuarter(date) {
|
|
372
|
+
const d = toPlainDate(date)
|
|
373
|
+
const month = (getQuarter(d) - 1) * 3 + 1
|
|
374
|
+
return toDayString(d.with({ month, day: 1 }))
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** @param {DayInput} date @returns {string} */
|
|
378
|
+
export function endOfQuarter(date) {
|
|
379
|
+
const d = toPlainDate(date)
|
|
380
|
+
const month = getQuarter(d) * 3
|
|
381
|
+
const mid = d.with({ month, day: 1 })
|
|
382
|
+
return toDayString(mid.with({ day: mid.daysInMonth }))
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @param {DayInput} date
|
|
387
|
+
* @param {WeekOptions} [options]
|
|
388
|
+
* @returns {string}
|
|
389
|
+
*/
|
|
390
|
+
export function startOfWeek(date, options) {
|
|
391
|
+
const d = toPlainDate(date)
|
|
392
|
+
const weekStartsOn = weekStartsOnFrom(options)
|
|
393
|
+
const day = isoToJsWeekday(d.dayOfWeek)
|
|
394
|
+
const diff = (day - weekStartsOn + 7) % 7
|
|
395
|
+
return toDayString(d.subtract({ days: diff }))
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @param {DayInput} date
|
|
400
|
+
* @param {WeekOptions} [options]
|
|
401
|
+
* @returns {string}
|
|
402
|
+
*/
|
|
403
|
+
export function endOfWeek(date, options) {
|
|
404
|
+
return addDays(startOfWeek(date, options), 6)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── differences ───────────────────────────────────────────────────
|
|
408
|
+
|
|
163
409
|
/**
|
|
164
410
|
* Full calendar days: dateLeft − dateRight (date-fns order).
|
|
165
411
|
* @param {DayInput} dateLeft
|
|
@@ -172,6 +418,89 @@ export function differenceInDays(dateLeft, dateRight) {
|
|
|
172
418
|
return left.since(right, { largestUnit: 'day' }).days
|
|
173
419
|
}
|
|
174
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Full weeks (trunc toward 0), like date-fns.
|
|
423
|
+
* @param {DayInput} dateLeft
|
|
424
|
+
* @param {DayInput} dateRight
|
|
425
|
+
* @returns {number}
|
|
426
|
+
*/
|
|
427
|
+
export function differenceInWeeks(dateLeft, dateRight) {
|
|
428
|
+
return Math.trunc(differenceInDays(dateLeft, dateRight) / 7)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Full months (signed), Temporal since with largestUnit month.
|
|
433
|
+
* @param {DayInput} dateLeft
|
|
434
|
+
* @param {DayInput} dateRight
|
|
435
|
+
* @returns {number}
|
|
436
|
+
*/
|
|
437
|
+
export function differenceInMonths(dateLeft, dateRight) {
|
|
438
|
+
const left = toPlainDate(dateLeft, 'dateLeft')
|
|
439
|
+
const right = toPlainDate(dateRight, 'dateRight')
|
|
440
|
+
const dur = left.since(right, { largestUnit: 'month' })
|
|
441
|
+
return dur.months
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Calendar month index diff: (yL−yR)*12 + (mL−mR). Ignores day-of-month.
|
|
446
|
+
* @param {DayInput} dateLeft
|
|
447
|
+
* @param {DayInput} dateRight
|
|
448
|
+
* @returns {number}
|
|
449
|
+
*/
|
|
450
|
+
export function differenceInCalendarMonths(dateLeft, dateRight) {
|
|
451
|
+
const left = toPlainDate(dateLeft, 'dateLeft')
|
|
452
|
+
const right = toPlainDate(dateRight, 'dateRight')
|
|
453
|
+
return (left.year - right.year) * 12 + (left.month - right.month)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Full years (signed).
|
|
458
|
+
* @param {DayInput} dateLeft
|
|
459
|
+
* @param {DayInput} dateRight
|
|
460
|
+
* @returns {number}
|
|
461
|
+
*/
|
|
462
|
+
export function differenceInYears(dateLeft, dateRight) {
|
|
463
|
+
const left = toPlainDate(dateLeft, 'dateLeft')
|
|
464
|
+
const right = toPlainDate(dateRight, 'dateRight')
|
|
465
|
+
return left.since(right, { largestUnit: 'year' }).years
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Calendar year number diff. Ignores month/day.
|
|
470
|
+
* @param {DayInput} dateLeft
|
|
471
|
+
* @param {DayInput} dateRight
|
|
472
|
+
* @returns {number}
|
|
473
|
+
*/
|
|
474
|
+
export function differenceInCalendarYears(dateLeft, dateRight) {
|
|
475
|
+
return getYear(dateLeft) - getYear(dateRight)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Full quarters (trunc toward 0 of calendar-month/3 style via months).
|
|
480
|
+
* @param {DayInput} dateLeft
|
|
481
|
+
* @param {DayInput} dateRight
|
|
482
|
+
* @returns {number}
|
|
483
|
+
*/
|
|
484
|
+
export function differenceInQuarters(dateLeft, dateRight) {
|
|
485
|
+
return Math.trunc(differenceInMonths(dateLeft, dateRight) / 3)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Calendar quarter index diff.
|
|
490
|
+
* @param {DayInput} dateLeft
|
|
491
|
+
* @param {DayInput} dateRight
|
|
492
|
+
* @returns {number}
|
|
493
|
+
*/
|
|
494
|
+
export function differenceInCalendarQuarters(dateLeft, dateRight) {
|
|
495
|
+
const left = toPlainDate(dateLeft, 'dateLeft')
|
|
496
|
+
const right = toPlainDate(dateRight, 'dateRight')
|
|
497
|
+
return (
|
|
498
|
+
(left.year - right.year) * 4 + (getQuarter(left) - getQuarter(right))
|
|
499
|
+
)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ─── compare / equal ───────────────────────────────────────────────
|
|
503
|
+
|
|
175
504
|
/**
|
|
176
505
|
* @param {DayInput} date
|
|
177
506
|
* @param {DayInput} dateToCompare
|
|
@@ -201,6 +530,7 @@ export function isAfter(date, dateToCompare) {
|
|
|
201
530
|
}
|
|
202
531
|
|
|
203
532
|
/**
|
|
533
|
+
* Same calendar day.
|
|
204
534
|
* @param {DayInput} dateLeft
|
|
205
535
|
* @param {DayInput} dateRight
|
|
206
536
|
* @returns {boolean}
|
|
@@ -214,6 +544,50 @@ export function isEqual(dateLeft, dateRight) {
|
|
|
214
544
|
)
|
|
215
545
|
}
|
|
216
546
|
|
|
547
|
+
/** Alias of `isEqual` (date-fns name for same calendar day). */
|
|
548
|
+
export const isSameDay = isEqual
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* @param {DayInput} dateLeft
|
|
552
|
+
* @param {DayInput} dateRight
|
|
553
|
+
* @param {WeekOptions} [options]
|
|
554
|
+
* @returns {boolean}
|
|
555
|
+
*/
|
|
556
|
+
export function isSameWeek(dateLeft, dateRight, options) {
|
|
557
|
+
return isEqual(startOfWeek(dateLeft, options), startOfWeek(dateRight, options))
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* @param {DayInput} dateLeft
|
|
562
|
+
* @param {DayInput} dateRight
|
|
563
|
+
* @returns {boolean}
|
|
564
|
+
*/
|
|
565
|
+
export function isSameMonth(dateLeft, dateRight) {
|
|
566
|
+
const a = toPlainDate(dateLeft, 'dateLeft')
|
|
567
|
+
const b = toPlainDate(dateRight, 'dateRight')
|
|
568
|
+
return a.year === b.year && a.month === b.month
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* @param {DayInput} dateLeft
|
|
573
|
+
* @param {DayInput} dateRight
|
|
574
|
+
* @returns {boolean}
|
|
575
|
+
*/
|
|
576
|
+
export function isSameYear(dateLeft, dateRight) {
|
|
577
|
+
return getYear(dateLeft) === getYear(dateRight)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* @param {DayInput} dateLeft
|
|
582
|
+
* @param {DayInput} dateRight
|
|
583
|
+
* @returns {boolean}
|
|
584
|
+
*/
|
|
585
|
+
export function isSameQuarter(dateLeft, dateRight) {
|
|
586
|
+
const a = toPlainDate(dateLeft, 'dateLeft')
|
|
587
|
+
const b = toPlainDate(dateRight, 'dateRight')
|
|
588
|
+
return a.year === b.year && getQuarter(a) === getQuarter(b)
|
|
589
|
+
}
|
|
590
|
+
|
|
217
591
|
/**
|
|
218
592
|
* @param {DayInput} dateLeft
|
|
219
593
|
* @param {DayInput} dateRight
|
|
@@ -244,7 +618,9 @@ export function compareDesc(dateLeft, dateRight) {
|
|
|
244
618
|
export function min(dates) {
|
|
245
619
|
assertNonEmptyDates(dates)
|
|
246
620
|
return toDayString(
|
|
247
|
-
dates
|
|
621
|
+
dates
|
|
622
|
+
.map((d) => toPlainDate(d))
|
|
623
|
+
.reduce((a, b) => (Temporal.PlainDate.compare(a, b) <= 0 ? a : b)),
|
|
248
624
|
)
|
|
249
625
|
}
|
|
250
626
|
|
|
@@ -255,23 +631,183 @@ export function min(dates) {
|
|
|
255
631
|
export function max(dates) {
|
|
256
632
|
assertNonEmptyDates(dates)
|
|
257
633
|
return toDayString(
|
|
258
|
-
dates
|
|
634
|
+
dates
|
|
635
|
+
.map((d) => toPlainDate(d))
|
|
636
|
+
.reduce((a, b) => (Temporal.PlainDate.compare(a, b) >= 0 ? a : b)),
|
|
259
637
|
)
|
|
260
638
|
}
|
|
261
639
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
640
|
+
// ─── weekday predicates ────────────────────────────────────────────
|
|
641
|
+
|
|
642
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
643
|
+
export function isSunday(date) {
|
|
644
|
+
return getDay(date) === 0
|
|
645
|
+
}
|
|
646
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
647
|
+
export function isMonday(date) {
|
|
648
|
+
return getDay(date) === 1
|
|
649
|
+
}
|
|
650
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
651
|
+
export function isTuesday(date) {
|
|
652
|
+
return getDay(date) === 2
|
|
653
|
+
}
|
|
654
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
655
|
+
export function isWednesday(date) {
|
|
656
|
+
return getDay(date) === 3
|
|
657
|
+
}
|
|
658
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
659
|
+
export function isThursday(date) {
|
|
660
|
+
return getDay(date) === 4
|
|
661
|
+
}
|
|
662
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
663
|
+
export function isFriday(date) {
|
|
664
|
+
return getDay(date) === 5
|
|
665
|
+
}
|
|
666
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
667
|
+
export function isSaturday(date) {
|
|
668
|
+
return getDay(date) === 6
|
|
669
|
+
}
|
|
670
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
671
|
+
export function isWeekend(date) {
|
|
672
|
+
const d = getDay(date)
|
|
673
|
+
return d === 0 || d === 6
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
677
|
+
export function isFirstDayOfMonth(date) {
|
|
678
|
+
return getDate(date) === 1
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** @param {DayInput} date @returns {boolean} */
|
|
682
|
+
export function isLastDayOfMonth(date) {
|
|
683
|
+
const d = toPlainDate(date)
|
|
684
|
+
return d.day === d.daysInMonth
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// ─── intervals ─────────────────────────────────────────────────────
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Inclusive day range. Throws if start > end.
|
|
691
|
+
* @param {Interval} interval
|
|
692
|
+
* @returns {string[]}
|
|
693
|
+
*/
|
|
694
|
+
export function eachDayOfInterval(interval) {
|
|
695
|
+
const { start, end } = toInterval(interval)
|
|
696
|
+
if (Temporal.PlainDate.compare(start, end) > 0) {
|
|
697
|
+
throw new RangeError('daymath: interval start must not be after end')
|
|
266
698
|
}
|
|
267
|
-
|
|
268
|
-
|
|
699
|
+
/** @type {string[]} */
|
|
700
|
+
const out = []
|
|
701
|
+
let cur = start
|
|
702
|
+
while (Temporal.PlainDate.compare(cur, end) <= 0) {
|
|
703
|
+
out.push(toDayString(cur))
|
|
704
|
+
cur = cur.add({ days: 1 })
|
|
269
705
|
}
|
|
706
|
+
return out
|
|
270
707
|
}
|
|
271
708
|
|
|
272
|
-
/**
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
709
|
+
/**
|
|
710
|
+
* First day of each month from start’s month through end’s month (date-fns shape).
|
|
711
|
+
* @param {Interval} interval
|
|
712
|
+
* @returns {string[]}
|
|
713
|
+
*/
|
|
714
|
+
export function eachMonthOfInterval(interval) {
|
|
715
|
+
const { start, end } = toInterval(interval)
|
|
716
|
+
if (Temporal.PlainDate.compare(start, end) > 0) {
|
|
717
|
+
throw new RangeError('daymath: interval start must not be after end')
|
|
718
|
+
}
|
|
719
|
+
/** @type {string[]} */
|
|
720
|
+
const out = []
|
|
721
|
+
let cur = start.with({ day: 1 })
|
|
722
|
+
const last = end.with({ day: 1 })
|
|
723
|
+
while (Temporal.PlainDate.compare(cur, last) <= 0) {
|
|
724
|
+
out.push(toDayString(cur))
|
|
725
|
+
cur = cur.add({ months: 1 })
|
|
726
|
+
}
|
|
727
|
+
return out
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Jan 1 of each year from start’s year through end’s year.
|
|
732
|
+
* @param {Interval} interval
|
|
733
|
+
* @returns {string[]}
|
|
734
|
+
*/
|
|
735
|
+
export function eachYearOfInterval(interval) {
|
|
736
|
+
const { start, end } = toInterval(interval)
|
|
737
|
+
if (Temporal.PlainDate.compare(start, end) > 0) {
|
|
738
|
+
throw new RangeError('daymath: interval start must not be after end')
|
|
739
|
+
}
|
|
740
|
+
/** @type {string[]} */
|
|
741
|
+
const out = []
|
|
742
|
+
let y = start.year
|
|
743
|
+
while (y <= end.year) {
|
|
744
|
+
out.push(toDayString(Temporal.PlainDate.from({ year: y, month: 1, day: 1 })))
|
|
745
|
+
y += 1
|
|
746
|
+
}
|
|
747
|
+
return out
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Inclusive: start ≤ date ≤ end.
|
|
752
|
+
* @param {DayInput} date
|
|
753
|
+
* @param {Interval} interval
|
|
754
|
+
* @returns {boolean}
|
|
755
|
+
*/
|
|
756
|
+
export function isWithinInterval(date, interval) {
|
|
757
|
+
const d = toPlainDate(date)
|
|
758
|
+
const { start, end } = toInterval(interval)
|
|
759
|
+
if (Temporal.PlainDate.compare(start, end) > 0) {
|
|
760
|
+
throw new RangeError('daymath: interval start must not be after end')
|
|
276
761
|
}
|
|
762
|
+
return (
|
|
763
|
+
Temporal.PlainDate.compare(d, start) >= 0 &&
|
|
764
|
+
Temporal.PlainDate.compare(d, end) <= 0
|
|
765
|
+
)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Clamp date into [start, end].
|
|
770
|
+
* @param {DayInput} date
|
|
771
|
+
* @param {Interval} interval
|
|
772
|
+
* @returns {string}
|
|
773
|
+
*/
|
|
774
|
+
export function clamp(date, interval) {
|
|
775
|
+
const d = toPlainDate(date)
|
|
776
|
+
const { start, end } = toInterval(interval)
|
|
777
|
+
if (Temporal.PlainDate.compare(start, end) > 0) {
|
|
778
|
+
throw new RangeError('daymath: interval start must not be after end')
|
|
779
|
+
}
|
|
780
|
+
if (Temporal.PlainDate.compare(d, start) < 0) return toDayString(start)
|
|
781
|
+
if (Temporal.PlainDate.compare(d, end) > 0) return toDayString(end)
|
|
782
|
+
return toDayString(d)
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Whether two inclusive intervals overlap.
|
|
787
|
+
* @param {Interval} intervalLeft
|
|
788
|
+
* @param {Interval} intervalRight
|
|
789
|
+
* @param {{ inclusive?: boolean }} [options] default inclusive true (date-fns default false uses half-open; we default true for plain days)
|
|
790
|
+
* @returns {boolean}
|
|
791
|
+
*/
|
|
792
|
+
export function areIntervalsOverlapping(intervalLeft, intervalRight, options) {
|
|
793
|
+
const a = toInterval(intervalLeft)
|
|
794
|
+
const b = toInterval(intervalRight)
|
|
795
|
+
if (Temporal.PlainDate.compare(a.start, a.end) > 0) {
|
|
796
|
+
throw new RangeError('daymath: intervalLeft start must not be after end')
|
|
797
|
+
}
|
|
798
|
+
if (Temporal.PlainDate.compare(b.start, b.end) > 0) {
|
|
799
|
+
throw new RangeError('daymath: intervalRight start must not be after end')
|
|
800
|
+
}
|
|
801
|
+
const inclusive = options?.inclusive ?? false
|
|
802
|
+
if (inclusive) {
|
|
803
|
+
return (
|
|
804
|
+
Temporal.PlainDate.compare(a.start, b.end) <= 0 &&
|
|
805
|
+
Temporal.PlainDate.compare(b.start, a.end) <= 0
|
|
806
|
+
)
|
|
807
|
+
}
|
|
808
|
+
// date-fns default: touch-at-endpoint is NOT overlap
|
|
809
|
+
return (
|
|
810
|
+
Temporal.PlainDate.compare(a.start, b.end) < 0 &&
|
|
811
|
+
Temporal.PlainDate.compare(b.start, a.end) < 0
|
|
812
|
+
)
|
|
277
813
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daymath",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Calendar date math (
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|