Completed
Push — master ( 5fc133...2a4989 )
by ignace nyamagana
13:37
created

Period::createFromDatepoints()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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