Completed
Pull Request — master (#53)
by BENOIT
03:19
created

Period::add()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * League.Period (http://period.thephpleague.com)
4
 *
5
 * @package   League.period
6
 * @author    Ignace Nyamagana Butera <[email protected]>
7
 * @copyright 2014-2015 Ignace Nyamagana Butera
8
 * @license   https://github.com/thephpleague/period/blob/master/LICENSE (MIT License)
9
 * @version   3.3.0
10
 * @link      https://github.com/thephpleague/period/
11
 */
12
namespace League\Period;
13
14
use DateInterval;
15
use DatePeriod;
16
use DateTime;
17
use DateTimeImmutable;
18
use DateTimeInterface;
19
use DateTimeZone;
20
use Generator;
21
use InvalidArgumentException;
22
use JsonSerializable;
23
use LogicException;
24
use OutOfRangeException;
25
26
/**
27
 * A immutable value object class to manipulate Time Range.
28
 *
29
 * @package League.period
30
 * @author  Ignace Nyamagana Butera <[email protected]>
31
 * @since   1.0.0
32
 */
33
class Period implements JsonSerializable
34
{
35
    /**
36
     * DateTime Format to create ISO8601 Interval format
37
     *
38
     * @internal
39
     */
40
    const DATE_ISO8601 = 'Y-m-d\TH:i:s\Z';
41
42
    /**
43
     * Date Format for timezoneless DateTimeInterface
44
     *
45
     * @internal
46
     */
47
    const DATE_LOCALE = 'Y-m-d H:i:s.u';
48
49
    /**
50
     * Period starting included date point.
51
     *
52
     * @var DateTimeImmutable
53
     */
54
    protected $startDate;
55
56
    /**
57
     * Period ending excluded date point.
58
     *
59
     * @var DateTimeImmutable
60
     */
61
    protected $endDate;
62
63
    /**
64
     * Create a new instance.
65
     *
66
     * @param DateTimeInterface|string $startDate starting date point
67
     * @param DateTimeInterface|string $endDate   ending date point
68
     *
69
     * @throws LogicException If $startDate is greater than $endDate
70
     */
71 267
    public function __construct($startDate, $endDate)
72
    {
73 267
        $startDate = static::filterDatePoint($startDate);
74 267
        $endDate = static::filterDatePoint($endDate);
75 267
        if ($startDate > $endDate) {
76 39
            throw new LogicException(
77 26
                'The ending datepoint must be greater or equal to the starting datepoint'
78 13
            );
79
        }
80 258
        $this->startDate = $startDate;
81 258
        $this->endDate = $endDate;
82 258
    }
83
84
    /**
85
     * Validate a DateTime.
86
     *
87
     * @param DateTimeInterface|string $datetime
88
     *
89
     * @return DateTimeImmutable
90
     */
91 294
    protected static function filterDatePoint($datetime)
92
    {
93 294
        if ($datetime instanceof DateTimeImmutable) {
94 261
            return $datetime;
95
        }
96
97 246
        if ($datetime instanceof DateTime) {
98 78
            return static::convertDateTime($datetime);
99
        }
100
101 177
        return new DateTimeImmutable($datetime);
102
    }
103
104
    /**
105
     * Convert a DateTime object into a DateTimeImmutable object
106
     *
107
     * @param DateTime $datetime
108
     *
109
     * @return DateTimeImmutable
110
     */
111 78
    protected static function convertDateTime(DateTime $datetime)
112
    {
113 78
        static $useFromMutable;
114
115 78
        if (null === $useFromMutable) {
116 3
            $useFromMutable = method_exists(new DateTimeImmutable(), 'createFromMutable');
117 1
        }
118
119 78
        if ($useFromMutable) {
120 78
            return DateTimeImmutable::createFromMutable($datetime);
121
        }
122
123
        return new DateTimeImmutable($datetime->format(self::DATE_LOCALE), $datetime->getTimeZone());
124
    }
125
126
    /**
127
     * @inheritdoc
128
     */
129 3
    public static function __set_state(array $period)
130
    {
131 3
        return new static($period['startDate'], $period['endDate']);
132
    }
133
134
    /**
135
     * Create a Period object for a specific day
136
     *
137
     * The date is truncated so that the Time range starts at midnight according to the date timezone.
138
     * The duration is equivalent to one full day.
139
     *
140
     * @param DateTimeInterface|string $day
141
     *
142
     * @return static
143
     */
144 9
    public static function createFromDay($day)
145
    {
146 9
        $date = static::filterDatePoint($day);
147
148 9
        $startDate = $date->createFromFormat(
149 9
            self::DATE_LOCALE,
150 9
            $date->format('Y-m-d').' 00:00:00.000000',
151 9
            $date->getTimeZone()
152 3
        );
153
154 9
        return new static($startDate, $startDate->add(new DateInterval('P1D')));
155
    }
156
157
    /**
158
     * Create a Period object from a starting point and an interval.
159
     *
160
     * The interval can be
161
     * <ul>
162
     * <li>a DateInterval object</li>
163
     * <li>an int interpreted as the duration expressed in seconds.</li>
164
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
165
     * </ul>
166
     *
167
     * @param DateTimeInterface|string $startDate The start date point
168
     * @param mixed                    $interval  The interval
169
     *
170
     * @return static
171
     */
172 159
    public static function createFromDuration($startDate, $interval)
173
    {
174 159
        $startDate = static::filterDatePoint($startDate);
175
176 159
        return new static($startDate, $startDate->add(static::filterDateInterval($interval)));
177
    }
178
179
    /**
180
     * Validate a DateInterval.
181
     *
182
     * The interval can be
183
     * <ul>
184
     * <li>a DateInterval object</li>
185
     * <li>an int interpreted as the duration expressed in seconds.</li>
186
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
187
     * </ul>
188
     *
189
     * @param mixed $interval The interval
190
     *
191
     * @return DateInterval
192
     */
193 204
    protected static function filterDateInterval($interval)
194
    {
195 204
        if ($interval instanceof DateInterval) {
196 42
            return $interval;
197
        }
198
199 192
        if (false !== ($res = filter_var($interval, FILTER_VALIDATE_INT))) {
200 33
            return new DateInterval('PT'.$res.'S');
201
        }
202
203 180
        return DateInterval::createFromDateString($interval);
204
    }
205
206
    /**
207
     * Create a Period object from a ending datepoint and an interval.
208
     *
209
     * The interval can be
210
     * <ul>
211
     * <li>a DateInterval object</li>
212
     * <li>an int interpreted as the duration expressed in seconds.</li>
213
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
214
     * </ul>
215
     *
216
     * @param DateTimeInterface|string $endDate  The start date point
217
     * @param mixed                    $interval The interval
218
     *
219
     * @return static
220
     */
221 24
    public static function createFromDurationBeforeEnd($endDate, $interval)
222
    {
223 24
        $endDate = static::filterDatePoint($endDate);
224
225 24
        return new static($endDate->sub(static::filterDateInterval($interval)), $endDate);
0 ignored issues
show
Security Bug introduced by
It seems like $endDate->sub(static::fi...ateInterval($interval)) targeting DateTimeImmutable::sub() can also be of type false; however, League\Period\Period::__construct() does only seem to accept object<DateTimeInterface>|string, did you maybe forget to handle an error condition?
Loading history...
226
    }
227
228
    /**
229
     * Create a Period object for a specific week
230
     *
231
     * @param int $year
232
     * @param int $week index from 1 to 53
233
     *
234
     * @return static
235
     */
236 27
    public static function createFromWeek($year, $week)
237
    {
238 27
        $week = static::validateYear($year).'W'.sprintf('%02d', static::validateRange($week, 1, 53));
239 18
        $startDate = new DateTimeImmutable($week);
240
241 18
        return new static($startDate, $startDate->add(new DateInterval('P1W')));
242
    }
243
244
    /**
245
     * Validate a year.
246
     *
247
     * @param int $year
248
     *
249
     * @throws InvalidArgumentException If year is not a valid int
250
     *
251
     * @return int
252
     */
253 84
    protected static function validateYear($year)
254
    {
255 84
        $year = filter_var($year, FILTER_VALIDATE_INT);
256 84
        if (false === $year) {
257 15
            throw new InvalidArgumentException('A Year must be a valid int');
258
        }
259
260 69
        return $year;
261
    }
262
263
    /**
264
     * Validate a int according to a range.
265
     *
266
     * @param int $value the value to validate
267
     * @param int $min   the minimum value
268
     * @param int $max   the maximal value
269
     *
270
     * @throws OutOfRangeException If the value is not in the range
271
     *
272
     * @return int
273
     */
274 84
    protected static function validateRange($value, $min, $max)
275
    {
276 84
        $res = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min, 'max_range' => $max]]);
277 84
        if (false === $res) {
278 24
            throw new OutOfRangeException('the submitted value is not contained within the valid range');
279
        }
280
281 60
        return $res;
282
    }
