Passed
Branch master (ea6097)
by Mars
02:03
created

TimePeriodHelper::time()   A

Complexity

Conditions 6
Paths 13

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 19
c 1
b 0
f 0
nc 13
nop 3
dl 0
loc 35
rs 9.0111
1
<?php
2
3
namespace marsapp\helper\timeperiod;
4
5
/**
6
 * Time Period Helper
7
 * 
8
 * Note:
9
 * 1. Format: $timePeriods = [[$startDatetime1, $endDatetime1], [$startDatetime2, $endDatetime2], ...];
10
 * - $Datetime = Y-m-d H:i:s ; Y-m-d H:i:00 ; Y-m-d H:00:00 ;
11
 * 2. If it is hour/minute/second, the end point is usually not included, for example, 8 o'clock to 9 o'clock is 1 hour.
12
 * 3. If it is a day/month/year, it usually includes an end point, for example, January to March is 3 months.
13
 * 4. When processing, assume that the $timePeriods format is correct. If necessary, you need to call the verification function to verify the data.
14
 * 5. Ensure performance by keeping the $timePeriods format correct:
15
 * - a. When getting the raw $timePeriods, sort out it by format(), filter(), union().
16
 * - b. Handle $timePeriods using only the functions provided by TimePeriodHelper (Will not break the format, sort)
17
 * - c. When you achieve the two operations described above, you can turn off auto sort out (TimePeriodHelper::setSortOut(false)) to improve performance.
18
 * 
19
 * @version 0.5.2
20
 * @author Mars Hung <[email protected]>
21
 * @see https://github.com/marshung24/TimePeriodHelper
22
 */
