chronos-ts 1.1.0 → 2.0.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 +249 -443
- package/dist/core/chronos.d.ts +460 -0
- package/dist/core/chronos.js +1259 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +19 -0
- package/dist/core/interval.d.ts +289 -0
- package/dist/core/interval.js +689 -0
- package/dist/core/period-collection.d.ts +205 -0
- package/dist/core/period-collection.js +562 -0
- package/dist/core/period.d.ts +428 -0
- package/dist/core/period.js +1007 -0
- package/dist/core/timezone.d.ts +289 -0
- package/dist/core/timezone.js +671 -0
- package/dist/index.d.ts +50 -4
- package/dist/index.js +148 -22
- package/dist/locales/index.d.ts +66 -0
- package/dist/locales/index.js +847 -0
- package/dist/types/index.d.ts +428 -0
- package/dist/types/index.js +71 -0
- package/dist/utils/index.d.ts +127 -0
- package/dist/utils/index.js +656 -0
- package/package.json +19 -3
- package/dist/interval.d.ts +0 -61
- package/dist/interval.js +0 -82
- package/dist/period.d.ts +0 -196
- package/dist/period.js +0 -365
- package/dist/precision.d.ts +0 -24
- package/dist/precision.js +0 -46
- package/dist/utils.d.ts +0 -190
- package/dist/utils.js +0 -374
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChronosPeriodCollection = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* ChronosPeriodCollection - Manage collections of ChronosPeriod
|
|
6
|
+
* Inspired by spatie/period PHP library
|
|
7
|
+
* @see https://github.com/spatie/period
|
|
8
|
+
*/
|
|
9
|
+
const period_1 = require("./period");
|
|
10
|
+
const chronos_1 = require("./chronos");
|
|
11
|
+
/**
|
|
12
|
+
* ChronosPeriodCollection - A collection of periods with powerful operations
|
|
13
|
+
*
|
|
14
|
+
* Inspired by spatie/period, this class provides a rich API for working with
|
|
15
|
+
* collections of time periods including overlap detection, gap analysis,
|
|
16
|
+
* and set operations.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const collection = new ChronosPeriodCollection([
|
|
21
|
+
* ChronosPeriod.create('2024-01-01', '2024-01-15'),
|
|
22
|
+
* ChronosPeriod.create('2024-01-10', '2024-01-25'),
|
|
23
|
+
* ChronosPeriod.create('2024-02-01', '2024-02-15'),
|
|
24
|
+
* ]);
|
|
25
|
+
*
|
|
26
|
+
* // Find overlapping periods
|
|
27
|
+
* const overlapping = collection.overlapAll();
|
|
28
|
+
*
|
|
29
|
+
* // Get gaps between periods
|
|
30
|
+
* const gaps = collection.gaps();
|
|
31
|
+
*
|
|
32
|
+
* // Get boundaries
|
|
33
|
+
* const boundaries = collection.boundaries();
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class ChronosPeriodCollection {
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Constructor & Factory Methods
|
|
39
|
+
// ============================================================================
|
|
40
|
+
constructor(periods = []) {
|
|
41
|
+
this._periods = [...periods];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a new collection from periods
|
|
45
|
+
*/
|
|
46
|
+
static create(...periods) {
|
|
47
|
+
return new ChronosPeriodCollection(periods);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create an empty collection
|
|
51
|
+
*/
|
|
52
|
+
static empty() {
|
|
53
|
+
return new ChronosPeriodCollection();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a collection from an array of date pairs
|
|
57
|
+
*/
|
|
58
|
+
static fromDatePairs(pairs) {
|
|
59
|
+
const periods = pairs.map(([start, end]) => period_1.ChronosPeriod.create(start, end));
|
|
60
|
+
return new ChronosPeriodCollection(periods);
|
|
61
|
+
}
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// Basic Operations
|
|
64
|
+
// ============================================================================
|
|
65
|
+
/** Add a period to the collection */
|
|
66
|
+
add(period) {
|
|
67
|
+
this._periods.push(period);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
/** Add multiple periods */
|
|
71
|
+
addAll(periods) {
|
|
72
|
+
this._periods.push(...periods);
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
/** Get a shallow copy of periods */
|
|
76
|
+
toArray() {
|
|
77
|
+
return [...this._periods];
|
|
78
|
+
}
|
|
79
|
+
/** Get the number of periods in the collection */
|
|
80
|
+
get length() {
|
|
81
|
+
return this._periods.length;
|
|
82
|
+
}
|
|
83
|
+
/** Check if collection is empty */
|
|
84
|
+
isEmpty() {
|
|
85
|
+
return this._periods.length === 0;
|
|
86
|
+
}
|
|
87
|
+
/** Check if collection is not empty */
|
|
88
|
+
isNotEmpty() {
|
|
89
|
+
return this._periods.length > 0;
|
|
90
|
+
}
|
|
91
|
+
/** Get a period at a specific index */
|
|
92
|
+
get(index) {
|
|
93
|
+
return this._periods[index];
|
|
94
|
+
}
|
|
95
|
+
/** Get the first period */
|
|
96
|
+
first() {
|
|
97
|
+
return this._periods[0];
|
|
98
|
+
}
|
|
99
|
+
/** Get the last period */
|
|
100
|
+
last() {
|
|
101
|
+
return this._periods[this._periods.length - 1];
|
|
102
|
+
}
|
|
103
|
+
/** Clear collection */
|
|
104
|
+
clear() {
|
|
105
|
+
this._periods = [];
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// Iteration
|
|
110
|
+
// ============================================================================
|
|
111
|
+
/** Iterator implementation */
|
|
112
|
+
*[Symbol.iterator]() {
|
|
113
|
+
for (const period of this._periods) {
|
|
114
|
+
yield period;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** ForEach iteration */
|
|
118
|
+
forEach(callback) {
|
|
119
|
+
this._periods.forEach(callback);
|
|
120
|
+
}
|
|
121
|
+
/** Map periods to a new array */
|
|
122
|
+
map(callback) {
|
|
123
|
+
return this._periods.map(callback);
|
|
124
|
+
}
|
|
125
|
+
/** Filter periods */
|
|
126
|
+
filter(predicate) {
|
|
127
|
+
return new ChronosPeriodCollection(this._periods.filter(predicate));
|
|
128
|
+
}
|
|
129
|
+
/** Reduce periods to a single value */
|
|
130
|
+
reduce(callback, initial) {
|
|
131
|
+
return this._periods.reduce(callback, initial);
|
|
132
|
+
}
|
|
133
|
+
/** Find a period matching a predicate */
|
|
134
|
+
find(predicate) {
|
|
135
|
+
return this._periods.find(predicate);
|
|
136
|
+
}
|
|
137
|
+
/** Check if any period matches a predicate */
|
|
138
|
+
some(predicate) {
|
|
139
|
+
return this._periods.some(predicate);
|
|
140
|
+
}
|
|
141
|
+
/** Check if all periods match a predicate */
|
|
142
|
+
every(predicate) {
|
|
143
|
+
return this._periods.every(predicate);
|
|
144
|
+
}
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Boundaries (spatie/period inspired)
|
|
147
|
+
// ============================================================================
|
|
148
|
+
/**
|
|
149
|
+
* Get the overall boundaries of the collection
|
|
150
|
+
* Returns a period from the earliest start to the latest end
|
|
151
|
+
*/
|
|
152
|
+
boundaries() {
|
|
153
|
+
var _a;
|
|
154
|
+
if (this._periods.length === 0)
|
|
155
|
+
return null;
|
|
156
|
+
let earliestStart = null;
|
|
157
|
+
let latestEnd = null;
|
|
158
|
+
for (const period of this._periods) {
|
|
159
|
+
if (!earliestStart || period.start.isBefore(earliestStart)) {
|
|
160
|
+
earliestStart = period.start;
|
|
161
|
+
}
|
|
162
|
+
const end = (_a = period.end) !== null && _a !== void 0 ? _a : period.last();
|
|
163
|
+
if (end && (!latestEnd || end.isAfter(latestEnd))) {
|
|
164
|
+
latestEnd = end;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (!earliestStart)
|
|
168
|
+
return null;
|
|
169
|
+
return period_1.ChronosPeriod.create(earliestStart, latestEnd !== null && latestEnd !== void 0 ? latestEnd : earliestStart);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get the earliest start date across all periods
|
|
173
|
+
*/
|
|
174
|
+
start() {
|
|
175
|
+
if (this._periods.length === 0)
|
|
176
|
+
return null;
|
|
177
|
+
let earliest = null;
|
|
178
|
+
for (const period of this._periods) {
|
|
179
|
+
if (!earliest || period.start.isBefore(earliest)) {
|
|
180
|
+
earliest = period.start;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return earliest;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get the latest end date across all periods
|
|
187
|
+
*/
|
|
188
|
+
end() {
|
|
189
|
+
var _a;
|
|
190
|
+
if (this._periods.length === 0)
|
|
191
|
+
return null;
|
|
192
|
+
let latest = null;
|
|
193
|
+
for (const period of this._periods) {
|
|
194
|
+
const end = (_a = period.end) !== null && _a !== void 0 ? _a : period.last();
|
|
195
|
+
if (end && (!latest || end.isAfter(latest))) {
|
|
196
|
+
latest = end;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return latest;
|
|
200
|
+
}
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// Overlap Operations (spatie/period inspired)
|
|
203
|
+
// ============================================================================
|
|
204
|
+
/** Normalize and merge overlapping/adjacent periods */
|
|
205
|
+
normalize() {
|
|
206
|
+
if (this._periods.length === 0)
|
|
207
|
+
return [];
|
|
208
|
+
// Sort by start
|
|
209
|
+
const sorted = this._periods.slice().sort((a, b) => {
|
|
210
|
+
const aStart = a.start.toDate().getTime();
|
|
211
|
+
const bStart = b.start.toDate().getTime();
|
|
212
|
+
return aStart - bStart;
|
|
213
|
+
});
|
|
214
|
+
const merged = [];
|
|
215
|
+
let current = sorted[0].clone();
|
|
216
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
217
|
+
const next = sorted[i];
|
|
218
|
+
const union = current.union(next);
|
|
219
|
+
if (union) {
|
|
220
|
+
current = union;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
merged.push(current);
|
|
224
|
+
current = next.clone();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
merged.push(current);
|
|
228
|
+
return merged;
|
|
229
|
+
}
|
|
230
|
+
/** Check if any period overlaps with the provided period */
|
|
231
|
+
overlaps(period) {
|
|
232
|
+
return this._periods.some((p) => p.overlaps(period));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Check if any period in the collection overlaps with any other period
|
|
236
|
+
* in the collection (internal overlaps)
|
|
237
|
+
*/
|
|
238
|
+
overlapAny() {
|
|
239
|
+
for (let i = 0; i < this._periods.length; i++) {
|
|
240
|
+
for (let j = i + 1; j < this._periods.length; j++) {
|
|
241
|
+
if (this._periods[i].overlaps(this._periods[j])) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get all overlapping period segments across the collection
|
|
250
|
+
* Returns periods where two or more periods in the collection overlap
|
|
251
|
+
*/
|
|
252
|
+
overlapAll() {
|
|
253
|
+
if (this._periods.length < 2) {
|
|
254
|
+
return ChronosPeriodCollection.empty();
|
|
255
|
+
}
|
|
256
|
+
const overlaps = [];
|
|
257
|
+
// Sort periods by start date
|
|
258
|
+
const sorted = this._periods
|
|
259
|
+
.slice()
|
|
260
|
+
.sort((a, b) => a.start.toDate().getTime() - b.start.toDate().getTime());
|
|
261
|
+
// Find all pairwise intersections
|
|
262
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
263
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
264
|
+
const intersection = sorted[i].intersect(sorted[j]);
|
|
265
|
+
if (intersection) {
|
|
266
|
+
// Check if this intersection is not already covered
|
|
267
|
+
const isDuplicate = overlaps.some((existing) => {
|
|
268
|
+
var _a, _b;
|
|
269
|
+
return existing.start.isSame(intersection.start, 'day') &&
|
|
270
|
+
((_a = existing.end) === null || _a === void 0 ? void 0 : _a.isSame((_b = intersection.end) !== null && _b !== void 0 ? _b : null, 'day'));
|
|
271
|
+
});
|
|
272
|
+
if (!isDuplicate) {
|
|
273
|
+
overlaps.push(intersection);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return new ChronosPeriodCollection(overlaps);
|
|
279
|
+
}
|
|
280
|
+
/** Return intersections between collection and a given period */
|
|
281
|
+
intersect(period) {
|
|
282
|
+
const intersections = [];
|
|
283
|
+
for (const p of this._periods) {
|
|
284
|
+
const inter = p.intersect(period);
|
|
285
|
+
if (inter)
|
|
286
|
+
intersections.push(inter);
|
|
287
|
+
}
|
|
288
|
+
return new ChronosPeriodCollection(intersections);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get intersection of all periods in the collection
|
|
292
|
+
* Returns the period where ALL periods overlap (if any)
|
|
293
|
+
*/
|
|
294
|
+
intersectAll() {
|
|
295
|
+
if (this._periods.length === 0)
|
|
296
|
+
return null;
|
|
297
|
+
if (this._periods.length === 1)
|
|
298
|
+
return this._periods[0].clone();
|
|
299
|
+
let result = this._periods[0].clone();
|
|
300
|
+
for (let i = 1; i < this._periods.length; i++) {
|
|
301
|
+
if (!result)
|
|
302
|
+
return null;
|
|
303
|
+
result = result.intersect(this._periods[i]);
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// Union Operations
|
|
309
|
+
// ============================================================================
|
|
310
|
+
/** Return the union (merged) of all periods in the collection */
|
|
311
|
+
union() {
|
|
312
|
+
return new ChronosPeriodCollection(this.normalize());
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Alias for normalize() - returns merged periods
|
|
316
|
+
* @deprecated Use union() instead
|
|
317
|
+
*/
|
|
318
|
+
unionAll() {
|
|
319
|
+
return this.normalize();
|
|
320
|
+
}
|
|
321
|
+
/** Merge collection into a single union period if contiguous/overlapping */
|
|
322
|
+
mergeToSingle() {
|
|
323
|
+
const merged = this.normalize();
|
|
324
|
+
if (merged.length === 0)
|
|
325
|
+
return null;
|
|
326
|
+
if (merged.length === 1)
|
|
327
|
+
return merged[0];
|
|
328
|
+
// If there are multiple, they are not adjacent/overlapping, cannot merge into single
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
// ============================================================================
|
|
332
|
+
// Gap Operations (spatie/period inspired)
|
|
333
|
+
// ============================================================================
|
|
334
|
+
/** Return gaps between merged periods */
|
|
335
|
+
gaps() {
|
|
336
|
+
var _a;
|
|
337
|
+
const merged = this.normalize();
|
|
338
|
+
const gaps = [];
|
|
339
|
+
for (let i = 0; i < merged.length - 1; i++) {
|
|
340
|
+
const current = merged[i];
|
|
341
|
+
const next = merged[i + 1];
|
|
342
|
+
const end = (_a = current.end) !== null && _a !== void 0 ? _a : current.last();
|
|
343
|
+
const startNext = next.start;
|
|
344
|
+
if (end && startNext) {
|
|
345
|
+
const gapStart = end.add(current.interval.toDuration());
|
|
346
|
+
const gapEnd = startNext.subtract(next.interval.toDuration());
|
|
347
|
+
// Only create gap if there's actual space between periods
|
|
348
|
+
if (gapStart.isSameOrBefore(gapEnd)) {
|
|
349
|
+
gaps.push(period_1.ChronosPeriod.create(gapStart, gapEnd, current.interval));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return new ChronosPeriodCollection(gaps);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Check if there are any gaps between periods
|
|
357
|
+
*/
|
|
358
|
+
hasGaps() {
|
|
359
|
+
return this.gaps().isNotEmpty();
|
|
360
|
+
}
|
|
361
|
+
// ============================================================================
|
|
362
|
+
// Subtraction Operations (spatie/period inspired)
|
|
363
|
+
// ============================================================================
|
|
364
|
+
/**
|
|
365
|
+
* Subtract a period from all periods in the collection
|
|
366
|
+
* Returns periods with the subtracted portion removed
|
|
367
|
+
*/
|
|
368
|
+
subtract(period) {
|
|
369
|
+
const results = [];
|
|
370
|
+
for (const p of this._periods) {
|
|
371
|
+
const diffs = p.diff(period);
|
|
372
|
+
results.push(...diffs);
|
|
373
|
+
}
|
|
374
|
+
return new ChronosPeriodCollection(results);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Subtract multiple periods from the collection
|
|
378
|
+
*/
|
|
379
|
+
subtractAll(periods) {
|
|
380
|
+
let result = new ChronosPeriodCollection([...this._periods]);
|
|
381
|
+
for (const period of periods) {
|
|
382
|
+
result = result.subtract(period);
|
|
383
|
+
}
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
// ============================================================================
|
|
387
|
+
// Touching/Adjacent Operations (spatie/period inspired)
|
|
388
|
+
// ============================================================================
|
|
389
|
+
/**
|
|
390
|
+
* Check if any period touches (is adjacent to) the given period
|
|
391
|
+
* Two periods touch if one ends exactly where the other begins
|
|
392
|
+
*/
|
|
393
|
+
touchesWith(period) {
|
|
394
|
+
return this._periods.some((p) => this._periodsTouch(p, period));
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Check if two periods touch (are adjacent)
|
|
398
|
+
*/
|
|
399
|
+
_periodsTouch(a, b) {
|
|
400
|
+
var _a, _b;
|
|
401
|
+
const aEnd = (_a = a.end) !== null && _a !== void 0 ? _a : a.last();
|
|
402
|
+
const bEnd = (_b = b.end) !== null && _b !== void 0 ? _b : b.last();
|
|
403
|
+
if (!aEnd || !bEnd)
|
|
404
|
+
return false;
|
|
405
|
+
// a ends exactly where b starts
|
|
406
|
+
if (aEnd.add(a.interval.toDuration()).isSame(b.start))
|
|
407
|
+
return true;
|
|
408
|
+
// b ends exactly where a starts
|
|
409
|
+
if (bEnd.add(b.interval.toDuration()).isSame(a.start))
|
|
410
|
+
return true;
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Get all periods that touch the given period
|
|
415
|
+
*/
|
|
416
|
+
touchingPeriods(period) {
|
|
417
|
+
return this.filter((p) => this._periodsTouch(p, period));
|
|
418
|
+
}
|
|
419
|
+
// ============================================================================
|
|
420
|
+
// Contains Operations (spatie/period inspired)
|
|
421
|
+
// ============================================================================
|
|
422
|
+
/**
|
|
423
|
+
* Check if a date is contained in any period of the collection
|
|
424
|
+
*/
|
|
425
|
+
contains(date) {
|
|
426
|
+
const target = chronos_1.Chronos.parse(date);
|
|
427
|
+
return this._periods.some((p) => p.contains(target));
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Check if a period is fully contained in any period of the collection
|
|
431
|
+
*/
|
|
432
|
+
containsPeriod(period) {
|
|
433
|
+
return this._periods.some((p) => {
|
|
434
|
+
var _a, _b;
|
|
435
|
+
const pEnd = (_a = p.end) !== null && _a !== void 0 ? _a : p.last();
|
|
436
|
+
const periodEnd = (_b = period.end) !== null && _b !== void 0 ? _b : period.last();
|
|
437
|
+
if (!pEnd || !periodEnd)
|
|
438
|
+
return false;
|
|
439
|
+
return (p.start.isSameOrBefore(period.start) && pEnd.isSameOrAfter(periodEnd));
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
// ============================================================================
|
|
443
|
+
// Equality Operations (spatie/period inspired)
|
|
444
|
+
// ============================================================================
|
|
445
|
+
/**
|
|
446
|
+
* Check if two collections are equal (same periods)
|
|
447
|
+
*/
|
|
448
|
+
equals(other) {
|
|
449
|
+
var _a, _b;
|
|
450
|
+
if (this._periods.length !== other._periods.length) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
const thisSorted = this._sortedByStart();
|
|
454
|
+
const otherSorted = other._sortedByStart();
|
|
455
|
+
for (let i = 0; i < thisSorted.length; i++) {
|
|
456
|
+
const thisEnd = (_a = thisSorted[i].end) !== null && _a !== void 0 ? _a : thisSorted[i].last();
|
|
457
|
+
const otherEnd = (_b = otherSorted[i].end) !== null && _b !== void 0 ? _b : otherSorted[i].last();
|
|
458
|
+
if (!thisSorted[i].start.isSame(otherSorted[i].start, 'day')) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
if (thisEnd && otherEnd && !thisEnd.isSame(otherEnd, 'day')) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get periods sorted by start date
|
|
469
|
+
*/
|
|
470
|
+
_sortedByStart() {
|
|
471
|
+
return this._periods
|
|
472
|
+
.slice()
|
|
473
|
+
.sort((a, b) => a.start.toDate().getTime() - b.start.toDate().getTime());
|
|
474
|
+
}
|
|
475
|
+
// ============================================================================
|
|
476
|
+
// Sorting & Reversing
|
|
477
|
+
// ============================================================================
|
|
478
|
+
/**
|
|
479
|
+
* Sort periods by start date (ascending)
|
|
480
|
+
*/
|
|
481
|
+
sortByStart() {
|
|
482
|
+
return new ChronosPeriodCollection(this._sortedByStart());
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Sort periods by end date (ascending)
|
|
486
|
+
*/
|
|
487
|
+
sortByEnd() {
|
|
488
|
+
const sorted = this._periods.slice().sort((a, b) => {
|
|
489
|
+
var _a, _b, _c, _d, _e, _f;
|
|
490
|
+
const aEnd = (_c = (_b = ((_a = a.end) !== null && _a !== void 0 ? _a : a.last())) === null || _b === void 0 ? void 0 : _b.toDate().getTime()) !== null && _c !== void 0 ? _c : 0;
|
|
491
|
+
const bEnd = (_f = (_e = ((_d = b.end) !== null && _d !== void 0 ? _d : b.last())) === null || _e === void 0 ? void 0 : _e.toDate().getTime()) !== null && _f !== void 0 ? _f : 0;
|
|
492
|
+
return aEnd - bEnd;
|
|
493
|
+
});
|
|
494
|
+
return new ChronosPeriodCollection(sorted);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Sort periods by duration (ascending)
|
|
498
|
+
*/
|
|
499
|
+
sortByDuration() {
|
|
500
|
+
const sorted = this._periods.slice().sort((a, b) => {
|
|
501
|
+
try {
|
|
502
|
+
return a.days() - b.days();
|
|
503
|
+
}
|
|
504
|
+
catch (_a) {
|
|
505
|
+
return 0;
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
return new ChronosPeriodCollection(sorted);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Reverse the order of periods
|
|
512
|
+
*/
|
|
513
|
+
reverse() {
|
|
514
|
+
return new ChronosPeriodCollection([...this._periods].reverse());
|
|
515
|
+
}
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// Conversion & Output
|
|
518
|
+
// ============================================================================
|
|
519
|
+
/**
|
|
520
|
+
* Convert to JSON
|
|
521
|
+
*/
|
|
522
|
+
toJSON() {
|
|
523
|
+
return this._periods.map((p) => p.toJSON());
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Convert to string
|
|
527
|
+
*/
|
|
528
|
+
toString() {
|
|
529
|
+
if (this._periods.length === 0)
|
|
530
|
+
return '(empty collection)';
|
|
531
|
+
return this._periods.map((p) => p.toString()).join(', ');
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Get total duration across all periods (in days)
|
|
535
|
+
* Note: Overlapping portions may be counted multiple times
|
|
536
|
+
*/
|
|
537
|
+
totalDays() {
|
|
538
|
+
return this._periods.reduce((sum, p) => {
|
|
539
|
+
try {
|
|
540
|
+
return sum + p.days();
|
|
541
|
+
}
|
|
542
|
+
catch (_a) {
|
|
543
|
+
return sum;
|
|
544
|
+
}
|
|
545
|
+
}, 0);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Get total unique duration (merged periods, no double-counting)
|
|
549
|
+
*/
|
|
550
|
+
uniqueDays() {
|
|
551
|
+
const merged = this.normalize();
|
|
552
|
+
return merged.reduce((sum, p) => {
|
|
553
|
+
try {
|
|
554
|
+
return sum + p.days();
|
|
555
|
+
}
|
|
556
|
+
catch (_a) {
|
|
557
|
+
return sum;
|
|
558
|
+
}
|
|
559
|
+
}, 0);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
exports.ChronosPeriodCollection = ChronosPeriodCollection;
|