283
284
    /**
285
     * Create a Period object for a specific month
286
     *
287
     * @param int $year
288
     * @param int $month Month index from 1 to 12
289
     *
290
     * @return static
291
     */
292 36
    public static function createFromMonth($year, $month)
293
    {
294 36
        return static::createFromYearInterval(1, $year, $month);
295
    }
296
297
    /**
298
     * Create a Period object for a specific interval in a given year
299
     *
300
     * @param int $interval
301
     * @param int $year
302
     * @param int $index
303
     *
304
     * @return static
305
     */
306 60
    protected static function createFromYearInterval($interval, $year, $index)
307
    {
308 60
        $month = sprintf('%02s', ((static::validateRange($index, 1, 12 / $interval) - 1) * $interval) + 1);
309 42
        $startDate = new DateTimeImmutable(static::validateYear($year).'-'.$month.'-01');
310
311 33
        return new static($startDate, $startDate->add(new DateInterval('P'.$interval.'M')));
312
    }
313
314
    /**
315
     * Create a Period object for a specific quarter
316
     *
317
     * @param int $year
318
     * @param int $quarter Quarter Index from 1 to 4
319
     *
320
     * @return static
321
     */
322 12
    public static function createFromQuarter($year, $quarter)
323
    {
324 12
        return static::createFromYearInterval(3, $year, $quarter);
325
    }
