Completed
Push — master ( 087fae...694027 )
by ignace nyamagana
11s
created

Period::moveStartDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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 252
    public function __construct($startDate, $endDate)
72
    {
73 252
        $startDate = static::filterDatePoint($startDate);
74 252
        $endDate = static::filterDatePoint($endDate);
75 252
        if ($startDate > $endDate) {
76 36
            throw new LogicException(
77 12
                'The ending datepoint must be greater or equal to the starting datepoint'
78 24
            );
79
        }
80 243
        $this->startDate = $startDate;
81 243
        $this->endDate = $endDate;
82 243
    }
83
84
    /**
85
     * Validate a DateTime.
86
     *
87
     * @param DateTimeInterface|string $datetime
88
     *
89
     * @return DateTimeImmutable
90
     */
91 279
    protected static function filterDatePoint($datetime)
92
    {
93 279
        if ($datetime instanceof DateTimeImmutable) {
94 246
            return $datetime;
95
        }
96
97 231
        if ($datetime instanceof DateTime) {
98 75
            return static::convertDateTime($datetime);
99
        }
100
101 165
        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 75
    protected static function convertDateTime(DateTime $datetime)
112
    {
113 75
        static $useFromMutable;
114
115 75
        if (null === $useFromMutable) {
116 3
            $useFromMutable = method_exists(new DateTimeImmutable(), 'createFromMutable');
117 2
        }
118
119 75
        if ($useFromMutable) {
120 50
            return DateTimeImmutable::createFromMutable($datetime);
121
        }
122
123 25
        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 6
        );
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 150
    public static function createFromDuration($startDate, $interval)
173
    {
174 150
        $startDate = static::filterDatePoint($startDate);
175
176 150
        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 189
    protected static function filterDateInterval($interval)
194
    {
195 189
        if ($interval instanceof DateInterval) {
196 45
            return $interval;
197
        }
198
199 177
        if (false !== ($res = filter_var($interval, FILTER_VALIDATE_INT))) {
200 33
            return new DateInterval('PT'.$res.'S');
201
        }
202
203 165
        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 18
    public static function createFromDurationBeforeEnd($endDate, $interval)
222
    {
223 18
        $endDate = static::filterDatePoint($endDate);
224
225 18
        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 81
    protected static function validateYear($year)
254
    {
255 81
        $year = filter_var($year, FILTER_VALIDATE_INT);
256 81
        if (false === $year) {
257 15
            throw new InvalidArgumentException('A Year must be a valid int');
258
        }
259
260 66
        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 81
    protected static function validateRange($value, $min, $max)
275
    {
276 81
        $res = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min, 'max_range' => $max]]);
277 81
        if (false === $res) {
278 24
            throw new OutOfRangeException('the submitted value is not contained within the valid range');
279
        }
280
281 57
        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 33
    public static function createFromMonth($year, $month)
293
    {
294 33
        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 $duration
301
     * @param int $year
302
     * @param int $index
303
     *
304
     * @return static
305
     */
306 57
    protected static function createFromYearInterval($duration, $year, $index)
307
    {
308 57
        $month = sprintf('%02s', ((static::validateRange($index, 1, 12 / $duration) - 1) * $duration) + 1);
309 39
        $startDate = new DateTimeImmutable(static::validateYear($year).'-'.$month.'-01');
310
311 30
        return new static($startDate, $startDate->add(new DateInterval('P'.$duration.'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 2
            ),
379 3
            'endDate' => new DateTime(
380 3
                $this->endDate->format(self::DATE_LOCALE),
381 3
                $this->endDate->getTimeZone()
382 2
            ),
383 2
        ];
384
    }
385
386
    /**
387
     * Returns the starting date point.
388
     *
389
     * @return DateTimeImmutable
390
     */
391 177
    public function getStartDate()
392
    {
393 177
        return $this->startDate;
394
    }
395
396
    /**
397
     * Returns the ending datepoint.
398
     *
399
     * @return DateTimeImmutable
400
     */
401 162
    public function getEndDate()
402
    {
403 162
        return $this->endDate;
404
    }
405
406
    /**
407
     * Returns the Period duration as expressed in seconds
408
     *
409
     * @return float
410
     */
411 15
    public function getTimestampInterval()
412
    {
413 15
        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
     * moved forward or backward by the given interval
677
     *
678
     * The interval can be
679
     * <ul>
680
     * <li>a DateInterval object</li>
681
     * <li>an int interpreted as the duration expressed in seconds.</li>
682
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
683
     * </ul>
684
     *
685
     * @param DateInterval|int|string $interval The interval
686
     *
687
     * @return static
688
     */
689 9
    public function moveStartDate($interval)
690
    {
691 9
        return new static($this->startDate->add(static::filterDateInterval($interval)), $this->endDate);
692
    }
693
694
    /**
695
     * Returns a new Period object with a new ending date point
696
     * moved forward or backward by the given interval
697
     *
698
     * The interval can be
699
     * <ul>
700
     * <li>a DateInterval object</li>
701
     * <li>an int interpreted as the duration expressed in seconds.</li>
702
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
703
     * </ul>
704
     *
705
     * @param DateInterval|int|string $interval The interval
706
     *
707
     * @return static
708
     */
709 12
    public function moveEndDate($interval)
710
    {
711 12
        return new static($this->startDate, $this->endDate->add(static::filterDateInterval($interval)));
712
    }
713
714
    /**
715
     * Returns a new Period object where the datepoints
716
     * are moved forwards or backward simultaneously by the given DateInterval
717
     *
718
     * The interval can be
719
     * <ul>
720
     * <li>a DateInterval object</li>
721
     * <li>an int interpreted as the duration expressed in seconds.</li>
722
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
723
     * </ul>
724
     *
725
     * @param DateInterval|int|string $interval The interval
726
     *
727
     * @return static
728
     */
729 12
    public function move($interval)
730
    {
731 12
        $interval = static::filterDateInterval($interval);
732
733 12
        return new static($this->startDate->add($interval), $this->endDate->add($interval));
734
    }
735
736
    /**
737
     * Returns a new Period object with an added interval
738
     *
739
     * DEPRECATION WARNING! This method will be removed in the next major point release
740
     *
741
     * @deprecated deprecated since version 3.3.0
742
     *
743
     * The interval can be
744
     * <ul>
745
     * <li>a DateInterval object</li>
746
     * <li>an int interpreted as the duration expressed in seconds.</li>
747
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
748
     * </ul>
749
     *
750
     * @param DateInterval|int|string $interval The interval
751
     *
752
     * @return static
753
     */
754
    public function add($interval)
755
    {
756
        return $this->moveEndDate($interval);
757
    }
758
759
    /**
760
     * Returns a new Period object with a Removed interval
761
     *
762
     * DEPRECATION WARNING! This method will be removed in the next major point release
763
     *
764
     * @deprecated deprecated since version 3.3.0
765
     *
766
     * The interval can be
767
     * <ul>
768
     * <li>a DateInterval object</li>
769
     * <li>an int interpreted as the duration expressed in seconds.</li>
770
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
771
     * </ul>
772
     *
773
     * @param DateInterval|int|string $interval The interval
774
     *
775
     * @return static
776
     */
777
    public function sub($interval)
778
    {
779
        $interval = static::filterDateInterval($interval);
780
        $interval->invert = 1;
781
782
        return $this->moveEndDate($interval);
783
    }
784
785
    /**
786
     * Returns a new Period object adjacent to the current Period
787
     * and starting with its ending datepoint.
788
     * If no duration is provided the new Period will be created
789
     * using the current object duration
790
     *
791
     * The interval can be
792
     * <ul>
793
     * <li>a DateInterval object</li>
794
     * <li>an int interpreted as the duration expressed in seconds.</li>
795
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
796
     * </ul>
797
     *
798
     * @param DateInterval|int|string $interval The interval
799
     *
800
     * @return static
801
     */
802 18
    public function next($interval = null)
803
    {
804 18
        if (is_null($interval)) {
805 6
            $interval = $this->getDateInterval();
806 4
        }
807
808 18
        return new static($this->endDate, $this->endDate->add(static::filterDateInterval($interval)));
809
    }
810
811
    /**
812
     * Returns a new Period object adjacent to the current Period
813
     * and ending with its starting datepoint.
814
     * If no duration is provided the new Period will have the
815
     * same duration as the current one
816
     *
817
     * The interval can be
818
     * <ul>
819
     * <li>a DateInterval object</li>
820
     * <li>an int interpreted as the duration expressed in seconds.</li>
821
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
822
     * </ul>
823
     *
824
     * @param DateInterval|int|string $interval The interval
825
     *
826
     * @return static
827
     */
828 9
    public function previous($interval = null)
829
    {
830 9
        if (is_null($interval)) {
831 3
            $interval = $this->getDateInterval();
832 2
        }
833
834 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...
835
    }
836
837
    /**
838
     * Merges one or more Period objects to return a new Period object.
839
     *
840
     * The resultant object represents the largest duration possible.
841
     *
842
     * @param Period ...$arg one or more Period objects
843
     *
844
     * @return static
845
     */
846 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...
847
    {
848
        $reducer = function (Period $carry, Period $period) {
849 6
            if ($carry->getStartDate() > $period->getStartDate()) {
850 3
                $carry = $carry->startingOn($period->getStartDate());
851 2
            }
852
853 6
            if ($carry->getEndDate() < $period->getEndDate()) {
854 6
                $carry = $carry->endingOn($period->getEndDate());
855 4
            }
856
857 6
            return $carry;
858 6
        };
859
860 6
        return array_reduce(func_get_args(), $reducer, $this);
861
    }
862
863
    /**
864
     * Split a Period by a given interval
865
     *
866
     * The interval can be
867
     * <ul>
868
     * <li>a DateInterval object</li>
869
     * <li>an int interpreted as the duration expressed in seconds.</li>
870
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
871
     * </ul>
872
     *
873
     * @param DateInterval|int|string $interval The interval
874
     *
875
     * @return Generator
876
     */
877 12
    public function split($interval)
878
    {
879 12
        $startDate = $this->startDate;
880 12
        $interval = static::filterDateInterval($interval);
881
        do {
882 12
            $endDate = $startDate->add($interval);
883 12
            if ($endDate > $this->endDate) {
884 6
                $endDate = $this->endDate;
885 4
            }
886 12
            yield new static($startDate, $endDate);
887
888 9
            $startDate = $endDate;
889 9
        } while ($startDate < $this->endDate);
890 9
    }
891
892
    /**
893
     * Computes the intersection between two Period objects.
894
     *
895
     * @param Period $period
896
     *
897
     * @throws LogicException If Both objects do not overlaps
898
     *
899
     * @return static
900
     */
901 9
    public function intersect(Period $period)
902
    {
903 9
        if (! $this->overlaps($period)) {
904 6
            throw new LogicException('Both object should at least overlaps');
905
        }
906
907 3
        return new static(
908 3
            ($period->getStartDate() > $this->startDate) ? $period->getStartDate() : $this->startDate,
909 3
            ($period->getEndDate() < $this->endDate) ? $period->getEndDate() : $this->endDate
910 2
        );
911
    }
912
913
    /**
914
     * Computes the gap between two Period objects.
915
     *
916
     * @param Period $period
917
     *
918
     * @return static
919
     */
920 15
    public function gap(Period $period)
921
    {
922 15
        if ($period->getStartDate() > $this->startDate) {
923 12
            return new static($this->endDate, $period->getStartDate());
924
        }
925
926 6
        return new static($period->getEndDate(), $this->startDate);
927
    }
928
929
    /**
930
     * Returns the difference between two Period objects expressed in seconds
931
     *
932
     * @param Period $period
933
     *
934
     * @return float
935
     */
936 3
    public function timestampIntervalDiff(Period $period)
937
    {
938 3
        return $this->getTimestampInterval() - $period->getTimestampInterval();
939
    }
940
941
    /**
942
     * Returns the difference between two Period objects expressed in \DateInterval
943
     *
944
     * @param Period $period
945
     *
946
     * @return DateInterval
947
     */
948 6
    public function dateIntervalDiff(Period $period)
949
    {
950 6
        return $this->endDate->diff($this->withDuration($period->getDateInterval())->endDate);
951
    }
952
953
    /**
954
     * Computes the difference between two overlapsing Period objects
955
     *
956
     * Returns an array containing the difference expressed as Period objects
957
     * The array will:
958
     *
959
     * <ul>
960
     * <li>be empty if both objects have the same datepoints</li>
961
     * <li>contain one Period object if both objects share one datepoint</li>
962
     * <li>contain two Period objects if both objects share no datepoint</li>
963
     * </ul>
964
     *
965
     * @param Period $period
966
     *
967
     * @throws LogicException if both object do not overlaps
968
     *
969
     * @return Period[]
970
     */
971 12
    public function diff(Period $period)
972
    {
973 12
        if (!$this->overlaps($period)) {
974 3
            throw new LogicException('Both Period objects should overlaps');
975
        }
976
977
        $res = [
978 9
            static::createFromDatepoints($this->startDate, $period->getStartDate()),
979 9
            static::createFromDatepoints($this->endDate, $period->getEndDate()),
980 6
        ];
981
982 9
        $filter = function (Period $period) {
983 9
            return $period->getStartDate() != $period->getEndDate();
984 9
        };
985
986 9
        return array_values(array_filter($res, $filter));
987
    }
988
989
    /**
990
     * Create a new instance given two datepoints
991
     *
992
     * The datepoints will be used as to allow the creation of
993
     * a Period object
994
     *
995
     * @param DateTimeInterface|string $datePoint1 datepoint
996
     * @param DateTimeInterface|string $datePoint2 datepoint
997
     *
998
     * @return Period
999
     */
1000 9
    protected static function createFromDatepoints($datePoint1, $datePoint2)
1001
    {
1002 9
        $startDate = static::filterDatePoint($datePoint1);
1003 9
        $endDate = static::filterDatePoint($datePoint2);
1004 9
        if ($startDate > $endDate) {
1005 3
            return new static($endDate, $startDate);
1006
        }
1007
1008 6
        return new static($startDate, $endDate);
1009
    }
1010
}
1011