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
02:27
created

EndlessPeriod::resolveDate()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.8497
c 0
b 0
f 0
cc 6
nc 6
nop 2
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
    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 = null;//$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()
60
            ? $this->end
61
            : $this->end->sub($this->interval);
0 ignored issues
show
Bug introduced by
The method sub cannot be called on $this->end (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
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
            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
            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(PeriodInterface $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 static|null
271
     * @throws \Exception
272
     */
273
    public function gap(PeriodInterface $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\EndlessPeriod::make() does only seem to accept string|object<DateTimeInterface>|null, did you maybe forget to handle an error condition?
Loading history...
289
                $this->getPrecisionMask()
290
            );
291
        }
292
293
        return static::make(
294
            $this->getIncludedEnd()->add($this->interval),
295
            $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...
296
            $this->getPrecisionMask()
297
        );
298
    }
299
300
    /**
301
     * @param \Spatie\Period\Period $period
302
     *
303
     * @return static|null
304
     */
305
    public function overlapSingle(PeriodInterface $period): ?Period
306
    {
307
        $this->ensurePrecisionMatches($period);
308
309
        $start = $this->getIncludedStart() > $period->getIncludedStart()
310
            ? $this->getIncludedStart()
311
            : $period->getIncludedStart();
312
313
        $end = $this->getIncludedEnd() < $period->getIncludedEnd()
314
            ? $this->getIncludedEnd()
315
            : $period->getIncludedEnd();
316
317
        if ($start > $end) {
318
            return null;
319
        }
320
321
        return static::make($start, $end, $this->getPrecisionMask());
322
    }
323
324
    /**
325
     * @param \Spatie\Period\Period ...$periods
326
     *
327
     * @return \Spatie\Period\PeriodCollection|static[]
328
     */
329
    public function overlap(PeriodInterface ...$periods): PeriodCollection
330
    {
331
        $overlapCollection = new PeriodCollection();
332
333
        foreach ($periods as $period) {
334
            $overlap = $this->overlapSingle($period);
335
336
            if ($overlap === null) {
337
                continue;
338
            }
339
340
            $overlapCollection[] = $overlap;
341
        }
342
343
        return $overlapCollection;
344
    }
345
346
    /**
347
     * @param \Spatie\Period\Period ...$periods
348
     *
349
     * @return static
350
     */
351
    public function overlapAll(PeriodInterface ...$periods): Period
352
    {
353
        $overlap = clone $this;
354
355
        if (! count($periods)) {
356
            return $overlap;
357
        }
358
359
        foreach ($periods as $period) {
360
            $overlap = $overlap->overlapSingle($period);
361
        }
362
363
        return $overlap;
364
    }
365
366
    public function diffSingle(PeriodInterface $period): PeriodCollection
367
    {
368
        $this->ensurePrecisionMatches($period);
369
370
        $periodCollection = new PeriodCollection();
371
372
        if (! $this->overlapsWith($period)) {
373
            $periodCollection[] = clone $this;
374
            $periodCollection[] = clone $period;
375
376
            return $periodCollection;
377
        }
378
379
        $overlap = $this->overlapSingle($period);
380
381
        $start = $this->getIncludedStart() < $period->getIncludedStart()
382
            ? $this->getIncludedStart()
383
            : $period->getIncludedStart();
384
385
        $end = $this->getIncludedEnd() > $period->getIncludedEnd()
386
            ? $this->getIncludedEnd()
387
            : $period->getIncludedEnd();
388
389
        if ($overlap->getIncludedStart() > $start) {
390
            $periodCollection[] = static::make(
391
                $start,
392
                $overlap->getIncludedStart()->sub($this->interval),
393
                $this->getPrecisionMask()
394
            );
395
        }
396
397
        if ($overlap->getIncludedEnd() < $end) {
398
            $periodCollection[] = static::make(
399
                $overlap->getIncludedEnd()->add($this->interval),
400
                $end,
401
                $this->getPrecisionMask()
402
            );
403
        }
404
405
        return $periodCollection;
406
    }
407
408
    /**
409
     * @param \Spatie\Period\Period ...$periods
410
     *
411
     * @return \Spatie\Period\PeriodCollection|static[]
412
     */
413
    public function diff(PeriodInterface ...$periods): PeriodCollection
414
    {
415
        if (count($periods) === 1 && ! $this->overlapsWith($periods[0])) {
416
            $collection = new PeriodCollection();
417
418
            $gap = $this->gap($periods[0]);
419
420
            if ($gap !== null) {
421
                $collection[] = $gap;
422
            }
423
424
            return $collection;
425
        }
426
427
        $diffs = [];
428
429
        foreach ($periods as $period) {
430
            $diffs[] = $this->diffSingle($period);
431
        }
432
433
        $collection = (new PeriodCollection($this))->overlap(...$diffs);
434
435
        return $collection;
436
    }
437
438
    public function getPrecisionMask(): int
439
    {
440
        return $this->precisionMask;
441
    }
442
443
    public function getIterator()
444
    {
445
        return new DatePeriod(
446
            $this->getIncludedStart(),
447
            $this->interval,
448
            $this->getIncludedEnd()->add($this->interval)
449
        );
450
    }
451
452
    protected static function resolveDate($date, ?string $format): DateTimeImmutable
453
    {
454
        if ($date instanceof DateTimeImmutable) {
455
            return $date;
456
        }
457
458
        if ($date instanceof DateTime) {
459
            return DateTimeImmutable::createFromMutable($date);
460
        }
461
462
        $format = static::resolveFormat($date, $format);
463
464
        if (! is_string($date)) {
465
            throw InvalidDate::forFormat($date, $format);
466
        }
467
468
        $dateTime = DateTimeImmutable::createFromFormat($format, $date);
469
470
        if ($dateTime === false) {
471
            throw InvalidDate::forFormat($date, $format);
472
        }
473
474
        if (strpos($format, ' ') === false) {
475
            $dateTime = $dateTime->setTime(0, 0, 0);
476
        }
477
478
        return $dateTime;
479
    }
480
481
    protected static function resolveFormat($date, ?string $format): string
482
    {
483
        if ($format !== null) {
484
            return $format;
485
        }
486
487
        if (strpos($format, ' ') === false && strpos($date, ' ') !== false) {
488
            return 'Y-m-d H:i:s';
489
        }
490
491
        return 'Y-m-d';
492
    }
493
494
    protected function roundDate(DateTimeInterface $date, int $precision): DateTimeImmutable
495
    {
496
        [$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...
497
498
        $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...
499
        $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...
500
        $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...
501
        $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...
502
        $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...
503
504
        return DateTimeImmutable::createFromFormat(
505
            'Y m d H i s',
506
            implode(' ', [$year, $month, $day, $hour, $minute, $second])
507
        );
508
    }
509
510
    protected function createDateInterval(int $precision): DateInterval
511
    {
512
        $interval = [
513
            Precision::SECOND => 'PT1S',
514
            Precision::MINUTE => 'PT1M',
515
            Precision::HOUR => 'PT1H',
516
            Precision::DAY => 'P1D',
517
            Precision::MONTH => 'P1M',
518
            Precision::YEAR => 'P1Y',
519
        ][$precision];
520
521
        return new DateInterval($interval);
522
    }
523
524
    protected function ensurePrecisionMatches(PeriodInterface $period): void
525
    {
526
        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...
527
            return;
528
        }
529
530
        throw CannotComparePeriods::precisionDoesNotMatch();
531
    }
532
}
533