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 ( f6507d...647223 )
by Kyle
15s queued 10s
created

OpeningHours::nextClose()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 28

Duplication

Lines 13
Ratio 46.43 %

Importance

Changes 0
Metric Value
dl 13
loc 28
rs 8.8497
c 0
b 0
f 0
cc 6
nc 6
nop 1
1
<?php
2
3
namespace Spatie\OpeningHours;
4
5
use DateTime;
6
use DateTimeZone;
7
use DateTimeImmutable;
8
use DateTimeInterface;
9
use Spatie\OpeningHours\Helpers\Arr;
10
use Spatie\OpeningHours\Helpers\DataTrait;
11
use Spatie\OpeningHours\Exceptions\Exception;
12
use Spatie\OpeningHours\Exceptions\InvalidDate;
13
use Spatie\OpeningHours\Exceptions\InvalidDayName;
14
15
class OpeningHours
16
{
17
    use DataTrait;
18
19
    /** @var \Spatie\OpeningHours\Day[] */
20
    protected $openingHours = [];
21
22
    /** @var \Spatie\OpeningHours\OpeningHoursForDay[] */
23
    protected $exceptions = [];
24
25
    /** @var callable[] */
26
    protected $filters = [];
27
28
    /** @var DateTimeZone|null */
29
    protected $timezone = null;
30
31
    public function __construct($timezone = null)
32
    {
33
        $this->timezone = $timezone ? new DateTimeZone($timezone) : null;
34
35
        $this->openingHours = Day::mapDays(function () {
36
            return new OpeningHoursForDay();
37
        });
38
    }
39
40
    /**
41
     * @param array $data
42
     *
43
     * @return static
44
     */
45
    public static function create(array $data)
46
    {
47
        return (new static())->fill($data);
48
    }
49
50
    /**
51
     * @param array $data
52
     *
53
     * @return array
54
     */
55
    public static function mergeOverlappingRanges(array $data)
56
    {
57
        $result = [];
58
        $ranges = [];
59
        foreach ($data as $key => $value) {
60
            $value = is_array($value)
61
                ? static::mergeOverlappingRanges($value)
62
                : (is_string($value) ? TimeRange::fromString($value) : $value);
63
64
            if ($value instanceof TimeRange) {
65
                $newRanges = [];
66
                foreach ($ranges as $range) {
67
                    if ($value->format() === $range->format()) {
68
                        continue 2;
69
                    }
70
71
                    if ($value->overlaps($range) || $range->overlaps($value)) {
72
                        $value = TimeRange::fromList([$value, $range]);
73
74
                        continue;
75
                    }
76
77
                    $newRanges[] = $range;
78
                }
79
80
                $newRanges[] = $value;
81
                $ranges = $newRanges;
82
83
                continue;
84
            }
85
86
            $result[$key] = $value;
87
        }
88
89
        foreach ($ranges as $range) {
90
            $result[] = $range;
91
        }
92
93
        return $result;
94
    }
95
96
    /**
97
     * @param array $data
98
     *
99
     * @return static
100
     */
101
    public static function createAndMergeOverlappingRanges(array $data)
102
    {
103
        return static::create(static::mergeOverlappingRanges($data));
104
    }
105
106
    /**
107
     * @param array $data
108
     *
109
     * @return bool
110
     */
111
    public static function isValid(array $data): bool
112
    {
113
        try {
114
            static::create($data);
115
116
            return true;
117
        } catch (Exception $exception) {
118
            return false;
119
        }
120
    }
121
122
    public function setFilters(array $filters)
123
    {
124
        $this->filters = $filters;
125
126
        return $this;
127
    }
128
129
    public function getFilters(): array
130
    {
131
        return $this->filters;
132
    }
133
134
    public function fill(array $data)
135
    {
136
        list($openingHours, $exceptions, $metaData, $filters) = $this->parseOpeningHoursAndExceptions($data);
137
138
        foreach ($openingHours as $day => $openingHoursForThisDay) {
139
            $this->setOpeningHoursFromStrings($day, $openingHoursForThisDay);
140
        }
141
142
        $this->setExceptionsFromStrings($exceptions);
143
144
        return $this->setFilters($filters)->setData($metaData);
145
    }
146
147
    public function forWeek(): array
148
    {
149
        return $this->openingHours;
150
    }
151
152
    public function forWeekCombined(): array
153
    {
154
        $equalDays = [];
155
        $allOpeningHours = $this->openingHours;
156
        $uniqueOpeningHours = array_unique($allOpeningHours);
157
        $nonUniqueOpeningHours = $allOpeningHours;
158
159
        foreach ($uniqueOpeningHours as $day => $value) {
160
            $equalDays[$day] = ['days' => [$day], 'opening_hours' => $value];
161
            unset($nonUniqueOpeningHours[$day]);
162
        }
163
164
        foreach ($uniqueOpeningHours as $uniqueDay => $uniqueValue) {
165
            foreach ($nonUniqueOpeningHours as $nonUniqueDay => $nonUniqueValue) {
166
                if ((string) $uniqueValue === (string) $nonUniqueValue) {
167
                    $equalDays[$uniqueDay]['days'][] = $nonUniqueDay;
168
                }
169
            }
170
        }
171
172
        return $equalDays;
173
    }
174
175
    public function forDay(string $day): OpeningHoursForDay
176
    {
177
        $day = $this->normalizeDayName($day);
178
179
        return $this->openingHours[$day];
180
    }
181
182
    public function forDate(DateTimeInterface $date): OpeningHoursForDay
183
    {
184
        $date = $this->applyTimezone($date);
185
186
        foreach ($this->filters as $filter) {
187
            $result = $filter($date);
188
189
            if (is_array($result)) {
190
                return OpeningHoursForDay::fromStrings($result);
191
            }
192
        }
193
194
        return $this->exceptions[$date->format('Y-m-d')] ?? ($this->exceptions[$date->format('m-d')] ?? $this->forDay(Day::onDateTime($date)));
195
    }
196
197
    public function exceptions(): array
198
    {
199
        return $this->exceptions;
200
    }
201
202
    public function isOpenOn(string $day): bool
203
    {
204
        return count($this->forDay($day)) > 0;
205
    }
206
207
    public function isClosedOn(string $day): bool
208
    {
209
        return ! $this->isOpenOn($day);
210
    }
211
212
    public function isOpenAt(DateTimeInterface $dateTime): bool
213
    {
214
        $dateTime = $this->applyTimezone($dateTime);
215
216
        $openingHoursForDay = $this->forDate($dateTime);
217
218
        return $openingHoursForDay->isOpenAt(Time::fromDateTime($dateTime));
219
    }
220
221
    public function isClosedAt(DateTimeInterface $dateTime): bool
222
    {
223
        return ! $this->isOpenAt($dateTime);
224
    }
225
226
    public function isOpen(): bool
227
    {
228
        return $this->isOpenAt(new DateTime());
229
    }
230
231
    public function isClosed(): bool
232
    {
233
        return $this->isClosedAt(new DateTime());
234
    }
235
236
    public function nextOpen(DateTimeInterface $dateTime): DateTimeInterface
237
    {
238
        if (! ($dateTime instanceof DateTimeImmutable)) {
239
            $dateTime = clone $dateTime;
240
        }
241
242
        $openingHoursForDay = $this->forDate($dateTime);
243
        $nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime));
