Passed
Push — master ( 1d62b4...50c2b7 )
by ignace nyamagana
02:09
created

Period::getDatepoint()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
crap 2
1
<?php
2
3
/**
4
 * League.Period (https://period.thephpleague.com).
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Period;
15
16
use DateInterval;
17
use DatePeriod;
18
use DateTimeImmutable;
19
use DateTimeInterface;
20
use DateTimeZone;
21
use JsonSerializable;
22
23
/**
24
 * A immutable value object class to manipulate Time interval.
25
 *
26
 * @package League.period
27
 * @author  Ignace Nyamagana Butera <[email protected]>
28
 * @since   1.0.0
29
 */
30
final class Period implements JsonSerializable
31
{
32
    private const ISO8601_FORMAT = 'Y-m-d\TH:i:s.u\Z';
33
34
    /**
35
     * The starting included datepoint.
36
     *
37
     * @var DateTimeImmutable
38
     */
39
    private $startDate;
40
41
    /**
42
     * The ending excluded datepoint.
43
     *
44
     * @var DateTimeImmutable
45
     */
46
    private $endDate;
47
48
    /**
49
     * @inheritdoc
50
     */
51 3
    public static function __set_state(array $interval)
52
    {
53 3
        return new self($interval['startDate'], $interval['endDate']);
54
    }
55
56
    /**
57
     * Creates new instance from a starting datepoint and a duration.
58
     */
59
    public static function after($datepoint, $duration): self
60
    {
61
        $datepoint = self::getDatepoint($datepoint);
62
63
        return new self($datepoint, $datepoint->add(self::getDuration($duration)));
64 255
    }
65
66 255
    /**
67 255
     * Returns a DateTimeImmutable instance.
68 255
     *
69 39
     * @param mixed $datepoint a Datepoint
70
     */
71 246
    private static function getDatepoint($datepoint): DateTimeImmutable
72 246
    {
73 246
        if ($datepoint instanceof DateTimeImmutable) {
74
            return $datepoint;
75
        }
76
77
        return Datepoint::create($datepoint);
78
    }
79
80
    /**
81
     * Returns a DateInterval instance.
82 282
     *
83
     * @param mixed $duration a Duration
84 282
     */
85 246
    private static function getDuration($duration): DateInterval
86
    {
87
        if ($duration instanceof DateInterval) {
88 222
            return $duration;
89 78
        }
90
91
        return Duration::create($duration);
92 153
    }
93
94
    /**
95
     * Creates new instance from a ending datepoint and a duration.
96
     */
97
    public static function before($datepoint, $duration): self
98
    {
99
        $datepoint = self::getDatepoint($datepoint);
100
        $duration = self::getDuration($duration);
101
102
        return new self($datepoint->sub($duration), $datepoint);
103
    }
104 6
105
    /**
106 6
     * Creates new instance where the given duration is simultaneously
107 3
     * substracted from and added to the datepoint.
108
     */
109
    public static function around($datepoint, $duration): self
110 3
    {
111
        $datepoint = self::getDatepoint($datepoint);
112
        $duration = self::getDuration($duration);
113
114
        return new self($datepoint->sub($duration), $datepoint->add($duration));
115
    }
116
117
    /**
118
     * Creates new instance for a specific year.
119
     */
120
    public static function fromYear(int $year): self
121
    {
122
        $startDate = (new Datepoint())->setDate($year, 1, 1)->setTime(0, 0);
123
124
        return new self($startDate, $startDate->add(new DateInterval('P1Y')));
125
    }
126
127
    /**
128 138
     * Creates new instance for a specific ISO year.
129
     */
130 138
    public static function fromIsoYear(int $year): self
131
    {
132 138
        return new self(
133
            (new Datepoint())->setISODate($year, 1)->setTime(0, 0),
134
            (new Datepoint())->setISODate(++$year, 1)->setTime(0, 0)
135
        );
136
    }
137
138
    /**
139
     * Creates new instance for a specific year and semester.
140
     */
141
    public static function fromSemester(int $year, int $semester = 1): self
142
    {
143
        $month = (($semester - 1) * 6) + 1;
144
        $startDate = (new Datepoint())->setDate($year, $month, 1)->setTime(0, 0);
145
146
        return new self($startDate, $startDate->add(new DateInterval('P6M')));
147
    }
148
149 177
    /**
150
     * Creates new instance for a specific year and quarter.
151 177
     */
152 27
    public static function fromQuarter(int $year, int $quarter = 1): self
153
    {
154
        $month = (($quarter - 1) * 3) + 1;
155 165
        $startDate = (new Datepoint())->setDate($year, $month, 1)->setTime(0, 0);
156 30
157
        return new self($startDate, $startDate->add(new DateInterval('P3M')));
158
    }
159 153
160
    /**
161
     * Creates new instance for a specific year and month.
162
     */
163
    public static function fromMonth(int $year, int $month = 1): self
164
    {
165
        $startDate = (new Datepoint())->setDate($year, $month, 1)->setTime(0, 0);
166
167
        return new self($startDate, $startDate->add(new DateInterval('P1M')));
168
    }
169
170
    /**
171
     * Creates new instance for a specific ISO8601 week.
172
     */
173
    public static function fromIsoWeek(int $year, int $week = 1): self
174
    {
175
        $startDate = (new Datepoint())->setISODate($year, $week, 1)->setTime(0, 0);
176
177 24
        return new self($startDate, $startDate->add(new DateInterval('P7D')));
178
    }
179 24
180
    /**
181 24
     * Creates new instance for a specific year, month and day.
182
     */
183
    public static function fromDay(int $year, int $month = 1, int $day = 1): self
184
    {
185
        $startDate = (new Datepoint())->setDate($year, $month, $day)->setTime(0, 0);
186
187
        return new self($startDate, $startDate->add(new DateInterval('P1D')));
188
    }
189
190
    /**
191 18
     * Creates new instance from a DatePeriod.
192
     */
193 18
    public static function fromDatePeriod(DatePeriod $datePeriod): self
194 15
    {
195
        return new self($datePeriod->getStartDate(), $datePeriod->getEndDate());
196 15
    }
197
198
    /**
199 6
     * Creates a new instance.
200
     *
201 6
     * @param mixed $startDate the starting included datepoint
202
     * @param mixed $endDate   the ending excluded datepoint
203
     *
204
     * @throws Exception If $startDate is greater than $endDate
205
     */
206
    public function __construct($startDate, $endDate)
207
    {
208
        $startDate = self::getDatepoint($startDate);
209
        $endDate = self::getDatepoint($endDate);
210
        if ($startDate > $endDate) {
211
            throw new Exception('The ending datepoint must be greater or equal to the starting datepoint');
212
        }
213 21
        $this->startDate = $startDate;
214
        $this->endDate = $endDate;
215 21
    }
216
217
    /**
218
     * Returns the starting included datepoint.
219
     */
220
    public function getStartDate(): DateTimeImmutable
221
    {
222
        return $this->startDate;
223
    }
224
225
    /**
226 12
     * Returns the ending excluded datepoint.
227
     */
228 12
    public function getEndDate(): DateTimeImmutable
229 3
    {
230 3
        return $this->endDate;
231 3
    }
232
233 3
    /**
234
     * Returns the instance duration as expressed in seconds.
235
     */
236 12
    public function getTimestampInterval(): float
237 6
    {
238
        return $this->endDate->getTimestamp() - $this->startDate->getTimestamp();
239 6
    }
240
241
    /**
242
     * Returns the instance duration as a DateInterval object.
243
     */
244
    public function getDateInterval(): DateInterval
245
    {
246
        return $this->startDate->diff($this->endDate);
247
    }
248
249
    /**
250
     * Allows iteration over a set of dates and times,
251
     * recurring at regular intervals, over the instance.
252
     *
253 75
     * @see http://php.net/manual/en/dateperiod.construct.php
254
     */
255 75
    public function getDatePeriod($duration, int $option = 0): DatePeriod
256 75
    {
257 51
        $duration = self::getDuration($duration);
258
259
        return new DatePeriod($this->startDate, $duration, $this->endDate, $option);
260 24
    }
261
262
    /**
263
     * Allows iteration over a set of dates and times,
264
     * recurring at regular intervals, over the instance backwards starting from
265
     * the instance ending datepoint.
266
     */
267
    public function getDatePeriodBackwards($duration, int $option = 0): iterable
268
    {
269
        $duration = self::getDuration($duration);
270
        $date = $this->endDate;
271 12
        if ((bool) ($option & DatePeriod::EXCLUDE_START_DATE)) {
272
            $date = $this->endDate->sub($duration);
273 12
        }
274 3
275 3
        while ($date > $this->startDate) {
276 3
            yield $date;
277
            $date = $date->sub($duration);
278 3
        }
279
    }
280
281 12
    /**
282 6
     * Returns the string representation as a ISO8601 interval format.
283
     *
284 6
     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
285
     *
286
     * @return string
287
     */
288
    public function __toString()
289
    {
290
        $interval = $this->jsonSerialize();
291
292
        return $interval['startDate'].'/'.$interval['endDate'];
293
    }
294
295 39
    /**
296
     * Returns the JSON representation of an instance.
297 39
     *
298 6
     * Based on the JSON representation of dates as
299
     * returned by Javascript Date.toJSON() method.
300 6
     *
301
     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON
302
     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
303 36
     *
304
     * @return array<string>
305 30
     */
306
    public function jsonSerialize()
307
    {
308
        $utc = new DateTimeZone('UTC');
309
310
        return [
311
            'startDate' => $this->startDate->setTimezone($utc)->format(self::ISO8601_FORMAT),
312
            'endDate' => $this->endDate->setTimezone($utc)->format(self::ISO8601_FORMAT),
313
        ];
314
    }
315
316 24
    /**
317
     * Returns the mathematical representation of an instance as a left close, right open interval.
318 24
     *
319 3
     * @see https://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals
320 3
     * @see https://php.net/manual/en/function.date.php
321 3
     * @see https://www.postgresql.org/docs/9.3/static/rangetypes.html
322 3
     *
323
     * @param string $format the format of the outputted date string
324
     */
325 3
    public function format(string $format): string
326
    {
327
        return '['.$this->startDate->format($format).', '.$this->endDate->format($format).')';
328 24
    }
329 24
330 18
    /**
331
     * Compares two instances according to their duration.
332
     *
333 18
     * Returns:
334
     * <ul>
335
     * <li> -1 if the current Interval is lesser than the submitted Interval object</li>
336
     * <li>  1 if the current Interval is greater than the submitted Interval object</li>
337
     * <li>  0 if both Interval objects have the same duration</li>
338
     * </ul>
339
     */
340
    public function durationCompare(self $interval): int
341
    {
342
        return $this->endDate <=> $this->startDate->add($interval->getDateInterval());
343
    }
344
345
    /**
346 3
     * Tells whether the current instance duration is equal to the submitted one.
347
     */
348 3
    public function durationEquals(self $interval): bool
349
    {
350 3
        return 0 === $this->durationCompare($interval);
351
    }
352
353
    /**
354
     * Tells whether the current instance duration is greater than the submitted one.
355
     */
356
    public function durationGreaterThan(self $interval): bool
357
    {
358
        return 1 === $this->durationCompare($interval);
359
    }
360
361
    /**
362
     * Tells whether the current instance duration is less than the submitted one.
363 3
     */
364
    public function durationLessThan(self $interval): bool
365 3
    {
366
        return -1 === $this->durationCompare($interval);
367 3
    }
368
369
    /**
370
     * Tells whether two intervals share the same datepoints.
371
     *
372
     * [--------------------)
373
     * [--------------------)
374
     */
375
    public function equals(self $interval): bool
376
    {
377
        return $this->startDate == $interval->startDate
378
            && $this->endDate == $interval->endDate;
379
    }
380 3
381
    /**
382 3
     * Tells whether two intervals abuts.
383
     *
384 3
     * [--------------------)
385
     *                      [--------------------)
386
     * or
387
     *                      [--------------------)
388
     * [--------------------)
389
     */
390
    public function abuts(self $interval): bool
391
    {
392
        return $this->startDate == $interval->endDate
393
            || $this->endDate == $interval->startDate;
394
    }
395
396
    /**
397 3
     * Tells whether two intervals overlaps.
398
     *
399 3
     * [--------------------)
400
     *          [--------------------)
401 3
     */
402
    public function overlaps(self $interval): bool
403
    {
404
        return $this->startDate < $interval->endDate
405
            && $this->endDate > $interval->startDate;
406
    }
407
408
    /**
409
     * Tells whether an interval is entirely after the specified index.
410
     * The index can be a DateTimeInterface object or another Period object.
411
     *
412 177
     *                          [--------------------)
413
     * [--------------------)
414 177
     */
415
    public function isAfter($index): bool
416
    {
417
        if ($index instanceof self) {
418
            return $this->startDate >= $index->endDate;
419
        }
420
421
        return $this->startDate > self::getDatepoint($index);
422
    }
423
424
    /**
425 162
     * Tells whether an instance is entirely before the specified index.
426
     *
427 162
     * The index can be a DateTimeInterface object or another Period object.
428
     *
429
     * [--------------------)
430
     *                          [--------------------)
431
     */
432
    public function isBefore($index): bool
433
    {
434
        if ($index instanceof self) {
435 18
            return $this->endDate <= $index->startDate;
436
        }
437 18
438
        return $this->endDate <= self::getDatepoint($index);
439
    }
440
441
    /**
442
     * Tells whether an instance fully contains the specified index.
443
     *
444
     * The index can be a DateTimeInterface object or another Period object.
445 33
     *
446
     */
447 33
    public function contains($index): bool
448
    {
449
        if ($index instanceof self) {
450
            return $this->containsInterval($index);
451
        }
452
453
        return $this->containsDatepoint(self::getDatepoint($index));
454
    }
455
456
    /**
457
     * Tells whether an instance fully contains another instance.
458
     *
459
     * [--------------------)
460
     *     [----------)
461
     */
462
    private function containsInterval(self $interval): bool
463
    {
464
        return $this->containsDatepoint($interval->startDate)
465
            && ($interval->endDate >= $this->startDate && $interval->endDate <= $this->endDate);
466
    }
467
468
    /**
469 24
     * Tells whether an instance contains a datepoint.
470
     *
471 24
     * [------|------------)
472
     */
473
    private function containsDatepoint(DateTimeInterface $datepoint): bool
474
    {
475
        return $datepoint >= $this->startDate && $datepoint < $this->endDate;
476
    }
477
478
    /**
479
     * Allows splitting an instance in smaller Period objects according to a given interval.
480
     *
481
     * The returned iterable Interval set is ordered so that:
482
     * <ul>
483
     * <li>The first returned object MUST share the starting datepoint of the parent object.</li>
484
     * <li>The last returned object MUST share the ending datepoint of the parent object.</li>
485
     * <li>The last returned object MUST have a duration equal or lesser than the submitted interval.</li>
486
     * <li>All returned objects except for the first one MUST start immediately after the previously returned object</li>
487
     * </ul>
488
     *
489
     * @return iterable<Period>
490
     */
491
    public function split($duration): iterable
492
    {
493
        $duration = self::getDuration($duration);
494
        foreach ($this->getDatePeriod($duration) as $startDate) {
495
            $endDate = $startDate->add($duration);
496
            if ($endDate > $this->endDate) {
497 12
                $endDate = $this->endDate;
498
            }
499 12
500 12
            yield new self($startDate, $endDate);
501
        }
502 12
    }
503 12
504 6
    /**
505
     * Allows splitting an instance in smaller Period objects according to a given interval.
506 12
     *
507
     * The returned iterable Period set is ordered so that:
508 9
     * <ul>
509 9
     * <li>The first returned object MUST share the ending datepoint of the parent object.</li>
510 9
     * <li>The last returned object MUST share the starting datepoint of the parent object.</li>
511
     * <li>The last returned object MUST have a duration equal or lesser than the submitted interval.</li>
512
     * <li>All returned objects except for the first one MUST end immediately before the previously returned object</li>
513
     * </ul>
514
     *
515
     * @return iterable<Period>
516
     */
517
    public function splitBackwards($duration): iterable
518
    {
519
        $endDate = $this->endDate;
520
        $duration = self::getDuration($duration);
521
        do {
522
            $startDate = $endDate->sub($duration);
523
            if ($startDate < $this->startDate) {
524
                $startDate = $this->startDate;
525
            }
526
            yield new self($startDate, $endDate);
527
528
            $endDate = $startDate;
529
        } while ($endDate > $this->startDate);
530
    }
531
532
    /**
533
     * Returns the computed intersection between two instances as a new instance.
534
     *
535 6
     * [--------------------)
536
     *          ∩
537 6
     *                 [----------)
538 6
     *          =
539
     *                 [----)
540 6
     *
541 6
     * @throws Exception If both objects do not overlaps
542 3
     */
543
    public function intersect(self $interval): self
544 6
    {
545
        if (!$this->overlaps($interval)) {
546 6
            throw new Exception('Both '.self::class.' objects should overlaps');
547 6
        }
548 6
549
        return new self(
550
            ($interval->startDate > $this->startDate) ? $interval->startDate : $this->startDate,
551
            ($interval->endDate < $this->endDate) ? $interval->endDate : $this->endDate
552
        );
553
    }
554
555
    /**
556
     * Returns the computed difference between two overlapping instances as
557
     * an array containing Period objects or the null value.
558 3
     *
559
     * The array will always contains 2 elements:
560 3
     *
561
     * <ul>
562 3
     * <li>an NULL filled array if both objects have the same datepoints</li>
563
     * <li>one Period object and NULL if both objects share one datepoint</li>
564
     * <li>two Period objects if both objects share no datepoint</li>
565
     * </ul>
566
     *
567
     * [--------------------)
568
     *          \
569
     *                [-----------)
570
     *          =
571
     * [--------------)  +  [-----)
572
     *
573 6
     * @return array<null|Period>
574
     */
575 6
    public function diff(self $interval): array
576 6
    {
577 6
        if ($interval->equals($this)) {
578
            return [null, null];
579
        }
580 6
581 6
        $intersect = $this->intersect($interval);
582
        $merge = $this->merge($interval);
583
        if ($merge->startDate == $intersect->startDate) {
584
            return [$merge->startingOn($intersect->endDate), null];
585
        }
586
587
        if ($merge->endDate == $intersect->endDate) {
588
            return [$merge->endingOn($intersect->startDate), null];
589
        }
590
591
        return [
592
            $merge->endingOn($intersect->startDate),
593
            $merge->startingOn($intersect->endDate),
594
        ];
595
    }
596
597
    /**
598
     * Returns the computed gap between two instances as a new instance.
599 21
     *
600
     * [--------------------)
601 21
     *          +
602
     *                          [----------)
603
     *          =
604
     *                      [---)
605
     *
606
     * @throws Exception If both instance overlaps
607
     */
608
    public function gap(self $interval): self
609
    {
610
        if ($this->overlaps($interval)) {
611
            throw new Exception('Both '.self::class.' objects must not overlaps');
612 9
        }
613
614 9
        if ($interval->startDate > $this->startDate) {
615
            return new self($this->endDate, $interval->startDate);
616
        }
617
618
        return new self($interval->endDate, $this->startDate);
619
    }
620
621
    /**
622
     * Returns the difference between two instances expressed in seconds.
623
     */
624
    public function timestampIntervalDiff(self $interval): float
625 9
    {
626
        return $this->getTimestampInterval() - $interval->getTimestampInterval();
627 9
    }
628
629
    /**
630
     * Returns the difference between two instances expressed with a DateInterval object.
631
     */
632
    public function dateIntervalDiff(self $interval): DateInterval
633
    {
634
        return $this->endDate->diff($this->startDate->add($interval->getDateInterval()));
635
    }
636
637
    /**
638 3
     * Returns an instance with the specified starting datepoint.
639
     *
640 3
     * This method MUST retain the state of the current instance, and return
641
     * an instance that contains the specified starting datepoint.
642
     *
643
     * @param mixed $startDate the new starting datepoint
644
     */
645
    public function startingOn($startDate): self
646
    {
647
        $startDate = self::getDatepoint($startDate);
648
        if ($startDate == $this->startDate) {
649
            return $this;
650 18
        }
651
652 18
        return new self($startDate, $this->endDate);
653 18
    }
654
655
    /**
656
     * Returns an instance with the specified ending datepoint.
657
     *
658
     * This method MUST retain the state of the current instance, and return
659
     * an instance that contains the specified ending datepoint.
660
     *
661
     * @param mixed $endDate the new ending datepoint
662
     */
663 42
    public function endingOn($endDate): self
664
    {
665 42
        $endDate = self::getDatepoint($endDate);
666 42
        if ($endDate == $this->endDate) {
667
            return $this;
668
        }
669
670
        return new self($this->startDate, $endDate);
671
    }
672
673
    /**
674
     * Returns a new instance with a new ending datepoint.
675
     *
676 36
     * This method MUST retain the state of the current instance, and return
677
     * an instance that contains the specified ending datepoint.
678 36
     */
679 36
    public function withDurationAfterStart($duration): self
680 36
    {
681
        return $this->endingOn($this->startDate->add(self::getDuration($duration)));
682
    }
683
684
    /**
685
     * Returns a new instance with a new starting datepoint.
686
     *
687
     * This method MUST retain the state of the current instance, and return
688
     * an instance that contains the specified starting datepoint.
689
     */
690
    public function withDurationBeforeEnd($duration): self
691
    {
692
        return $this->startingOn($this->endDate->sub(self::getDuration($duration)));
693
    }
694
695
    /**
696
     * Returns a new instance with a new starting datepoint
697 9
     * moved forward or backward by the given interval.
698
     *
699 9
     * This method MUST retain the state of the current instance, and return
700 3
     * an instance that contains the specified starting datepoint.
701
     */
702
    public function moveStartDate($duration): self
703 6
    {
704
        return $this->startingOn($this->startDate->add(self::getDuration($duration)));
705
    }
706
707
    /**
708
     * Returns a new instance with a new ending datepoint
709
     * moved forward or backward by the given interval.
710
     *
711
     * This method MUST retain the state of the current instance, and return
712
     * an instance that contains the specified ending datepoint.
713
     */
714
    public function moveEndDate($duration): self
715
    {
716
        return $this->endingOn($this->endDate->add(self::getDuration($duration)));
717
    }
718
719
    /**
720 12
     * Returns a new instance where the datepoints
721
     * are moved forwards or backward simultaneously by the given DateInterval.
722 12
     *
723 6
     * This method MUST retain the state of the current instance, and return
724
     * an instance that contains the specified new datepoints.
725
     */
726 6
    public function move($duration): self
727
    {
728
        $duration = self::getDuration($duration);
729
        $interval = new self($this->startDate->add($duration), $this->endDate->add($duration));
730
        if ($this->equals($interval)) {
731
            return $this;
732
        }
733
734
        return $interval;
735
    }
736
737
    /**
738
     * Returns an instance where the given DateInterval is simultaneously
739
     * substracted from the starting datepoint and added to the ending datepoint.
740
     *
741
     * Depending on the duration value, the resulting instance duration will be expanded or shrinked.
742
     *
743
     * This method MUST retain the state of the current instance, and return
744 24
     * an instance that contains the specified new datepoints.
745
     */
746 24
    public function expand($duration): self
747 9
    {
748
        $duration = self::getDuration($duration);
749
        $interval = new self($this->startDate->sub($duration), $this->endDate->add($duration));
750 24
        if ($this->equals($interval)) {
751
            return $this;
752
        }
753
754
        return $interval;
755
    }
756
757
    /**
758
     * Merges one or more instances to return a new instance.
759
     * The resulting instance represents the largest duration possible.
760
     *
761 9
     * This method MUST retain the state of the current instance, and return
762
     * an instance that contains the specified new datepoints.
763 9
     *
764 9
     * [--------------------)
765
     *          U
766
     *                 [----------)
767
     *          =
768
     * [--------------------------)
769
     *
770
     *
771
     * @param Period ...$intervals
772
     */
773
    public function merge(self $interval, self ...$intervals): self
774
    {
775 24
        $intervals[] = $interval;
776
        $carry = $this;
777 24
        foreach ($intervals as $interval) {
778
            if ($carry->startDate > $interval->startDate) {
779 24
                $carry = $carry->startingOn($interval->startDate);
780 24
            }
781
782
            if ($carry->endDate < $interval->endDate) {
783
                $carry = $carry->endingOn($interval->endDate);
784
            }
785
        }
786
787
        return $carry;
788
    }
789
}
790