Completed
Push — master ( 5cc35e...4f18d4 )
by Bushlanov
02:08
created

WorkingTime::buildDate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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