244
245 View Code Duplication
        while ($nextOpen === false || $nextOpen->hours() >= 24) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
246
            $dateTime = $dateTime
247
                ->modify('+1 day')
248
                ->setTime(0, 0, 0);
249
250
            if ($this->isOpenAt($dateTime) && ! $openingHoursForDay->isOpenAt(Time::fromString('23:59'))) {
251
                return $dateTime;
252
            }
253
254
            $openingHoursForDay = $this->forDate($dateTime);
255
256
            $nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime));
257
        }
258
259
        if ($dateTime->format('H:i') === '00:00' && $this->isOpenAt((clone $dateTime)->modify('-1 second'))) {
260
            return $this->nextOpen($dateTime->modify('+1 minute'));
261
        }
262
263
        $nextDateTime = $nextOpen->toDateTime();
264
        $dateTime = $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0);
265
266
        return $dateTime;
267
    }
268
269
    public function nextClose(DateTimeInterface $dateTime): DateTimeInterface
270
    {
271
        if (! ($dateTime instanceof DateTimeImmutable)) {
272
            $dateTime = clone $dateTime;
273
        }
274
275
        $openingHoursForDay = $this->forDate($dateTime);
276
        $nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime));
277
278 View Code Duplication
        while ($nextClose === false || $nextClose->hours() >= 24) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279
            $dateTime = $dateTime
280
                ->modify('+1 day')
281
                ->setTime(0, 0, 0);
282
283
            if ($this->isClosedAt($dateTime) && $openingHoursForDay->isOpenAt(Time::fromString('23:59'))) {
284
                return $dateTime;
285
            }
286
287
            $openingHoursForDay = $this->forDate($dateTime);
288
289
            $nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime));
290
        }
291
292
        $nextDateTime = $nextClose->toDateTime();
293
        $dateTime = $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0);
294
295
        return $dateTime;
296
    }
297
298
    public function regularClosingDays(): array
299
    {
300
        return array_keys($this->filter(function (OpeningHoursForDay $openingHoursForDay) {
301
            return $openingHoursForDay->isEmpty();
302
        }));
303
    }
304
305
    public function regularClosingDaysISO(): array
306
    {
307
        return Arr::map($this->regularClosingDays(), [Day::class, 'toISO']);
308
    }
309
310
    public function exceptionalClosingDates(): array
311
    {
312
        $dates = array_keys($this->filterExceptions(function (OpeningHoursForDay $openingHoursForDay) {
313
            return $openingHoursForDay->isEmpty();
314
        }));
315
316
        return Arr::map($dates, function ($date) {
317
            return DateTime::createFromFormat('Y-m-d', $date);
318
        });
319
    }
