Completed
Push — master ( 0b563b...526987 )
by ignace nyamagana
18:36 queued 03:47
created

Period::__set_state()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 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.1.1
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
    public function __construct($startDate, $endDate)
72
    {
73
        $startDate = static::filterDatePoint($startDate);
74
        $endDate   = static::filterDatePoint($endDate);
75
        if ($startDate > $endDate) {
76
            throw new LogicException(
77
                'The ending datepoint must be greater or equal to the starting datepoint'
78
            );
79
        }
80
        $this->startDate = $startDate;
81
        $this->endDate   = $endDate;
82
    }
83
84
    /**
85
     * Validate a DateTime.
86
     *
87
     * @param DateTimeInterface|string $datetime
88
     *
89
     * @return DateTimeImmutable
90
     */
91
    protected static function filterDatePoint($datetime)
92
    {
93
        if ($datetime instanceof DateTimeImmutable) {
94
            return $datetime;
95
        }
96
97
        if ($datetime instanceof DateTime) {
98
            return static::convertDateTime($datetime);
99
        }
100
101
        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
    protected static function convertDateTime(DateTime $datetime)
112
    {
113
        static $useFromMutable;
114
115
        if (null === $useFromMutable) {
116
            $useFromMutable = method_exists(new DateTimeImmutable(), 'createFromMutable');
117
        }
118
119
        if ($useFromMutable) {
120
            return DateTimeImmutable::createFromMutable($datetime);
121
        }
122
123
        return new DateTimeImmutable($datetime->format(static::DATE_LOCALE), $datetime->getTimeZone());
124
    }
125
126
    /**
127
     * @inheritdoc
128
     */
129
    public static function __set_state(array $period)
130
    {
131
        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
    public static function createFromDay($day)
145
    {
146
        $startDate = static::filterDatePoint($day);
147
148
        $startDate = $startDate->createFromFormat(
149
            static::DATE_LOCALE,
150
            $startDate->format('Y-m-d').' 00:00:00.000000',
151
            $startDate->getTimeZone()
152
        );
153
154
        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
    public static function createFromDuration($startDate, $interval)
173
    {
174
        $startDate = static::filterDatePoint($startDate);
175
176
        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
    protected static function filterDateInterval($interval)
194
    {
195
        if ($interval instanceof DateInterval) {
196
            return $interval;
197
        }
198
199
        if (false !== ($res = filter_var($interval, FILTER_VALIDATE_INT))) {
200
            return new DateInterval('PT'.$res.'S');
201
        }
202
203
        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
    public static function createFromDurationBeforeEnd($endDate, $interval)
222
    {
223
        $endDate = static::filterDatePoint($endDate);
224
225
        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
    public static function createFromWeek($year, $week)
237
    {
238
        $week = static::validateYear($year).'W'.sprintf('%02d', static::validateRange($week, 1, 53));
239
        $startDate = new DateTimeImmutable($week);
240
241
        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
    protected static function validateYear($year)
254
    {
255
        $year = filter_var($year, FILTER_VALIDATE_INT);
256
        if (false === $year) {
257
            throw new InvalidArgumentException('A Year must be a valid int');
258
        }
259
260
        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
    protected static function validateRange($value, $min, $max)
275
    {
276
        $res = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min, 'max_range' => $max]]);
277
        if (false === $res) {
278
            throw new OutOfRangeException('the submitted value is not contained within the valid range');
279
        }
280
281
        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
    public static function createFromMonth($year, $month)
293
    {
294
        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
    protected static function createFromYearInterval($duration, $year, $index)
307
    {
308
        $month = sprintf('%02s', ((static::validateRange($index, 1, 12 / $duration) - 1) * $duration) + 1);
309
        $startDate = new DateTimeImmutable(static::validateYear($year).'-'.$month.'-01');
310
311
        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
    public static function createFromQuarter($year, $quarter)
323
    {
324
        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
    public static function createFromSemester($year, $semester)
336
    {
337
        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
    public static function createFromYear($year)
348
    {
349
        $startDate = new DateTimeImmutable(static::validateYear($year).'-01-01');
350
351
        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
    public function __toString()
360
    {
361
        $utc = new DateTimeZone('UTC');
362
363
        return $this->startDate->setTimeZone($utc)->format(static::DATE_ISO8601)
364
            .'/'.$this->endDate->setTimeZone($utc)->format(static::DATE_ISO8601);
365
    }
366
367
    /**
368
     * implement JsonSerializable interface
369
     *
370
     * @return DateTime[]
371
     */
372
    public function jsonSerialize()
373
    {
374
        return [
375
            'startDate' => new DateTime(
376
                $this->startDate->format(static::DATE_LOCALE),
377
                $this->startDate->getTimeZone()
378
            ),
379
            'endDate' => new DateTime(
380
                $this->endDate->format(static::DATE_LOCALE),
381
                $this->endDate->getTimeZone()
382
            ),
383
        ];
384
    }
385
386
    /**
387
     * Returns the starting date point.
388
     *
389
     * @return DateTimeImmutable
390
     */
391
    public function getStartDate()
392
    {
393
        return $this->startDate;
394
    }
395
396
    /**
397
     * Returns the ending datepoint.
398
     *
399
     * @return DateTimeImmutable
400
     */
401
    public function getEndDate()
402
    {
403
        return $this->endDate;
404
    }
405
406
    /**
407
     * Returns the Period duration as expressed in seconds
408
     *
409
     * @return float
410
     */
411
    public function getTimestampInterval()
412
    {
413
        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
    public function getDateInterval()
422
    {
423
        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
    public function getDatePeriod($interval, $option = 0)
446
    {
447
        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
    public function sameValueAs(Period $period)
458
    {
459
        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
    public function abuts(Period $period)
470
    {
471
        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
    public function overlaps(Period $period)
482
    {
483
        if ($this->abuts($period)) {
484
            return false;
485
        }
486
487
        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
    public function isAfter($index)
498
    {
499
        if ($index instanceof Period) {
500
            return $this->startDate >= $index->getEndDate();
501
        }
502
503
        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
    public function isBefore($index)
514
    {
515
        if ($index instanceof Period) {
516
            return $this->endDate <= $index->getStartDate();
517
        }
518
519
        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
    public function contains($index)
531
    {
532
        if ($index instanceof Period) {
533
            return $this->containsPeriod($index);
534
        }
535
536
        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
    protected function containsPeriod(Period $period)
548
    {
549
        $endDate = $period->getEndDate();
550
551
        return $this->contains($period->getStartDate())
552
            && ($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
    protected function containsDatePoint($datepoint)
564
    {
565
        $datetime = static::filterDatePoint($datepoint);
566
567
        return ($datetime >= $this->startDate && $datetime < $this->endDate)
568
            || ($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
    public function compareDuration(Period $period)
579
    {
580
        $datetime = $this->startDate->add($period->getDateInterval());
581
        if ($this->endDate > $datetime) {
582
            return 1;
583
        }
584
585
        if ($this->endDate < $datetime) {
586
            return -1;
587
        }
588
589
        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
    public function durationGreaterThan(Period $period)
601
    {
602
        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
    public function durationLessThan(Period $period)
614
    {
615
        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
    public function sameDurationAs(Period $period)
627
    {
628
        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
    public function startingOn($startDate)
639
    {
640
        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
    public function endingOn($endDate)
651
    {
652
        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
    public function withDuration($interval)
670
    {
671
        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
    public function add($interval)
689
    {
690
        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
    public function sub($interval)
708
    {
709
        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
    public function next($interval = null)
730
    {
731
        if (is_null($interval)) {
732
            $interval = $this->getDateInterval();
733
        }
734
735
        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
    public function previous($interval = null)
756
    {
757
        if (is_null($interval)) {
758
            $interval = $this->getDateInterval();
759
        }
760
761
        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
    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
            if ($carry->getStartDate() > $period->getStartDate()) {
777
                $carry = $carry->startingOn($period->getStartDate());
778
            }
779
780
            if ($carry->getEndDate() < $period->getEndDate()) {
781
                $carry = $carry->endingOn($period->getEndDate());
782
            }
783
784
            return $carry;
785
        };
786
787
        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
    public function split($interval)
805
    {
806
        $startDate = $this->startDate;
807
        $interval = static::filterDateInterval($interval);
808
        do {
809
            $endDate = $startDate->add($interval);
810
            if ($endDate > $this->endDate) {
811
                $endDate = $this->endDate;
812
            }
813
            yield new static($startDate, $endDate);
814
815
            $startDate = $endDate;
816
        } while ($startDate < $this->endDate);
817
    }
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
    public function intersect(Period $period)
829
    {
830
        if ($this->abuts($period)) {
831
            throw new LogicException('Both object should not abuts');
832
        }
833
834
        return new static(
835
            ($period->getStartDate() > $this->startDate) ? $period->getStartDate() : $this->startDate,
836
            ($period->getEndDate() < $this->endDate) ? $period->getEndDate() : $this->endDate
837
        );
838
    }
839
840
    /**
841
     * Computes the gap between two Period objects.
842
     *
843
     * @param Period $period
844
     *
845
     * @return static
846
     */
847
    public function gap(Period $period)
848
    {
849
        if ($period->getStartDate() > $this->startDate) {
850
            return new static($this->endDate, $period->getStartDate());
851
        }
852
853
        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
    public function timestampIntervalDiff(Period $period)
864
    {
865
        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
    public function dateIntervalDiff(Period $period)
876
    {
877
        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
    public function diff(Period $period)
899
    {
900
        if (! $this->overlaps($period)) {
901
            throw new LogicException('Both Period objects should overlaps');
902
        }
903
904
        $res = [
905
            static::createFromDatepoints($this->startDate, $period->getStartDate()),
906
            static::createFromDatepoints($this->endDate, $period->getEndDate()),
907
        ];
908
909
        return array_values(array_filter($res, function (Period $period) {
910
            return $period->getStartDate() != $period->getEndDate();
911
        }));
912
    }
913
914
    /**
915
     * Create a new instance given two datepoints
916
     *
917
     * The datepoints will be used as to allow the creation of
918
     * a Period object
919
     *
920
     * @param DateTimeInterface|string $datePoint1 datepoint
921
     * @param DateTimeInterface|string $datePoint2 datepoint
922
     *
923
     * @return Period
924
     */
925
    protected static function createFromDatepoints($datePoint1, $datePoint2)
926
    {
927
        $startDate = static::filterDatePoint($datePoint1);
928
        $endDate   = static::filterDatePoint($datePoint2);
929
        if ($startDate > $endDate) {
930
            return new static($endDate, $startDate);
931
        }
932
933
        return new static($startDate, $endDate);
934
    }
935
}
936