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