326
327
    /**
328
     * Create a Period object for a specific semester
329
     *
330
     * @param int $year
331
     * @param int $semester Semester Index from 1 to 2
332
     *
333
     * @return static
334
     */
335 12
    public static function createFromSemester($year, $semester)
336
    {
337 12
        return static::createFromYearInterval(6, $year, $semester);
338
    }
339
340
    /**
341
     * Create a Period object for a specific Year
342
     *
343
     * @param int $year
344
     *
345
     * @return static
346
     */
347 15
    public static function createFromYear($year)
348
    {
349 15
        $startDate = new DateTimeImmutable(static::validateYear($year).'-01-01');
350
351 12
        return new static($startDate, $startDate->add(new DateInterval('P1Y')));
352
    }
353
354
    /**
355
     * String representation of a Period using ISO8601 Time interval format
356
     *
357
     * @return string
358
     */
359 3
    public function __toString()
360
    {
361 3
        $utc = new DateTimeZone('UTC');
362
363 3
        return $this->startDate->setTimeZone($utc)->format(self::DATE_ISO8601)
364 3
            .'/'.$this->endDate->setTimeZone($utc)->format(self::DATE_ISO8601);
365
    }
366
367
    /**
368
     * implement JsonSerializable interface
369
     *
370
     * @return DateTime[]
371
     */
372 3
    public function jsonSerialize()
373
    {
374
        return [
375 3
            'startDate' => new DateTime(
376 3
                $this->startDate->format(self::DATE_LOCALE),
377 3
                $this->startDate->getTimeZone()
378 1
            ),
379 3
            'endDate' => new DateTime(
380 3
                $this->endDate->format(self::DATE_LOCALE),
381 3
                $this->endDate->getTimeZone()
382 1
            ),
383 1
        ];
384
    }
385
386
    /**
387
     * Returns the starting date point.
388
     *
389
     * @return DateTimeImmutable
390
     */
391 183
    public function getStartDate()
392
    {
393 183
        return $this->startDate;
394
    }
