chronos-ts 2.0.2 → 2.0.3

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.
@@ -1,1062 +0,0 @@
1
- "use strict";
2
- /**
3
- * ChronosPeriod - Date range iteration
4
- * @module ChronosPeriod
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.ChronosPeriod = void 0;
8
- const utils_1 = require("../utils");
9
- const locales_1 = require("../locales");
10
- const chronos_1 = require("./chronos");
11
- const interval_1 = require("./interval");
12
- // ============================================================================
13
- // ChronosPeriod Class
14
- // ============================================================================
15
- /**
16
- * ChronosPeriod - Represents a date range with iteration capabilities
17
- *
18
- * Inspired by CarbonPeriod, this class provides powerful date range
19
- * iteration with support for various interval types, filters, and
20
- * recurrence patterns.
21
- *
22
- * @example
23
- * ```typescript
24
- * // Basic iteration
25
- * const period = ChronosPeriod.create('2024-01-01', '2024-01-31');
26
- * for (const date of period) {
27
- * console.log(date.format('YYYY-MM-DD'));
28
- * }
29
- *
30
- * // With custom interval
31
- * const weekly = ChronosPeriod.create('2024-01-01', '2024-03-31')
32
- * .setInterval(ChronosInterval.weeks(1));
33
- *
34
- * // With filters
35
- * const weekdays = period.filter(date => date.dayOfWeek < 6);
36
- *
37
- * // Recurrence
38
- * const recur = ChronosPeriod.recur('2024-01-01')
39
- * .every(1, 'month')
40
- * .times(12);
41
- * ```
42
- */
43
- class ChronosPeriod {
44
- // ============================================================================
45
- // Constructor
46
- // ============================================================================
47
- /**
48
- * Create a new ChronosPeriod
49
- */
50
- constructor(start, end, interval, options = {}) {
51
- var _a, _b, _c;
52
- this._start = chronos_1.Chronos.parse(start);
53
- this._end = end !== undefined ? chronos_1.Chronos.parse(end) : null;
54
- this._interval =
55
- interval instanceof interval_1.ChronosInterval
56
- ? interval
57
- : interval_1.ChronosInterval.create(interval || { days: 1 });
58
- // Validate interval is not zero to prevent infinite loops
59
- if (this._interval.isZero()) {
60
- throw new Error('ChronosPeriod: Interval cannot be zero');
61
- }
62
- // Validate interval is not negative
63
- if (this._interval.isNegative()) {
64
- throw new Error('ChronosPeriod: Interval cannot be negative');
65
- }
66
- // Warn about potentially large iterations (optional safety check)
67
- if (this._end !== null) {
68
- const durationMs = Math.abs(this._end.valueOf() - this._start.valueOf());
69
- const intervalMs = Math.abs(this._interval.totalMilliseconds());
70
- if (intervalMs > 0) {
71
- const estimatedIterations = durationMs / intervalMs;
72
- // Warn if period would generate more than 1 million iterations
73
- if (estimatedIterations > 1000000) {
74
- console.warn(`ChronosPeriod: Large number of iterations detected (~${Math.floor(estimatedIterations).toLocaleString()}). ` +
75
- `This may cause performance issues. Consider using a larger interval or setting a recurrence limit.`);
76
- }
77
- // Hard limit: throw error if more than 10 million iterations
78
- if (estimatedIterations > 10000000) {
79
- throw new Error(`ChronosPeriod: Period would generate ~${Math.floor(estimatedIterations).toLocaleString()} iterations, ` +
80
- `which exceeds the safety limit of 10 million. Use a larger interval or set explicit recurrence limits.`);
81
- }
82
- }
83
- }
84
- this._recurrences = null;
85
- this._options = {
86
- excludeStart: (_a = options.excludeStart) !== null && _a !== void 0 ? _a : false,
87
- excludeEnd: (_b = options.excludeEnd) !== null && _b !== void 0 ? _b : false,
88
- immutable: (_c = options.immutable) !== null && _c !== void 0 ? _c : true,
89
- };
90
- this._filters = [];
91
- this._current = 0;
92
- this._locale = (0, locales_1.getLocale)('en');
93
- }
94
- // ============================================================================
95
- // Static Factory Methods
96
- // ============================================================================
97
- /**
98
- * Create a period between two dates
99
- */
100
- static create(start, end, interval) {
101
- return new ChronosPeriod(start, end, interval);
102
- }
103
- /**
104
- * Create a period from a start date with recurrences
105
- */
106
- static recur(start, interval) {
107
- return new ChronosPeriod(start, undefined, interval);
108
- }
109
- /**
110
- * Create a period for a specific number of days
111
- */
112
- static days(start, count) {
113
- const startDate = chronos_1.Chronos.parse(start);
114
- const endDate = startDate.addDays(count - 1);
115
- return new ChronosPeriod(startDate, endDate, { days: 1 });
116
- }
117
- /**
118
- * Create a period for a specific number of weeks
119
- */
120
- static weeks(start, count) {
121
- const startDate = chronos_1.Chronos.parse(start);
122
- const endDate = startDate.addWeeks(count);
123
- return new ChronosPeriod(startDate, endDate, { weeks: 1 });
124
- }
125
- /**
126
- * Create a period for a specific number of months
127
- */
128
- static months(start, count) {
129
- const startDate = chronos_1.Chronos.parse(start);
130
- const endDate = startDate.addMonths(count);
131
- return new ChronosPeriod(startDate, endDate, { months: 1 });
132
- }
133
- /**
134
- * Create a period for a specific number of years
135
- */
136
- static years(start, count) {
137
- const startDate = chronos_1.Chronos.parse(start);
138
- const endDate = startDate.addYears(count);
139
- return new ChronosPeriod(startDate, endDate, { years: 1 });
140
- }
141
- /**
142
- * Create a period for the current month
143
- */
144
- static currentMonth() {
145
- const now = chronos_1.Chronos.now();
146
- return new ChronosPeriod(now.startOf('month'), now.endOf('month'), {
147
- days: 1,
148
- });
149
- }
150
- /**
151
- * Create a period for the current year
152
- */
153
- static currentYear() {
154
- const now = chronos_1.Chronos.now();
155
- return new ChronosPeriod(now.startOf('year'), now.endOf('year'), {
156
- days: 1,
157
- });
158
- }
159
- /**
160
- * Create a period for the current week
161
- */
162
- static currentWeek() {
163
- const now = chronos_1.Chronos.now();
164
- return new ChronosPeriod(now.startOf('week'), now.endOf('week'), {
165
- days: 1,
166
- });
167
- }
168
- /**
169
- * Create a period for the current quarter
170
- */
171
- static currentQuarter() {
172
- const now = chronos_1.Chronos.now();
173
- return new ChronosPeriod(now.startOf('quarter'), now.endOf('quarter'), {
174
- days: 1,
175
- });
176
- }
177
- // ============================================================================
178
- // Convenience Aliases (thisWeek, thisMonth, lastWeek, etc.)
179
- // ============================================================================
180
- /**
181
- * Alias for currentWeek()
182
- */
183
- static thisWeek() {
184
- return ChronosPeriod.currentWeek();
185
- }
186
- /**
187
- * Alias for currentMonth()
188
- */
189
- static thisMonth() {
190
- return ChronosPeriod.currentMonth();
191
- }
192
- /**
193
- * Alias for currentYear()
194
- */
195
- static thisYear() {
196
- return ChronosPeriod.currentYear();
197
- }
198
- /**
199
- * Alias for currentQuarter()
200
- */
201
- static thisQuarter() {
202
- return ChronosPeriod.currentQuarter();
203
- }
204
- /**
205
- * Create a period for the previous week
206
- */
207
- static lastWeek() {
208
- const now = chronos_1.Chronos.now().subtract({ weeks: 1 });
209
- return new ChronosPeriod(now.startOf('week'), now.endOf('week'), {
210
- days: 1,
211
- });
212
- }
213
- /**
214
- * Create a period for the previous month
215
- */
216
- static lastMonth() {
217
- const now = chronos_1.Chronos.now().subtract({ months: 1 });
218
- return new ChronosPeriod(now.startOf('month'), now.endOf('month'), {
219
- days: 1,
220
- });
221
- }
222
- /**
223
- * Create a period for the previous year
224
- */
225
- static lastYear() {
226
- const now = chronos_1.Chronos.now().subtract({ years: 1 });
227
- return new ChronosPeriod(now.startOf('year'), now.endOf('year'), {
228
- days: 1,
229
- });
230
- }
231
- /**
232
- * Create a period for the previous quarter
233
- */
234
- static lastQuarter() {
235
- const now = chronos_1.Chronos.now().subtract({ months: 3 });
236
- return new ChronosPeriod(now.startOf('quarter'), now.endOf('quarter'), {
237
- days: 1,
238
- });
239
- }
240
- /**
241
- * Create a period for the next week
242
- */
243
- static nextWeek() {
244
- const now = chronos_1.Chronos.now().add({ weeks: 1 });
245
- return new ChronosPeriod(now.startOf('week'), now.endOf('week'), {
246
- days: 1,
247
- });
248
- }
249
- /**
250
- * Create a period for the next month
251
- */
252
- static nextMonth() {
253
- const now = chronos_1.Chronos.now().add({ months: 1 });
254
- return new ChronosPeriod(now.startOf('month'), now.endOf('month'), {
255
- days: 1,
256
- });
257
- }
258
- /**
259
- * Create a period for the next year
260
- */
261
- static nextYear() {
262
- const now = chronos_1.Chronos.now().add({ years: 1 });
263
- return new ChronosPeriod(now.startOf('year'), now.endOf('year'), {
264
- days: 1,
265
- });
266
- }
267
- /**
268
- * Create a period for the next quarter
269
- */
270
- static nextQuarter() {
271
- const now = chronos_1.Chronos.now().add({ months: 3 });
272
- return new ChronosPeriod(now.startOf('quarter'), now.endOf('quarter'), {
273
- days: 1,
274
- });
275
- }
276
- /**
277
- * Create a period between two dates (alias for create)
278
- */
279
- static between(start, end, interval) {
280
- return ChronosPeriod.create(start, end, interval);
281
- }
282
- /**
283
- * Create a period from an ISO 8601 repeating interval string
284
- * @example
285
- * ```typescript
286
- * ChronosPeriod.fromISO('R5/2024-01-01/P1D') // 5 recurrences, daily from 2024-01-01
287
- * ChronosPeriod.fromISO('2024-01-01/2024-01-31') // Date range
288
- * ChronosPeriod.fromISO('2024-01-01/P1M') // From date with duration
289
- * ```
290
- */
291
- static fromISO(iso) {
292
- // Pattern for repeating interval: R[n]/start/duration or R[n]/start/end
293
- const repeatMatch = iso.match(/^R(\d*)\/([\d\-T:Z]+)\/(P.+|[\d\-T:Z]+)$/);
294
- if (repeatMatch) {
295
- const recurrences = repeatMatch[1]
296
- ? parseInt(repeatMatch[1], 10)
297
- : Infinity;
298
- const start = chronos_1.Chronos.parse(repeatMatch[2]);
299
- const durationOrEnd = repeatMatch[3];
300
- if (durationOrEnd.startsWith('P')) {
301
- const interval = interval_1.ChronosInterval.fromISO(durationOrEnd);
302
- return new ChronosPeriod(start, undefined, interval).times(recurrences);
303
- }
304
- else {
305
- const end = chronos_1.Chronos.parse(durationOrEnd);
306
- return new ChronosPeriod(start, end).times(recurrences);
307
- }
308
- }
309
- // Pattern for date range: start/end or start/duration
310
- const rangeMatch = iso.match(/^([\d\-T:Z]+)\/(P.+|[\d\-T:Z]+)$/);
311
- if (rangeMatch) {
312
- const start = chronos_1.Chronos.parse(rangeMatch[1]);
313
- const durationOrEnd = rangeMatch[2];
314
- if (durationOrEnd.startsWith('P')) {
315
- const interval = interval_1.ChronosInterval.fromISO(durationOrEnd);
316
- const end = start.add(interval.toDuration());
317
- return new ChronosPeriod(start, end, interval);
318
- }
319
- else {
320
- const end = chronos_1.Chronos.parse(durationOrEnd);
321
- return new ChronosPeriod(start, end);
322
- }
323
- }
324
- throw new Error(`Invalid ISO 8601 period: ${iso}`);
325
- }
326
- // ============================================================================
327
- // Getters
328
- // ============================================================================
329
- /**
330
- * Get the start date
331
- */
332
- get start() {
333
- return this._start;
334
- }
335
- /**
336
- * Get the end date
337
- */
338
- get end() {
339
- return this._end;
340
- }
341
- /**
342
- * Get the interval
343
- */
344
- get interval() {
345
- return this._interval;
346
- }
347
- /**
348
- * Get the number of recurrences
349
- */
350
- get recurrences() {
351
- return this._recurrences;
352
- }
353
- /**
354
- * Check if the period includes the start boundary
355
- */
356
- get includesStart() {
357
- return !this._options.excludeStart;
358
- }
359
- /**
360
- * Check if the period includes the end boundary
361
- */
362
- get includesEnd() {
363
- return !this._options.excludeEnd;
364
- }
365
- /**
366
- * Check if the period has an end date
367
- */
368
- get hasEnd() {
369
- return this._end !== null || this._recurrences !== null;
370
- }
371
- /**
372
- * Check if the period is unbounded
373
- */
374
- get isUnbounded() {
375
- return this._end === null && this._recurrences === null;
376
- }
377
- // ============================================================================
378
- // Setters (Fluent Interface)
379
- // ============================================================================
380
- /**
381
- * Set the start date
382
- */
383
- setStart(start) {
384
- const period = this._cloneForModification();
385
- period._start = chronos_1.Chronos.parse(start);
386
- return period;
387
- }
388
- /**
389
- * Set the end date
390
- */
391
- setEnd(end) {
392
- const period = this._cloneForModification();
393
- period._end = chronos_1.Chronos.parse(end);
394
- return period;
395
- }
396
- /**
397
- * Set the interval
398
- */
399
- setInterval(interval) {
400
- const period = this._cloneForModification();
401
- const newInterval = interval instanceof interval_1.ChronosInterval
402
- ? interval
403
- : interval_1.ChronosInterval.create(interval);
404
- // Validate interval is not zero to prevent infinite loops
405
- if (newInterval.isZero()) {
406
- throw new Error('ChronosPeriod: Interval cannot be zero');
407
- }
408
- // Validate interval is not negative
409
- if (newInterval.isNegative()) {
410
- throw new Error('ChronosPeriod: Interval cannot be negative');
411
- }
412
- period._interval = newInterval;
413
- return period;
414
- }
415
- /**
416
- * Set the number of recurrences
417
- */
418
- times(count) {
419
- const period = this._cloneForModification();
420
- period._recurrences = count;
421
- return period;
422
- }
423
- /**
424
- * Set interval by unit
425
- */
426
- every(amount, unit) {
427
- // Validate amount is positive
428
- if (amount <= 0) {
429
- throw new Error('ChronosPeriod: Amount must be positive');
430
- }
431
- const normalizedUnit = (0, utils_1.normalizeUnit)(unit);
432
- const duration = {};
433
- switch (normalizedUnit) {
434
- case 'millisecond':
435
- duration.milliseconds = amount;
436
- break;
437
- case 'second':
438
- duration.seconds = amount;
439
- break;
440
- case 'minute':
441
- duration.minutes = amount;
442
- break;
443
- case 'hour':
444
- duration.hours = amount;
445
- break;
446
- case 'day':
447
- duration.days = amount;
448
- break;
449
- case 'week':
450
- duration.weeks = amount;
451
- break;
452
- case 'month':
453
- duration.months = amount;
454
- break;
455
- case 'quarter':
456
- duration.months = amount * 3;
457
- break;
458
- case 'year':
459
- duration.years = amount;
460
- break;
461
- default:
462
- duration.days = amount;
463
- }
464
- return this.setInterval(duration);
465
- }
466
- /**
467
- * Exclude the start boundary
468
- */
469
- excludeStart() {
470
- const period = this._cloneForModification();
471
- period._options.excludeStart = true;
472
- return period;
473
- }
474
- /**
475
- * Exclude the end boundary
476
- */
477
- excludeEnd() {
478
- const period = this._cloneForModification();
479
- period._options.excludeEnd = true;
480
- return period;
481
- }
482
- /**
483
- * Include the start boundary
484
- */
485
- includeStart() {
486
- const period = this._cloneForModification();
487
- period._options.excludeStart = false;
488
- return period;
489
- }
490
- /**
491
- * Include the end boundary
492
- */
493
- includeEnd() {
494
- const period = this._cloneForModification();
495
- period._options.excludeEnd = false;
496
- return period;
497
- }
498
- // ============================================================================
499
- // Filters
500
- // ============================================================================
501
- /**
502
- * Add a filter function
503
- */
504
- filter(fn) {
505
- const period = this._cloneForModification();
506
- period._filters.push(fn);
507
- return period;
508
- }
509
- /**
510
- * Filter to only include weekdays
511
- */
512
- weekdays() {
513
- return this.filter((date) => date.dayOfWeek !== 0 && date.dayOfWeek !== 6);
514
- }
515
- /**
516
- * Alias for weekdays()
517
- */
518
- filterWeekdays() {
519
- return this.weekdays();
520
- }
521
- /**
522
- * Filter to only include weekends
523
- */
524
- weekends() {
525
- return this.filter((date) => date.dayOfWeek === 0 || date.dayOfWeek === 6);
526
- }
527
- /**
528
- * Alias for weekends()
529
- */
530
- filterWeekends() {
531
- return this.weekends();
532
- }
533
- /**
534
- * Filter to only include specific days of week
535
- */
536
- onlyDays(...days) {
537
- return this.filter((date) => days.includes(date.dayOfWeek));
538
- }
539
- /**
540
- * Filter to exclude specific days of week
541
- */
542
- exceptDays(...days) {
543
- return this.filter((date) => !days.includes(date.dayOfWeek));
544
- }
545
- /**
546
- * Filter to only include specific months
547
- */
548
- onlyMonths(...months) {
549
- return this.filter((date) => months.includes(date.month));
550
- }
551
- /**
552
- * Filter to exclude specific months
553
- */
554
- exceptMonths(...months) {
555
- return this.filter((date) => !months.includes(date.month));
556
- }
557
- /**
558
- * Clear all filters
559
- */
560
- clearFilters() {
561
- const period = this._cloneForModification();
562
- period._filters = [];
563
- return period;
564
- }
565
- // ============================================================================
566
- // Iteration
567
- // ============================================================================
568
- /**
569
- * Get all dates in the period as an array
570
- */
571
- toArray() {
572
- return [...this];
573
- }
574
- /**
575
- * Iterate over the period
576
- */
577
- *[Symbol.iterator]() {
578
- let current = this._start.clone();
579
- let count = 0;
580
- // Handle excludeStart
581
- if (this._options.excludeStart) {
582
- current = this._applyInterval(current);
583
- count++;
584
- }
585
- while (this._shouldContinue(current, count)) {
586
- // Apply filters
587
- if (this._passesFilters(current, count)) {
588
- yield this._options.immutable ? current.clone() : current;
589
- }
590
- current = this._applyInterval(current);
591
- count++;
592
- }
593
- }
594
- /**
595
- * Apply the interval to a date
596
- */
597
- _applyInterval(date) {
598
- return date.add(this._interval.toDuration());
599
- }
600
- /**
601
- * Check if iteration should continue
602
- */
603
- _shouldContinue(date, count) {
604
- // Check recurrence limit
605
- if (this._recurrences !== null && count >= this._recurrences) {
606
- return false;
607
- }
608
- // Check end date
609
- if (this._end !== null) {
610
- if (this._options.excludeEnd) {
611
- return date.isBefore(this._end);
612
- }
613
- return date.isSameOrBefore(this._end);
614
- }
615
- // No end - check for unbounded
616
- if (this._recurrences === null) {
617
- // Prevent infinite iteration
618
- if (count > 10000) {
619
- throw new Error('ChronosPeriod: Maximum iteration limit reached. Set an end date or recurrence limit.');
620
- }
621
- }
622
- return true;
623
- }
624
- /**
625
- * Check if a date passes all filters
626
- */
627
- _passesFilters(date, key) {
628
- return this._filters.every((filter) => filter(date, key));
629
- }
630
- /**
631
- * Get count of dates in the period
632
- */
633
- count() {
634
- return this.toArray().length;
635
- }
636
- /**
637
- * Get the first date in the period
638
- */
639
- first() {
640
- const iterator = this[Symbol.iterator]();
641
- const result = iterator.next();
642
- return result.done ? null : result.value;
643
- }
644
- /**
645
- * Get the last date in the period
646
- */
647
- last() {
648
- let lastDate = null;
649
- for (const date of this) {
650
- lastDate = date;
651
- }
652
- return lastDate;
653
- }
654
- /**
655
- * Get a date at a specific index
656
- */
657
- nth(index) {
658
- let count = 0;
659
- for (const date of this) {
660
- if (count === index) {
661
- return date;
662
- }
663
- count++;
664
- }
665
- return null;
666
- }
667
- /**
668
- * Check if a date is within the period
669
- */
670
- contains(date) {
671
- const target = chronos_1.Chronos.parse(date);
672
- for (const d of this) {
673
- if (d.isSame(target, 'day')) {
674
- return true;
675
- }
676
- }
677
- return false;
678
- }
679
- /**
680
- * For each iteration
681
- */
682
- forEach(callback) {
683
- let index = 0;
684
- for (const date of this) {
685
- callback(date, index);
686
- index++;
687
- }
688
- }
689
- /**
690
- * Map dates to another type
691
- */
692
- map(callback) {
693
- const result = [];
694
- let index = 0;
695
- for (const date of this) {
696
- result.push(callback(date, index));
697
- index++;
698
- }
699
- return result;
700
- }
701
- /**
702
- * Reduce dates to a single value
703
- */
704
- reduce(callback, initial) {
705
- let acc = initial;
706
- let index = 0;
707
- for (const date of this) {
708
- acc = callback(acc, date, index);
709
- index++;
710
- }
711
- return acc;
712
- }
713
- // ============================================================================
714
- // Range Operations
715
- // ============================================================================
716
- /**
717
- * Check if two periods overlap
718
- */
719
- overlaps(other) {
720
- var _a, _b;
721
- const thisEnd = (_a = this._end) !== null && _a !== void 0 ? _a : this.last();
722
- const otherEnd = (_b = other._end) !== null && _b !== void 0 ? _b : other.last();
723
- if (!thisEnd || !otherEnd) {
724
- return true; // Unbounded periods always overlap
725
- }
726
- return (this._start.isSameOrBefore(otherEnd) &&
727
- thisEnd.isSameOrAfter(other._start));
728
- }
729
- /**
730
- * Get the intersection of two periods
731
- */
732
- intersect(other) {
733
- var _a, _b;
734
- if (!this.overlaps(other)) {
735
- return null;
736
- }
737
- const start = this._start.isAfter(other._start)
738
- ? this._start
739
- : other._start;
740
- const thisEnd = (_a = this._end) !== null && _a !== void 0 ? _a : this.last();
741
- const otherEnd = (_b = other._end) !== null && _b !== void 0 ? _b : other.last();
742
- if (!thisEnd || !otherEnd) {
743
- return new ChronosPeriod(start, undefined, this._interval);
744
- }
745
- const end = thisEnd.isBefore(otherEnd) ? thisEnd : otherEnd;
746
- return new ChronosPeriod(start, end, this._interval);
747
- }
748
- /**
749
- * Get the union of two periods
750
- */
751
- union(other) {
752
- var _a, _b;
753
- if (!this.overlaps(other) && !this._adjacentTo(other)) {
754
- return null;
755
- }
756
- const start = this._start.isBefore(other._start)
757
- ? this._start
758
- : other._start;
759
- const thisEnd = (_a = this._end) !== null && _a !== void 0 ? _a : this.last();
760
- const otherEnd = (_b = other._end) !== null && _b !== void 0 ? _b : other.last();
761
- if (!thisEnd || !otherEnd) {
762
- return new ChronosPeriod(start, undefined, this._interval);
763
- }
764
- const end = thisEnd.isAfter(otherEnd) ? thisEnd : otherEnd;
765
- return new ChronosPeriod(start, end, this._interval);
766
- }
767
- /**
768
- * Get the difference between two periods
769
- * Returns the parts of this period that don't overlap with the other period
770
- */
771
- diff(other) {
772
- var _a, _b;
773
- if (!this.overlaps(other)) {
774
- return [this.clone()];
775
- }
776
- const results = [];
777
- const thisEnd = (_a = this._end) !== null && _a !== void 0 ? _a : this.last();
778
- const otherEnd = (_b = other._end) !== null && _b !== void 0 ? _b : other.last();
779
- // Before the other period starts - create gap from this start to other start
780
- if (this._start.isBefore(other._start)) {
781
- results.push(new ChronosPeriod(this._start, other._start, this._interval));
782
- }
783
- // After the other period ends - create gap from other end to this end
784
- if (otherEnd && thisEnd && thisEnd.isAfter(otherEnd)) {
785
- results.push(new ChronosPeriod(otherEnd, thisEnd, this._interval));
786
- }
787
- return results;
788
- }
789
- /**
790
- * Check if this period is adjacent to another
791
- */
792
- _adjacentTo(other) {
793
- var _a, _b;
794
- const thisEnd = (_a = this._end) !== null && _a !== void 0 ? _a : this.last();
795
- const otherEnd = (_b = other._end) !== null && _b !== void 0 ? _b : other.last();
796
- if (!thisEnd || !otherEnd) {
797
- return false;
798
- }
799
- return (thisEnd.add(this._interval.toDuration()).isSame(other._start) ||
800
- otherEnd.add(other._interval.toDuration()).isSame(this._start));
801
- }
802
- // ============================================================================
803
- // Duration
804
- // ============================================================================
805
- /**
806
- * Get the duration of the period
807
- */
808
- duration() {
809
- if (this._end === null) {
810
- throw new Error('Cannot get duration of unbounded period');
811
- }
812
- return interval_1.ChronosInterval.between(this._start.toDate(), this._end.toDate());
813
- }
814
- /**
815
- * Get the number of days in the period
816
- */
817
- days() {
818
- if (this._end === null) {
819
- throw new Error('Cannot get days of unbounded period');
820
- }
821
- return Math.abs(this._start.diff(this._end, 'day'));
822
- }
823
- /**
824
- * Get the number of weeks in the period
825
- */
826
- weeks() {
827
- if (this._end === null) {
828
- throw new Error('Cannot get weeks of unbounded period');
829
- }
830
- return Math.abs(this._start.diff(this._end, 'week'));
831
- }
832
- /**
833
- * Get the number of months in the period
834
- */
835
- monthCount() {
836
- if (this._end === null) {
837
- throw new Error('Cannot get months of unbounded period');
838
- }
839
- return Math.abs(this._start.diff(this._end, 'month'));
840
- }
841
- /**
842
- * Get the number of years in the period
843
- */
844
- yearCount() {
845
- if (this._end === null) {
846
- throw new Error('Cannot get years of unbounded period');
847
- }
848
- return Math.abs(this._start.diff(this._end, 'year'));
849
- }
850
- // ============================================================================
851
- // Splitting
852
- // ============================================================================
853
- /**
854
- * Split the period into chunks
855
- */
856
- split(count) {
857
- if (this._end === null) {
858
- throw new Error('Cannot split unbounded period');
859
- }
860
- const totalDays = this.days();
861
- const daysPerChunk = Math.ceil(totalDays / count);
862
- const chunks = [];
863
- let current = this._start.clone();
864
- for (let i = 0; i < count && current.isSameOrBefore(this._end); i++) {
865
- const chunkEnd = current.addDays(daysPerChunk - 1);
866
- const end = chunkEnd.isAfter(this._end) ? this._end : chunkEnd;
867
- chunks.push(new ChronosPeriod(current, end, this._interval));
868
- current = end.addDays(1);
869
- }
870
- return chunks;
871
- }
872
- /**
873
- * Split by a specific interval
874
- */
875
- splitBy(interval) {
876
- if (this._end === null) {
877
- throw new Error('Cannot split unbounded period');
878
- }
879
- const splitInterval = interval instanceof interval_1.ChronosInterval
880
- ? interval
881
- : interval_1.ChronosInterval.create(interval);
882
- // Validate that the interval is not zero
883
- if (splitInterval.isZero()) {
884
- throw new Error('Cannot split by zero interval');
885
- }
886
- // Validate that the interval is positive
887
- if (splitInterval.isNegative()) {
888
- throw new Error('Cannot split by negative interval');
889
- }
890
- const chunks = [];
891
- let current = this._start.clone();
892
- while (current.isSameOrBefore(this._end)) {
893
- const chunkEnd = current
894
- .add(splitInterval.toDuration())
895
- .subtract({ days: 1 });
896
- const end = chunkEnd.isAfter(this._end) ? this._end : chunkEnd;
897
- chunks.push(new ChronosPeriod(current, end, this._interval));
898
- current = current.add(splitInterval.toDuration());
899
- }
900
- return chunks;
901
- }
902
- /**
903
- * Split the period by a specified number of days
904
- */
905
- splitByDays(days) {
906
- return this.splitBy({ days });
907
- }
908
- /**
909
- * Split the period by a specified number of weeks
910
- */
911
- splitByWeeks(weeks) {
912
- return this.splitBy({ weeks });
913
- }
914
- /**
915
- * Split the period by a specified number of months
916
- */
917
- splitByMonths(months) {
918
- return this.splitBy({ months });
919
- }
920
- /**
921
- * Split the period by a specified number of years
922
- */
923
- splitByYears(years) {
924
- return this.splitBy({ years });
925
- }
926
- /**
927
- * Skip specific dates from the period iteration
928
- * @param dates - Dates to exclude from iteration
929
- */
930
- skip(dates) {
931
- const skipDates = dates.map((d) => chronos_1.Chronos.parse(d).format('YYYY-MM-DD'));
932
- return this.filter((date) => !skipDates.includes(date.format('YYYY-MM-DD')));
933
- }
934
- // ============================================================================
935
- // Formatting
936
- // ============================================================================
937
- /**
938
- * Convert to ISO 8601 string
939
- */
940
- toISO() {
941
- let iso = '';
942
- if (this._recurrences !== null && this._recurrences !== Infinity) {
943
- iso += `R${this._recurrences}/`;
944
- }
945
- else if (this._recurrences === Infinity) {
946
- iso += 'R/';
947
- }
948
- iso += this._start.toISOString().split('T')[0];
949
- iso += '/';
950
- if (this._end) {
951
- iso += this._end.toISOString().split('T')[0];
952
- }
953
- else {
954
- iso += this._interval.toISO();
955
- }
956
- return iso;
957
- }
958
- /**
959
- * Convert to string
960
- */
961
- toString() {
962
- const start = this._start.format('YYYY-MM-DD');
963
- const end = this._end ? this._end.format('YYYY-MM-DD') : '...';
964
- return `${start} to ${end}`;
965
- }
966
- /**
967
- * Convert to human-readable string
968
- */
969
- forHumans() {
970
- const count = this.count();
971
- const start = this._start.format('MMMM D, YYYY');
972
- const end = this._end ? this._end.format('MMMM D, YYYY') : 'indefinitely';
973
- return `${count} dates from ${start} to ${end}`;
974
- }
975
- /**
976
- * Convert to JSON
977
- */
978
- toJSON() {
979
- var _a, _b;
980
- return {
981
- start: this._start.toISOString(),
982
- end: (_b = (_a = this._end) === null || _a === void 0 ? void 0 : _a.toISOString()) !== null && _b !== void 0 ? _b : null,
983
- interval: this._interval.toISO(),
984
- recurrences: this._recurrences,
985
- options: this._options,
986
- };
987
- }
988
- // ============================================================================
989
- // Cloning and Locale
990
- // ============================================================================
991
- /**
992
- * Clone this period
993
- */
994
- clone() {
995
- var _a;
996
- const period = new ChronosPeriod(this._start, (_a = this._end) !== null && _a !== void 0 ? _a : undefined, this._interval, Object.assign({}, this._options));
997
- period._recurrences = this._recurrences;
998
- period._filters = [...this._filters];
999
- period._locale = this._locale;
1000
- return period;
1001
- }
1002
- /**
1003
- * Clone for modification (respects immutable option)
1004
- */
1005
- _cloneForModification() {
1006
- return this._options.immutable ? this.clone() : this;
1007
- }
1008
- /**
1009
- * Set locale for this period
1010
- */
1011
- locale(code) {
1012
- const period = this._cloneForModification();
1013
- period._locale = (0, locales_1.getLocale)(code);
1014
- return period;
1015
- }
1016
- // ============================================================================
1017
- // Static Helpers
1018
- // ============================================================================
1019
- /**
1020
- * Create a period for a specific month
1021
- */
1022
- static month(year, month) {
1023
- const start = chronos_1.Chronos.create(year, month, 1);
1024
- const end = start.endOf('month');
1025
- return new ChronosPeriod(start, end, { days: 1 });
1026
- }
1027
- /**
1028
- * Create a period for a specific year
1029
- */
1030
- static year(year) {
1031
- const start = chronos_1.Chronos.create(year, 1, 1);
1032
- const end = chronos_1.Chronos.create(year, 12, 31);
1033
- return new ChronosPeriod(start, end, { days: 1 });
1034
- }
1035
- /**
1036
- * Create a period for a specific quarter
1037
- */
1038
- static quarter(year, quarter) {
1039
- const startMonth = (quarter - 1) * 3 + 1;
1040
- const start = chronos_1.Chronos.create(year, startMonth, 1);
1041
- const end = start.endOf('quarter');
1042
- return new ChronosPeriod(start, end, { days: 1 });
1043
- }
1044
- /**
1045
- * Create a period between two dates as weekdays only
1046
- */
1047
- static weekdaysBetween(start, end) {
1048
- return ChronosPeriod.create(start, end).weekdays();
1049
- }
1050
- /**
1051
- * Create a period with business days only (weekdays, can add holidays filter)
1052
- */
1053
- static businessDays(start, end, holidays) {
1054
- let period = ChronosPeriod.create(start, end).weekdays();
1055
- if (holidays && holidays.length > 0) {
1056
- const holidayDates = holidays.map((h) => chronos_1.Chronos.parse(h).format('YYYY-MM-DD'));
1057
- period = period.filter((date) => !holidayDates.includes(date.format('YYYY-MM-DD')));
1058
- }
1059
- return period;
1060
- }
1061
- }
1062
- exports.ChronosPeriod = ChronosPeriod;