Completed
Push — master ( abf022...147852 )
by Bushlanov
02:45
created

WorkingTime::isWorkingDate()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 3
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 = 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
    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 ($this->isWeekend($date) || $this->isHoliday($date)) {
126
            // Выходной или праздничный день
127
            return false;
128
        } else {
129
            // Рабочий день
130
            return true;
131
        }
132
    }
133
134
    /**
135
     * Проверяет евляется ли время рабочим.
136
     *
137
     * @param string|null $time Формат времени - "H:i" или полный "Y-m-d H:i"
138
     * @return bool
139
     * @throws \InvalidArgumentException
140
     */
141
    public function isWorkingTime(string $time = null) : bool
142
    {
143
        if ($time === null) {
144
            $dateTime = $this->dateTime;
145
        } else {
146
            if (self::validateDate($time, 'H:i')) {
147
                $dateTime = new \DateTime($this->dateTime->format('Y-m-d') . ' ' . $time);
148
            } elseif (self::validateDate($time, 'Y-m-d H:i')) {
149
                $dateTime = new \DateTime($time);
150
            } else {
151
                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`.");
152
            }
153
        }
154
155
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]);
156
157
        $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
158
        $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd);
159
160
        return $dateTime > $dtStart && $dateTime < $dtEnd;
161
    }
162
163
    /**
164
     * Возвращает следующий рабочий день.
165
     *
166
     * @param string|null $date Формат даты - "Y-m-d"
167
     * @return string
168
     */
169
    public function nextWorkingDay(string $date = null) : string
170
    {
171
        if ($date === null) {
172
            $dateTime = new \DateTime($this->dateTime->format('Y-m-d'));
173
        } else {
174
            $dateTime = new \DateTime($date);
175
        }
176
177
        do {
178
            $dateTime->modify('+1 day');
179
        } while (!$this->isWorkingDate($dateTime->format('Y-m-d')));
180
181
        return $dateTime->format('Y-m-d');
182
    }
183
184
    /**
185
     * Возвращает ближайшее рабочее время. Либо пустую строку если текущее время уже рабочее
186
     *
187
     * @param string|null $date
188
     * @return string
189
     */
190
    public function nextWorkingTime(string $date = null) : string
191
    {
192
        $dateTime = $this->buildDate($date);
193
194
        $nextWorkingTime = '';
195
196
        // Если дня нет в конфиге считаем его выходным
197
        if (!isset($this->workingDays[$dateTime->format('w')])) {
198
            $nextWorkingDay = $this->nextWorkingDay($dateTime->format('Y-m-d'));
199
            $nWDateTime = new \DateTime($nextWorkingDay);
200
            $workTime = explode('-', $this->workingDays[$nWDateTime->format('w')]);
201
            return $nextWorkingDay . ' ' . $workTime[0];
202
        }
203
204
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[$dateTime->format('w')]);
205
206
        if ($this->isWorkingDate($dateTime->format('Y-m-d'))) { // Если день рабочий проверяем время
207
208
            $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
209
            $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobSEnd);
210
211
            // Если начало дня еще не наступило (утро) возвращаем указанную дату + время
212
            if ($dateTime < $dtStart) {
213
                $nextWorkingTime = $dateTime->format('Y-m-d') . ' ' . $jobStart;
214
            } elseif ($dateTime >= $dtEnd) { // Если рабочий день уже закончился
215
                // Ищем следующий рабочий день и выводим его + время начало дня
216
                $nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart;
217
            }
218
        } else { // Если день не рабочий
219
220
            // Ищем следующий рабочий день и выводим его + время начало дня
221
            $nextWorkingTime = $this->nextWorkingDay($dateTime->format('Y-m-d')) . ' ' . $jobStart;
222
        }
223
224
        return $nextWorkingTime;
225
    }
226
227
    /**
228
     * Возвращает длинну рабочего дня в минутах.
229
     *
230
     * @param string|null $date Формат даты - "Y-m-d"
231
     * @return int
232
     */
233
    public function getJobMinutesInDay(string $date = null) : int
234
    {
235
        $day = $this->buildDataPartString($date, 'w');
236
237
        $nextWorkingTime = $this->nextWorkingTime($date ?? $this->dateTime->format('Y-m-d H:i'));
238
239
        list($jobStart, $jobSEnd) = explode('-', $this->workingDays[(int)$day]);
240
        // Считаем остаток рабочего времени
241
        if (empty($nextWorkingTime)) {
242
            $jobStart = ($date === null ? date('H:i', $this->dateTime->getTimestamp()) : date('H:i', strtotime($date)));
243
        }
244
245
        $dtStart = new \DateTime($jobStart);
246
        $dtEnd = new \DateTime($jobSEnd);
247
        $diff = $dtEnd->diff($dtStart);
248
249
        return ($diff->h * 60 + $diff->i);
250
    }
251
252
    /**
253
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
254
     *
255
     * @param int $minutes
256
     * @param string|null $date
257
     * @return \DateTime
258
     */
259
    private function modifyDate(int $minutes, string $date = null) : \DateTime
260
    {
261
        $dateTime = new \DateTime($date);
262
        $jobMinutesInDay = $this->getJobMinutesInDay($date);
263
264
        // Если длинна дня больше чем время модификации
265
        if ($jobMinutesInDay > $minutes) {
266
            $dateTime->modify("+$minutes minutes");
267
        } else { // Если длинна дня меньше чем время модификации
268
            do {
269
                $dateTime->modify("+$jobMinutesInDay minutes");
270
                $minutes -= $jobMinutesInDay;
271
                $nextWorkingTime = $this->nextWorkingTime($dateTime->format('Y-m-d H:i'));
272
                $dateTime = new \DateTime($nextWorkingTime);
273
                $jobMinutesInNextDay = $this->getJobMinutesInDay($dateTime->format('Y-m-d H:i'));
274
                if ($jobMinutesInNextDay > $minutes) {
275
                    $dateTime->modify("+$minutes minutes");
276
                    $minutes = 0;
277
                }
278
            } while ($minutes > 0);
279
        }
280
281
        return $dateTime;
282
    }
283
284
    /**
285
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
286
     *
287
     * @param int $minutes
288
     * @param string $date
289
     * @return string
290
     */
291
    public function modify(int $minutes, string $date = null) : string
292
    {
293
        $nextWorkingTime = $this->nextWorkingTime($date);
294
        // Если дата вне рабочего времени
295
        if (!empty($nextWorkingTime)) {
296
            $dateTime = $this->modifyDate($minutes, $nextWorkingTime);
297
        } else { // если дата в пределах рабочего времени
298
            $dateTime = $this->modifyDate($minutes, $date);
299
        }
300
        if ($date === null) {
301
            $this->dateTime->setTimestamp($dateTime->getTimestamp());
302
        }
303
        return $dateTime->format('Y-m-d H:i');
304
    }
305
306
    /**
307
     * Проверяет является ли строка корректной датой.
308
     *
309
     * @param $date
310
     * @param string $format
311
     * @return bool
312
     */
313
    public static function validateDate(string $date, string $format = 'Y-m-d H:i:s') : bool
314
    {
315
        $vDate = \DateTime::createFromFormat($format, $date);
316
        return $vDate && $vDate->format($format) === $date;
317
    }
318
}
319