395
396
    /**
397
     * Returns the ending datepoint.
398
     *
399
     * @return DateTimeImmutable
400
     */
401 168
    public function getEndDate()
402
    {
403 168
        return $this->endDate;
404
    }
405
406
    /**
407
     * Returns the Period duration as expressed in seconds
408
     *
409
     * @return float
410
     */
411 18
    public function getTimestampInterval()
412
    {
413 18
        return $this->endDate->getTimestamp() - $this->startDate->getTimestamp();
414
    }
415
416
    /**
417
     * Returns the Period duration as a DateInterval object.
418
     *
419
     * @return DateInterval
420
     */
421 42
    public function getDateInterval()
422
    {
423 42
        return $this->startDate->diff($this->endDate);
424
    }
425
426
    /**
427
     * Allows iteration over a set of dates and times,
428
     * recurring at regular intervals, over the Period object.
429
     *
430
     * The interval can be
431
     * <ul>
432
     * <li>a DateInterval object</li>
433
     * <li>an int interpreted as the duration expressed in seconds.</li>
434
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
435
     * </ul>
436
     *
437
     * @param DateInterval|int|string $interval The interval
438
     *
439
     * @param int $option can be set to DatePeriod::EXCLUDE_START_DATE
440
     *                    to exclude the start date from the set of
441
     *                    recurring dates within the period.
442
     *
443
     * @return DatePeriod
444
     */
445 27
    public function getDatePeriod($interval, $option = 0)
446
    {
447 27
        return new DatePeriod($this->startDate, static::filterDateInterval($interval), $this->endDate, $option);
448
    }
449
450
    /**
451
     * Tells whether two Period share the same datepoints.
452
     *
453
     * @param Period $period
454
     *
455
     * @return bool
456
     */
457 18
    public function sameValueAs(Period $period)
458
    {
459 18
        return $this->startDate == $period->getStartDate() && $this->endDate == $period->getEndDate();
460
    }
461
462
    /**
463
     * Tells whether two Period object abuts
464
     *
465
     * @param Period $period
466
     *
467
     * @return bool
468
     */
469 45
    public function abuts(Period $period)
470
    {
471 45
        return $this->startDate == $period->getEndDate() || $this->endDate == $period->getStartDate();
472
    }
473
474
    /**
475
     * Tells whether two Period objects overlaps.
476
     *
477
     * @param Period $period
478
     *
479
     * @return bool
480
     */
481 39
    public function overlaps(Period $period)
482
    {
483 39
        if ($this->abuts($period)) {
484 6
            return false;
485
        }
486
487 33
        return $this->startDate < $period->getEndDate() && $this->endDate > $period->getStartDate();
488
    }
489
490
    /**
491
     * Tells whether a Period is entirely after the specified index
492
     *
493
     * @param Period|DateTimeInterface|string $index
494
     *
495
     * @return bool
496
     */
497 12
    public function isAfter($index)
498
    {
499 12
        if ($index instanceof Period) {
500 6
            return $this->startDate >= $index->getEndDate();
501
        }
502
503 6
        return $this->startDate > static::filterDatePoint($index);
504
    }
505
506
    /**
507
     * Tells whether a Period is entirely before the specified index
508
     *
509
     * @param Period|DateTimeInterface|string $index
510
     *
511
     * @return bool
512
     */
513 12
    public function isBefore($index)
514
    {
515 12
        if ($index instanceof Period) {
516 6
            return $this->endDate <= $index->getStartDate();
517
        }
518
519 6
        return $this->endDate <= static::filterDatePoint($index);
520
    }
521
522
    /**
523
     * Tells whether the specified index is fully contained within
524
     * the current Period object.
525
     *
526
     * @param Period|DateTimeInterface|string $index
527
     *
528
     * @return bool
529
     */
530 24
    public function contains($index)
531
    {
532 24
        if ($index instanceof Period) {
533 9
            return $this->containsPeriod($index);
534
        }
535
536 24
        return $this->containsDatePoint($index);
537
    }
538
539
    /**
540
     * Tells whether a Period object is fully contained within
541
     * the current Period object.
542
     *
543
     * @param Period $period
544
     *
545
     * @return bool
546
     */
