1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @copyright 2016, Aleksandr Bushlanov |
4
|
|
|
* @package gtvolk\WorkingTime |
5
|
|
|
* @link https://github.com/GT-Volk/ |
6
|
|
|
* @license https://github.com/GT-Volk/working-time/blob/master/LICENSE.md |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace gtvolk\WorkingTime; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Класс рассчитывает временнЫе интервалы, учитывая рабочие часы и выходные дни. |
13
|
|
|
* |
14
|
|
|
* @property \DateTime $dateTime |
15
|
|
|
* @property array $workingDays |
16
|
|
|
* @property array $weekends |
17
|
|
|
* @property array $holidays |
18
|
|
|
* |
19
|
|
|
* @since 0.9 |
20
|
|
|
* |
21
|
|
|
* @author Aleksandr Bushlanov <[email protected]> |
22
|
|
|
*/ |
23
|
|
|
class WorkingTime |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var \DateTime |
27
|
|
|
*/ |
28
|
|
|
public $dateTime; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var array |
32
|
|
|
*/ |
33
|
|
|
public $workingDays; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var array |
37
|
|
|
*/ |
38
|
|
|
public $weekends; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var array |
42
|
|
|
*/ |
43
|
|
|
public $holidays; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* WorkingTime constructor. |
47
|
|
|
* |
48
|
|
|
* @param array $workTimeConfig |
49
|
|
|
* @param string $dateTime |
50
|
|
|
*/ |
51
|
|
|
public function __construct(array $workTimeConfig, string $dateTime = 'now') |
52
|
|
|
{ |
53
|
|
|
$this->dateTime = new \DateTime($dateTime); |
54
|
|
|
foreach ($workTimeConfig as $name => $value) { |
55
|
|
|
$this->$name = $value; |
56
|
|
|
} |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Проверяет является ли дата праздничным днём. |
61
|
|
|
* |
62
|
|
|
* @param string $date |
63
|
|
|
* @return bool |
64
|
|
|
*/ |
65
|
|
View Code Duplication |
public function isHoliday(string $date = null) : bool |
|
|
|
|
66
|
|
|
{ |
67
|
|
|
if ($date === null) { |
68
|
|
|
$day = date('m-d', $this->dateTime->getTimestamp()); |
69
|
|
|
} else { |
70
|
|
|
$day = date('m-d', strtotime($date)); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
return in_array($day, $this->holidays, false); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Проверяет является ли дата выходным днём. |
78
|
|
|
* |
79
|
|
|
* @param string $date |
80
|
|
|
* @return bool |
81
|
|
|
*/ |
82
|
|
View Code Duplication |
public function isWeekend(string $date = null) : bool |
|
|
|
|
83
|
|
|
{ |
84
|
|
|
if ($date === null) { |
85
|
|
|
$day = date('w', $this->dateTime->getTimestamp()); |
86
|
|
|
} else { |
87
|
|
|
$day = date('w', strtotime($date)); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
return in_array($day, $this->weekends, false); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Проверяет евляется ли дата рабочим днём. |
95
|
|
|
* |
96
|
|
|
* Формат даты - "Y-m-d" |
97
|
|
|
* @param string $date |
98
|
|
|
* @return bool |
99
|
|
|
*/ |
100
|
|
|
public function isWorkingDate(string $date = null) : bool |
101
|
|
|
{ |
102
|
|
|
if ($date === null) { |
103
|
|
|
$unixTime = $this->dateTime->getTimestamp(); |
104
|
|
|
} else { |
105
|
|
|
$unixTime = strtotime($date); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
$dayNumber = date('w', $unixTime); |
109
|
|
|
if (in_array($dayNumber, $this->weekends, false)) { |
110
|
|
|
// Это выходной день |
111
|
|
|
return false; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$dayAndMonth = date('m-d', $unixTime); |
115
|
|
|
if (in_array($dayAndMonth, $this->holidays, false)) { |
116
|
|
|
// Это праздничный день |
117
|
|
|
return false; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
// Рабочий день |
121
|
|
|
return true; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Проверяет евляется ли время рабочим. |
126
|
|
|
* |
127
|
|
|
* Формат времени - "H:i" или полный "Y-m-d H:i" |
128
|
|
|
* @param string|null $time |
129
|
|
|
* @return bool |
130
|
|
|
* @throws \Exception |
131
|
|
|
*/ |
132
|
|
|
public function isWorkingTime(string $time = null) : bool |
133
|
|
|
{ |
134
|
|
|
if ($time === null) { |
135
|
|
|
$dateTime = $this->dateTime; |
136
|
|
|
} else { |
137
|
|
|
if (self::validateDate($time, 'H:i')) { |
138
|
|
|
$dateTime = new \DateTime($this->dateTime->format('Y-m-d') . ' ' . $time); |
139
|
|
|
} elseif (self::validateDate($time, 'Y-m-d H:i')) { |
140
|
|
|
$dateTime = new \DateTime($time); |
141
|
|
|
} else { |
142
|
|
|
throw new \InvalidArgumentException("Date `{$time}` isn't a valid date. Dates should be formatted as Y-m-d, e.g. `2016-12-25`."); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]); |
148
|
|
|
|
149
|
|
|
$dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart); |
150
|
|
|
$dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd); |
151
|
|
|
|
152
|
|
|
if ($dateTime > $dtStart && $dateTime < $dtEnd) { |
153
|
|
|
return true; |
154
|
|
|
} else { |
155
|
|
|
return false; |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Возвращает следующий рабочий день. |
161
|
|
|
* |
162
|
|
|
* Формат даты - "Y-m-d" |
163
|
|
|
* @param string|null $date |
164
|
|
|
* @return string |
165
|
|
|
*/ |
166
|
|
|
public function nextWorkingDay(string $date = null) : string |
167
|
|
|
{ |
168
|
|
View Code Duplication |
if ($date === null) { |
|
|
|
|
169
|
|
|
$dateTime = new \DateTime($this->dateTime->format('Y-m-d')); |
170
|
|
|
} else { |
171
|
|
|
$dateTime = new \DateTime($date); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
do { |
175
|
|
|
$dateTime->modify('+1 day'); |
176
|
|
|
} while (!$this->isWorkingDate($dateTime->format('Y-m-d'))); |
177
|
|
|
|
178
|
|
|
return $dateTime->format('Y-m-d'); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Возвращает ближайшее рабочее время. Либо пустую строку если текущее время уже рабочее |
183
|
|
|
* |
184
|
|
|
* @param string|null $date |
185
|
|
|
* @return string |
186
|
|
|
*/ |
187
|
|
|
public function nextWorkingTime(string $date = null) : string |
188
|
|
|
{ |
189
|
|
View Code Duplication |
if ($date === null) { |
|
|
|
|
190
|
|
|
$dateTime = new \DateTime($this->dateTime->format('Y-m-d H:i')); |
191
|
|
|
} else { |
192
|
|
|
$dateTime = new \DateTime($date); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
$nextWorkingTime = ''; |
196
|
|
|
|
197
|
|
|
// Если дня нет в конфиге считаем его выходным |
198
|
|
|
if (!isset($this->workingDays[$dateTime->format('w')])) { |
199
|
|
|
$nextWorkingDay = $this->nextWorkingDay($dateTime->format('Y-m-d')); |
200
|
|
|
$nWDateTime = new \DateTime($nextWorkingDay); |
201
|
|
|
$workTime = explode('-', $this->workingDays[$nWDateTime->format('w')]); |
202
|
|
|
return $nextWorkingDay . ' ' . $workTime[0]; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]); |
206
|
|
|
|
207
|
|
|
if ($this->isWorkingDate($dateTime->format('Y-m-d'))) { // Если день рабочий проверяем время |
208
|
|
|
|
209
|
|
|
$dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart); |
210
|
|
|
$dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd); |
211
|
|
|
|
212
|
|
|
// Если начало дня еще не наступило (утро) возвращаем указанную дату + время |
213
|
|
|
if ($dateTime < $dtStart) { |
214
|
|
|
$nextWorkingTime = $dateTime->format('Y-m-d') . ' ' . $jobStart; |
215
|
|
|
} elseif ($dateTime >= $dtEnd) { // Если рабочий день уже закончился |
216
|
|
|
// Ищем следующий рабочий день и выводим его + время начало дня |
217
|
|
|
$nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
} else { // Если день не рабочий |
221
|
|
|
|
222
|
|
|
// Ищем следующий рабочий день и выводим его + время начало дня |
223
|
|
|
$nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart; |
224
|
|
|
|
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
return $nextWorkingTime; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Возвращает длинну рабочего дня в минутах. |
232
|
|
|
* |
233
|
|
|
* Формат даты - "Y-m-d" |
234
|
|
|
* @param string|null $date |
235
|
|
|
* @return int |
236
|
|
|
*/ |
237
|
|
|
public function getJobMinutesInDay(string $date = null) : int |
238
|
|
|
{ |
239
|
|
|
|
240
|
|
|
if ($date === null) { |
241
|
|
|
$day = date('w', $this->dateTime->getTimestamp()); |
242
|
|
|
} else { |
243
|
|
|
$day = date('w', strtotime($date)); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
$nextWorkingTime = $this->nextWorkingTime($date ?? $this->dateTime->format('Y-m-d H:i')); |
247
|
|
|
|
248
|
|
|
list($jobStart, $jobSEnd) = explode('-', $this->workingDays[(int)$day]); |
249
|
|
|
// Считаем остаток рабочего времени |
250
|
|
|
if (empty($nextWorkingTime)) { |
251
|
|
|
$jobStart = ($date === null ? date('H:i', $this->dateTime->getTimestamp()) : date('H:i', |
252
|
|
|
strtotime($date))); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
$dtStart = new \DateTime($jobStart); |
256
|
|
|
$dtEnd = new \DateTime($jobSEnd); |
257
|
|
|
$diff = $dtEnd->diff($dtStart); |
258
|
|
|
return ($diff->h * 60 + $diff->i); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Прибавляет заданное количество минут к дате с учетом рабочего времени. |
263
|
|
|
* |
264
|
|
|
* @param int $minutes |
265
|
|
|
* @param string $date |
266
|
|
|
* @return string |
267
|
|
|
*/ |
268
|
|
|
public function modify(int $minutes, string $date = null) : string |
269
|
|
|
{ |
270
|
|
View Code Duplication |
if ($date === null) { |
|
|
|
|
271
|
|
|
$dateTime = new \DateTime($this->dateTime->format('Y-m-d H:i')); |
272
|
|
|
} else { |
273
|
|
|
$dateTime = new \DateTime($date); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
$nextWorkingTime = $this->nextWorkingTime($dateTime->format('Y-m-d H:i')); |
277
|
|
|
|
278
|
|
|
// Если дата вне рабочего времени |
279
|
|
|
if (!empty($nextWorkingTime)) { |
280
|
|
|
|
281
|
|
|
$nextJobDT = new \DateTime($nextWorkingTime); |
282
|
|
|
//$diff = $nextJobDT->diff($dateTime); |
|
|
|
|
283
|
|
|
$jobMinutesInDay = $this->getJobMinutesInDay($nextJobDT->format('Y-m-d H:i')); |
284
|
|
|
// Если длинна дня больше чем время модификации |
285
|
|
View Code Duplication |
if ($jobMinutesInDay > $minutes) { |
|
|
|
|
286
|
|
|
$nextJobDT->modify("+$minutes minutes"); |
287
|
|
|
} else { // Если длинна дня меньше чем время модификации |
288
|
|
|
|
289
|
|
|
do { |
290
|
|
|
$nextJobDT->modify("+$jobMinutesInDay minutes"); |
291
|
|
|
$minutes -= $jobMinutesInDay; |
292
|
|
|
$nextWorkingTime = $this->nextWorkingTime($nextJobDT->format('Y-m-d H:i')); |
293
|
|
|
$nextJobDT = new \DateTime($nextWorkingTime); |
294
|
|
|
$jobMinutesInDay = $this->getJobMinutesInDay($nextJobDT->format('Y-m-d H:i')); |
295
|
|
|
if ($jobMinutesInDay > $minutes) { |
296
|
|
|
$nextJobDT->modify("+$minutes minutes"); |
297
|
|
|
$minutes = 0; |
298
|
|
|
} |
299
|
|
|
} while ($minutes > 0); |
300
|
|
|
|
301
|
|
|
} |
302
|
|
|
if ($date === null) { |
303
|
|
|
$this->dateTime->setTimestamp($nextJobDT->getTimestamp()); |
304
|
|
|
} |
305
|
|
|
return $nextJobDT->format('Y-m-d H:i'); |
306
|
|
|
|
307
|
|
|
} else { // если дата в пределах рабочего времени |
308
|
|
|
|
309
|
|
|
$jobMinutesInDay = $this->getJobMinutesInDay($dateTime->format('Y-m-d H:i')); |
310
|
|
|
|
311
|
|
View Code Duplication |
if ($jobMinutesInDay > $minutes) { |
|
|
|
|
312
|
|
|
$dateTime->modify("+$minutes minutes"); |
313
|
|
|
} else { // Если длинна дня меньше чем время модификации |
314
|
|
|
|
315
|
|
|
do { |
316
|
|
|
$dateTime->modify("+$jobMinutesInDay minutes"); |
317
|
|
|
$minutes -= $jobMinutesInDay; |
318
|
|
|
$nextWorkingTime = $this->nextWorkingTime($dateTime->format('Y-m-d H:i')); |
319
|
|
|
$dateTime = new \DateTime($nextWorkingTime); |
320
|
|
|
$jobMinutesInDay = $this->getJobMinutesInDay($dateTime->format('Y-m-d H:i')); |
321
|
|
|
if ($jobMinutesInDay > $minutes) { |
322
|
|
|
$dateTime->modify("+$minutes minutes"); |
323
|
|
|
$minutes = 0; |
324
|
|
|
} |
325
|
|
|
} while ($minutes > 0); |
326
|
|
|
|
327
|
|
|
} |
328
|
|
|
if ($date === null) { |
329
|
|
|
$this->dateTime->setTimestamp($dateTime->getTimestamp()); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
return $dateTime->format('Y-m-d H:i'); |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Проверяет является ли строка корректной датой. |
338
|
|
|
* |
339
|
|
|
* @param $date |
340
|
|
|
* @param string $format |
341
|
|
|
* @return bool |
342
|
|
|
*/ |
343
|
|
|
public static function validateDate(string $date, string $format = 'Y-m-d H:i:s') : bool |
344
|
|
|
{ |
345
|
|
|
$vDate = \DateTime::createFromFormat($format, $date); |
346
|
|
|
return $vDate && $vDate->format($format) === $date; |
347
|
|
|
} |
348
|
|
|
} |
349
|
|
|
|
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.