Completed
Push — master ( e7eb19...a8aa4f )
by Bushlanov
02:02
created

WorkingTime::calculatingWorkingTime()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.4661

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 22
ccs 9
cts 13
cp 0.6923
rs 8.9197
cc 4
eloc 14
nc 3
nop 2
crap 4.4661
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 13
    public function __construct(array $workTimeConfig, string $dateTime = 'now')
52
    {
53 13
        $this->dateTime = new \DateTime($dateTime);
54 13
        foreach ($workTimeConfig as $name => $value) {
55 13
            $this->$name = $value;
56
        }
57 13
    }
58
59
    /**
60
     * Фурмирует строку дня.
61
     *
62
     * @param string|null $date
63
     * @param string $format
64
     * @return string
65
     */
66 10
    private function buildDataPartString(string $date = null, string $format) : string
67
    {
68 10
        if ($date === null) {
69 1
            $day = date($format, $this->dateTime->getTimestamp());
70
        } else {
71 10
            $day = date($format, strtotime($date));
72
        }
73
74 10
        return $day;
75
    }
76
77
    /**
78
     * Формирует дату из строки.
79
     *
80
     * @param string $date
81
     * @return \DateTime
82
     */
83 7
    private function buildDate(string $date = null) : \DateTime
84
    {
85 7
        if ($date === null) {
86 1
            return new \DateTime($this->dateTime->format('Y-m-d H:i'));
87
        } else {
88 7
            return new \DateTime($date);
89
        }
90
    }
91
92
    /**
93
     * Проверяет является ли дата праздничным днём.
94
     *
95
     * @param string $date
96
     * @return bool
97
     */
98 10 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 10
        if (empty($this->holidays)) {
101
            // Если не указаны праздничные дни, то день рабочий.
102
            return false;
103
        }
104
105 10
        $day = $this->buildDataPartString($date, 'm-d');
106 10
        return in_array($day, $this->holidays, false);
107
    }
108
109
    /**
110
     * Проверяет является ли дата выходным днём.
111
     *
112
     * @param string $date
113
     * @return bool
114
     */
115 10 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 10
        if (empty($this->weekends)) {
118
            // Если не указаны выходные дни, то день рабочий.
119
            return false;
120
        }
121
122 10
        $day = $this->buildDataPartString($date, 'w');
123 10
        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 9
    public function isWorkingDate(string $date = null) : bool
134
    {
135 9
        if ($this->isWeekend($date) || $this->isHoliday($date)) {
136
            // Выходной или праздничный день
137 4
            return false;
138
        } else {
139
            // Рабочий день
140 9
            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 2
    public function isWorkingTime(string $time = null) : bool
152
    {
153 2
        if ($time === null) {
154 1
            $dateTime = $this->dateTime;
155
        } else {
156 2
            if (self::validateDate($time, 'H:i')) {
157 1
                $dateTime = new \DateTime($this->dateTime->format('Y-m-d') . ' ' . $time);
158 2
            } elseif (self::validateDate($time, 'Y-m-d H:i')) {
159 1
                $dateTime = new \DateTime($time);
160
            } else {
161 1
                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 1
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]);
166
167 1
        $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
168 1
        $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd);
169
170 1
        return $dateTime > $dtStart && $dateTime < $dtEnd;
171
    }
172
173
    /**
174
     * Возвращает следующий рабочий день.
175
     *
176
     * @param string|null $date Формат даты - "Y-m-d"
177
     * @return string
178
     */
179 4
    public function nextWorkingDay(string $date = null) : string
180
    {
181 4
        if ($date === null) {
182 1
            $dateTime = new \DateTime($this->dateTime->format('Y-m-d'));
183
        } else {
184 4
            $dateTime = new \DateTime($date);
185
        }
186
187
        do {
188 4
            $dateTime->modify('+1 day');
189 4
        } while (!$this->isWorkingDate($dateTime->format('Y-m-d')));
190
191 4
        return $dateTime->format('Y-m-d');
192
    }
193
194
    /**
195
     * Возвращает ближайшее рабочее время. Либо пустую строку если текущее время уже рабочее
196
     *
197
     * @param string|null $date
198
     * @return string
199
     */
200 7
    public function nextWorkingTime(string $date = null) : string
201
    {
202 7
        $dateTime = $this->buildDate($date);
203
204 7
        $nextWorkingTime = '';
205
206
        // Если дня нет в конфиге считаем его выходным
207 7
        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 7
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]);
215
216 7
        if ($this->isWorkingDate($dateTime->format('Y-m-d'))) { // Если день рабочий проверяем время
217
218 7
            $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
219 7
            $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd);
220
221
            // Если начало дня еще не наступило (утро) возвращаем указанную дату + время
222 7
            if ($dateTime < $dtStart) {
223 4
                $nextWorkingTime = $dateTime->format('Y-m-d') . ' ' . $jobStart;
224 6
            } elseif ($dateTime >= $dtEnd) { // Если рабочий день уже закончился
225
                // Ищем следующий рабочий день и выводим его + время начало дня
226 7
                $nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart;
227
            }