547 9
    protected function containsPeriod(Period $period)
548
    {
549 9
        $endDate = $period->getEndDate();
550
551 9
        return $this->contains($period->getStartDate())
552 9
            && ($endDate >= $this->startDate && $endDate <= $this->endDate);
553
    }
554
555
    /**
556
     * Tells whether a datepoint is fully contained within
557
     * the current Period object.
558
     *
559
     * @param DateTimeInterface|string $datepoint
560
     *
561
     * @return bool
562
     */
563 24
    protected function containsDatePoint($datepoint)
564
    {
565 24
        $datetime = static::filterDatePoint($datepoint);
566
567 24
        return ($datetime >= $this->startDate && $datetime < $this->endDate)
568 24
            || ($datetime == $this->startDate && $datetime == $this->endDate);
569
    }
570
571
    /**
572
     * Compares two Period objects according to their duration.
573
     *
574
     * @param Period $period
575
     *
576
     * @return int
577
     */
578 21
    public function compareDuration(Period $period)
579
    {
580 21
        $datetime = $this->startDate->add($period->getDateInterval());
581 21
        if ($this->endDate > $datetime) {
582 9
            return 1;
583
        }
584
585 12
        if ($this->endDate < $datetime) {
586 9
            return -1;
587
        }
588
589 3
        return 0;
590
    }
591
592
    /**
593
     * Tells whether the current Period object duration
594
     * is greater than the submitted one.
595
     *
596
     * @param Period $period
597
     *
598
     * @return bool
599
     */
600 9
    public function durationGreaterThan(Period $period)
601
    {
602 9
        return 1 === $this->compareDuration($period);
603
    }
604
605
    /**
606
     * Tells whether the current Period object duration
607
     * is less than the submitted one.
608
     *
609
     * @param Period $period
610
     *
611
     * @return bool
612
     */
613 9
    public function durationLessThan(Period $period)
614
    {
615 9
        return -1 === $this->compareDuration($period);
616
    }
617
618
    /**
619
     * Tells whether the current Period object duration
620
     * is equal to the submitted one
621
     *
622
     * @param Period $period
623
     *
624
     * @return bool
625
     */
626 3
    public function sameDurationAs(Period $period)
627
    {
628 3
        return 0 === $this->compareDuration($period);
629
    }
630
631
    /**
632
     * Returns a new Period object with a new included starting date point.
633
     *
634
     * @param DateTimeInterface|string $startDate date point
635
     *
636
     * @return static
637
     */
638 9
    public function startingOn($startDate)
639
    {
640 9
        return new static(static::filterDatePoint($startDate), $this->endDate);
641
    }
642
643
    /**
644
     * Returns a new Period object with a new ending date point.
645
     *
646
     * @param DateTimeInterface|string $endDate date point
647
     *
648
     * @return static
649
     */
650 12
    public function endingOn($endDate)
651
    {
652 12
        return new static($this->startDate, static::filterDatePoint($endDate));
653
    }
654
655
    /**
656
     * Returns a new Period object with a new ending date point.
657
     *
658
     * The interval can be
659
     * <ul>
660
     * <li>a DateInterval object</li>
661
     * <li>an int interpreted as the duration expressed in seconds.</li>
662
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
663
     * </ul>
664
     *
665
     * @param DateInterval|int|string $interval The interval
666
     *
667
     * @return static
668
     */
669 12
    public function withDuration($interval)
670
    {
671 12
        return new static($this->startDate, $this->startDate->add(static::filterDateInterval($interval)));
672
    }
673
674
    /**
675
     * Returns a new Period object with a new starting date point.
676
     *
677
     * The interval can be
678
     * <ul>
679
     * <li>a DateInterval object</li>
680
     * <li>an int interpreted as the duration expressed in seconds.</li>
681
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
682
     * </ul>
683
     *
684
     * @param DateInterval|int|string $interval The interval
685
     *
686
     * @return static
687
     */
688 6
    public function withDurationBeforeEnd($interval)
