1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Spatie\OpeningHours; |
4
|
|
|
|
5
|
|
|
use Countable; |
6
|
|
|
use Generator; |
7
|
|
|
use ArrayAccess; |
8
|
|
|
use ArrayIterator; |
9
|
|
|
use IteratorAggregate; |
10
|
|
|
use Spatie\OpeningHours\Helpers\Arr; |
11
|
|
|
use Spatie\OpeningHours\Helpers\DataTrait; |
12
|
|
|
use Spatie\OpeningHours\Helpers\RangeFinder; |
13
|
|
|
use Spatie\OpeningHours\Exceptions\NonMutableOffsets; |
14
|
|
|
use Spatie\OpeningHours\Exceptions\OverlappingTimeRanges; |
15
|
|
|
|
16
|
|
|
class OpeningHoursForDay implements ArrayAccess, Countable, IteratorAggregate |
17
|
|
|
{ |
18
|
|
|
use DataTrait, RangeFinder; |
19
|
|
|
|
20
|
|
|
/** @var \Spatie\OpeningHours\TimeRange[] */ |
21
|
|
|
protected $openingHours = []; |
22
|
|
|
|
23
|
|
|
public static function fromStrings(array $strings) |
24
|
|
|
{ |
25
|
|
|
if (isset($strings['hours'])) { |
26
|
|
|
return static::fromStrings($strings['hours'])->setData($strings['data'] ?? null); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
$openingHoursForDay = new static(); |
30
|
|
|
|
31
|
|
|
if (isset($strings['data'])) { |
32
|
|
|
$openingHoursForDay->setData($strings['data'] ?? null); |
33
|
|
|
unset($strings['data']); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
uasort($strings, function ($a, $b) { |
37
|
|
|
return strcmp(static::getHoursFromRange($a), static::getHoursFromRange($b)); |
38
|
|
|
}); |
39
|
|
|
|
40
|
|
|
$timeRanges = Arr::map($strings, function ($string) { |
41
|
|
|
return TimeRange::fromDefinition($string); |
42
|
|
|
}); |
43
|
|
|
|
44
|
|
|
$openingHoursForDay->guardAgainstTimeRangeOverlaps($timeRanges); |
45
|
|
|
|
46
|
|
|
$openingHoursForDay->openingHours = $timeRanges; |
47
|
|
|
|
48
|
|
|
return $openingHoursForDay; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
public function isOpenAt(Time $time) |
52
|
|
|
{ |
53
|
|
|
foreach ($this->openingHours as $timeRange) { |
54
|
|
|
if ($timeRange->containsTime($time)) { |
55
|
|
|
return true; |
56
|
|
|
} |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
return false; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
public function isOpenAtNight(Time $time) |
63
|
|
|
{ |
64
|
|
|
foreach ($this->openingHours as $timeRange) { |
65
|
|
|
if ($timeRange->containsNightTime($time)) { |
66
|
|
|
return true; |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
return false; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @param callable[] $filters |
75
|
|
|
* @param bool $reverse |
76
|
|
|
* |
77
|
|
|
* @return Time|bool |
78
|
|
|
*/ |
79
|
|
|
public function openingHoursFilter(array $filters, bool $reverse = false) |
80
|
|
|
{ |
81
|
|
|
foreach (($reverse ? array_reverse($this->openingHours) : $this->openingHours) as $timeRange) { |
82
|
|
|
foreach ($filters as $filter) { |
83
|
|
|
if ($result = $filter($timeRange)) { |
84
|
|
|
reset($timeRange); |
85
|
|
|
|
86
|
|
|
return $result; |
87
|
|
|
} |
88
|
|
|
} |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
return false; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* @param Time $time |
96
|
|
|
* |
97
|
|
|
* @return bool|Time |
98
|
|
|
*/ |
99
|
|
|
public function nextOpen(Time $time) |
100
|
|
|
{ |
101
|
|
|
return $this->openingHoursFilter([ |
102
|
|
|
function ($timeRange) use ($time) { |
103
|
|
|
return $this->findOpenInFreeTime($time, $timeRange); |
104
|
|
|
}, |
105
|
|
|
]); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @param Time $time |
110
|
|
|
* |
111
|
|
|
* @return bool|TimeRange |
112
|
|
|
*/ |
113
|
|
|
public function nextOpenRange(Time $time) |
114
|
|
|
{ |
115
|
|
|
return $this->openingHoursFilter([ |
116
|
|
|
function ($timeRange) use ($time) { |
117
|
|
|
return $this->findRangeInFreeTime($time, $timeRange); |
118
|
|
|
}, |
119
|
|
|
]); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @param Time $time |
124
|
|
|
* |
125
|
|
|
* @return bool|Time |
126
|
|
|
*/ |
127
|
|
View Code Duplication |
public function nextClose(Time $time) |
|
|
|
|
128
|
|
|
{ |
129
|
|
|
return $this->openingHoursFilter([ |
130
|
|
|
function ($timeRange) use ($time) { |
131
|
|
|
return $this->findCloseInWorkingHours($time, $timeRange); |
132
|
|
|
}, |
133
|
|
|
function ($timeRange) use ($time) { |
134
|
|
|
return $this->findCloseInFreeTime($time, $timeRange); |
135
|
|
|
}, |
136
|
|
|
]); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* @param Time $time |
141
|
|
|
* |
142
|
|
|
* @return bool|TimeRange |
143
|
|
|
*/ |
144
|
|
View Code Duplication |
public function nextCloseRange(Time $time) |
|
|
|
|
145
|
|
|
{ |
146
|
|
|
return $this->openingHoursFilter([ |
147
|
|
|
function ($timeRange) use ($time) { |
148
|
|
|
return $this->findCloseRangeInWorkingHours($time, $timeRange); |
149
|
|
|
}, |
150
|
|
|
function ($timeRange) use ($time) { |
151
|
|
|
return $this->findRangeInFreeTime($time, $timeRange); |
152
|
|
|
}, |
153
|
|
|
]); |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* @param Time $time |
158
|
|
|
* |
159
|
|
|
* @return bool|Time |
160
|
|
|
*/ |
161
|
|
View Code Duplication |
public function previousOpen(Time $time) |
|
|
|
|
162
|
|
|
{ |
163
|
|
|
return $this->openingHoursFilter([ |
164
|
|
|
function ($timeRange) use ($time) { |
165
|
|
|
return $this->findPreviousOpenInFreeTime($time, $timeRange); |
166
|
|
|
}, |
167
|
|
|
function ($timeRange) use ($time) { |
168
|
|
|
return $this->findOpenInWorkingHours($time, $timeRange); |
169
|
|
|
}, |
170
|
|
|
], true); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* @param Time $time |
175
|
|
|
* |
176
|
|
|
* @return bool|TimeRange |
177
|
|
|
*/ |
178
|
|
|
public function previousOpenRange(Time $time) |
179
|
|
|
{ |
180
|
|
|
return $this->openingHoursFilter([ |
181
|
|
|
function ($timeRange) use ($time) { |
182
|
|
|
return $this->findRangeInFreeTime($time, $timeRange); |
183
|
|
|
}, |
184
|
|
|
], true); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @param Time $time |
189
|
|
|
* |
190
|
|
|
* @return bool|Time |
191
|
|
|
*/ |
192
|
|
|
public function previousClose(Time $time) |
193
|
|
|
{ |
194
|
|
|
return $this->openingHoursFilter([ |
195
|
|
|
function ($timeRange) use ($time) { |
196
|
|
|
return $this->findPreviousCloseInWorkingHours($time, $timeRange); |
197
|
|
|
}, |
198
|
|
|
], true); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @param Time $time |
203
|
|
|
* |
204
|
|
|
* @return bool|TimeRange |
205
|
|
|
*/ |
206
|
|
|
public function previousCloseRange(Time $time) |
207
|
|
|
{ |
208
|
|
|
return $this->openingHoursFilter([ |
209
|
|
|
function ($timeRange) use ($time) { |
210
|
|
|
return $this->findPreviousRangeInFreeTime($time, $timeRange); |
211
|
|
|
}, |
212
|
|
|
], true); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
protected static function getHoursFromRange($range) |
216
|
|
|
{ |
217
|
|
|
return strval((is_array($range) |
218
|
|
|
? ($range['hours'] ?? array_values($range)[0] ?? null) |
219
|
|
|
: null |
220
|
|
|
) ?: $range); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
public function offsetExists($offset): bool |
224
|
|
|
{ |
225
|
|
|
return isset($this->openingHours[$offset]); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
public function offsetGet($offset) |
229
|
|
|
{ |
230
|
|
|
return $this->openingHours[$offset]; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
public function offsetSet($offset, $value) |
234
|
|
|
{ |
235
|
|
|
throw NonMutableOffsets::forClass(static::class); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
public function offsetUnset($offset) |
239
|
|
|
{ |
240
|
|
|
unset($this->openingHours[$offset]); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
public function count(): int |
244
|
|
|
{ |
245
|
|
|
return count($this->openingHours); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
public function getIterator() |
249
|
|
|
{ |
250
|
|
|
return new ArrayIterator($this->openingHours); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* @param Time $time |
255
|
|
|
* |
256
|
|
|
* @return TimeRange[] |
257
|
|
|
*/ |
258
|
|
|
public function forTime(Time $time): Generator |
259
|
|
|
{ |
260
|
|
|
foreach ($this as $range) { |
261
|
|
|
/* @var TimeRange $range */ |
262
|
|
|
|
263
|
|
|
if ($range->containsTime($time)) { |
264
|
|
|
yield $range; |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* @param Time $time |
271
|
|
|
* |
272
|
|
|
* @return TimeRange[] |
273
|
|
|
*/ |
274
|
|
|
public function forNightTime(Time $time): Generator |
275
|
|
|
{ |
276
|
|
|
foreach ($this as $range) { |
277
|
|
|
/* @var TimeRange $range */ |
278
|
|
|
|
279
|
|
|
if ($range->containsNightTime($time)) { |
280
|
|
|
yield $range; |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
public function isEmpty(): bool |
286
|
|
|
{ |
287
|
|
|
return empty($this->openingHours); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
public function map(callable $callback): array |
291
|
|
|
{ |
292
|
|
|
return Arr::map($this->openingHours, $callback); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
protected function guardAgainstTimeRangeOverlaps(array $openingHours) |
296
|
|
|
{ |
297
|
|
|
foreach (Arr::createUniquePairs($openingHours) as $timeRanges) { |
298
|
|
|
if ($timeRanges[0]->overlaps($timeRanges[1])) { |
299
|
|
|
throw OverlappingTimeRanges::forRanges($timeRanges[0], $timeRanges[1]); |
300
|
|
|
} |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
public function __toString() |
305
|
|
|
{ |
306
|
|
|
$values = []; |
307
|
|
|
foreach ($this->openingHours as $openingHour) { |
308
|
|
|
$values[] = (string) $openingHour; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
return implode(',', $values); |
312
|
|
|
} |
313
|
|
|
} |
314
|
|
|
|
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.