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
Pull Request — master (#68)
by
unknown
01:28 queued 11s
created

Period::touchesWith()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.8337
c 0
b 0
f 0
cc 6
nc 10
nop 1
1
<?php
2
3
namespace Spatie\Period;
4
5
use DateInterval;
6
use DatePeriod;
7
use DateTime;
8
use DateTimeImmutable;
9
use DateTimeInterface;
10
use IteratorAggregate;
11
use Spatie\Period\Exceptions\CannotComparePeriods;
12
use Spatie\Period\Exceptions\InvalidDate;
13
use Spatie\Period\Exceptions\InvalidPeriod;
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
            static::resolveDate($start, $format),
90
            static::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
        $diff = $this->getIncludedStart()->diff($period->getIncludedEnd());
163
164
        if($this->endsBefore($period->getIncludedStart())){
165
            $diff = $this->getIncludedEnd()->diff($period->getIncludedStart());
166
        }
167
168
        $intervals = [
169
            Precision::YEAR     => 'y',
170
            Precision::MONTH    => 'm',
171
            Precision::DAY      => 'd',
172
            Precision::HOUR     => 'h',
173
            Precision::MINUTE   => 'i',
174
            Precision::SECOND   => 's'
175
        ];
176
        $touches = true;
177
        $precisionMask = $this->getPrecisionMask();
178
        foreach ($intervals as $precision => $interval) {
179
            if($precisionMask === $precision){
180
                $touches = $touches && $diff->$interval <= 1;
181
            } else {
182
                $touches = $touches && $diff->$interval === 0;
183
            }
184
        }
185
        return $touches;
186
    }
187
188
    public function startsBefore(DateTimeInterface $date): bool
189
    {
190
        return $this->getIncludedStart() < $date;
191
    }
192
193
    public function startsBeforeOrAt(DateTimeInterface $date): bool
194
    {
195
        return $this->getIncludedStart() <= $date;
196
    }
197
198
    public function startsAfter(DateTimeInterface $date): bool
199
    {
200
        return $this->getIncludedStart() > $date;
201
    }
202
203
    public function startsAfterOrAt(DateTimeInterface $date): bool
204
    {
205
        return $this->getIncludedStart() >= $date;
206
    }
207
208
    public function startsAt(DateTimeInterface $date): bool
209
    {
210
        return $this->getIncludedStart()->getTimestamp() === $this->roundDate(
211
            $date,
212
            $this->precisionMask
213
        )->getTimestamp();
214
    }
215
216
    public function endsBefore(DateTimeInterface $date): bool
217
    {
218
        return $this->getIncludedEnd() < $this->roundDate(
219
                $date,
220
                $this->precisionMask
221
            );
222
    }
223
224
    public function endsBeforeOrAt(DateTimeInterface $date): bool
225
    {
226
        return $this->getIncludedEnd() <= $this->roundDate(
227
                $date,
228
                $this->precisionMask
229
            );
230
    }
231
232
    public function endsAfter(DateTimeInterface $date): bool
233
    {
234
        return $this->getIncludedEnd() > $this->roundDate(
235
                $date,
236
                $this->precisionMask
237
            );
238
    }
239
240
    public function endsAfterOrAt(DateTimeInterface $date): bool
241
    {
242
        return $this->getIncludedEnd() >= $this->roundDate(
243
                $date,
244
                $this->precisionMask
245
            );
246
    }
247
248
    public function endsAt(DateTimeInterface $date): bool
249
    {
250
        return $this->getIncludedEnd()->getTimestamp() === $this->roundDate(
251
                $date,
252
                $this->precisionMask
253
            )->getTimestamp();
254
    }
255
256
    public function contains(DateTimeInterface $date): bool
257
    {
258
        if ($this->roundDate($date, $this->precisionMask) < $this->getIncludedStart()) {
259
            return false;
260
        }
261
262
        if ($this->roundDate($date, $this->precisionMask) > $this->getIncludedEnd()) {
263
            return false;
264
        }
265
266
        return true;
267
    }