689
    {
690 6
        return new static($this->endDate->sub(static::filterDateInterval($interval)), $this->endDate);
0 ignored issues
show
Security Bug introduced by
It seems like $this->endDate->sub(stat...ateInterval($interval)) targeting DateTimeImmutable::sub() can also be of type false; however, League\Period\Period::__construct() does only seem to accept object<DateTimeInterface>|string, did you maybe forget to handle an error condition?
Loading history...
691
    }
692
693
    /**
694
     * Returns a new Period object with a new starting date point
695
     * moved forward or backward by the given interval
696
     *
697
     * The interval can be
698
     * <ul>
699
     * <li>a DateInterval object</li>
700
     * <li>an int interpreted as the duration expressed in seconds.</li>
701
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
702
     * </ul>
703
     *
704
     * @param DateInterval|int|string $interval The interval
705
     *
706
     * @return static
707
     */
708 9
    public function moveStartDate($interval)
709
    {
710 9
        return new static($this->startDate->add(static::filterDateInterval($interval)), $this->endDate);
711
    }
712
713
    /**
714
     * Returns a new Period object with a new ending date point
715
     * moved forward or backward by the given interval
716
     *
717
     * The interval can be
718
     * <ul>
719
     * <li>a DateInterval object</li>
720
     * <li>an int interpreted as the duration expressed in seconds.</li>
721
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
722
     * </ul>
723
     *
724
     * @param DateInterval|int|string $interval The interval
725
     *
726
     * @return static
727
     */
728 6
    public function moveEndDate($interval)
729
    {
730 6
        return new static($this->startDate, $this->endDate->add(static::filterDateInterval($interval)));
731
    }
732
733
    /**
734
     * Returns a new Period object where the datepoints
735
     * are moved forwards or backward simultaneously by the given DateInterval
736
     *
737
     * The interval can be
738
     * <ul>
739
     * <li>a DateInterval object</li>
740
     * <li>an int interpreted as the duration expressed in seconds.</li>
741
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
742
     * </ul>
743
     *
744
     * @param DateInterval|int|string $interval The interval
745
     *
746
     * @return static
747
     */
748 12
    public function move($interval)
749
    {
750 12
        $interval = static::filterDateInterval($interval);
751
752 12
        return new static($this->startDate->add($interval), $this->endDate->add($interval));
753
    }
754
755
    /**
756
     * Returns a new Period object with an added interval
757
     *
758
     * DEPRECATION WARNING! This method will be removed in the next major point release
759
     *
760
     * @deprecated deprecated since version 3.3.0
761
     *
762
     * The interval can be
763
     * <ul>
764
     * <li>a DateInterval object</li>
765
     * <li>an int interpreted as the duration expressed in seconds.</li>
766
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
767
     * </ul>
768
     *
769
     * @param DateInterval|int|string $interval The interval
770
     *
771
     * @return static
772
     */
773
    public function add($interval)
774
    {
775
        return $this->moveEndDate($interval);
776
    }
777
778
    /**
779
     * Returns a new Period object with a Removed interval
780
     *
781
     * DEPRECATION WARNING! This method will be removed in the next major point release
782
     *
783
     * @deprecated deprecated since version 3.3.0
784
     *
785
     * The interval can be
786
     * <ul>
787
     * <li>a DateInterval object</li>
788
     * <li>an int interpreted as the duration expressed in seconds.</li>
789
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
790
     * </ul>
791
     *
792
     * @param DateInterval|int|string $interval The interval
793
     *
794
     * @return static
795
     */
796
    public function sub($interval)
797
    {
798
        return new static($this->startDate, $this->endDate->sub(static::filterDateInterval($interval)));
0 ignored issues
show
Security Bug introduced by
It seems like $this->endDate->sub(stat...ateInterval($interval)) targeting DateTimeImmutable::sub() can also be of type false; however, League\Period\Period::__construct() does only seem to accept object<DateTimeInterface>|string, did you maybe forget to handle an error condition?
Loading history...
799
    }
800
801
    /**
802
     * Returns a new Period object adjacent to the current Period
803
     * and starting with its ending datepoint.
804
     * If no duration is provided the new Period will be created
805
     * using the current object duration
806
     *
807
     * The interval can be
808
     * <ul>
809
     * <li>a DateInterval object</li>
810
     * <li>an int interpreted as the duration expressed in seconds.</li>
811
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
812
     * </ul>
813
     *
814
     * @param DateInterval|int|string $interval The interval
815
     *
816
     * @return static
817
     */
