Completed
Pull Request — master (#40)
by Adam
05:17
created

Period::move()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
ccs 1
cts 1
cp 1
rs 9.4285
cc 1
eloc 3
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.2.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 231
    public function __construct($startDate, $endDate)
72
    {
73 231
        $startDate = static::filterDatePoint($startDate);
74 231
        $endDate   = static::filterDatePoint($endDate);
75 231
        if ($startDate > $endDate) {
76 33
            throw new LogicException(
77 11
                'The ending datepoint must be greater or equal to the starting datepoint'
78 22
            );
79
        }
80 222
        $this->startDate = $startDate;
81 222
        $this->endDate   = $endDate;
82 222
    }
83
84
    /**
85
     * Validate a DateTime.
86
     *
87
     * @param DateTimeInterface|string $datetime
88
     *
89
     * @return DateTimeImmutable
90
     */
91 258
    protected static function filterDatePoint($datetime)
92
    {
93 258
        if ($datetime instanceof DateTimeImmutable) {
94 225
            return $datetime;
95
        }
96
97 216
        if ($datetime instanceof DateTime) {
98 75
            return static::convertDateTime($datetime);
99
        }
100
101 150
        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 147
    public static function createFromDuration($startDate, $interval)
173
    {
174 147
        $startDate = static::filterDatePoint($startDate);
175
176 147
        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 168
    protected static function filterDateInterval($interval)
194
    {
195 168
        if ($interval instanceof DateInterval) {
196 33
            return $interval;
197
        }
198
199 162
        if (false !== ($res = filter_var($interval, FILTER_VALIDATE_INT))) {
200 33
            return new DateInterval('PT'.$res.'S');
201
        }
202
203 150
        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 75
    protected static function validateYear($year)
254
    {
255 75
        $year = filter_var($year, FILTER_VALIDATE_INT);
256 75
        if (false === $year) {
257 15
            throw new InvalidArgumentException('A Year must be a valid int');
258
        }
259
260 60
        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 75
    protected static function validateRange($value, $min, $max)
275
    {
276 75
        $res = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min, 'max_range' => $max]]);
277 75
        if (false === $res) {
278 24
            throw new OutOfRangeException('the submitted value is not contained within the valid range');
279
        }
280
281 51
        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 27
    public static function createFromMonth($year, $month)
293
    {
294 27
        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 51
    protected static function createFromYearInterval($duration, $year, $index)
307
    {
308 51
        $month = sprintf('%02s', ((static::validateRange($index, 1, 12 / $duration) - 1) * $duration) + 1);
309 33
        $startDate = new DateTimeImmutable(static::validateYear($year).'-'.$month.'-01');
310
311 24
        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 171
    public function getStartDate()
392
    {
393 171
        return $this->startDate;
394
    }
395
396
    /**
397
     * Returns the ending datepoint.
398
     *
399
     * @return DateTimeImmutable
400
     */
401 156
    public function getEndDate()
402
    {
403 156
        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 36
    public function getDateInterval()
422
    {
423 36
        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 15
    public function compareDuration(Period $period)
579
    {
580 15
        $datetime = $this->startDate->add($period->getDateInterval());
581 15
        if ($this->endDate > $datetime) {
582 6
            return 1;
583
        }
584
585 9
        if ($this->endDate < $datetime) {
586 6
            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 6
    public function durationGreaterThan(Period $period)
601
    {
602 6
        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 6
    public function durationLessThan(Period $period)
614
    {
615 6
        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 an added interval
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 add($interval)
689
    {
690 6
        return new static($this->startDate, $this->endDate->add(static::filterDateInterval($interval)));
691
    }
692
693
    /**
694
     * Returns a new Period object with a Removed interval
695
     *
696
     * The interval can be
697
     * <ul>
698
     * <li>a DateInterval object</li>
699
     * <li>an int interpreted as the duration expressed in seconds.</li>
700
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
701
     * </ul>
702
     *
703
     * @param DateInterval|int|string $interval The interval
704
     *
705
     * @return static
706
     */
707 6
    public function sub($interval)
708
    {
709 6
        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...
710
    }
711
712
    /**
713
     * Returns a new Period object adjacent to the current Period
714
     * and starting with its ending datepoint.
715
     * If no duration is provided the new Period will be created
716
     * using the current object duration
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 18
    public function next($interval = null)
730
    {
731 18
        if (is_null($interval)) {
732 6
            $interval = $this->getDateInterval();
733 4
        }
734
735 18
        return new static($this->endDate, $this->endDate->add(static::filterDateInterval($interval)));
736
    }
737
738
    /**
739
     * Returns a new Period object adjacent to the current Period
740
     * and ending with its starting datepoint.
741
     * If no duration is provided the new Period will have the
742
     * same duration as the current one
743
     *
744
     * The interval can be
745
     * <ul>
746
     * <li>a DateInterval object</li>
747
     * <li>an int interpreted as the duration expressed in seconds.</li>
748
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
749
     * </ul>
750
     *
751
     * @param DateInterval|int|string $interval The interval
752
     *
753
     * @return static
754
     */
755 9
    public function previous($interval = null)
756
    {
757 9
        if (is_null($interval)) {
758 3
            $interval = $this->getDateInterval();
759 2
        }
760
761 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...
762
    }
763
764
    /**
765
     * Merges one or more Period objects to return a new Period object.
766
     *
767
     * The resultant object represents the largest duration possible.
768
     *
769
     * @param Period ...$arg one or more Period objects
770
     *
771
     * @return static
772
     */
773 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...
774
    {
775
        $reducer = function (Period $carry, Period $period) {
776 6
            if ($carry->getStartDate() > $period->getStartDate()) {
777 3
                $carry = $carry->startingOn($period->getStartDate());
778 2
            }
779
780 6
            if ($carry->getEndDate() < $period->getEndDate()) {
781 6
                $carry = $carry->endingOn($period->getEndDate());
782 4
            }
783
784 6
            return $carry;
785 6
        };
786
787 6
        return array_reduce(func_get_args(), $reducer, $this);
788
    }
789
790
    /**
791
     * Split a Period by a given interval
792
     *
793
     * The interval can be
794
     * <ul>
795
     * <li>a DateInterval object</li>
796
     * <li>an int interpreted as the duration expressed in seconds.</li>
797
     * <li>a string in a format supported by DateInterval::createFromDateString</li>
798
     * </ul>
799
     *
800
     * @param DateInterval|int|string $interval The interval
801
     *
802
     * @return Generator
803
     */
804 12
    public function split($interval)
805
    {
806 12
        $startDate = $this->startDate;
807 12
        $interval = static::filterDateInterval($interval);
808
        do {
809 12
            $endDate = $startDate->add($interval);
810 12
            if ($endDate > $this->endDate) {
811 6
                $endDate = $this->endDate;
812 4
            }
813 12
            yield new static($startDate, $endDate);
814
815 9
            $startDate = $endDate;
816 9
        } while ($startDate < $this->endDate);
817 9
    }
818
819
    /**
820
     * Computes the intersection between two Period objects.
821
     *
822
     * @param Period $period
823
     *
824
     * @throws LogicException If Both objects do not overlaps
825
     *
826
     * @return static
827
     */
828 9
    public function intersect(Period $period)
829
    {
830 9
        if (! $this->overlaps($period)) {
831 6
            throw new LogicException('Both object should at least overlaps');
832
        }
833
834 3
        return new static(
835 3
            ($period->getStartDate() > $this->startDate) ? $period->getStartDate() : $this->startDate,
836 3
            ($period->getEndDate() < $this->endDate) ? $period->getEndDate() : $this->endDate
837 2
        );
838
    }
839
840
    /**
841
     * Computes the gap between two Period objects.
842
     *
843
     * @param Period $period
844
     *
845
     * @return static
846
     */
847 15
    public function gap(Period $period)
848
    {
849 15
        if ($period->getStartDate() > $this->startDate) {
850 12
            return new static($this->endDate, $period->getStartDate());
851
        }
852
853 6
        return new static($period->getEndDate(), $this->startDate);
854
    }
855
856
    /**
857
     * Returns the difference between two Period objects expressed in seconds
858
     *
859
     * @param Period $period
860
     *
861
     * @return float
862
     */
863 3
    public function timestampIntervalDiff(Period $period)
864
    {
865 3
        return $this->getTimestampInterval() - $period->getTimestampInterval();
866
    }
867
868
    /**
869
     * Returns the difference between two Period objects expressed in \DateInterval
870
     *
871
     * @param Period $period
872
     *
873
     * @return DateInterval
874
     */
875 6
    public function dateIntervalDiff(Period $period)
876
    {
877 6
        return $this->endDate->diff($this->withDuration($period->getDateInterval())->endDate);
878
    }
879
880
    /**
881
     * Computes the difference between two overlapsing Period objects
882
     *
883
     * Returns an array containing the difference expressed as Period objects
884
     * The array will:
885
     *
886
     * <ul>
887
     * <li>be empty if both objects have the same datepoints</li>
888
     * <li>contain one Period object if both objects share one datepoint</li>
889
     * <li>contain two Period objects if both objects share no datepoint</li>
890
     * </ul>
891
     *
892
     * @param Period $period
893
     *
894
     * @throws LogicException if both object do not overlaps
895
     *
896
     * @return Period[]
897
     */
898 12
    public function diff(Period $period)
899
    {
900 12
        if (! $this->overlaps($period)) {
901 3
            throw new LogicException('Both Period objects should overlaps');
902
        }
903
904
        $res = [
905 9
            static::createFromDatepoints($this->startDate, $period->getStartDate()),
906 9
            static::createFromDatepoints($this->endDate, $period->getEndDate()),
907 6
        ];
908
909 9
        return array_values(array_filter($res, function (Period $period) {
910 9
            return $period->getStartDate() != $period->getEndDate();
911 9
        }));
912
    }
913
914
    /**
915
     * Returns a new Period instance where the start/end dates have moved forwards in time by the given DateInterval
916
     *
917
     * @param DateInterval|int|string $interval The interval
918
     * @return Period
919
     */
920
    public function move($interval)
921
    {
922
        $interval = static::filterDateInterval($interval);
923
924
        return new static($this->startDate->add($interval), $this->endDate->add($interval));
925 9
    }
926
927 9
    /**
928 9
     * Create a new instance given two datepoints
929 9
     *
930 3
     * The datepoints will be used as to allow the creation of
931
     * a Period object
932
     *
933 6
     * @param DateTimeInterface|string $datePoint1 datepoint
934
     * @param DateTimeInterface|string $datePoint2 datepoint
935
     *
936
     * @return Period
937
     */
938
    protected static function createFromDatepoints($datePoint1, $datePoint2)
939
    {
940
        $startDate = static::filterDatePoint($datePoint1);
941
        $endDate   = static::filterDatePoint($datePoint2);
942
        if ($startDate > $endDate) {
943
            return new static($endDate, $startDate);
944
        }
945
946
        return new static($startDate, $endDate);
947
    }
948
}
949