268
269
    public function equals(Period $period): bool
270
    {
271
        $this->ensurePrecisionMatches($period);
272
273
        if ($period->getIncludedStart()->getTimestamp() !== $this->getIncludedStart()->getTimestamp()) {
274
            return false;
275
        }
276
277
        if ($period->getIncludedEnd()->getTimestamp() !== $this->getIncludedEnd()->getTimestamp()) {
278
            return false;
279
        }
280
281
        return true;
282
    }
283
284
    /**
285
     * @param \Spatie\Period\Period $period
286
     *
287
     * @return static|null
288
     * @throws \Exception
289
     */
290
    public function gap(Period $period): ?Period
291
    {
292
        $this->ensurePrecisionMatches($period);
293
294
        if ($this->overlapsWith($period)) {
295
            return null;
296
        }
297
298
        if ($this->touchesWith($period)) {
299
            return null;
300
        }
301
302
        if ($this->getIncludedStart() >= $period->getIncludedEnd()) {
303
            return static::make(
304
                $period->getIncludedEnd()->add($this->interval),
305
                $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...
306
                $this->getPrecisionMask()
307
            );
308
        }
309
310
        return static::make(
311
            $this->getIncludedEnd()->add($this->interval),
312
            $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...
313
            $this->getPrecisionMask()
314
        );
315
    }
316
317
    /**
318
     * @param \Spatie\Period\Period $period
319
     *
320
     * @return static|null
321
     */
322
    public function overlapSingle(Period $period): ?Period
323
    {
324
        $this->ensurePrecisionMatches($period);
325
326
        $start = $this->getIncludedStart() > $period->getIncludedStart()
327
            ? $this->getIncludedStart()
328
            : $period->getIncludedStart();
329
330
        $end = $this->getIncludedEnd() < $period->getIncludedEnd()
331
            ? $this->getIncludedEnd()
332
            : $period->getIncludedEnd();
333
334
        if ($start > $end) {
335
            return null;
336
        }
337
338
        return static::make($start, $end, $this->getPrecisionMask());
339
    }
340
341
    /**
342
     * @param \Spatie\Period\Period ...$periods
343
     *
344
     * @return \Spatie\Period\PeriodCollection|static[]
345
     */
346
    public function overlap(Period ...$periods): PeriodCollection
347
    {
348
        $overlapCollection = new PeriodCollection();
349
350
        foreach ($periods as $period) {
351
            $overlap = $this->overlapSingle($period);
352
353
            if ($overlap === null) {
354
                continue;
355
            }
356
357
            $overlapCollection[] = $overlap;
358
        }
359
360
        return $overlapCollection;
361
    }
362
363
    /**
364
     * @param \Spatie\Period\Period ...$periods
365
     *
366
     * @return static
367
     */
368
    public function overlapAll(Period ...$periods): Period
369
    {
370
        $overlap = clone $this;
371
372
        if (! count($periods)) {
373
            return $overlap;
374
        }
375
376
        foreach ($periods as $period) {
377
            $overlap = $overlap->overlapSingle($period);
378
        }
379
380
        return $overlap;
381
    }
382
383
    public function diffSingle(Period $period): PeriodCollection
384
    {
385
        $this->ensurePrecisionMatches($period);
386
387
        $periodCollection = new PeriodCollection();
388
389
        if (! $this->overlapsWith($period)) {
390
            $periodCollection[] = clone $this;
391
            $periodCollection[] = clone $period;
392
393
            return $periodCollection;
394
        }
395
396
        $overlap = $this->overlapSingle($period);
397
398
        $start = $this->getIncludedStart() < $period->getIncludedStart()
399
            ? $this->getIncludedStart()
400
            : $period->getIncludedStart();
401
402
        $end = $this->getIncludedEnd() > $period->getIncludedEnd()
403
            ? $this->getIncludedEnd()
404
            : $period->getIncludedEnd();
405
406
        if ($overlap->getIncludedStart() > $start) {
407
            $periodCollection[] = static::make(
408
                $start,
409
                $overlap->getIncludedStart()->sub($this->interval),
410
                $this->getPrecisionMask()
411
            );
412
        }
413
414
        if ($overlap->getIncludedEnd() < $end) {
415
            $periodCollection[] = static::make(
416
                $overlap->getIncludedEnd()->add($this->interval),
417
                $end,
418
                $this->getPrecisionMask()
419
            );
420
        }
421
422
        return $periodCollection;
423
    }