818 18
    public function next($interval = null)
819
    {
820 18
        if (is_null($interval)) {
821 6
            $interval = $this->getDateInterval();
822 2
        }
823
824 18
        return new static($this->endDate, $this->endDate->add(static::filterDateInterval($interval)));
825
    }
826
827
    /**
828
     * Returns a new Period object adjacent to the current Period
829
     * and ending with its starting datepoint.
830
     * If no duration is provided the new Period will have the
831
     * same duration as the current one
832
     *
833
     * The interval can be
834
     * <ul>
835
     * <li>a DateInterval object</li>
836
     * <li>an int interpreted as the duration expressed in seconds.</li>
837
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
838
     * </ul>
839
     *
840
     * @param DateInterval|int|string $interval The interval
841
     *
842
     * @return static
843
     */
844 9
    public function previous($interval = null)
845
    {
846 9
        if (is_null($interval)) {
847 3
            $interval = $this->getDateInterval();
848 1
        }
849
850 9
        return new static($this->startDate->sub(static::filterDateInterval($interval)), $this->startDate);
0 ignored issues
show
Security Bug introduced by
It seems like $this->startDate->sub(st...ateInterval($interval)) targeting DateTimeImmutable::sub() can also be of type false; however, League\Period\Period::__construct() does only seem to accept object<DateTimeInterface>|string, did you maybe forget to handle an error condition?
Loading history...
851
    }
852
853
    /**
854
     * Merges one or more Period objects to return a new Period object.
855
     *
856
     * The resultant object represents the largest duration possible.
857
     *
858
     * @param Period ...$arg one or more Period objects
859
     *
860
     * @return static
861
     */
862 6
    public function merge(Period $arg)
0 ignored issues
show
Unused Code introduced by
The parameter $arg is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
863
    {
864
        $reducer = function (Period $carry, Period $period) {
865 6
            if ($carry->getStartDate() > $period->getStartDate()) {
866 3
                $carry = $carry->startingOn($period->getStartDate());
867 1
            }
868
869 6
            if ($carry->getEndDate() < $period->getEndDate()) {
870 6
                $carry = $carry->endingOn($period->getEndDate());
871 2
            }
872
873 6
            return $carry;
874 6
        };
875
876 6
        return array_reduce(func_get_args(), $reducer, $this);
877
    }
878
879
    /**
880
     * Split a Period by a given interval (from start to end)
881
     *
882
     * The interval can be
883
     * <ul>
884
     * <li>a DateInterval object</li>
885
     * <li>an int interpreted as the duration expressed in seconds.</li>
886
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
887
     * </ul>
888
     *
889
     * @param DateInterval|int|string $interval The interval
890
     *
891
     * @return Generator|Period[]
892
     */
893 15
    public function split($interval)
894
    {
895 15
        $startDate = $this->startDate;
896 15
        $interval = static::filterDateInterval($interval);
897
        do {
898 15
            $endDate = $startDate->add($interval);
899 15
            if ($endDate > $this->endDate) {
900 6
                $endDate = $this->endDate;
901 2
            }
902 15
            yield new static($startDate, $endDate);
903
904 12
            $startDate = $endDate;
905 12
        } while ($startDate < $this->endDate);
906 12
    }
907
908
    /**
909
     * Split a Period by a given interval (from end to start)
910
     *
911
     * The interval can be
912
     * <ul>
913
     * <li>a DateInterval object</li>
914
     * <li>an int interpreted as the duration expressed in seconds.</li>
915
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
916
     * </ul>
917
     *
918
     * @param DateInterval|int|string $interval The interval
919
     *
920
     * @return Generator|Period[]
921
     */
922 6
    public function splitBackwards($interval)
923
    {
924 6
        $endDate = $this->endDate;
925 6
        $interval = static::filterDateInterval($interval);
926
        do {
927 6
            $startDate = $endDate->sub($interval);
928 6
            if ($startDate < $this->startDate) {
929 3
                $startDate = $this->startDate;
930 1
            }
931 6
            yield new static($startDate, $endDate);
932
933 6
            $endDate = $startDate;
934 6
        } while ($endDate > $this->startDate);
935 6
    }
