Completed
Push — master ( 75b4c3...65e143 )
by Bushlanov
02:43
created

WorkingTime::isWorkingTime()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 21
rs 8.7624
cc 5
eloc 14
nc 7
nop 1
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  1.0.3
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|null $date
63
     * @param string $format
64
     * @return string
65
     */
66
    private function buildDataPartString(string $date = null, string $format) : string
67
    {
68
        if ($date === null) {
69
            $day = date($format, $this->dateTime->getTimestamp());
70
        } else {
71
            $day = date($format, strtotime($date));
72
        }
73
74
        return $day;
75
    }
76
77
    /**
78
     * Формирует дату из строки.
79
     *
80
     * @param string $date
81
     * @return \DateTime
82
     */
83
    private function buildDate(string $date = null) : \DateTime
84
    {
85
        if ($date === null) {
86
            return new \DateTime($this->dateTime->format('Y-m-d H:i'));
87
        } else {
88
            return new \DateTime($date);
89
        }
90
    }
91
92
    /**
93
     * Проверяет является ли дата праздничным днём.
94
     *
95
     * @param string $date
96
     * @return bool
97
     */
98 View Code Duplication
    public function isHoliday(string $date = null) : bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
99
    {
100
        if (empty($this->holidays)) {
101
            // Если не указаны праздничные дни, то день рабочий.
102
            return false;
103
        }
104
105
        $day = $this->buildDataPartString($date, 'm-d');
106
        return in_array($day, $this->holidays, false);
107
    }
108
109
    /**
110
     * Проверяет является ли дата выходным днём.
111
     *
112
     * @param string $date
113
     * @return bool
114
     */
115 View Code Duplication
    public function isWeekend(string $date = null) : bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
116
    {
117
        if (empty($this->weekends)) {
118
            // Если не указаны выходные дни, то день рабочий.
119
            return false;
120
        }
121
122
        $day = $this->buildDataPartString($date, 'w');
123
        return in_array($day, $this->weekends, false);
124
    }
125
126
    /**
127
     * Проверяет евляется ли дата рабочим днём.
128
     *
129
     * Формат даты - "Y-m-d"
130
     * @param string $date
131
     * @return bool
132
     */
133
    public function isWorkingDate(string $date = null) : bool
134
    {
135
        if ($this->isWeekend($date) || $this->isHoliday($date)) {
136
            // Выходной или праздничный день
137
            return false;
138
        } else {
139
            // Рабочий день
140
            return true;
141
        }
142
    }
143
144
    /**
145
     * Проверяет евляется ли время рабочим.
146
     *
147
     * @param string|null $time Формат времени - "H:i" или полный "Y-m-d H:i"
148
     * @return bool
149
     * @throws \InvalidArgumentException
150
     */
151
    public function isWorkingTime(string $time = null) : bool
152
    {
153
        if ($time === null) {
154
            $dateTime = $this->dateTime;
155
        } else {
156
            if (self::validateDate($time, 'H:i')) {
157
                $dateTime = new \DateTime($this->dateTime->format('Y-m-d') . ' ' . $time);
158
            } elseif (self::validateDate($time, 'Y-m-d H:i')) {
159
                $dateTime = new \DateTime($time);
160
            } else {
161
                throw new \InvalidArgumentException("Date `{$time}` isn't a valid date. Dates should be formatted as Y-m-d or H:i, e.g. `2016-10-27` or `17:30`.");
162
            }
163
        }
164
165
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]);
166
167
        $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
168
        $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd);
169
170
        return $dateTime > $dtStart && $dateTime < $dtEnd;
171
    }
172
173
    /**
174
     * Возвращает следующий рабочий день.
175
     *
176
     * @param string|null $date Формат даты - "Y-m-d"
177
     * @return string
178
     */
179
    public function nextWorkingDay(string $date = null) : string
180
    {
181
        if ($date === null) {
182
            $dateTime = new \DateTime($this->dateTime->format('Y-m-d'));
183
        } else {
184
            $dateTime = new \DateTime($date);
185
        }
186
187
        do {
188
            $dateTime->modify('+1 day');
189
        } while (!$this->isWorkingDate($dateTime->format('Y-m-d')));
190
191
        return $dateTime->format('Y-m-d');
192
    }
193
194
    /**
195
     * Возвращает ближайшее рабочее время. Либо пустую строку если текущее время уже рабочее
196
     *
197
     * @param string|null $date
198
     * @return string
199
     */
200
    public function nextWorkingTime(string $date = null) : string
201
    {
202
        $dateTime = $this->buildDate($date);
203
204
        $nextWorkingTime = '';
205
206
        // Если дня нет в конфиге считаем его выходным
207
        if (!array_key_exists($dateTime->format('w'), $this->workingDays)) {
208
            $nextWorkingDay = $this->nextWorkingDay($dateTime->format('Y-m-d'));
209
            $nWDateTime = new \DateTime($nextWorkingDay);
210
            $workTime = explode('-', $this->workingDays[$nWDateTime->format('w')]);
211
            return $nextWorkingDay . ' ' . $workTime[0];
212
        }
213
214
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]);
215
216
        if ($this->isWorkingDate($dateTime->format('Y-m-d'))) { // Если день рабочий проверяем время
217
218
            $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
219
            $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd);
