GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 5628d2...1c2af9 )
by Brent
01:18
created

Period::intersect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Spatie\Period;
4
5
use DateTime;
6
use DatePeriod;
7
use DateInterval;
8
use DateTimeImmutable;
9
use DateTimeInterface;
10
use IteratorAggregate;
11
use Spatie\Period\Exceptions\InvalidDate;
12
use Spatie\Period\Exceptions\InvalidPeriod;
13
use Spatie\Period\Exceptions\CannotComparePeriods;
14
15
class Period implements IteratorAggregate
16
{
17
    /** @var \DateTimeImmutable */
18
    protected $start;
19
20
    /** @var \DateTimeImmutable */
21
    protected $end;
22
23
    /** @var \DateInterval */
24
    protected $interval;
25
26
    /** @var \DateTimeImmutable */
27
    private $includedStart;
28
29
    /** @var \DateTimeImmutable */
30
    private $includedEnd;
31
32
    /** @var int */
33
    private $boundaryExclusionMask;
34
35
    /** @var int */
36
    private $precisionMask;
37
38
    public function __construct(
39
        DateTimeImmutable $start,
40
        DateTimeImmutable $end,
41
        ?int $precisionMask = null,
42
        ?int $boundaryExclusionMask = null
43
    ) {
44
        if ($start > $end) {
45
            throw InvalidPeriod::endBeforeStart($start, $end);
46
        }
47
48
        $this->boundaryExclusionMask = $boundaryExclusionMask ?? Boundaries::EXCLUDE_NONE;
49
        $this->precisionMask = $precisionMask ?? Precision::DAY;
50
51
        $this->start = $this->roundDate($start, $this->precisionMask);
52
        $this->end = $this->roundDate($end, $this->precisionMask);
53
        $this->interval = $this->createDateInterval($this->precisionMask);
54
55
        $this->includedStart = $this->startIncluded()
56
            ? $this->start
57
            : $this->start->add($this->interval);
58
59
        $this->includedEnd = $this->endIncluded()
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->endIncluded() ? $...d->sub($this->interval) can also be of type false. However, the property $includedEnd is declared as type object<DateTimeImmutable>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
60
            ? $this->end
61
            : $this->end->sub($this->interval);
62
    }
63
64
    /**
65
     * @param string|DateTimeInterface $start
66
     * @param string|DateTimeInterface $end
67
     * @param int|null $precisionMask
68
     * @param int|null $boundaryExclusionMask
69
     * @param string|null $format
70
     *
71
     * @return static
72
     */
73
    public static function make(
74
        $start,
75
        $end,
76
        ?int $precisionMask = null,
77
        ?int $boundaryExclusionMask = null,
78
        ?string $format = null
79
    ): Period {
80
        if ($start === null) {
81
            throw InvalidDate::cannotBeNull('Start date');
82
        }
83
84
        if ($end === null) {
85
            throw InvalidDate::cannotBeNull('End date');
86
        }
87
88
        return new static(
89
            self::resolveDate($start, $format),
90
            self::resolveDate($end, $format),
91
            $precisionMask,
92
            $boundaryExclusionMask
93
        );
94
    }
95
96
    public function startIncluded(): bool
97
    {
98
        return ! $this->startExcluded();
99
    }
100
101
    public function startExcluded(): bool
102
    {
103
        return Boundaries::EXCLUDE_START & $this->boundaryExclusionMask;
104
    }
105
106
    public function endIncluded(): bool
107
    {
108
        return ! $this->endExcluded();
109
    }
110
111
    public function endExcluded(): bool
112
    {
113
        return Boundaries::EXCLUDE_END & $this->boundaryExclusionMask;
114
    }
115
116
    public function getStart(): DateTimeImmutable
117
    {
118
        return $this->start;
119
    }
120
121
    public function getIncludedStart(): DateTimeImmutable
122
    {
123
        return $this->includedStart;
124
    }
125
126
    public function getEnd(): DateTimeImmutable
127
    {
128
        return $this->end;
129
    }
130
131
    public function getIncludedEnd(): DateTimeImmutable
132
    {
133
        return $this->includedEnd;
134
    }
135
136
    public function length(): int
137
    {
138
        $length = $this->getIncludedStart()->diff($this->getIncludedEnd())->days + 1;
139
140
        return $length;
141
    }
142
143
    public function overlapsWith(Period $period): bool
144
    {
145
        $this->ensurePrecisionMatches($period);
146
147
        if ($this->getIncludedStart() > $period->getIncludedEnd()) {
148
            return false;
149
        }
150
151
        if ($period->getIncludedStart() > $this->getIncludedEnd()) {
152
            return false;
153
        }
154
155
        return true;
156
    }
157
158
    public function touchesWith(Period $period): bool
159
    {
160
        $this->ensurePrecisionMatches($period);
161
162
        if ($this->getIncludedEnd()->diff($period->getIncludedStart())->days <= 1) {
163
            return true;
164
        }
165
166
        if ($this->getIncludedStart()->diff($period->getIncludedEnd())->days <= 1) {
167
            return true;
168
        }
169
170
        return false;
171
    }
172
173
    public function startsBefore(DateTimeInterface $date): bool
174
    {
175
        return $this->getIncludedStart() < $date;
176
    }
177
178
    public function startsBeforeOrAt(DateTimeInterface $date): bool
179
    {
180
        return $this->getIncludedStart() <= $date;
181
    }
182
183
    public function startsAfter(DateTimeInterface $date): bool
184
    {
185
        return $this->getIncludedStart() > $date;
186
    }
187
188
    public function startsAfterOrAt(DateTimeInterface $date): bool
189
    {
190
        return $this->getIncludedStart() >= $date;
191
    }
192
193
    public function startsAt(DateTimeInterface $date): bool
194
    {
195
        return $this->getIncludedStart()->getTimestamp() === $this->roundDate(
196
            $date,
197
            $this->precisionMask
198
        )->getTimestamp();
199
    }
200
201
    public function endsBefore(DateTimeInterface $date): bool
202
    {
203
        return $this->getIncludedEnd() < $this->roundDate(
204
                $date,
205
                $this->precisionMask
206
            );
207
    }
208
209
    public function endsBeforeOrAt(DateTimeInterface $date): bool
210
    {
211
        return $this->getIncludedEnd() <= $this->roundDate(
212
                $date,
213
                $this->precisionMask
214
            );
215
    }
216
217
    public function endsAfter(DateTimeInterface $date): bool
218
    {
219
        return $this->getIncludedEnd() > $this->roundDate(
220
                $date,
221
                $this->precisionMask
222
            );
223
    }
224
225
    public function endsAfterOrAt(DateTimeInterface $date): bool
226
    {
227
        return $this->getIncludedEnd() >= $this->roundDate(
228
                $date,
229
                $this->precisionMask
230
            );
231
    }
232
233
    public function endsAt(DateTimeInterface $date): bool
234
    {
235
        return $this->getIncludedEnd()->getTimestamp() === $this->roundDate(
236
                $date,
237
                $this->precisionMask
238
            )->getTimestamp();
239
    }
240
241
    public function contains(DateTimeInterface $date): bool
242
    {
243
        if ($this->roundDate($date, $this->precisionMask) < $this->getIncludedStart()) {
244
            return false;
245
        }
246
247
        if ($this->roundDate($date, $this->precisionMask) > $this->getIncludedEnd()) {
248
            return false;
249
        }
250
251
        return true;
252
    }
253
254
    public function equals(Period $period): bool
255
    {
256
        $this->ensurePrecisionMatches($period);
257
258
        if ($period->getIncludedStart()->getTimestamp() !== $this->getIncludedStart()->getTimestamp()) {
259
            return false;
260
        }
261
262
        if ($period->getIncludedEnd()->getTimestamp() !== $this->getIncludedEnd()->getTimestamp()) {
263
            return false;
264
        }
265
266
        return true;
267
    }
268
269
    /**
270
     * @param \Spatie\Period\Period $period
271
     *
272
     * @return static|null
273
     * @throws \Exception
274
     */
275
    public function gap(Period $period): ?Period
276
    {
277
        $this->ensurePrecisionMatches($period);
278
279
        if ($this->overlapsWith($period)) {
280
            return null;
281
        }
282
283
        if ($this->touchesWith($period)) {
284
            return null;
285
        }
286
287
        if ($this->getIncludedStart() >= $period->getIncludedEnd()) {
288
            return static::make(
289
                $period->getIncludedEnd()->add($this->interval),
290
                $this->getIncludedStart()->sub($this->interval),
0 ignored issues
show
Security Bug introduced by
It seems like $this->getIncludedStart()->sub($this->interval) targeting DateTimeImmutable::sub() can also be of type false; however, Spatie\Period\Period::make() does only seem to accept string|object<DateTimeInterface>, did you maybe forget to handle an error condition?
Loading history...
291
                $this->getPrecisionMask()
292
            );
293
        }
294
295
        return static::make(
296
            $this->getIncludedEnd()->add($this->interval),
297
            $period->getIncludedStart()->sub($this->interval),
0 ignored issues
show
Security Bug introduced by
It seems like $period->getIncludedStart()->sub($this->interval) targeting DateTimeImmutable::sub() can also be of type false; however, Spatie\Period\Period::make() does only seem to accept string|object<DateTimeInterface>, did you maybe forget to handle an error condition?
Loading history...
298
            $this->getPrecisionMask()
299
        );
300
    }
301
302
    /**
303
     * @param \Spatie\Period\Period $period
304
     *
305
     * @return static|null
306
     */
307
    public function overlapSingle(Period $period): ?Period
308
    {
309
        $this->ensurePrecisionMatches($period);
310
311
        $start = $this->getIncludedStart() > $period->getIncludedStart()
312
            ? $this->getIncludedStart()
313
            : $period->getIncludedStart();
314
315
        $end = $this->getIncludedEnd() < $period->getIncludedEnd()
316
            ? $this->getIncludedEnd()
317
            : $period->getIncludedEnd();
318
319
        if ($start > $end) {
320
            return null;
321
        }
322
323
        return static::make($start, $end, $this->getPrecisionMask());
324
    }
325
326
    /**
327
     * @param \Spatie\Period\Period ...$periods
328
     *
329
     * @return \Spatie\Period\PeriodCollection|static[]
330
     */
331
    public function overlap(Period ...$periods): PeriodCollection
332
    {
333
        $overlapCollection = new PeriodCollection();
334
335
        foreach ($periods as $period) {
336
            $overlap = $this->overlapSingle($period);
337
338
            if ($overlap === null) {
339
                continue;
340
            }
341
342
            $overlapCollection[] = $overlap;
343
        }
344
345
        return $overlapCollection;
346
    }
347
348
    /**
349
     * @param \Spatie\Period\Period ...$periods
350
     *
351
     * @return static
352
     */
353
    public function overlapAll(Period ...$periods): Period
354
    {
355
        $overlap = clone $this;
356
357
        if (! count($periods)) {
358
            return $overlap;
359
        }
360
361
        foreach ($periods as $period) {
362
            $overlap = $overlap->overlapSingle($period);
363
        }
364
365
        return $overlap;
366
    }
367
368
    public function diffSingle(Period $period): PeriodCollection
369
    {
370
        $this->ensurePrecisionMatches($period);
371
372
        $periodCollection = new PeriodCollection();
373
374
        if (! $this->overlapsWith($period)) {
375
            $periodCollection[] = clone $this;
376
            $periodCollection[] = clone $period;
377
378
            return $periodCollection;
379
        }
380
381
        $overlap = $this->overlapSingle($period);
382
383
        $start = $this->getIncludedStart() < $period->getIncludedStart()
384
            ? $this->getIncludedStart()
385
            : $period->getIncludedStart();
386
387
        $end = $this->getIncludedEnd() > $period->getIncludedEnd()
388
            ? $this->getIncludedEnd()
389
            : $period->getIncludedEnd();
390
391
        if ($overlap->getIncludedStart() > $start) {
392
            $periodCollection[] = static::make(
393
                $start,
394
                $overlap->getIncludedStart()->sub($this->interval),
395
                $this->getPrecisionMask()
396
            );
397
        }
398
399
        if ($overlap->getIncludedEnd() < $end) {
400
            $periodCollection[] = static::make(
401
                $overlap->getIncludedEnd()->add($this->interval),
402
                $end,
403
                $this->getPrecisionMask()
404
            );
405
        }
406
407
        return $periodCollection;
408
    }
409
410
    /**
411
     * @param \Spatie\Period\Period ...$periods
412
     *
413
     * @return \Spatie\Period\PeriodCollection|static[]
414
     */
415
    public function diff(Period ...$periods): PeriodCollection
416
    {
417
        if (count($periods) === 1 && ! $this->overlapsWith($periods[0])) {
418
            $collection = new PeriodCollection();
419
420
            $gap = $this->gap($periods[0]);
421
422
            if ($gap !== null) {
423
                $collection[] = $gap;
424
            }
425
426
            return $collection;
427
        }
428
429
        $diffs = [];
430
431
        foreach ($periods as $period) {
432
            $diffs[] = $this->diffSingle($period);
433
        }
434
435
        $collection = (new PeriodCollection($this))->overlap(...$diffs);
436
437
        return $collection;
438
    }
439
440
    /**
441
     * @param \Spatie\Period\Period $period
442
     *
443
     * @return static
444
     */
445
    public function intersect(Period $period): Period
0 ignored issues
show
Unused Code introduced by
The parameter $period 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...
446
    {
447
448
    }
449
450
    public function getPrecisionMask(): int
451
    {
452
        return $this->precisionMask;
453
    }
454
455
    public function getIterator()
456
    {
457
        return new DatePeriod(
458
            $this->getIncludedStart(),
459
            $this->interval,
460
            $this->getIncludedEnd()->add($this->interval)
461
        );
462
    }
463
464
    protected static function resolveDate($date, ?string $format): DateTimeImmutable
465
    {
466
        if ($date instanceof DateTimeImmutable) {
467
            return $date;
468
        }
469
470
        if ($date instanceof DateTime) {
471
            return DateTimeImmutable::createFromMutable($date);
472
        }
473
474
        $format = self::resolveFormat($date, $format);
475
476
        if (! is_string($date)) {
477
            throw InvalidDate::forFormat($date, $format);
478
        }
479
480
        $dateTime = DateTimeImmutable::createFromFormat($format, $date);
481
482
        if ($dateTime === false) {
483
            throw InvalidDate::forFormat($date, $format);
484
        }
485
486
        if (strpos($format, ' ') === false) {
487
            $dateTime = $dateTime->setTime(0, 0, 0);
488
        }
489
490
        return $dateTime;
491
    }
492
493
    protected static function resolveFormat($date, ?string $format): string
494
    {
495
        if ($format !== null) {
496
            return $format;
497
        }
498
499
        if (strpos($format, ' ') === false && strpos($date, ' ') !== false) {
500
            return 'Y-m-d H:i:s';
501
        }
502
503
        return 'Y-m-d';
504
    }
505
506
    protected function roundDate(DateTimeInterface $date, int $precision): DateTimeImmutable
507
    {
508
        [$year, $month, $day, $hour, $minute, $second] = explode(' ', $date->format('Y m d H i s'));
0 ignored issues
show
Bug introduced by
The variable $year does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $month seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $day seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $hour seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $minute seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $second seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
509
510
        $month = (Precision::MONTH & $precision) === Precision::MONTH ? $month : '01';
0 ignored issues
show
Bug introduced by
The variable $month seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
511
        $day = (Precision::DAY & $precision) === Precision::DAY ? $day : '01';
0 ignored issues
show
Bug introduced by
The variable $day seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
512
        $hour = (Precision::HOUR & $precision) === Precision::HOUR ? $hour : '00';
0 ignored issues
show
Bug introduced by
The variable $hour seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
513
        $minute = (Precision::MINUTE & $precision) === Precision::MINUTE ? $minute : '00';
0 ignored issues
show
Bug introduced by
The variable $minute seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
514
        $second = (Precision::SECOND & $precision) === Precision::SECOND ? $second : '00';
0 ignored issues
show
Bug introduced by
The variable $second seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
515
516
        return DateTimeImmutable::createFromFormat(
517
            'Y m d H i s',
518
            implode(' ', [$year, $month, $day, $hour, $minute, $second])
519
        );
520
    }
521
522
    protected function createDateInterval(int $precision): DateInterval
523
    {
524
        $interval = [
525
            Precision::SECOND => 'PT1S',
526
            Precision::MINUTE => 'PT1M',
527
            Precision::HOUR => 'PT1H',
528
            Precision::DAY => 'P1D',
529
            Precision::MONTH => 'P1M',
530
            Precision::YEAR => 'P1Y',
531
        ][$precision];
532
533
        return new DateInterval($interval);
534
    }
535
536
    protected function ensurePrecisionMatches(Period $period): void
537
    {
538
        if ($this->precisionMask === $period->precisionMask) {
539
            return;
540
        }
541
542
        throw CannotComparePeriods::precisionDoesNotMatch();
543
    }
544
}
545