23
class TimePeriodHelper
24
{
25
    /**
26
     * Option set
27
     * @var array
28
     */
29
    protected static $_options = [
30
        'unit' => [
31
            // Time calculate unit - hour, minute, second(default)
32
            'time' => 'second',
33
            // Format unit - hour, minute, second(default)
34
            'format' => 'second',
35
        ],
36
        'unitMap' => [
37
            'hour' => 'hour',
38
            'hours' => 'hour',
39
            'h' => 'hour',
40
            'minute' => 'minute',
41
            'minutes' => 'minute',
42
            'i' => 'minute',
43
            'm' => 'minute',
44
            'second' => 'second',
45
            'seconds' => 'second',
46
            's' => 'second',
47
        ],
48
        'filter' => [
49
            'isDatetime' => true
50
        ],
51
        // Default sort out $timePeriods
52
        'sortOut' => true,
53
    ];
54
55
56
    /**
57
     * ************************************************
58
     * ************** Operation Function **************
59
     * ************************************************
60
     */
61
62
    /**
63
     * Sort time periods (Order by ASC)
64
     * 
65
     * 1. When sorting, sort the start time first, if the start time is the same, then sort the end time
66
     * 2. Sort Priority: Start Time => End Time
67
     * 
68
     * @param array $timePeriods
69
     * @return array
70
     */
71
    public static function sort(array $timePeriods)
72
    {
73
        // Closure in PHP 7.0.X loop maybe die
74
        usort($timePeriods, function ($a, $b) {
75
            if ($a[0] == $b[0]) {
76
                // Start time is equal, compare end time
77
                $r = $a[1] < $b[1] ? -1 : 1;
78
            } else {
79
                // Compare Start time
80
                $r = $a[0] < $b[0] ? -1 : 1;
81
            }
82
83
            return $r;
84
        });
85
86
        return $timePeriods;
87
    }
88
89
    /**
90
     * Union one or more time periods
91
     * 
92
     * 1. Sort and merge one or more time periods with contacts
93
     * 2. TimePeriodHelper::union($timePeriods1, $timePeriods2, $timePeriods3, ......);
94
     * 
95
     * @param array $timePeriods
96
     * @return array
97
     */
98
    public static function union()
99
    {
100
        $opt = [];
101
102
        // Combine and sort
103
        $merge = call_user_func_array('array_merge', func_get_args());
104
        $merge = self::sort($merge);
105
106
        if (empty($merge)) {
107
            return $opt;
108
        }
109
110
        $tmp = array_shift($merge);
111
        foreach ($merge as $k => $tp) {
112
            if ($tp[0] > $tmp[1]) {
113
                // Got it, and set next.
114
                $opt[] = $tmp;
115
                $tmp = $tp;
116
            } elseif ($tp[1] > $tmp[1]) {
117
                // Extend end time
118
                $tmp[1] = $tp[1];
119
            }
120
        }
121
        $opt[] = $tmp;
122
123
        return $opt;
124
    }
125
126
    /**
127
     * Computes the difference of time periods
128
     * 
129
     * 1. Compares $timePeriods1 against $timePeriods2 and returns the values in $timePeriods1 that are not present in $timePeriods2.
130
     * 2. e.g. TimePeriodHelper::diff($timePeriods1, $timePeriods2);
131
     * 3. Whether $timePeriods is sorted out will affect the correctness of the results. Please refer to Note 5. Ensure performance by keeping the $timePeriods format correct.
132
     * 
133
     * @param array $timePeriods1
134
     * @param array $timePeriods2
135
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
136
     * @return array
137
     */
138
    public static function diff(array $timePeriods1, array $timePeriods2, $sortOut = 'default')
139
    {
140
        /*** Arguments prepare ***/
141
        // Subject or pattern is empty, do nothing
142
        if (empty($timePeriods1) || empty($timePeriods2)) {
143
            return $timePeriods1;
144
        }
145
146
        // Data sorting out
147
        self::dataSortOut($sortOut, $timePeriods1, $timePeriods2);
148
149
        $opt = [];
150
        foreach ($timePeriods1 as $k1 => $ori) {
151
            foreach ($timePeriods2 as $ko => $sub) {
152
                if ($sub[1] <= $ori[0]) {
153
                    // No overlap && Passed: --sub0--sub1--ori0--ori1--
154
                    unset($timePeriods2[$ko]);
155
                    continue;
156
                } elseif ($ori[1] <= $sub[0]) {
157
                    // No overlap: --ori0--ori1--sub0--sub1--
158
                    continue;
159
                } elseif ($sub[0] <= $ori[0] && $ori[1] <= $sub[1]) {
160
                    // Subtract all: --sub0--ori0--ori1--sub1--
161
                    $ori = [];
162
                    break;
163
                } elseif ($ori[0] < $sub[0] && $sub[1] < $ori[1]) {
164
                    // Delete internal: --ori0--sub0--sub1--ori1--
165
                    $opt[] = [$ori[0], $sub[0]];
166
                    $ori = [$sub[1], $ori[1]];
167
                    //} elseif ($sub[0] <= $ori[0] && $sub[1] <= $ori[1]) { // Complete condition
168
                } elseif ($sub[0] <= $ori[0]) { // Equivalent condition
169
                    // Delete overlap: --sub0--ori0--sub1--ori1--
170
                    $ori = [$sub[1], $ori[1]];
171
                    //} elseif ($ori[0] <= $sub[0] && $ori[1] <= $sub[1]) { // Complete condition
172
                    //} elseif ($ori[1] <= $sub[1]) { // Equivalent condition
173
                } else { // Equivalent condition
174
                    // Delete overlap: --ori0--sub0--ori1--sub1--
175
                    $ori = [$ori[0], $sub[0]];
176
                }
177
            }
178
179
            // All No overlap
180
            if (!empty($ori)) {
181
                $opt[] = $ori;
182
            }
183
        }
184
185
        return $opt;
186
    }
187
188
    /**
189
     * Computes the intersection of time periods
190
     * 
191
     * 1. e.g. TimePeriodHelper::intersect($timePeriods1, $timePeriods2);
192
     * 2. Whether $timePeriods is sorted out will affect the correctness of the results. Please refer to Note 5. Ensure performance by keeping the $timePeriods format correct.
193
     * 
194
     * @param array $timePeriods1
195
     * @param array $timePeriods2
196
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
197
     * @return array
198
     */
199
    public static function intersect(array $timePeriods1, array $timePeriods2, $sortOut = 'default')
200
    {
201
        // Subject or pattern is empty, do nothing
202
        if (empty($timePeriods1) || empty($timePeriods2)) {
203
            return [];
204
        }
205
206
        // Data sorting out
207
        self::dataSortOut($sortOut, $timePeriods1, $timePeriods2);
208
209
        $opt = [];
210
        foreach ($timePeriods1 as $k1 => $ori) {
211
            foreach ($timePeriods2 as $ko => $sub) {
212
                if ($sub[1] <= $ori[0]) {
213
                    // No overlap && Passed: --sub0--sub1--ori0--ori1--
214
                    unset($timePeriods2[$ko]);
215
                    continue;
216
                } elseif ($ori[1] <= $sub[0]) {
217
                    // No overlap: --ori0--ori1--sub0--sub1--
218
                    continue;
219
                } elseif ($sub[0] <= $ori[0] && $ori[1] <= $sub[1]) {
220
                    // Subtract all: --sub0--ori0--ori1--sub1--
221
                    $opt[] = [$ori[0], $ori[1]];
222
                    break;
223
                } elseif ($ori[0] < $sub[0] && $sub[1] < $ori[1]) {
224
                    // Delete internal: --ori0--sub0--sub1--ori1--
225
                    $opt[] = [$sub[0], $sub[1]];
226
                    $ori = [$sub[1], $ori[1]];
227
                    //} elseif ($sub[0] <= $ori[0] && $sub[1] <= $ori[1]) { // Complete condition
228
                } elseif ($sub[0] <= $ori[0]) { // Equivalent condition
229
                    // Delete overlap: --sub0--ori0--sub1--ori1--
230
                    $opt[] = [$ori[0], $sub[1]];
231
                    $ori = [$sub[1], $ori[1]];
232
                    //} elseif ($ori[0] <= $sub[0] && $ori[1] <= $sub[1]) { // Complete condition
233
                    //} elseif ($ori[1] <= $sub[1]) { // Equivalent condition
234
                } else { // Equivalent condition
235
                    // Delete overlap: --ori0--sub0--ori1--sub1--
236
                    $opt[] = [$sub[0], $ori[1]];
237
                    break;
238
                }
239
            }
240
        }
241
242
        return $opt;
243
    }
244
245
    /**
246
     * Time period is overlap
247
     * 
248
     * 1. Determine if there is overlap between the two time periods
249
     * 2. Only when there is no intersection, no data is needed.
250
     * 3. Logic is similar to intersect.
251
     *  
252
     * @param array $timePeriods1
253
     * @param array $timePeriods2
254
     * @return bool
255
     */
256
    public static function isOverlap(array $timePeriods1, array $timePeriods2)
257
    {
258
        // Subject or pattern is empty, do nothing
259
        if (empty($timePeriods1) || empty($timePeriods2)) {
260
            return false;
261
        }
262
263
        foreach ($timePeriods1 as $k1 => $ori) {
264
            foreach ($timePeriods2 as $ko => $sub) {
265
                if ($sub[1] <= $ori[0]) {
266
                    // No overlap && Passed: --sub0--sub1--ori0--ori1--
267
                    unset($timePeriods2[$ko]);
268
                    continue;
269
                } elseif ($ori[1] <= $sub[0]) {
270
                    // No overlap: --ori0--ori1--sub0--sub1--
271
                    continue;
272
                } elseif ($sub[0] <= $ori[0] && $ori[1] <= $sub[1]) {
273
                    // Subtract all: --sub0--ori0--ori1--sub1--
274
                    return true;
275
                } elseif ($ori[0] < $sub[0] && $sub[1] < $ori[1]) {
276
                    // Delete internal: --ori0--sub0--sub1--ori1--
277
                    return true;
278
                    //} elseif ($sub[0] <= $ori[0] && $sub[1] <= $ori[1]) { // Complete condition
279
                } elseif ($sub[0] <= $ori[0]) { // Equivalent condition
280
                    // Delete overlap: --sub0--ori0--sub1--ori1--
281
                    return true;
282
                    //} elseif ($ori[0] <= $sub[0] && $ori[1] <= $sub[1]) { // Complete condition
283
                    //} elseif ($ori[1] <= $sub[1]) { // Equivalent condition
284
                } else { // Equivalent condition
285
                    // Delete overlap: --ori0--sub0--ori1--sub1--
286
                    return true;
287
                }
288
            }
289
        }
290
291
        return false;
292
    }
293
294
    /**
295
     * The time period is in contact with the specified time (time period)
296
     * 
297
     * @param array $timePeriods
298
     * @param string $sDateTime
299
     * @param string $eDateTime
300
     * @param string $sortOut
301
     * @return array
302
     */
303
    public static function contact(array $timePeriods, $sDateTime, $eDateTime = null, $sortOut = 'default')
304
    {
305
        // Subject is empty, do nothing
306
        if (empty($timePeriods)) {
307
            return [];
308
        }
309
310
        // Set $eDateTime
311
        $eDateTime = $eDateTime ?: $sDateTime;
312
313
        // Data sorting out
314
        self::dataSortOut($sortOut, $timePeriods);
315
        if ($sortOut) {
316
            $sDTime = min($sDateTime, $eDateTime);
317
            $eDTime = max($sDateTime, $eDateTime);
318
            $sDateTime = $sDTime;
319
            $eDateTime = $eDTime;
320
        }
321
322
        // Get Contact time periods
323
        $opt = [];
324
        foreach ($timePeriods as $k => $tp) {
325
            if ($eDateTime <= $tp[0]) {
326
                // No overlap && Passed: --$sDateTime--$eDateTime--$tp0--$tp1--
327
                if ($sDateTime == $tp[0]) {
328
                    // But 
329
                    $opt[] = $tp;
330
                }
331
            } elseif ($tp[1] <= $sDateTime) {
332
                // No overlap: --$tp0--$tp1--$sDateTime--$eDateTime--
333
            } else {
334
                // Overlap
335
                $opt[] = $tp;
336
            }
337
        }
338
339
        return $opt;
340
    }
341
342
    /**
343
     * Time period greater than the specified time
344
     * 
345
     * @param array $timePeriods
346
     * @param string $refDatetime
347
     * @param string $intactTime
348
     * @param string $sortOut
349
     * @return array
350
     */
351
    public static function greaterThan(array $timePeriods, $refDatetime, $intactTime = true, $sortOut = 'default')
352
    {
353
        // Subject is empty, do nothing
354
        if (empty($timePeriods)) {
355
            return [];
356
        }
357
358
        // Data sorting out
359
        self::dataSortOut($sortOut, $timePeriods);
360
361
        // Get Contact time periods
362
        $opt = [];
363
        foreach ($timePeriods as $k => $tp) {
364
            if ($intactTime) {
365
                // Time period is intact
366
                if ($tp[0] >= $refDatetime) {
367
                    $opt[] = $tp;
368
                }
369
            } else {
370
                // Time period not intact
371
                if ($tp[1] > $refDatetime) {
372
                    $opt[] = $tp;
373
                }
374
            }
375
        }
376
377
        return $opt;
378
    }
379
380
    /**
381
     * Time period less than the specified time
382
     * 
383
     * @param array $timePeriods
384
     * @param string $refDatetime
385
     * @param string $intactTime
386
     * @param string $sortOut
387
     * @return array
388
     */
389
    public static function lessThan(array $timePeriods, $refDatetime, $intactTime = true, $sortOut = 'default')
390
    {
391
        // Subject is empty, do nothing
392
        if (empty($timePeriods)) {
393
            return [];
394
        }
395
396
        // Data sorting out
397
        self::dataSortOut($sortOut, $timePeriods);
398
399
        // Get Contact time periods
400
        $opt = [];
401
        foreach ($timePeriods as $k => $tp) {
402
            if ($intactTime) {
403
                // Time period is intact
404
                if ($tp[1] <= $refDatetime) {
405
                    $opt[] = $tp;
406
                }
407
            } else {
408
                // Time period not intact
409
                if ($tp[0] < $refDatetime) {
410
                    $opt[] = $tp;
411
                }
412
            }
413
        }
414
415
        return $opt;
416
    }
417
418
    /**
419
     * Fill time periods
420
     * 
421
     * Leaving only the first start time and the last end time
422
     * 
423
     * @param array $timePeriods
424
     * @return array
425
     */
426
    public static function fill(array $timePeriods)
427
    {
428
        $opt = [];
429
430
        if (isset($timePeriods[0][0])) {
431
            $tmp = array_shift($timePeriods);
432
            $start = $tmp[0];
433
            $end = $tmp[1];
434
            foreach ($timePeriods as $k => $tp) {
435
                $start = min($start, $tp[0]);
436
                $end = max($end, $tp[1]);
437
            }
438
            $opt = [[$start, $end]];
439
        }
440
441
        return $opt;
442
    }
443
444
    /**
445
     * Get gap time periods of multiple sets of time periods
446
     * 
447
     * 1. Whether $timePeriods is sorted out will affect the correctness of the results. Please refer to Note 5. Ensure performance by keeping the $timePeriods format correct.
448
     * 
449
     * @param array $timePeriods
450
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
451
     * @return array
452
     */
453
    public static function gap(array $timePeriods, $sortOut = 'default')
454
    {
455
        // Subject is empty, do nothing
456
        if (empty($timePeriods)) {
457
            return [];
458
        }
459
460
        // Data sorting out
461
        self::dataSortOut($sortOut, $timePeriods);
462
463
        $opt = [];
464
        foreach ($timePeriods as $k => $tp) {
465
            if (isset($timePeriods[$k + 1])) {
466
                $opt[] = [$tp[1], $timePeriods[$k + 1][0]];
467
            }
468
        }
469
470
        return $opt;
471
    }
472
473
    /**
474
     * Calculation period total time
475
     *
476
     * 1. You can specify the smallest unit (from setUnit())
477
     * 2. Whether $timePeriods is sorted out will affect the correctness of the results. Please refer to Note 5. Ensure performance by keeping the $timePeriods format correct.
478
     * 3. approximation: chop off
479
     *
480
     * @param array $timePeriods            
481
     * @param int $precision
482
     *            Optional decimal places for the decimal point
483
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
484
     * @return number
485
     */
486
    public static function time(array $timePeriods, $precision = 0, $sortOut = 'default')
487
    {
488
        // Subject is empty, do nothing
489
        if (empty($timePeriods)) {
490
            return 0;
491
        }
492
493
        // Data sorting out
494
        self::dataSortOut($sortOut, $timePeriods);
495
496
        // Calculate time
497
        $time = 0;
498
        foreach ($timePeriods as $k => $tp) {
499
            $time += strtotime($tp[1]) - strtotime($tp[0]);
500
        }
501
502
        // Time unit convert
503
        switch (self::getUnit('time')) {
504
            case 'minute':
505
                $time = $time / 60;
506
                break;
507
            case 'hour':
508
                $time = $time / 3600;
509
                break;
510
        }
511
512
        // Precision
513
        if ($precision > 0) {
514
            $pow = pow(10, (int) $precision);
515
            $time = ((int) ($time * $pow)) / $pow;
516
        } else {
517
            $time = (int) ($time);
518
        }
519
520
        return $time;
521
    }
522
523
    /**
524
     * Cut the time period of the specified length of time
525
     *
526
     * 1. You can specify the smallest unit (from setUnit())
527
     * 2. Whether $timePeriods is sorted out will affect the correctness of the results. Please refer to Note 5. Ensure performance by keeping the $timePeriods format correct.
528
     * 
529
     * @param array $timePeriods            
530
     * @param number $time
531
     *            Specified length of time
532
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
533
     * @return array
534
     */
535
    public static function cut(array $timePeriods, $time, $sortOut = 'default')
536
    {
537
        // Subject is empty, do nothing
538
        if (empty($timePeriods)) {
539
            return [];
540
        }
541
542
        // Data sorting out
543
        self::dataSortOut($sortOut, $timePeriods);
544
545
        // Convert time by unit
546
        $time = self::time2Second($time);
547
548
        $opt = [];
549
        $timeLen = 0;
550
        foreach ($timePeriods as $k => $tp) {
551
            // Calculation time
552
            $tlen = strtotime($tp[1]) - strtotime($tp[0]);
553
554
            // Judging the length of time
555
            if ($timeLen + $tlen <= $time) {
556
                // Within limits
557
                $opt[] = $tp;
558
                $timeLen = $timeLen + $tlen;
559
            } else {
560
                // Outside the limits
561
                $lastTime = $time - $timeLen;
562
                if ($lastTime > 0) {
563
                    $tps = $tp[0];
564
                    $tpe = self::extendTime($tps, $lastTime);
565
                    if ($tps != $tpe) {
566
                        $opt[] = [$tps, $tpe];
567
                    }
568
                }
569
                $timeLen = $time;
0 ignored issues
show
Unused Code introduced by
The assignment to $timeLen is dead and can be removed.
Loading history...
570
                break;
571
            }
572
        }
573
574
        return $opt;
575
    }
576
577
    /**
578
     * Increase the time period of the specified length of time after the last time period
579
     *
580
     * 1. You can specify the smallest unit (from setUnit())
581
     * 2. Whether $timePeriods is sorted out will affect the correctness of the results. Please refer to Note 5. Ensure performance by keeping the $timePeriods format correct.
582
     * 
583
     * @param array $timePeriods            
584
     * @param number $time
585
     *            Specified length of time (default uint:second)
586
     * @param number $interval
587
     *            Interval with existing time period
588
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
589
     * @return array
590
     */
591
    public static function extend(array $timePeriods, $time, $interval = 0, $sortOut = 'default')
592
    {
593
        // Subject is empty, do nothing
594
        if (empty($timePeriods)) {
595
            return [];
596
        }
597
598
        // Data sorting out
599
        self::dataSortOut($sortOut, $timePeriods);
600
601
        // Convert time by unit
602
        $time = self::time2Second($time);
603
        $interval = self::time2Second($interval);
604
605
        // last time period index
606
        $eIdx = sizeof($timePeriods) - 1;
607
608
        if (!$interval) {
609
            // No gap, Directly extend the end time
610
            $timePeriods[$eIdx][1] = self::extendTime($timePeriods[$eIdx][1], $time);
611
        } else {
612
            // Has gap
613
            $tps = self::extendTime($timePeriods[$eIdx][1], $interval);
614
            $tpe = self::extendTime($tps, $time);
615
            if ($tps != $tpe) {
616
                $timePeriods[] = [$tps, $tpe];
617
            }
618
        }
619
620
        return $timePeriods;
621
    }
622
623
    /**
624
     * Shorten the specified length of time from behind
625
     *
626
     * 1. You can specify the smallest unit (from setUnit())
627
     * 2. Whether $timePeriods is sorted out will affect the correctness of the results. Please refer to Note 5. Ensure performance by keeping the $timePeriods format correct.
628
     * 
629
     * @param array $timePeriods            
630
     * @param number $time
631
     *            Specified length of time (default uint:second)
632
     * @param bool $crossperiod
633
     *            Whether to shorten across time
634
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
635
     * @return array
636
     */
637
    public static function shorten(array $timePeriods, $time, $crossperiod = true, $sortOut = 'default')
638
    {
639
        // Subject is empty, do nothing
640
        if (empty($timePeriods)) {
641
            return [];
642
        }
643
644
        // Data sorting out
645
        self::dataSortOut($sortOut, $timePeriods);
646
647
        // Convert time by unit
648
        $time = self::time2Second($time);
649
650
        // last time period index
651
        $eIdx = sizeof($timePeriods) - 1;
652
653
        for ($i = $eIdx; $i >= 0; $i--) {
654
            $tps = $timePeriods[$i][0];
655
            $tpe = $timePeriods[$i][1];
656
            $tTime = strtotime($tpe) - strtotime($tps);
657
658
            if ($tTime <= $time) {
659
                // Not enough, unset this timeperiod
660
                unset($timePeriods[$i]);
661
                $time -= $tTime;
662
            } else {
663
                // Enough, shorten end time.
664
                $timePeriods[$i][1] = self::extendTime($timePeriods[$i][0], $tTime - $time);
665
                break;
666
            }
667
668
            // End or No cross-period
669
            if ($time <= 0 || !$crossperiod) {
670
                break;
671
            }
672
        }
673
674
        return $timePeriods;
675
    }
676
677
    /**
678
     * Transform format
679
     * 
680
     * @param array $timePeriods
681
     * @param string $unit Time unit, if default,use class options setting
682
     * @return array
683
     */
684
    public static function format(array $timePeriods, $unit = 'default')
685
    {
686
        foreach ($timePeriods as $k => &$tp) {
687
            $tp[0] = self::timeFormatConv($tp[0], $unit);
688
            $tp[1] = self::timeFormatConv($tp[1], $unit);
689
        }
690
691
        return $timePeriods;
692
    }
693
694
    /**
695
     * Validate time period
696
     * 
697
     * Verify format, size, start/end time
698
     * 
699
     * @param mixed|array $timePeriods
700
     * @throws \Exception
701
     * @return bool
702
     */
703
    public static function validate($timePeriods)
704
    {
705
        // If not array, return.
706
        if (!is_array($timePeriods)) {
707
            throw new \Exception('Time periods format error !', 400);
708
        }
709
710
        foreach ($timePeriods as $k => $tp) {
711
            // filter format, number
712
            if (!is_array($tp) || sizeof($tp) != 2) {
713
                    throw new \Exception('Time periods format error !', 400);
714
            }
715
            // filter time period
716
            if ($tp[0] >= $tp[1]) {
717
                    throw new \Exception('Time periods format error !', 400);
718
            }
719
            // filter time format
720
            if (self::getFilterDatetime() && (!self::isDatetime($tp[0]) || !self::isDatetime($tp[1]))) {
721
                    throw new \Exception('Time periods format error !', 400);
722
            }
723
        }
724
725
        return true;
726
    }
727
728
    /**
729
     * Remove invalid time period
730
     * 
731
     * 1. Verify format, size, start/end time, and remove invalid.
732
     * 2. time carry problem processing, e.g. 2019-01-01 24:00:00 => 2019-01-02 00:00:00
733
     * 
734
     * @param mixed|array $timePeriods
735
     * @throws \Exception
736
     * @return array
737
     */
738
    public static function filter($timePeriods)
739
    {
740
        // If not array, return.
741
        if (!is_array($timePeriods)) {
742
            return [];
743
        }
744
745
        foreach ($timePeriods as $k => $tp) {
746
            // filter format, number
747
            if (!is_array($tp) || sizeof($tp) != 2) {
748
                unset($timePeriods[$k]);
749
                continue;
750
            }
751
            // filter time period
752
            if ($tp[0] >= $tp[1]) {
753
                unset($timePeriods[$k]);
754
                continue;
755
            }
756
            // filter time format
757
            if (self::getFilterDatetime() && (!self::isDatetime($tp[0]) || !self::isDatetime($tp[1]))) {
758
                unset($timePeriods[$k]);
759
                continue;
760
            }
761
762
            // Time carry
763
            $timeLen = strlen($tp[0]);
764
            if ($timeLen >= 13) {
765
                if (substr($tp[0], 11, 2) == '24') {
766
                    $timePeriods[$k][0] = self::extendTime($timePeriods[$k][0], 0);
767
                }
768
                if (substr($tp[1], 11, 2) == '24') {
769
                    $timePeriods[$k][1] = self::extendTime($timePeriods[$k][1], 0);
770
                }
771
            }
772
        }
773
774
        return $timePeriods;
775
    }
776
777
778
    /**
779
     * **********************************************
780
     * ************** Options Function **************
781
     * **********************************************
782
     */
783
784
785
    /**
786
     * Specify the minimum unit of calculation
787
     * 
788
     * 1. Scope: Global
789
     * 2. hour,minute,second
790
     * 
791
     * @param string $unit time unit. e.g. hour, minute, second.
792
     * @param string $target Specify function,or all functions
793
     * @throws \Exception
794
     * @return $this
795
     */
796
    public static function setUnit(string $unit, string $target = 'all')
797
    {
798
        /*** Arguments prepare ***/
799
        if (!isset(self::$_options['unitMap'][$unit])) {
800
            throw new \Exception('Error Unit: ' . $unit, 400);
801
        }
802
        // conv unit
803
        $unit = self::$_options['unitMap'][$unit];
804
805
        if ($target != 'all' && !isset(self::$_options['unit'][$target])) {
806
            throw new \Exception('Error Target: ' . $target, 400);
807
        }
808
809
        /* Setting */
810
        if ($target != 'all') {
811
            self::$_options['unit'][$target] = $unit;
812
        } else {
813
            foreach (self::$_options['unit'] as $tar => &$value) {
814
                $value = $unit;
815
            }
816
        }
817
818
        return new static();
819
    }
820
821
    /**
822
     * Get the unit used by the specified function
823
     * 
824
     * @param string $target Specify function's unit
825
     * @throws \Exception
826
     * @return string
827
     */
828
    public static function getUnit(string $target)
829
    {
830
        if (isset(self::$_options['unit'][$target])) {
831
            return self::$_options['unit'][$target];
832
        } else {
833
            throw new \Exception('Error Target: ' . $target, 400);
834
        }
835
    }
836
837
    /**
838
     * If neet filter datetime : Set option
839
     * 
840
     * 1. Scope: Global
841
     * 2. If you do not want to filter the datetime format, set it to false.  
842
     * 3. Maybe the time format is not Y-m-d H:i:s (such as Y-m-d H:i), you need to close it.
843
     * 4. Effect function: filter(), validate()
844
     * 
845
     * @param bool $bool
846
     * @return $this
847
     */
848
    public static function setFilterDatetime($bool)
849
    {
850
        self::$_options['filter']['isDatetime'] = !!$bool;
851
852
        return new static();
853
    }
854
855
    /**
856
     * If neet filter datetime : Get option
857
     * 
858
     * @return bool
859
     */
860
    public static function getFilterDatetime()
861
    {
862
        return self::$_options['filter']['isDatetime'];
863
    }
864
865
    /**
866
     * Auto sort out $timePeriods : Set option
867
     *
868
     * 1. Scope: Global
869
     * 2. Before the function is processed, union() will be used to organize $timePeriods format.
870
     * 
871
     * @param bool $bool default true
872
     * @return $this
873
     */
874
    public static function setSortOut($bool = true)
875
    {
876
        self::$_options['sortOut'] = !!$bool;
877
878
        return new static();
879
    }
880
881
    /**
882
     * Auto sort out $timePeriods : Get option
883
     *
884
     * @return bool
885
     */
886
    public static function getSortOut()
887
    {
888
        return self::$_options['sortOut'];
889
    }
890
891
    /**
892
     * ********************************************
893
     * ************** Tools Function **************
894
     * ********************************************
895
     */
896
897
    /**
898
     * Check datetime fast
899
     * 
900
     * Only check format,no check for reasonableness
901
     * 
902
     * @param string $datetime
903
     * @return boolean
904
     */
905
    public static function isDatetime(string $datetime)
906
    {
907
        return (bool) preg_match('|^[0-9]{4}\-[0-9]{2}\-[0-9]{2}\ [0-9]{2}\:[0-9]{2}\:[0-9]{2}$|', $datetime);
908
    }
909
910
    /**
911
     * Time format convert
912
     * 
913
     * format:Y-m-d H:i:s
914
     * When the length is insufficient, it will add the missing
915
     * 
916
     * @param string $datetime
917
     * @param string $unit Time unit, if default,use self::$_options setting
918
     * @return string
919
     */
920
    public static function timeFormatConv(string $datetime, $unit = 'default')
921
    {
922
        // fill format
923
        $strlen = strlen($datetime);
924
        $datetime .= substr(' 00:00:00', $strlen - 10);
925
926
        $unit = !isset(self::$_options['unitMap'][$unit]) ? self::$_options['unit']['format'] : self::$_options['unitMap'][$unit];
927
        // replace
928
        if ($unit == 'minute') {
929
            $datetime = substr_replace($datetime, "00", 17, 2);
930
        } elseif ($unit == 'hour') {
931
            $datetime = substr_replace($datetime, "00:00", 14, 5);
932
        }
933
934
        return $datetime;
935
    }
936
937
    /**
938
     * Extend time
939
     * 
940
     * @param string $datetime
941
     * @param int $timeLen 
942
     * @return string
943
     */
944
    protected static function extendTime(String $datetime, $timeLen)
945
    {
946
        $tout = date('Y-m-d H:i:s', strtotime($datetime) + $timeLen);
947
        return substr($tout, 0, strlen($datetime));
948
    }
949
950
    /**
951
     * Time Conversion frm unit to second
952
     * 
953
     * @param number $time
954
     * @param string $unit Time unit, if default,use self::$_options setting
955
     * @return int
956
     */
957
    public static function time2Second($time, $unit = 'default')
958
    {
959
        // Git time unit
960
        $unit = !isset(self::$_options['unitMap'][$unit]) ? self::getUnit('time') : self::$_options['unitMap'][$unit];
961
962
        // Convert
963
        switch ($unit) {
964
            case 'minute':
965
                $time = $time * 60;
966
                break;
967
            case 'hour':
968
                $time = $time * 3600;
969
                break;
970
        }
971
972
        return (int) $time;
973
    }
974
975
    /**
976
     * Data sorting out
977
     *
978
     * @param string|bool $sortOut
979
     * @param array $timePeriods1
980
     * @param array|null $timePeriods2
981
     * @return void
982
     */
983
    protected static function dataSortOut(&$sortOut, &$timePeriods1, &$timePeriods2 = null)
984
    {
985
        // Data sorting out
986
        $sortOut = $sortOut === 'default' ? self::getSortOut() : !!$sortOut;
987
        if ($sortOut) {
988
            $timePeriods1 = self::union($timePeriods1);
989
            if (!is_null($timePeriods2)) {
990
                $timePeriods2 = self::union($timePeriods2);
991
            }
992
        }
993
    }
994
}
995