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 (#52)
by
unknown
01:12
created

EndlessPeriod::diffSingle()   B

Complexity

Conditions 6
Paths 17

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 8.6417
c 0
b 0
f 0
cc 6
nc 17
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 EndlessPeriod implements IteratorAggregate, PeriodInterface
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
    public $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 = $end ? $this->roundDate($end, $this->precisionMask) : null;
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()
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 = null,
76
        ?int $precisionMask = null,
77
        ?int $boundaryExclusionMask = null,
78
        ?string $format = null
79
    ): PeriodInterface {
80
        if ($start === null) {
81
            throw InvalidDate::cannotBeNull('Start date');
82
        }
83
84
//        if ($end !== null) {
85
//            throw InvalidDate::shouldBeNull('End date');
86
//        }
87
88
        return new static(
89
            static::resolveDate($start, $format),
90
            $end ? static::resolveDate($end, $format) : null,
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
        return null;
139
    }
140
141
    public function overlapsWith(PeriodInterface $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(PeriodInterface $period): bool
157
    {
158
        $this->ensurePrecisionMatches($period);
159
160
        if ($period instanceof EndlessPeriod
161
            && (null == $this->getIncludedEnd() && null == $period->getIncludedEnd())) {
162
            return true;
163
        } else {
164
            return false;
165
        }
166
167
        if ($this->getIncludedStart()->diff($period->getIncludedEnd())->days <= 1) {
0 ignored issues
show
Unused Code introduced by
if ($this->getIncludedSt...1) { return true; } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

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

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
530
            return;
531
        }
532
533
        throw CannotComparePeriods::precisionDoesNotMatch();
534
    }
535
}
536