320
321
    public function setTimezone($timezone)
322
    {
323
        $this->timezone = new DateTimeZone($timezone);
324
    }
325
326
    protected function parseOpeningHoursAndExceptions(array $data): array
327
    {
328
        $metaData = Arr::pull($data, 'data', null);
329
        $exceptions = [];
330
        $filters = Arr::pull($data, 'filters', []);
331
        foreach (Arr::pull($data, 'exceptions', []) as $key => $exception) {
332
            if (is_callable($exception)) {
333
                $filters[] = $exception;
334
335
                continue;
336
            }
337
338
            $exceptions[$key] = $exception;
339
        }
340
        $openingHours = [];
341
342
        foreach ($data as $day => $openingHoursData) {
343
            $openingHours[$this->normalizeDayName($day)] = $openingHoursData;
344
        }
345
346
        return [$openingHours, $exceptions, $metaData, $filters];
347
    }
348
349
    protected function setOpeningHoursFromStrings(string $day, array $openingHours)
350
    {
351
        $day = $this->normalizeDayName($day);
352
353
        $data = null;
354
355
        if (isset($openingHours['data'])) {
356
            $data = $openingHours['data'];
357
            unset($openingHours['data']);
358
        }
359
360
        $this->openingHours[$day] = OpeningHoursForDay::fromStrings($openingHours)->setData($data);
361
    }
362
363
    protected function setExceptionsFromStrings(array $exceptions)
364
    {
365
        $this->exceptions = Arr::map($exceptions, function (array $openingHours, string $date) {
366
            $recurring = DateTime::createFromFormat('m-d', $date);
367
368
            if ($recurring === false || $recurring->format('m-d') !== $date) {
369
                $dateTime = DateTime::createFromFormat('Y-m-d', $date);
370
371
                if ($dateTime === false || $dateTime->format('Y-m-d') !== $date) {
372
                    throw InvalidDate::invalidDate($date);
373
                }
374
            }
375
376
            return OpeningHoursForDay::fromStrings($openingHours);
377
        });
378
    }
379
380
    protected function normalizeDayName(string $day)
381
    {
382
        $day = strtolower($day);
383
384
        if (! Day::isValid($day)) {
385
            throw InvalidDayName::invalidDayName($day);
386
        }
387
388
        return $day;
389
    }
390
391
    protected function applyTimezone(DateTimeInterface $date)
392
    {
393
        if ($this->timezone) {
394
            $date = $date->setTimezone($this->timezone);
395
        }
396
397
        return $date;
398
    }
399
400
    public function filter(callable $callback): array
401
    {
402
        return Arr::filter($this->openingHours, $callback);
403
    }
404
405
    public function map(callable $callback): array
406
    {
407
        return Arr::map($this->openingHours, $callback);
408
    }
409
410
    public function flatMap(callable $callback): array
411
    {
412
        return Arr::flatMap($this->openingHours, $callback);
413
    }
414
415
    public function filterExceptions(callable $callback): array
416
    {
417
        return Arr::filter($this->exceptions, $callback);
418
    }
419
420
    public function mapExceptions(callable $callback): array
421
    {
422
        return Arr::map($this->exceptions, $callback);
423
    }
424
425
    public function flatMapExceptions(callable $callback): array
426
    {
427
        return Arr::flatMap($this->exceptions, $callback);
428
    }
429
430
    public function asStructuredData(): array
431
    {
432 View Code Duplication
        $regularHours = $this->flatMap(function (OpeningHoursForDay $openingHoursForDay, string $day) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
433
            return $openingHoursForDay->map(function (TimeRange $timeRange) use ($day) {
434
                return [
435
                    '@type' => 'OpeningHoursSpecification',
436
                    'dayOfWeek' => ucfirst($day),
437
                    'opens' => (string) $timeRange->start(),
438
                    'closes' => (string) $timeRange->end(),
439
                ];
440
            });
441
        });
442
443
        $exceptions = $this->flatMapExceptions(function (OpeningHoursForDay $openingHoursForDay, string $date) {
444
            if ($openingHoursForDay->isEmpty()) {
445
                return [[
446
                    '@type' => 'OpeningHoursSpecification',
447
                    'opens' => '00:00',
448
                    'closes' => '00:00',
449
                    'validFrom' => $date,
450
                    'validThrough' => $date,
451
                ]];
452
            }
453
454 View Code Duplication
            return $openingHoursForDay->map(function (TimeRange $timeRange) use ($date) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
455
                return [
456
                    '@type' => 'OpeningHoursSpecification',
457
                    'opens' => (string) $timeRange->start(),
458
                    'closes' => (string) $timeRange->end(),
459
                    'validFrom' => $date,
460
                    'validThrough' => $date,
461
                ];
462
            });
463
        });
464
465
        return array_merge($regularHours, $exceptions);
466
    }
467
}
468