220
221
            // Если начало дня еще не наступило (утро) возвращаем указанную дату + время
222
            if ($dateTime < $dtStart) {
223
                $nextWorkingTime = $dateTime->format('Y-m-d') . ' ' . $jobStart;
224
            } elseif ($dateTime >= $dtEnd) { // Если рабочий день уже закончился
225
                // Ищем следующий рабочий день и выводим его + время начало дня
226
                $nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart;
227
            }
228
        } else { // Если день не рабочий
229
230
            // Ищем следующий рабочий день и выводим его + время начало дня
231
            $nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart;
232
        }
233
234
        return $nextWorkingTime;
235
    }
236
237
    /**
238
     * Возвращает длинну рабочего дня в минутах.
239
     *
240
     * @param string|null $date Формат даты - "Y-m-d"
241
     * @return int
242
     */
243
    public function getJobMinutesInDay(string $date = null) : int
244
    {
245
        $day = $this->buildDataPartString($date, 'w');
246
247
        $nextWorkingTime = $this->nextWorkingTime($date ?? $this->dateTime->format('Y-m-d H:i'));
248
249
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[(int)$day]);
250
        // Считаем остаток рабочего времени
251
        if (empty($nextWorkingTime)) {
252
            $jobStart = ($date === null ? date('H:i', $this->dateTime->getTimestamp()) : date('H:i', strtotime($date)));
253
        }
254
255
        $dtStart = new \DateTime($jobStart);
256
        $dtEnd = new \DateTime($jobSEnd);
257
        $diff = $dtEnd->diff($dtStart);
258
259
        return ($diff->h * 60 + $diff->i);
260
    }
261
262
    /**
263
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
264
     *
265
     * @param int $minutes
266
     * @param string $date
267
     * @return \DateTime
268
     */
269
    private function modifyDate(int $minutes, string $date) : \DateTime
270
    {
271
        $dateTime = new \DateTime($date);
272
        $jobMinutesInDay = $this->getJobMinutesInDay($date);
273
274
        // Если длинна дня больше чем время модификации
275
        if ($jobMinutesInDay > $minutes) {
276
            $dateTime->modify("+$minutes minutes");
277
        } else { // Если длинна дня меньше чем время модификации
278
            do {
279
                $dateTime->modify("+$jobMinutesInDay minutes");
280
                $minutes -= $jobMinutesInDay;
281
                $nextWorkingTime = $this->nextWorkingTime($dateTime->format('Y-m-d H:i'));
282
                $dateTime = new \DateTime($nextWorkingTime);
283
                $jobMinutesInNextDay = $this->getJobMinutesInDay($dateTime->format('Y-m-d H:i'));
284
                if ($jobMinutesInNextDay > $minutes) {
285
                    $dateTime->modify("+$minutes minutes");
286
                    $minutes = 0;
287
                }
288
            } while ($minutes > 0);
289
        }
290
291
        return $dateTime;
292
    }
293
294
    /**
295
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
296
     *
297
     * @param int $minutes
298
     * @param string $date
299
     * @return string
300
     */
301
    public function modify(int $minutes, string $date = null) : string
302
    {
303
        $nextWorkingTime = $this->nextWorkingTime($date);
304
        // Если дата вне рабочего времени
305
        if (!empty($nextWorkingTime)) {
306
            $dateTime = $this->modifyDate($minutes, $nextWorkingTime);
307
        } else { // если дата в пределах рабочего времени
308
            $dateTime = $this->modifyDate($minutes, $date ?? $this->dateTime->format('Y-m-d H:i'));
309
        }
310
        if ($date === null) {
311
            $this->dateTime->setTimestamp($dateTime->getTimestamp());
312
        }
313
        return $dateTime->format('Y-m-d H:i');
314
    }
315
316
    /**
317
     * @param string $startDate
318
     * @param string $endDate
319
     * @return int
320
     * @throws \InvalidArgumentException
321
     */
322
    public function calculatingWorkingTime(string $startDate, string $endDate) : int
323
    {
324
        if (!self::validateDate($startDate) || !self::validateDate($endDate)) {
325
            throw new \InvalidArgumentException("Date isn't a valid date. Dates should be formatted as Y-m-d H:i:s, e.g. `2016-10-27 17:30:00`.");
326
        }
327
328
        $dtStart = $this->buildDate($startDate);
329
        $dtEnd = $this->buildDate($endDate);
330
331
        $diff = $dtEnd->diff($dtStart);
332
        $diffMinutes = $diff->d * 1440 + $diff->h * 60 + $diff->i;
333
334
        // Если разница во времени больше длинны рабочего дня
335
        if ($diffMinutes > $this->getJobMinutesInDay($dtStart->format('Y-m-d H:i'))) {
336
            return 100;
337
        } else {
338
            return $diffMinutes;
339
        }
340
    }
341
342
    /**
343
     * Проверяет является ли строка корректной датой.
344
     *
345
     * @param $date
346
     * @param string $format
347
     * @return bool
348
     */
349
    public static function validateDate(string $date, string $format = 'Y-m-d H:i:s') : bool
350
    {
351
        $vDate = \DateTime::createFromFormat($format, $date);
352
        return $vDate && $vDate->format($format) === $date;
353
    }
354
}
355