@intech-software/chronera 0.1.6 → 0.1.7

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.
@@ -0,0 +1,253 @@
1
+ import { ChroneraError } from "../errors/errors.js";
2
+ import { dateRange } from "../core/range.js";
3
+ import { addDays, addMonths, getAbsoluteDay, isAfter, isEqual, } from "./convenience.js";
4
+ // ---------------------------------------------------------------------------
5
+ // Core Predicates
6
+ // ---------------------------------------------------------------------------
7
+ /**
8
+ * Returns true if the given date falls within the range, respecting inclusivity flags.
9
+ */
10
+ export function rangeContains(range, date) {
11
+ const startAbs = getAbsoluteDay(range.start);
12
+ const endAbs = getAbsoluteDay(range.end);
13
+ const dateAbs = getAbsoluteDay(date);
14
+ const afterStart = range.startInclusive
15
+ ? dateAbs >= startAbs
16
+ : dateAbs > startAbs;
17
+ const beforeEnd = range.endInclusive ? dateAbs <= endAbs : dateAbs < endAbs;
18
+ return afterStart && beforeEnd;
19
+ }
20
+ /**
21
+ * Returns true if two ranges have any overlapping dates (respecting inclusivity).
22
+ */
23
+ export function rangeOverlaps(a, b) {
24
+ const aStartAbs = getAbsoluteDay(a.start);
25
+ const aEndAbs = getAbsoluteDay(a.end);
26
+ const bStartAbs = getAbsoluteDay(b.start);
27
+ const bEndAbs = getAbsoluteDay(b.end);
28
+ // a starts before b ends AND b starts before a ends
29
+ const aBeforeBEnd = a.startInclusive && b.endInclusive
30
+ ? aStartAbs <= bEndAbs
31
+ : a.startInclusive || b.endInclusive
32
+ ? aStartAbs < bEndAbs
33
+ : aStartAbs < bEndAbs;
34
+ const bBeforeAEnd = b.startInclusive && a.endInclusive
35
+ ? bStartAbs <= aEndAbs
36
+ : b.startInclusive || a.endInclusive
37
+ ? bStartAbs < aEndAbs
38
+ : bStartAbs < aEndAbs;
39
+ return aBeforeBEnd && bBeforeAEnd;
40
+ }
41
+ /**
42
+ * Returns the intersection of two ranges, or null if they do not overlap.
43
+ * The resulting range uses the most restrictive inclusivity at each boundary.
44
+ */
45
+ export function rangeIntersection(a, b) {
46
+ if (!rangeOverlaps(a, b)) {
47
+ return null;
48
+ }
49
+ const aStartAbs = getAbsoluteDay(a.start);
50
+ const bStartAbs = getAbsoluteDay(b.start);
51
+ const aEndAbs = getAbsoluteDay(a.end);
52
+ const bEndAbs = getAbsoluteDay(b.end);
53
+ let start;
54
+ let startInclusive;
55
+ if (aStartAbs > bStartAbs) {
56
+ start = a.start;
57
+ startInclusive = a.startInclusive;
58
+ }
59
+ else if (bStartAbs > aStartAbs) {
60
+ start = b.start;
61
+ startInclusive = b.startInclusive;
62
+ }
63
+ else {
64
+ // same absolute day — use the more restrictive (exclusive takes priority)
65
+ start = a.start;
66
+ startInclusive = a.startInclusive && b.startInclusive;
67
+ }
68
+ let end;
69
+ let endInclusive;
70
+ if (aEndAbs < bEndAbs) {
71
+ end = a.end;
72
+ endInclusive = a.endInclusive;
73
+ }
74
+ else if (bEndAbs < aEndAbs) {
75
+ end = b.end;
76
+ endInclusive = b.endInclusive;
77
+ }
78
+ else {
79
+ // same absolute day — use more restrictive
80
+ end = a.end;
81
+ endInclusive = a.endInclusive && b.endInclusive;
82
+ }
83
+ return dateRange(start, end, startInclusive, endInclusive);
84
+ }
85
+ /**
86
+ * Returns the union (bounding range) of two ranges.
87
+ * Throws if the ranges neither overlap nor are adjacent.
88
+ */
89
+ export function rangeUnion(a, b) {
90
+ const aStartAbs = getAbsoluteDay(a.start);
91
+ const bStartAbs = getAbsoluteDay(b.start);
92
+ const aEndAbs = getAbsoluteDay(a.end);
93
+ const bEndAbs = getAbsoluteDay(b.end);
94
+ // Check overlap or adjacency (within 1 day)
95
+ const overlapsOrAdjacent = rangeOverlaps(a, b) ||
96
+ Math.abs(aEndAbs - bStartAbs) <= 1 ||
97
+ Math.abs(bEndAbs - aStartAbs) <= 1;
98
+ if (!overlapsOrAdjacent) {
99
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", "Cannot compute union of non-overlapping, non-adjacent ranges.");
100
+ }
101
+ let start;
102
+ let startInclusive;
103
+ if (aStartAbs < bStartAbs) {
104
+ start = a.start;
105
+ startInclusive = a.startInclusive;
106
+ }
107
+ else if (bStartAbs < aStartAbs) {
108
+ start = b.start;
109
+ startInclusive = b.startInclusive;
110
+ }
111
+ else {
112
+ // same day — use the more inclusive (inclusive beats exclusive)
113
+ start = a.start;
114
+ startInclusive = a.startInclusive || b.startInclusive;
115
+ }
116
+ let end;
117
+ let endInclusive;
118
+ if (aEndAbs > bEndAbs) {
119
+ end = a.end;
120
+ endInclusive = a.endInclusive;
121
+ }
122
+ else if (bEndAbs > aEndAbs) {
123
+ end = b.end;
124
+ endInclusive = b.endInclusive;
125
+ }
126
+ else {
127
+ // same day — use more inclusive
128
+ end = a.end;
129
+ endInclusive = a.endInclusive || b.endInclusive;
130
+ }
131
+ return dateRange(start, end, startInclusive, endInclusive);
132
+ }
133
+ /**
134
+ * Returns the length of the range in days (absolute difference, respecting inclusivity).
135
+ * Exclusive boundaries reduce the count by one on the corresponding side.
136
+ *
137
+ * - `[s, e]` inclusive-inclusive → `e - s + 1`
138
+ * - `[s, e)` half-open → `e - s`
139
+ * - `(s, e]` half-open → `e - s`
140
+ * - `(s, e)` exclusive-exclusive → `e - s - 1`
141
+ */
142
+ export function rangeLengthInDays(range) {
143
+ const startAbs = getAbsoluteDay(range.start);
144
+ const endAbs = getAbsoluteDay(range.end);
145
+ const base = endAbs - startAbs;
146
+ const startAdj = range.startInclusive ? 0 : 1;
147
+ const endAdj = range.endInclusive ? 1 : 0;
148
+ return base - startAdj + endAdj;
149
+ }
150
+ // ---------------------------------------------------------------------------
151
+ // Iterators
152
+ // ---------------------------------------------------------------------------
153
+ const MAX_EACH_DAY = 3650;
154
+ /**
155
+ * Returns an array of every day from start to end (both inclusive).
156
+ * Throws `ChroneraError('CHRONERA_OUT_OF_RANGE')` if the range exceeds 3650 days.
157
+ */
158
+ export function eachDayOfInterval(start, end) {
159
+ const startAbs = getAbsoluteDay(start);
160
+ const endAbs = getAbsoluteDay(end);
161
+ const count = endAbs - startAbs + 1;
162
+ if (count > MAX_EACH_DAY) {
163
+ throw new ChroneraError("CHRONERA_OUT_OF_RANGE", `eachDayOfInterval: range of ${count} days exceeds the maximum of ${MAX_EACH_DAY} days (~10 years). Use eachWeekOfInterval or eachMonthOfInterval for larger ranges.`);
164
+ }
165
+ if (count <= 0) {
166
+ return [];
167
+ }
168
+ const result = [];
169
+ let current = start;
170
+ for (let i = 0; i < count; i++) {
171
+ result.push(current);
172
+ current = addDays(current, 1);
173
+ }
174
+ return result;
175
+ }
176
+ /**
177
+ * Returns an array of the first day of each 7-day week interval within [start, end].
178
+ */
179
+ export function eachWeekOfInterval(start, end) {
180
+ const endAbs = getAbsoluteDay(end);
181
+ const result = [];
182
+ let current = start;
183
+ while (!isAfter(current, end) && getAbsoluteDay(current) <= endAbs) {
184
+ result.push(current);
185
+ current = addDays(current, 7);
186
+ }
187
+ return result;
188
+ }
189
+ /**
190
+ * Returns the first day of each calendar month within [start, end].
191
+ */
192
+ export function eachMonthOfInterval(start, end) {
193
+ const result = [];
194
+ let current = start;
195
+ while (!isAfter(current, end)) {
196
+ result.push(current);
197
+ // Move to start of next month relative to the original start
198
+ const nextMonth = addMonths(start, result.length, "constrain");
199
+ if (isAfter(nextMonth, end))
200
+ break;
201
+ current = nextMonth;
202
+ }
203
+ return result;
204
+ }
205
+ // ---------------------------------------------------------------------------
206
+ // Splitters
207
+ // ---------------------------------------------------------------------------
208
+ /**
209
+ * Splits a [start, end] interval into individual day ranges (each 1 day long).
210
+ */
211
+ export function splitByDay(start, end) {
212
+ const days = eachDayOfInterval(start, end);
213
+ return days.map((day) => dateRange(day, day, true, true));
214
+ }
215
+ /**
216
+ * Splits a [start, end] interval into week-long sub-ranges (7 days each),
217
+ * with the first and last sub-ranges bounded by the original start/end.
218
+ */
219
+ export function splitByWeek(start, end) {
220
+ const result = [];
221
+ let weekStart = start;
222
+ while (!isAfter(weekStart, end)) {
223
+ const weekEnd = addDays(weekStart, 6);
224
+ const clampedEnd = isAfter(weekEnd, end) ? end : weekEnd;
225
+ result.push(dateRange(weekStart, clampedEnd, true, true));
226
+ weekStart = addDays(weekStart, 7);
227
+ }
228
+ return result;
229
+ }
230
+ /**
231
+ * Splits a [start, end] interval into month sub-ranges, bounded by original start/end.
232
+ */
233
+ export function splitByMonth(start, end) {
234
+ const result = [];
235
+ let monthStart = start;
236
+ let offset = 0;
237
+ while (!isAfter(monthStart, end)) {
238
+ // Calculate the end of the current month relative to `start`
239
+ // by getting the next month start and subtracting 1 day
240
+ const nextMonthStart = addMonths(start, offset + 1, "constrain");
241
+ const monthEnd = isAfter(nextMonthStart, end)
242
+ ? end
243
+ : addDays(nextMonthStart, -1);
244
+ result.push(dateRange(monthStart, monthEnd, true, true));
245
+ if (isAfter(nextMonthStart, end) || isEqual(nextMonthStart, end)) {
246
+ break;
247
+ }
248
+ monthStart = nextMonthStart;
249
+ offset++;
250
+ }
251
+ return result;
252
+ }
253
+ //# sourceMappingURL=interval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interval.js","sourceRoot":"","sources":["../../src/operations/interval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EACL,OAAO,EACP,SAAS,EACT,cAAc,EACd,OAAO,EACP,OAAO,GACR,MAAM,kBAAkB,CAAC;AAI1B,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAmB,EACnB,IAAO;IAEP,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAErC,MAAM,UAAU,GAAG,KAAK,CAAC,cAAc;QACrC,CAAC,CAAC,OAAO,IAAI,QAAQ;QACrB,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC;IACvB,MAAM,SAAS,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC;IAE5E,OAAO,UAAU,IAAI,SAAS,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,CAAe,EACf,CAAe;IAEf,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEtC,oDAAoD;IACpD,MAAM,WAAW,GACf,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,YAAY;QAChC,CAAC,CAAC,SAAS,IAAI,OAAO;QACtB,CAAC,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,YAAY;YAClC,CAAC,CAAC,SAAS,GAAG,OAAO;YACrB,CAAC,CAAC,SAAS,GAAG,OAAO,CAAC;IAE5B,MAAM,WAAW,GACf,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,YAAY;QAChC,CAAC,CAAC,SAAS,IAAI,OAAO;QACtB,CAAC,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,YAAY;YAClC,CAAC,CAAC,SAAS,GAAG,OAAO;YACrB,CAAC,CAAC,SAAS,GAAG,OAAO,CAAC;IAE5B,OAAO,WAAW,IAAI,WAAW,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,CAAe,EACf,CAAe;IAEf,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEtC,IAAI,KAAQ,CAAC;IACb,IAAI,cAAuB,CAAC;IAC5B,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1B,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAChB,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;IACpC,CAAC;SAAM,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QACjC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAChB,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,0EAA0E;QAC1E,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAChB,cAAc,GAAG,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,cAAc,CAAC;IACxD,CAAC;IAED,IAAI,GAAM,CAAC;IACX,IAAI,YAAqB,CAAC;IAC1B,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QACZ,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IAChC,CAAC;SAAM,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;QAC7B,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QACZ,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,2CAA2C;QAC3C,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QACZ,YAAY,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC;IAClD,CAAC;IAED,OAAO,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,CAAe,EACf,CAAe;IAEf,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEtC,4CAA4C;IAC5C,MAAM,kBAAkB,GACtB,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC;QACnB,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC;QAClC,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAErC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACxB,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,+DAA+D,CAChE,CAAC;IACJ,CAAC;IAED,IAAI,KAAQ,CAAC;IACb,IAAI,cAAuB,CAAC;IAC5B,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1B,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAChB,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;IACpC,CAAC;SAAM,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QACjC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAChB,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,gEAAgE;QAChE,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAChB,cAAc,GAAG,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,cAAc,CAAC;IACxD,CAAC;IAED,IAAI,GAAM,CAAC;IACX,IAAI,YAAqB,CAAC;IAC1B,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QACZ,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IAChC,CAAC;SAAM,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;QAC7B,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QACZ,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,gCAAgC;QAChC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QACZ,YAAY,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC;IAClD,CAAC;IAED,OAAO,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAmB;IAEnB,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,OAAO,IAAI,GAAG,QAAQ,GAAG,MAAM,CAAC;AAClC,CAAC;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAQ,EACR,GAAM;IAEN,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,CAAC,CAAC;IAEpC,IAAI,KAAK,GAAG,YAAY,EAAE,CAAC;QACzB,MAAM,IAAI,aAAa,CACrB,uBAAuB,EACvB,+BAA+B,KAAK,gCAAgC,YAAY,qFAAqF,CACtK,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAQ,EACR,GAAM;IAEN,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,cAAc,CAAC,OAAO,CAAC,IAAI,MAAM,EAAE,CAAC;QACnE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAQ,EACR,GAAM;IAEN,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrB,6DAA6D;QAC7D,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC/D,IAAI,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;YAAE,MAAM;QACnC,OAAO,GAAG,SAAS,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,KAAQ,EACR,GAAM;IAEN,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAAQ,EACR,GAAM;IAEN,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1D,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAQ,EACR,GAAM;IAEN,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC;QACjC,6DAA6D;QAC7D,wDAAwD;QACxD,MAAM,cAAc,GAAG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,WAAW,CAAC,CAAC;QACjE,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;YAC3C,CAAC,CAAC,GAAG;YACL,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;QAEhC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAEzD,IAAI,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,IAAI,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,EAAE,CAAC;YACjE,MAAM;QACR,CAAC;QAED,UAAU,GAAG,cAAc,CAAC;QAC5B,MAAM,EAAE,CAAC;IACX,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { ChroneraError } from \"../errors/errors.js\";\nimport { dateRange } from \"../core/range.js\";\nimport {\n addDays,\n addMonths,\n getAbsoluteDay,\n isAfter,\n isEqual,\n} from \"./convenience.js\";\n\nimport type { DateOrCalendarDate, DateRange } from \"../public-types.js\";\n\n// ---------------------------------------------------------------------------\n// Core Predicates\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if the given date falls within the range, respecting inclusivity flags.\n */\nexport function rangeContains<T extends DateOrCalendarDate>(\n range: DateRange<T>,\n date: T,\n): boolean {\n const startAbs = getAbsoluteDay(range.start);\n const endAbs = getAbsoluteDay(range.end);\n const dateAbs = getAbsoluteDay(date);\n\n const afterStart = range.startInclusive\n ? dateAbs >= startAbs\n : dateAbs > startAbs;\n const beforeEnd = range.endInclusive ? dateAbs <= endAbs : dateAbs < endAbs;\n\n return afterStart && beforeEnd;\n}\n\n/**\n * Returns true if two ranges have any overlapping dates (respecting inclusivity).\n */\nexport function rangeOverlaps<T extends DateOrCalendarDate>(\n a: DateRange<T>,\n b: DateRange<T>,\n): boolean {\n const aStartAbs = getAbsoluteDay(a.start);\n const aEndAbs = getAbsoluteDay(a.end);\n const bStartAbs = getAbsoluteDay(b.start);\n const bEndAbs = getAbsoluteDay(b.end);\n\n // a starts before b ends AND b starts before a ends\n const aBeforeBEnd =\n a.startInclusive && b.endInclusive\n ? aStartAbs <= bEndAbs\n : a.startInclusive || b.endInclusive\n ? aStartAbs < bEndAbs\n : aStartAbs < bEndAbs;\n\n const bBeforeAEnd =\n b.startInclusive && a.endInclusive\n ? bStartAbs <= aEndAbs\n : b.startInclusive || a.endInclusive\n ? bStartAbs < aEndAbs\n : bStartAbs < aEndAbs;\n\n return aBeforeBEnd && bBeforeAEnd;\n}\n\n/**\n * Returns the intersection of two ranges, or null if they do not overlap.\n * The resulting range uses the most restrictive inclusivity at each boundary.\n */\nexport function rangeIntersection<T extends DateOrCalendarDate>(\n a: DateRange<T>,\n b: DateRange<T>,\n): DateRange<T> | null {\n if (!rangeOverlaps(a, b)) {\n return null;\n }\n\n const aStartAbs = getAbsoluteDay(a.start);\n const bStartAbs = getAbsoluteDay(b.start);\n const aEndAbs = getAbsoluteDay(a.end);\n const bEndAbs = getAbsoluteDay(b.end);\n\n let start: T;\n let startInclusive: boolean;\n if (aStartAbs > bStartAbs) {\n start = a.start;\n startInclusive = a.startInclusive;\n } else if (bStartAbs > aStartAbs) {\n start = b.start;\n startInclusive = b.startInclusive;\n } else {\n // same absolute day — use the more restrictive (exclusive takes priority)\n start = a.start;\n startInclusive = a.startInclusive && b.startInclusive;\n }\n\n let end: T;\n let endInclusive: boolean;\n if (aEndAbs < bEndAbs) {\n end = a.end;\n endInclusive = a.endInclusive;\n } else if (bEndAbs < aEndAbs) {\n end = b.end;\n endInclusive = b.endInclusive;\n } else {\n // same absolute day — use more restrictive\n end = a.end;\n endInclusive = a.endInclusive && b.endInclusive;\n }\n\n return dateRange(start, end, startInclusive, endInclusive);\n}\n\n/**\n * Returns the union (bounding range) of two ranges.\n * Throws if the ranges neither overlap nor are adjacent.\n */\nexport function rangeUnion<T extends DateOrCalendarDate>(\n a: DateRange<T>,\n b: DateRange<T>,\n): DateRange<T> {\n const aStartAbs = getAbsoluteDay(a.start);\n const bStartAbs = getAbsoluteDay(b.start);\n const aEndAbs = getAbsoluteDay(a.end);\n const bEndAbs = getAbsoluteDay(b.end);\n\n // Check overlap or adjacency (within 1 day)\n const overlapsOrAdjacent =\n rangeOverlaps(a, b) ||\n Math.abs(aEndAbs - bStartAbs) <= 1 ||\n Math.abs(bEndAbs - aStartAbs) <= 1;\n\n if (!overlapsOrAdjacent) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n \"Cannot compute union of non-overlapping, non-adjacent ranges.\",\n );\n }\n\n let start: T;\n let startInclusive: boolean;\n if (aStartAbs < bStartAbs) {\n start = a.start;\n startInclusive = a.startInclusive;\n } else if (bStartAbs < aStartAbs) {\n start = b.start;\n startInclusive = b.startInclusive;\n } else {\n // same day — use the more inclusive (inclusive beats exclusive)\n start = a.start;\n startInclusive = a.startInclusive || b.startInclusive;\n }\n\n let end: T;\n let endInclusive: boolean;\n if (aEndAbs > bEndAbs) {\n end = a.end;\n endInclusive = a.endInclusive;\n } else if (bEndAbs > aEndAbs) {\n end = b.end;\n endInclusive = b.endInclusive;\n } else {\n // same day — use more inclusive\n end = a.end;\n endInclusive = a.endInclusive || b.endInclusive;\n }\n\n return dateRange(start, end, startInclusive, endInclusive);\n}\n\n/**\n * Returns the length of the range in days (absolute difference, respecting inclusivity).\n * Exclusive boundaries reduce the count by one on the corresponding side.\n *\n * - `[s, e]` inclusive-inclusive → `e - s + 1`\n * - `[s, e)` half-open → `e - s`\n * - `(s, e]` half-open → `e - s`\n * - `(s, e)` exclusive-exclusive → `e - s - 1`\n */\nexport function rangeLengthInDays<T extends DateOrCalendarDate>(\n range: DateRange<T>,\n): number {\n const startAbs = getAbsoluteDay(range.start);\n const endAbs = getAbsoluteDay(range.end);\n const base = endAbs - startAbs;\n const startAdj = range.startInclusive ? 0 : 1;\n const endAdj = range.endInclusive ? 1 : 0;\n return base - startAdj + endAdj;\n}\n\n// ---------------------------------------------------------------------------\n// Iterators\n// ---------------------------------------------------------------------------\n\nconst MAX_EACH_DAY = 3650;\n\n/**\n * Returns an array of every day from start to end (both inclusive).\n * Throws `ChroneraError('CHRONERA_OUT_OF_RANGE')` if the range exceeds 3650 days.\n */\nexport function eachDayOfInterval<T extends DateOrCalendarDate>(\n start: T,\n end: T,\n): T[] {\n const startAbs = getAbsoluteDay(start);\n const endAbs = getAbsoluteDay(end);\n const count = endAbs - startAbs + 1;\n\n if (count > MAX_EACH_DAY) {\n throw new ChroneraError(\n \"CHRONERA_OUT_OF_RANGE\",\n `eachDayOfInterval: range of ${count} days exceeds the maximum of ${MAX_EACH_DAY} days (~10 years). Use eachWeekOfInterval or eachMonthOfInterval for larger ranges.`,\n );\n }\n\n if (count <= 0) {\n return [];\n }\n\n const result: T[] = [];\n let current = start;\n for (let i = 0; i < count; i++) {\n result.push(current);\n current = addDays(current, 1);\n }\n return result;\n}\n\n/**\n * Returns an array of the first day of each 7-day week interval within [start, end].\n */\nexport function eachWeekOfInterval<T extends DateOrCalendarDate>(\n start: T,\n end: T,\n): T[] {\n const endAbs = getAbsoluteDay(end);\n const result: T[] = [];\n let current = start;\n\n while (!isAfter(current, end) && getAbsoluteDay(current) <= endAbs) {\n result.push(current);\n current = addDays(current, 7);\n }\n return result;\n}\n\n/**\n * Returns the first day of each calendar month within [start, end].\n */\nexport function eachMonthOfInterval<T extends DateOrCalendarDate>(\n start: T,\n end: T,\n): T[] {\n const result: T[] = [];\n let current = start;\n\n while (!isAfter(current, end)) {\n result.push(current);\n // Move to start of next month relative to the original start\n const nextMonth = addMonths(start, result.length, \"constrain\");\n if (isAfter(nextMonth, end)) break;\n current = nextMonth;\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Splitters\n// ---------------------------------------------------------------------------\n\n/**\n * Splits a [start, end] interval into individual day ranges (each 1 day long).\n */\nexport function splitByDay<T extends DateOrCalendarDate>(\n start: T,\n end: T,\n): DateRange<T>[] {\n const days = eachDayOfInterval(start, end);\n return days.map((day) => dateRange(day, day, true, true));\n}\n\n/**\n * Splits a [start, end] interval into week-long sub-ranges (7 days each),\n * with the first and last sub-ranges bounded by the original start/end.\n */\nexport function splitByWeek<T extends DateOrCalendarDate>(\n start: T,\n end: T,\n): DateRange<T>[] {\n const result: DateRange<T>[] = [];\n let weekStart = start;\n\n while (!isAfter(weekStart, end)) {\n const weekEnd = addDays(weekStart, 6);\n const clampedEnd = isAfter(weekEnd, end) ? end : weekEnd;\n result.push(dateRange(weekStart, clampedEnd, true, true));\n weekStart = addDays(weekStart, 7);\n }\n return result;\n}\n\n/**\n * Splits a [start, end] interval into month sub-ranges, bounded by original start/end.\n */\nexport function splitByMonth<T extends DateOrCalendarDate>(\n start: T,\n end: T,\n): DateRange<T>[] {\n const result: DateRange<T>[] = [];\n let monthStart = start;\n let offset = 0;\n\n while (!isAfter(monthStart, end)) {\n // Calculate the end of the current month relative to `start`\n // by getting the next month start and subtracting 1 day\n const nextMonthStart = addMonths(start, offset + 1, \"constrain\");\n const monthEnd = isAfter(nextMonthStart, end)\n ? end\n : addDays(nextMonthStart, -1);\n\n result.push(dateRange(monthStart, monthEnd, true, true));\n\n if (isAfter(nextMonthStart, end) || isEqual(nextMonthStart, end)) {\n break;\n }\n\n monthStart = nextMonthStart;\n offset++;\n }\n\n return result;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import type { LocalDate, TimeZoneId } from "../public-types.js";
2
+ export interface ParseNaturalDateOptions {
3
+ readonly locale?: string;
4
+ readonly referenceDate?: LocalDate;
5
+ readonly timeZone?: TimeZoneId;
6
+ }
7
+ export interface ParseNaturalDateResult {
8
+ readonly date: LocalDate;
9
+ readonly pattern: string;
10
+ }
11
+ export declare function parseNaturalDate(input: string, options?: ParseNaturalDateOptions): LocalDate;
12
+ export declare function safeParseNaturalDate(input: string, options?: ParseNaturalDateOptions): LocalDate | null;
13
+ export declare function parseNaturalDateDebug(input: string, options?: ParseNaturalDateOptions): ParseNaturalDateResult;