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 ( fbb30b...8f4bd7 )
by Brent
9s
created

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