424
425
    /**
426
     * @param \Spatie\Period\Period ...$periods
427
     *
428
     * @return \Spatie\Period\PeriodCollection|static[]
429
     */
430
    public function diff(Period ...$periods): PeriodCollection
431
    {
432
        if (count($periods) === 1 && ! $this->overlapsWith($periods[0])) {
433
            $collection = new PeriodCollection();
434
435
            $gap = $this->gap($periods[0]);
436
437
            if ($gap !== null) {
438
                $collection[] = $gap;
439
            }
440
441
            return $collection;
442
        }
443
444
        $diffs = [];
445
446
        foreach ($periods as $period) {
447
            $diffs[] = $this->diffSingle($period);
448
        }
449
450
        $collection = (new PeriodCollection($this))->overlap(...$diffs);
451
452
        return $collection;
453
    }
454
455
    public function getPrecisionMask(): int
456
    {
457
        return $this->precisionMask;
458
    }
459
460
    public function getIterator()
461
    {
462
        return new DatePeriod(
463
            $this->getIncludedStart(),
464
            $this->interval,
465
            $this->getIncludedEnd()->add($this->interval)
466
        );
467
    }
468
469
    protected static function resolveDate($date, ?string $format): DateTimeImmutable
470
    {
471
        if ($date instanceof DateTimeImmutable) {
472
            return $date;
473
        }
474
475
        if ($date instanceof DateTime) {
476
            return DateTimeImmutable::createFromMutable($date);
477
        }
478
479
        $format = static::resolveFormat($date, $format);
480
481
        if (! is_string($date)) {
482
            throw InvalidDate::forFormat($date, $format);
483
        }
484
485
        $dateTime = DateTimeImmutable::createFromFormat($format, $date);
486
487
        if ($dateTime === false) {
488
            throw InvalidDate::forFormat($date, $format);
489
        }
490
491
        if (strpos($format, ' ') === false) {
492
            $dateTime = $dateTime->setTime(0, 0, 0);
493
        }
494
495
        return $dateTime;
496
    }
497
498
    protected static function resolveFormat($date, ?string $format): string
499
    {
500
        if ($format !== null) {
501
            return $format;
502
        }
503
504
        if (strpos($format, ' ') === false && strpos($date, ' ') !== false) {
505
            return 'Y-m-d H:i:s';
506
        }
507
508
        return 'Y-m-d';
509
    }
510
511
    protected function roundDate(DateTimeInterface $date, int $precision): DateTimeImmutable
512
    {
513
        [$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...
514
515
        $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...
516
        $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...
517
        $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...
518
        $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...
519
        $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...
520
521
        return DateTimeImmutable::createFromFormat(
522
            'Y m d H i s',
523
            implode(' ', [$year, $month, $day, $hour, $minute, $second])
524
        );
525
    }
526
527
    protected function createDateInterval(int $precision): DateInterval
528
    {
529
        $interval = [
530
            Precision::SECOND => 'PT1S',
531
            Precision::MINUTE => 'PT1M',
532
            Precision::HOUR => 'PT1H',
533
            Precision::DAY => 'P1D',
534
            Precision::MONTH => 'P1M',
535
            Precision::YEAR => 'P1Y',
536
        ][$precision];
537
538
        return new DateInterval($interval);
539
    }
540
541
    protected function ensurePrecisionMatches(Period $period): void
542
    {
543
        if ($this->precisionMask === $period->precisionMask) {
544
            return;
545
        }
546
547
        throw CannotComparePeriods::precisionDoesNotMatch();
548
    }
549
}
550