Passed
Branch master (c53e22)
by Bushlanov
02:01
created

WorkingTime::isWeekend()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
ccs 0
cts 8
cp 0
rs 9.6666
cc 2
eloc 5
nc 2
nop 1
crap 6
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
     * @param null|string $date
62
     * @param string $format
63
     * @return string
64
     */
65
    private function buildDataPartString(?string $date, string $format): string
66
    {
67
        if (null === $date) {
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 \DateTime
81
     */
82
    private function buildDate(string $date = null): \DateTime
83
    {
84
        if (null === $date) {
85
            return new \DateTime($this->dateTime->format('Y-m-d H:i'));
86
        }
87
88
        return new \DateTime($date);
89
    }
90
91
    /**
92
     * Проверяет является ли дата праздничным днём.
93
     *
94
     * @param string $date
95
     * @return bool
96
     */
97
    public function isHoliday(string $date = null): bool
98
    {
99
        if (empty($this->holidays)) {
100
            return false; // Если не указаны праздничные дни, то день рабочий.
101
        }
102
        $day = $this->buildDataPartString($date, 'm-d');
103
104
        return in_array($day, $this->holidays, false);
105
    }
106
107
    /**
108
     * Проверяет является ли дата выходным днём.
109
     *
110
     * @param string $date
111
     * @return bool
112
     */
113
    public function isWeekend(string $date = null): bool
114
    {
115
        if (empty($this->weekends)) {
116
            return false; // Если не указаны выходные дни, то день рабочий.
117
        }
118
        $day = $this->buildDataPartString($date, 'w');
119
120
        return in_array($day, $this->weekends, false);
121
    }
122
123
    /**
124
     * Проверяет евляется ли дата рабочим днём.
125
     *
126
     * Формат даты - "Y-m-d"
127
     * @param string $date
128
     * @return bool
129
     */
130
    public function isWorkingDate(string $date = null): bool
131
    {
132
        return !($this->isWeekend($date) || $this->isHoliday($date));
133
    }
134
135
    /**
136
     * Проверяет евляется ли время рабочим.
137
     *
138
     * @param string|null $time Формат времени - "H:i" или полный "Y-m-d H:i"
139
     * @return bool
140
     * @throws \InvalidArgumentException
141
     */
142
    public function isWorkingTime(string $time = null): bool
143
    {
144
        if (null === $time) {
145
            $dateTime = $this->dateTime;
146
        } else {
147
            if (self::validateDate($time, 'H:i')) {
148
                $dateTime = new \DateTime($this->dateTime->format('Y-m-d') . ' ' . $time);
149
            } elseif (self::validateDate($time, 'Y-m-d H:i')) {
150
                $dateTime = new \DateTime($time);
151
            } else {
152
                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`.");
153
            }
154
        }
155
156
        [$jobStart, $jobEnd] = explode('-', $this->workingDays[$dateTime->format('w')]);
0 ignored issues
show
Bug introduced by
The variable $jobStart does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $jobEnd does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
157
158
        $dtStart = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobStart);
159
        $dtEnd = new \DateTime($dateTime->format('Y-m-d') . ' ' . $jobEnd);
160
161
        return $dateTime > $dtStart && $dateTime < $dtEnd;
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
        if (null === $date) {
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
     * Возвращает ближайшее рабочее время. Либо null если текущее время уже рабочее.
187
     *
188
     * @param string|null $date
189
     * @return null|string
190
     */
191
    public function nextWorkingTime(string $date = null): ?string
192
    {
193
        $dateTime = $this->buildDate($date);
194
        $nextWorkingTime = null;
195
196
        // Если дня нет в конфиге считаем его выходным
197
        if (!array_key_exists($dateTime->format('w'), $this->workingDays)) {
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
        [$jobStart, $jobEnd] = explode('-', $this->workingDays[$dateTime->format('w')]);
0 ignored issues
show
Bug introduced by
The variable $jobStart does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $jobEnd does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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') . ' ' . $jobEnd);
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
231
     * @return string
232
     */
233
    public function nextWorkingDayStart(string $date = null): string
234
    {
235
        $nextWorkingDayDT = new \DateTime($this->nextWorkingDay($date));
236
        $day = $nextWorkingDayDT->format('w');
237
        $jobStart = explode('-', $this->workingDays[$day])[0];
238
239
        return $nextWorkingDayDT->format('Y-m-d') . ' ' . $jobStart;
240
    }
241
242
    /**
243
     * Возвращает дату время начала следующего дня.
244
     *
245
     * @param string|null $date
246
     * @return string
247
     */
248
    private function nextWorkingDayEnd(string $date = null): string
249
    {
250
        $nextWorkingDayDT = new \DateTime($this->nextWorkingDay($date));
251
        $day = $nextWorkingDayDT->format('w');
252
        $jobEnd = explode('-', $this->workingDays[$day])[1];
253
254
        return $nextWorkingDayDT->format('Y-m-d') . ' ' . $jobEnd;
255
    }
256
257
    /**
258
     * Возвращает длинну рабочего дня в минутах.
259
     *
260
     * @param string|null $date Формат даты - "Y-m-d"
261
     * @return int
262
     */
263
    public function getJobMinutesInDay(string $date = null): int
264
    {
265
        $day = $this->buildDataPartString($date, 'w');
266
        $nextWorkingTime = $this->nextWorkingTime($date ?? $this->dateTime->format('Y-m-d H:i'));
267
268
        [$jobStart, $jobEnd] = explode('-', $this->workingDays[$day]);
0 ignored issues
show
Bug introduced by
The variable $jobStart seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $jobEnd does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
269
        // Считаем остаток рабочего времени
270
        if ($nextWorkingTime === null) {
271
            $jobStart = ($date === null ? date('H:i', $this->dateTime->getTimestamp()) : date('H:i', strtotime($date)));
272
        }
273
274
        $dtStart = new \DateTime($jobStart);
0 ignored issues
show
Bug introduced by
The variable $jobStart does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
275
        $dtEnd = new \DateTime($jobEnd);
276
        $diff = $dtEnd->diff($dtStart);
277
278
        return ($diff->h * 60 + $diff->i);
279
    }
280
281
    /**
282
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
283
     *
284
     * @param int $minutes
285
     * @param string $date
286
     * @return \DateTime
287
     */
288
    private function modifyDate(int $minutes, string $date): \DateTime
289
    {
290
        $dateTime = new \DateTime($date);
291
        $jobMinutesInDay = $this->getJobMinutesInDay($date);
292
293
        // Если длинна дня больше чем время модификации
294
        if ($jobMinutesInDay > $minutes) {
295
            $dateTime->modify("+$minutes minutes");
296
        } else { // Если длинна дня меньше чем время модификации
297
            do {
298
                $dateTime->modify("+$jobMinutesInDay minutes");
299
                $minutes -= $jobMinutesInDay;
300
                $nextWorkingTime = $this->nextWorkingTime($dateTime->format('Y-m-d H:i'));
301
                $dateTime = new \DateTime($nextWorkingTime);
302
                $jobMinutesInNextDay = $this->getJobMinutesInDay($dateTime->format('Y-m-d H:i'));
303
                if ($jobMinutesInNextDay > $minutes) {
304
                    $dateTime->modify("+$minutes minutes");
305
                    $minutes = 0;
306
                }
307
            } while ($minutes > 0);
308
        }
309
310
        return $dateTime;
311
    }
312
313
    /**
314
     * Прибавляет заданное количество минут к дате с учетом рабочего времени.
315
     *
316
     * @param int $minutes
317
     * @param string $date
318
     * @return string
319
     */
320
    public function modify(int $minutes, string $date = null): string
321
    {
322
        $nextWorkingTime = $this->nextWorkingTime($date);
323
        // Если дата вне рабочего времени
324
        if ($nextWorkingTime !== null) {
325
            $dateTime = $this->modifyDate($minutes, $nextWorkingTime);
326
        } else { // если дата в пределах рабочего времени
327
            $dateTime = $this->modifyDate($minutes, $date ?? $this->dateTime->format('Y-m-d H:i'));
328
        }
329
        if (null === $date) {
330
            $this->dateTime->setTimestamp($dateTime->getTimestamp());
331
        }
332
        return $dateTime->format('Y-m-d H:i');
333
    }
334
335
    /**
336
     * Возвращает рабочее время в минутах в заданном временном интервале.
337
     *
338
     * @param string $startDate
339
     * @param string $endDate
340
     * @return int
341
     * @throws \InvalidArgumentException
342
     */
343
    public function calculatingWorkingTime(string $startDate, string $endDate): int
344
    {
345
        if (!self::validateDate($startDate) || !self::validateDate($endDate)) {
346
            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`.");
347
        }
348
349
        $dtStart = $this->buildDate($startDate);
350
        $dtEnd = $this->buildDate($endDate);
351
352
        $diff = $dtEnd->diff($dtStart);
353
        $diffMinutes = $diff->d * 1440 + $diff->h * 60 + $diff->i; // Разница между датами в минутах
354
        $jobMinutesInDay = $this->getJobMinutesInDay($dtStart->format('Y-m-d H:i')); // Длинна рабочего дня
355
356
        // Если разница во времени меньше длинны рабочего дня
357
        if ($diffMinutes < $jobMinutesInDay) {
358
            return $diffMinutes;
359
        }
360
        // Если разница больше то перебираем дни
361
        $nextWorkingDayStartDT = new \DateTime($this->nextWorkingDayStart($dtStart->format('Y-m-d')));
362
        $nextWorkingDayEndDT = new \DateTime($this->nextWorkingDayEnd($dtStart->format('Y-m-d')));
363
        do {
364
            // Дата находится в промежутке ДО наступления следующего рабочего времени
365
            if ($nextWorkingDayStartDT > $dtEnd) {
366
                return $jobMinutesInDay; // Возвращает остатки рабочего времени
367
            }
368
            // Дата в промежутке следующего рабочего дня
369
            if ($nextWorkingDayStartDT < $dtEnd && $dtEnd < $nextWorkingDayEndDT && ($nextWorkingDayStartDT->format('Y-m-d') === ($dtEnd->format('Y-m-d')))) {
370
                $nextDiff = $dtEnd->diff($nextWorkingDayStartDT);
371
                $nextDiffMinutes = $nextDiff->d * 1440 + $nextDiff->h * 60 + $nextDiff->i;
372
                return $nextDiffMinutes + $jobMinutesInDay;
373
            } else {
374
                $jobMinutesInDay += $this->getJobMinutesInDay($nextWorkingDayStartDT->format('Y-m-d H:i')); // Длинна рабочего дня
375
                $nextWorkingDayStartDT = new \DateTime($this->nextWorkingDayStart($nextWorkingDayStartDT->format('Y-m-d')));
376
                $nextWorkingDayEndDT = new \DateTime($this->nextWorkingDayEnd($nextWorkingDayStartDT->format('Y-m-d')));
377
            }
378
        } while (true);
379
    }
380
381
    /**
382
     * Проверяет является ли строка корректной датой.
383
     *
384
     * @param $date
385
     * @param string $format
386
     * @return bool
387
     */
388
    public static function validateDate(string $date, string $format = 'Y-m-d H:i:s'): bool
389
    {
390
        $vDate = \DateTime::createFromFormat($format, $date);
391
        return $vDate && $vDate->format($format) === $date;
392
    }
393
}
394