936
937
    /**
938
     * Computes the intersection between two Period objects.
939
     *
940
     * @param Period $period
941
     *
942
     * @throws LogicException If Both objects do not overlaps
943
     *
944
     * @return static
945
     */
946 9
    public function intersect(Period $period)
947
    {
948 9
        if (! $this->overlaps($period)) {
949 6
            throw new LogicException('Both object should at least overlaps');
950
        }
951
952 3
        return new static(
953 3
            ($period->getStartDate() > $this->startDate) ? $period->getStartDate() : $this->startDate,
954 3
            ($period->getEndDate() < $this->endDate) ? $period->getEndDate() : $this->endDate
955 1
        );
956
    }
957
958
    /**
959
     * Computes the gap between two Period objects.
960
     *
961
     * @param Period $period
962
     *
963
     * @return static
964
     */
965 15
    public function gap(Period $period)
966
    {
967 15
        if ($period->getStartDate() > $this->startDate) {
968 12
            return new static($this->endDate, $period->getStartDate());
969
        }
970
971 6
        return new static($period->getEndDate(), $this->startDate);
972
    }
973
974
    /**
975
     * Returns the difference between two Period objects expressed in seconds
976
     *
977
     * @param Period $period
978
     *
979
     * @return float
980
     */
981 3
    public function timestampIntervalDiff(Period $period)
982
    {
983 3
        return $this->getTimestampInterval() - $period->getTimestampInterval();
984
    }
985
986
    /**
987
     * Returns the difference between two Period objects expressed in \DateInterval
988
     *
989
     * @param Period $period
990
     *
991
     * @return DateInterval
992
     */
993 6
    public function dateIntervalDiff(Period $period)
994
    {
995 6
        return $this->endDate->diff($this->withDuration($period->getDateInterval())->endDate);
996
    }
997
998
    /**
999
     * Computes the difference between two overlapsing Period objects
1000
     *
1001
     * Returns an array containing the difference expressed as Period objects
1002
     * The array will:
1003
     *
1004
     * <ul>
1005
     * <li>be empty if both objects have the same datepoints</li>
1006
     * <li>contain one Period object if both objects share one datepoint</li>
1007
     * <li>contain two Period objects if both objects share no datepoint</li>
1008
     * </ul>
1009
     *
1010
     * @param Period $period
1011
     *
1012
     * @throws LogicException if both object do not overlaps
1013
     *
1014
     * @return Period[]
1015
     */
1016 12
    public function diff(Period $period)
1017
    {
1018 12
        if (!$this->overlaps($period)) {
1019 3
            throw new LogicException('Both Period objects should overlaps');
1020
        }
1021
1022
        $res = [
1023 9
            static::createFromDatepoints($this->startDate, $period->getStartDate()),
1024 9
            static::createFromDatepoints($this->endDate, $period->getEndDate()),
1025 3
        ];
1026
1027 9
        $filter = function (Period $period) {
1028 9
            return $period->getStartDate() != $period->getEndDate();
1029 9
        };
1030
1031 9
        return array_values(array_filter($res, $filter));
1032
    }
1033
1034
    /**
1035
     * Create a new instance given two datepoints
1036
     *
1037
     * The datepoints will be used as to allow the creation of
1038
     * a Period object
1039
     *
1040
     * @param DateTimeInterface|string $datePoint1 datepoint
1041
     * @param DateTimeInterface|string $datePoint2 datepoint
1042
     *
1043
     * @return Period
1044
     */
1045 9
    protected static function createFromDatepoints($datePoint1, $datePoint2)
1046
    {
1047 9
        $startDate = static::filterDatePoint($datePoint1);
1048 9
        $endDate = static::filterDatePoint($datePoint2);
1049 9
        if ($startDate > $endDate) {
1050 3
            return new static($endDate, $startDate);
1051
        }
1052
1053 6
        return new static($startDate, $endDate);
1054
    }
1055
}
1056