228
        } else { // Если день не рабочий
229
230
            // Ищем следующий рабочий день и выводим его + время начало дня
231 1
            $nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart;
232
        }
233
234 7
        return $nextWorkingTime;
235
    }
236
237
    /**
238
     * Возвращает длинну рабочего дня в минутах.
239
     *
240
     * @param string|null $date Формат даты - "Y-m-d"
241
     * @return int
242
     */
243 6
    public function getJobMinutesInDay(string $date = null) : int
244
    {
245 6
        $day = $this->buildDataPartString($date, 'w');
246
247 6
        $nextWorkingTime = $this->nextWorkingTime($date ?? $this->dateTime->format('Y-m-d H:i'));
248
249 6
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[(int)$day]);
250
        // Считаем остаток рабочего времени
251 6
        if (empty($nextWorkingTime)) {
252 5
            $jobStart = ($date === null ? date('H:i', $this->dateTime->getTimestamp()) : date('H:i', strtotime($date)));
253
        }
254
255 6
        $dtStart = new \DateTime($jobStart);
256 6
        $dtEnd = new \DateTime($jobSEnd);
257 6
        $diff = $dtEnd->diff($dtStart);
258
259 6
        return ($diff->h * 60 + $diff->i);
260
    }
261
262
    /**
263
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
264
     *
265
     * @param int $minutes
266
     * @param string $date
267
     * @return \DateTime
268
     */
269 3
    private function modifyDate(int $minutes, string $date) : \DateTime
270
    {
271 3
        $dateTime = new \DateTime($date);
272 3
        $jobMinutesInDay = $this->getJobMinutesInDay($date);
273
274
        // Если длинна дня больше чем время модификации
275 3
        if ($jobMinutesInDay > $minutes) {
276 3
            $dateTime->modify("+$minutes minutes");
277
        } else { // Если длинна дня меньше чем время модификации
278
            do {
279 1
                $dateTime->modify("+$jobMinutesInDay minutes");
280 1
                $minutes -= $jobMinutesInDay;
281 1
                $nextWorkingTime = $this->nextWorkingTime($dateTime->format('Y-m-d H:i'));
282 1
                $dateTime = new \DateTime($nextWorkingTime);
283 1
                $jobMinutesInNextDay = $this->getJobMinutesInDay($dateTime->format('Y-m-d H:i'));
284 1
                if ($jobMinutesInNextDay > $minutes) {
285 1
                    $dateTime->modify("+$minutes minutes");
286 1
                    $minutes = 0;
287
                }
288 1
            } while ($minutes > 0);
289
        }
290
291 3
        return $dateTime;
292
    }
293
294
    /**
295
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
296
     *
297
     * @param int $minutes
298
     * @param string $date
299
     * @return string
300
     */
301 3
    public function modify(int $minutes, string $date = null) : string
302
    {
303 3
        $nextWorkingTime = $this->nextWorkingTime($date);
304
        // Если дата вне рабочего времени
305 3
        if (!empty($nextWorkingTime)) {
306 3
            $dateTime = $this->modifyDate($minutes, $nextWorkingTime);
307
        } else { // если дата в пределах рабочего времени
308 2
            $dateTime = $this->modifyDate($minutes, $date ?? $this->dateTime->format('Y-m-d H:i'));
309
        }
310 3
        if ($date === null) {
311 1
            $this->dateTime->setTimestamp($dateTime->getTimestamp());
312
        }
313 3
        return $dateTime->format('Y-m-d H:i');
314
    }
315
316
    /**
317
     * Возвращает рабочее время в минутах в заданном временном интервале.
318
     *
319
     * @param string $startDate
320
     * @param string $endDate
321
     * @return int
322
     * @throws \InvalidArgumentException
323
     */
324 1
    public function calculatingWorkingTime(string $startDate, string $endDate) : int
325
    {
326 1
        if (!self::validateDate($startDate) || !self::validateDate($endDate)) {
327
            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`.");
328
        }
329
330 1
        $dtStart = $this->buildDate($startDate);
331 1
        $dtEnd = $this->buildDate($endDate);
332
333 1
        $diff = $dtEnd->diff($dtStart);
334 1
        $diffMinutes = $diff->d * 1440 + $diff->h * 60 + $diff->i;
335 1
        $jobMinutesInDay = $this->getJobMinutesInDay($dtStart->format('Y-m-d H:i'));
336
337
        // Если разница во времени больше длинны рабочего дня
338 1
        if ($diffMinutes > $jobMinutesInDay) {
339
            $workingMinutes = 0;
340
            $workingMinutes++;
341
            return $workingMinutes;
342
        } else {
343 1
            return $diffMinutes;
344
        }
345
    }
346
347
    /**
348
     * Проверяет является ли строка корректной датой.
349
     *
350
     * @param $date
351
     * @param string $format
352
     * @return bool
353
     */
354 4
    public static function validateDate(string $date, string $format = 'Y-m-d H:i:s') : bool
355
    {
356 4
        $vDate = \DateTime::createFromFormat($format, $date);
357 4
        return $vDate && $vDate->format($format) === $date;
358
    }
359
}
360