Completed
Push — master ( 7e21d5...5cc35e )
by Bushlanov
02:18
created

WorkingTime::buildDataPartString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 2
eloc 6
nc 2
nop 2
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.0
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
     * Фурмирует строку дня ддя функций isHoliday и isWeekend.
61
     *
62
     * @param string $format
63
     * @return string
64
     */
65
    private function buildDataPartString(string $date, string $format) : string
66
    {
67
        if ($date === null) {
68
            $day = date($format, $this->dateTime->getTimestamp());
69
        } else {
70
            $day = date($format, strtotime($date));
71
        }
72
73
        return $day;
74
    }
75
76
    /**
77
     * Проверяет является ли дата праздничным днём.
78
     *
79
     * @param string $date
80
     * @return bool
81
     */
82
    public function isHoliday(string $date = null) : bool
83
    {
84
        $day = $this->buildDataPartString($date, 'm-d');
85
        return in_array($day, $this->holidays, false);
86
    }
87
88
    /**
89
     * Проверяет является ли дата выходным днём.
90
     *
91
     * @param string $date
92
     * @return bool
93
     */
94
    public function isWeekend(string $date = null) : bool
95
    {
96
        $day = $this->buildDataPartString($date, 'w');
97
        return in_array($day, $this->weekends, false);
98
    }
99
100
    /**
101
     * Проверяет евляется ли дата рабочим днём.
102
     *
103
     * Формат даты - "Y-m-d"
104
     * @param string $date
105
     * @return bool
106
     */
107
    public function isWorkingDate(string $date = null) : bool
108
    {
109
        if ($date === null) {
110
            $unixTime = $this->dateTime->getTimestamp();
111
        } else {
112
            $unixTime = strtotime($date);
113
        }
114
115
        $dayNumber = date('w', $unixTime);
116
        if (in_array($dayNumber, $this->weekends, false)) {
117
            // Это выходной день
118
            return false;
119
        }
120
121
        $dayAndMonth = date('m-d', $unixTime);
122
        if (in_array($dayAndMonth, $this->holidays, false)) {
123
            // Это праздничный день
124
            return false;
125
        }
126
127
        // Рабочий день
128
        return true;
129
    }
130
131
    /**
132
     * Проверяет евляется ли время рабочим.
133
     *
134
     * @param string|null $time Формат времени - "H:i" или полный "Y-m-d H:i"
135
     * @return bool
136
     * @throws \Exception
137
     */
138
    public function isWorkingTime(string $time = null) : bool
139
    {
140
        if ($time === null) {
141
            $dateTime = $this->dateTime;
142
        } else {
143
            if (self::validateDate($time, 'H:i')) {
144
                $dateTime = new \DateTime($this->dateTime->format('Y-m-d') . ' ' . $time);
145
            } elseif (self::validateDate($time, 'Y-m-d H:i')) {
146
                $dateTime = new \DateTime($time);
147
            } else {
148
                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`.");
149
            }
150
        }
151
152
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]);
153
154
        $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
155
        $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd);
156
157
        if ($dateTime > $dtStart && $dateTime < $dtEnd) {
158
            return true;
159
        } else {
160
            return false;
161
        }
162
    }
163
164
    /**
165
     * Возвращает следующий рабочий день.
166
     *
167
     * @param string|null $date Формат даты - "Y-m-d"
168
     * @return string
169
     */
170
    public function nextWorkingDay(string $date = null) : string
171
    {
172 View Code Duplication
        if ($date === null) {
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...
173
            $dateTime = new \DateTime($this->dateTime->format('Y-m-d'));
174
        } else {
175
            $dateTime = new \DateTime($date);
176
        }
177
178
        do {
179
            $dateTime->modify('+1 day');
180
        } while (!$this->isWorkingDate($dateTime->format('Y-m-d')));
181
182
        return $dateTime->format('Y-m-d');
183
    }
184
185
    /**
186
     * Возвращает ближайшее рабочее время. Либо пустую строку если текущее время уже рабочее
187
     *
188
     * @param string|null $date
189
     * @return string
190
     */
191
    public function nextWorkingTime(string $date = null) : string
192
    {
193 View Code Duplication
        if ($date === null) {
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...
194
            $dateTime = new \DateTime($this->dateTime->format('Y-m-d H:i'));
195
        } else {
196
            $dateTime = new \DateTime($date);
197
        }
198
199
        $nextWorkingTime = '';
200
201
        // Если дня нет в конфиге считаем его выходным
202
        if (!isset($this->workingDays[$dateTime->format('w')])) {
203
            $nextWorkingDay = $this->nextWorkingDay($dateTime->format('Y-m-d'));
204
            $nWDateTime = new \DateTime($nextWorkingDay);
205
            $workTime = explode('-', $this->workingDays[$nWDateTime->format('w')]);
206
            return $nextWorkingDay . ' ' . $workTime[0];
207
        }
208
209
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]);
210
211
        if ($this->isWorkingDate($dateTime->format('Y-m-d'))) { // Если день рабочий проверяем время
212
213
            $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
214
            $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd);
215
216
            // Если начало дня еще не наступило (утро) возвращаем указанную дату + время
217
            if ($dateTime < $dtStart) {
218
                $nextWorkingTime = $dateTime->format('Y-m-d') . ' ' . $jobStart;
219
            } elseif ($dateTime >= $dtEnd) { // Если рабочий день уже закончился
220
                // Ищем следующий рабочий день и выводим его + время начало дня
221
                $nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart;
222
            }
223
        } else { // Если день не рабочий
224
225
            // Ищем следующий рабочий день и выводим его + время начало дня
226
            $nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart;
227
        }
228
229
        return $nextWorkingTime;
230
    }
231
232
    /**
233
     * Возвращает длинну рабочего дня в минутах.
234
     *
235
     * @param string|null $date Формат даты - "Y-m-d"
236
     * @return int
237
     */
238
    public function getJobMinutesInDay(string $date = null) : int
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
259
        return ($diff->h * 60 + $diff->i);
260
    }
261
262
    /**
263
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
264
     *
265
     * @param int $minutes
266
     * @param string $date
267
     * @return string
268
     */
269
    public function modify(int $minutes, string $date = null) : string
270
    {
271 View Code Duplication
        if ($date === null) {
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...
272
            $dateTime = new \DateTime($this->dateTime->format('Y-m-d H:i'));
273
        } else {
274
            $dateTime = new \DateTime($date);
275
        }
276
277
        $nextWorkingTime = $this->nextWorkingTime($dateTime->format('Y-m-d H:i'));
278
279
        // Если дата вне рабочего времени
280
        if (!empty($nextWorkingTime)) {
281
            $nextJobDT = new \DateTime($nextWorkingTime);
282
            $jobMinutesInDay = $this->getJobMinutesInDay($nextJobDT->format('Y-m-d H:i'));
283
284
            // Если длинна дня больше чем время модификации
285 View Code Duplication
            if ($jobMinutesInDay > $minutes) {
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...
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
306
            return $nextJobDT->format('Y-m-d H:i');
307
        } else { // если дата в пределах рабочего времени
308
309
            $jobMinutesInDay = $this->getJobMinutesInDay($dateTime->format('Y-m-d H:i'));
310
311 View Code Duplication
            if ($jobMinutesInDay > $minutes) {
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...
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