Passed
Branch master (42da90)
by Mars
02:36
created

TimePeriodHelper::setSortOut()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
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
        // Data sorting out
311
        self::dataSortOut($sortOut, $timePeriods);
312
        // Set $eDateTime
313
        $eDateTime = $eDateTime ?: $sDateTime;
314
        $sTime = min($sDateTime, $eDateTime);
315
        $eTime = max($sDateTime, $eDateTime);
316
317
        // Get Contact time periods
318
        $opt = [];
319
        foreach ($timePeriods as $k => $tp) {
320
            if ($eTime <= $tp[0]) {
321
                // No overlap && Passed: --$sTime--$eTime--$tp0--$tp1--
322
                if ($sTime == $tp[0]) {
323
                    // But 
324
                    $opt[] = $tp;
325
                }
326
            } elseif ($tp[1] <= $sTime) {
327
                // No overlap: --$tp0--$tp1--$sTime--$eTime--
328
            } else {
329
                // Overlap
330
                $opt[] = $tp;
331
            }
332
        }
333
334
        return $opt;
335
    }
336
337
    /**
338
     * Time period greater than the specified time
339
     * 
340
     * @param array $timePeriods
341
     * @param string $refDatetime
342
     * @param string $intactTime
343
     * @param string $sortOut
344
     * @return array
345
     */
346
    public static function greaterThan(array $timePeriods, $refDatetime, $intactTime = true, $sortOut = 'default')
347
    {
348
        // Subject is empty, do nothing
349
        if (empty($timePeriods)) {
350
            return [];
351
        }
352
353
        // Data sorting out
354
        self::dataSortOut($sortOut, $timePeriods);
355
356
        // Get Contact time periods
357
        $opt = [];
358
        foreach ($timePeriods as $k => $tp) {
359
            if ($intactTime) {
360
                // Time period is intact
361
                if ($tp[0] >= $refDatetime) {
362
                    $opt[] = $tp;
363
                }
364
            } else {
365
                // Time period not intact
366
                if ($tp[1] > $refDatetime) {
367
                    $opt[] = $tp;
368
                }
369
            }
370
        }
371
372
        return $opt;
373
    }
374
375
    /**
376
     * Time period less than the specified time
377
     * 
378
     * @param array $timePeriods
379
     * @param string $refDatetime
380
     * @param string $intactTime
381
     * @param string $sortOut
382
     * @return array
383
     */
384
    public static function lessThan(array $timePeriods, $refDatetime, $intactTime = true, $sortOut = 'default')
385
    {
386
        // Subject is empty, do nothing
387
        if (empty($timePeriods)) {
388
            return [];
389
        }
390
391
        // Data sorting out
392
        self::dataSortOut($sortOut, $timePeriods);
393
394
        // Get Contact time periods
395
        $opt = [];
396
        foreach ($timePeriods as $k => $tp) {
397
            if ($intactTime) {
398
                // Time period is intact
399
                if ($tp[1] <= $refDatetime) {
400
                    $opt[] = $tp;
401
                }
402
            } else {
403
                // Time period not intact
404
                if ($tp[0] < $refDatetime) {
405
                    $opt[] = $tp;
406
                }
407
            }
408
        }
409
410
        return $opt;
411
    }
412
413
    /**
414
     * Fill time periods
415
     * 
416
     * Leaving only the first start time and the last end time
417
     * 
418
     * @param array $timePeriods
419
     * @return array
420
     */
421
    public static function fill(array $timePeriods)
422
    {
423
        $opt = [];
424
425
        if (isset($timePeriods[0][0])) {
426
            $tmp = array_shift($timePeriods);
427
            $start = $tmp[0];
428
            $end = $tmp[1];
429
            foreach ($timePeriods as $k => $tp) {
430
                $start = min($start, $tp[0]);
431
                $end = max($end, $tp[1]);
432
            }
433
            $opt = [[$start, $end]];
434
        }
435
436
        return $opt;
437
    }
438
439
    /**
440
     * Get gap time periods of multiple sets of time periods
441
     * 
442
     * 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.
443
     * 
444
     * @param array $timePeriods
445
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
446
     * @return array
447
     */
448
    public static function gap(array $timePeriods, $sortOut = 'default')
449
    {
450
        // Subject is empty, do nothing
451
        if (empty($timePeriods)) {
452
            return [];
453
        }
454
455
        // Data sorting out
456
        self::dataSortOut($sortOut, $timePeriods);
457
458
        $opt = [];
459
        foreach ($timePeriods as $k => $tp) {
460
            if (isset($timePeriods[$k + 1])) {
461
                $opt[] = [$tp[1], $timePeriods[$k + 1][0]];
462
            }
463
        }
464
465
        return $opt;
466
    }
467
468
    /**
469
     * Calculation period total time
470
     *
471
     * 1. You can specify the smallest unit (from setUnit())
472
     * 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.
473
     * 3. approximation: chop off
474
     *
475
     * @param array $timePeriods            
476
     * @param int $precision
477
     *            Optional decimal places for the decimal point
478
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
479
     * @return number
480
     */
481
    public static function time(array $timePeriods, $precision = 0, $sortOut = 'default')
482
    {
483
        // Subject is empty, do nothing
484
        if (empty($timePeriods)) {
485
            return 0;
486
        }
487
488
        // Data sorting out
489
        self::dataSortOut($sortOut, $timePeriods);
490
491
        // Calculate time
492
        $time = 0;
493
        foreach ($timePeriods as $k => $tp) {
494
            $time += strtotime($tp[1]) - strtotime($tp[0]);
495
        }
496
497
        // Time unit convert
498
        switch (self::getUnit('time')) {
499
            case 'minute':
500
                $time = $time / 60;
501
                break;
502
            case 'hour':
503
                $time = $time / 3600;
504
                break;
505
        }
506
507
        // Precision
508
        if ($precision > 0) {
509
            $pow = pow(10, (int) $precision);
510
            $time = ((int) ($time * $pow)) / $pow;
511
        } else {
512
            $time = (int) ($time);
513
        }
514
515
        return $time;
516
    }
517
518
    /**
519
     * Cut the time period of the specified length of time
520
     *
521
     * 1. You can specify the smallest unit (from setUnit())
522
     * 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.
523
     * 
524
     * @param array $timePeriods            
525
     * @param number $time
526
     *            Specified length of time
527
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
528
     * @return array
529
     */
530
    public static function cut(array $timePeriods, $time, $sortOut = 'default')
531
    {
532
        // Subject is empty, do nothing
533
        if (empty($timePeriods)) {
534
            return [];
535
        }
536
537
        // Data sorting out
538
        self::dataSortOut($sortOut, $timePeriods);
539
        // Convert time by unit
540
        $time = self::time2Second($time);
541
542
        $opt = [];
543
        $timeLen = 0;
544
        foreach ($timePeriods as $k => $tp) {
545
            // Calculation time
546
            $tlen = strtotime($tp[1]) - strtotime($tp[0]);
547
548
            // Judging the length of time
549
            if ($timeLen + $tlen <= $time) {
550
                // Within limits
551
                $opt[] = $tp;
552
                $timeLen = $timeLen + $tlen;
553
            } else {
554
                // Outside the limits
555
                $lastTime = $time - $timeLen;
556
                if ($lastTime > 0) {
557
                    $tps = $tp[0];
558
                    $tpe = self::extendTime($tps, $lastTime);
559
                    if ($tps != $tpe) {
560
                        $opt[] = [$tps, $tpe];
561
                    }
562
                }
563
                break;
564
            }
565
        }
566
567
        return $opt;
568
    }
569
570
    /**
571
     * Increase the time period of the specified length of time after the last time period
572
     *
573
     * 1. You can specify the smallest unit (from setUnit())
574
     * 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.
575
     * 
576
     * @param array $timePeriods            
577
     * @param number $time
578
     *            Specified length of time (default uint:second)
579
     * @param number $interval
580
     *            Interval with existing time period
581
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
582
     * @return array
583
     */
584
    public static function extend(array $timePeriods, $time, $interval = 0, $sortOut = 'default')
585
    {
586
        // Subject is empty, do nothing
587
        if (empty($timePeriods)) {
588
            return [];
589
        }
590
591
        // Data sorting out
592
        self::dataSortOut($sortOut, $timePeriods);
593
594
        // Convert time by unit
595
        $time = self::time2Second($time);
596
        $interval = self::time2Second($interval);
597
598
        // last time period index
599
        $eIdx = sizeof($timePeriods) - 1;
600
601
        if (!$interval) {
602
            // No gap, Directly extend the end time
603
            $timePeriods[$eIdx][1] = self::extendTime($timePeriods[$eIdx][1], $time);
604
        } else {
605
            // Has gap
606
            $tps = self::extendTime($timePeriods[$eIdx][1], $interval);
607
            $tpe = self::extendTime($tps, $time);
608
            if ($tps != $tpe) {
609
                $timePeriods[] = [$tps, $tpe];
610
            }
611
        }
612
613
        return $timePeriods;
614
    }
615
616
    /**
617
     * Shorten the specified length of time from behind
618
     *
619
     * 1. You can specify the smallest unit (from setUnit())
620
     * 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.
621
     * 
622
     * @param array $timePeriods            
623
     * @param number $time
624
     *            Specified length of time (default uint:second)
625
     * @param bool $crossperiod
626
     *            Whether to shorten across time
627
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
628
     * @return array
629
     */
630
    public static function shorten(array $timePeriods, $time, $crossperiod = true, $sortOut = 'default')
631
    {
632
        // Subject is empty, do nothing
633
        if (empty($timePeriods)) {
634
            return [];
635
        }
636
637
        // Data sorting out
638
        self::dataSortOut($sortOut, $timePeriods);
639
640
        // Convert time by unit
641
        $time = self::time2Second($time);
642
643
        // last time period index
644
        $eIdx = sizeof($timePeriods) - 1;
645
646
        for ($i = $eIdx; $i >= 0; $i--) {
647
            $tps = $timePeriods[$i][0];
648
            $tpe = $timePeriods[$i][1];
649
            $tTime = strtotime($tpe) - strtotime($tps);
650
651
            if ($tTime <= $time) {
652
                // Not enough, unset this timeperiod
653
                unset($timePeriods[$i]);
654
                $time -= $tTime;
655
            } else {
656
                // Enough, shorten end time.
657
                $timePeriods[$i][1] = self::extendTime($timePeriods[$i][0], $tTime - $time);
658
                break;
659
            }
660
661
            // End or No cross-period
662
            if ($time <= 0 || !$crossperiod) {
663
                break;
664
            }
665
        }
666
667
        return $timePeriods;
668
    }
669
670
    /**
671
     * Transform format
672
     * 
673
     * @param array $timePeriods
674
     * @param string $unit Time unit, if default,use class options setting
675
     * @return array
676
     */
677
    public static function format(array $timePeriods, $unit = 'default')
678
    {
679
        foreach ($timePeriods as $k => &$tp) {
680
            $tp[0] = self::timeFormatConv($tp[0], $unit);
681
            $tp[1] = self::timeFormatConv($tp[1], $unit);
682
        }
683
684
        return $timePeriods;
685
    }
686
687
    /**
688
     * Validate time period
689
     * 
690
     * Verify format, size, start/end time
691
     * 
692
     * @param mixed|array $timePeriods
693
     * @throws \Exception
694
     * @return bool
695
     */
696
    public static function validate($timePeriods)
697
    {
698
        // If not array, return.
699
        if (!is_array($timePeriods)) {
700
            throw new \Exception('Time periods format error !', 400);
701
        }
702
703
        foreach ($timePeriods as $k => $tp) {
704
            // filter format, number
705
            if (!is_array($tp) || sizeof($tp) != 2) {
706
                    throw new \Exception('Time periods format error !', 400);
707
            }
708
            // filter time period
709
            if ($tp[0] >= $tp[1]) {
710
                    throw new \Exception('Time periods format error !', 400);
711
            }
712
            // filter time format
713
            if (self::getFilterDatetime() && (!self::isDatetime($tp[0]) || !self::isDatetime($tp[1]))) {
714
                    throw new \Exception('Time periods format error !', 400);
715
            }
716
        }
717
718
        return true;
719
    }
720
721
    /**
722
     * Remove invalid time period
723
     * 
724
     * 1. Verify format, size, start/end time, and remove invalid.
725
     * 2. time carry problem processing, e.g. 2019-01-01 24:00:00 => 2019-01-02 00:00:00
726
     * 
727
     * @param mixed|array $timePeriods
728
     * @throws \Exception
729
     * @return array
730
     */
731
    public static function filter($timePeriods)
732
    {
733
        // If not array, return.
734
        if (!is_array($timePeriods)) {
735
            return [];
736
        }
737
738
        foreach ($timePeriods as $k => $tp) {
739
            // filter format, number
740
            if (!is_array($tp) || sizeof($tp) != 2) {
741
                unset($timePeriods[$k]);
742
                continue;
743
            }
744
            // filter time period
745
            if ($tp[0] >= $tp[1]) {
746
                unset($timePeriods[$k]);
747
                continue;
748
            }
749
            // filter time format
750
            if (self::getFilterDatetime() && (!self::isDatetime($tp[0]) || !self::isDatetime($tp[1]))) {
751
                unset($timePeriods[$k]);
752
                continue;
753
            }
754
755
            // Time carry
756
            $timeLen = strlen($tp[0]);
757
            if ($timeLen >= 13) {
758
                if (substr($tp[0], 11, 2) == '24') {
759
                    $timePeriods[$k][0] = self::extendTime($timePeriods[$k][0], 0);
760
                }
761
                if (substr($tp[1], 11, 2) == '24') {
762
                    $timePeriods[$k][1] = self::extendTime($timePeriods[$k][1], 0);
763
                }
764
            }
765
        }
766
767
        return $timePeriods;
768
    }
769
770
771
    /**
772
     * **********************************************
773
     * ************** Options Function **************
774
     * **********************************************
775
     */
776
777
778
    /**
779
     * Specify the minimum unit of calculation
780
     * 
781
     * 1. Scope: Global
782
     * 2. hour,minute,second
783
     * 
784
     * @param string $unit time unit. e.g. hour, minute, second.
785
     * @param string $target Specify function,or all functions
786
     * @throws \Exception
787
     * @return $this
788
     */
789
    public static function setUnit(string $unit, string $target = 'all')
790
    {
791
        /*** Arguments prepare ***/
792
        if (!isset(self::$_options['unitMap'][$unit])) {
793
            throw new \Exception('Error Unit: ' . $unit, 400);
794
        }
795
        // conv unit
796
        $unit = self::$_options['unitMap'][$unit];
797
798
        if ($target != 'all' && !isset(self::$_options['unit'][$target])) {
799
            throw new \Exception('Error Target: ' . $target, 400);
800
        }
801
802
        /* Setting */
803
        if ($target != 'all') {
804
            self::$_options['unit'][$target] = $unit;
805
        } else {
806
            foreach (self::$_options['unit'] as $tar => &$value) {
807
                $value = $unit;
808
            }
809
        }
810
811
        return new static();
812
    }
813
814
    /**
815
     * Get the unit used by the specified function
816
     * 
817
     * @param string $target Specify function's unit
818
     * @throws \Exception
819
     * @return string
820
     */
821
    public static function getUnit(string $target)
822
    {
823
        if (isset(self::$_options['unit'][$target])) {
824
            return self::$_options['unit'][$target];
825
        } else {
826
            throw new \Exception('Error Target: ' . $target, 400);
827
        }
828
    }
829
830
    /**
831
     * If neet filter datetime : Set option
832
     * 
833
     * 1. Scope: Global
834
     * 2. If you do not want to filter the datetime format, set it to false.  
835
     * 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.
836
     * 4. Effect function: filter(), validate()
837
     * 
838
     * @param bool $bool
839
     * @return $this
840
     */
841
    public static function setFilterDatetime($bool)
842
    {
843
        self::$_options['filter']['isDatetime'] = !!$bool;
844
845
        return new static();
846
    }
847
848
    /**
849
     * If neet filter datetime : Get option
850
     * 
851
     * @return bool
852
     */
853
    public static function getFilterDatetime()
854
    {
855
        return self::$_options['filter']['isDatetime'];
856
    }
857
858
    /**
859
     * Auto sort out $timePeriods : Set option
860
     *
861
     * 1. Scope: Global
862
     * 2. Before the function is processed, union() will be used to organize $timePeriods format.
863
     * 
864
     * @param bool $bool default true
865
     * @return $this
866
     */
867
    public static function setSortOut($bool = true)
868
    {
869
        self::$_options['sortOut'] = !!$bool;
870
871
        return new static();
872
    }
873
874
    /**
875
     * Auto sort out $timePeriods : Get option
876
     *
877
     * @return bool
878
     */
879
    public static function getSortOut()
880
    {
881
        return self::$_options['sortOut'];
882
    }
883
884
    /**
885
     * ********************************************
886
     * ************** Tools Function **************
887
     * ********************************************
888
     */
889
890
    /**
891
     * Check datetime fast
892
     * 
893
     * Only check format,no check for reasonableness
894
     * 
895
     * @param string $datetime
896
     * @return boolean
897
     */
898
    public static function isDatetime(string $datetime)
899
    {
900
        return (bool) preg_match('|^[0-9]{4}\-[0-9]{2}\-[0-9]{2}\ [0-9]{2}\:[0-9]{2}\:[0-9]{2}$|', $datetime);
901
    }
902
903
    /**
904
     * Time format convert
905
     * 
906
     * format:Y-m-d H:i:s
907
     * When the length is insufficient, it will add the missing
908
     * 
909
     * @param string $datetime
910
     * @param string $unit Time unit, if default,use self::$_options setting
911
     * @return string
912
     */
913
    public static function timeFormatConv(string $datetime, $unit = 'default')
914
    {
915
        // fill format
916
        $strlen = strlen($datetime);
917
        $datetime .= substr(' 00:00:00', $strlen - 10);
918
919
        $unit = !isset(self::$_options['unitMap'][$unit]) ? self::$_options['unit']['format'] : self::$_options['unitMap'][$unit];
920
        // replace
921
        if ($unit == 'minute') {
922
            $datetime = substr_replace($datetime, "00", 17, 2);
923
        } elseif ($unit == 'hour') {
924
            $datetime = substr_replace($datetime, "00:00", 14, 5);
925
        }
926
927
        return $datetime;
928
    }
929
930
    /**
931
     * Extend time
932
     * 
933
     * @param string $datetime
934
     * @param int $timeLen 
935
     * @return string
936
     */
937
    protected static function extendTime(String $datetime, $timeLen)
938
    {
939
        $tout = date('Y-m-d H:i:s', strtotime($datetime) + $timeLen);
940
        return substr($tout, 0, strlen($datetime));
941
    }
942
943
    /**
944
     * Time Conversion frm unit to second
945
     * 
946
     * @param number $time
947
     * @param string $unit Time unit, if default,use self::$_options setting
948
     * @return int
949
     */
950
    public static function time2Second($time, $unit = 'default')
951
    {
952
        // Git time unit
953
        $unit = !isset(self::$_options['unitMap'][$unit]) ? self::getUnit('time') : self::$_options['unitMap'][$unit];
954
955
        // Convert
956
        switch ($unit) {
957
            case 'minute':
958
                $time = $time * 60;
959
                break;
960
            case 'hour':
961
                $time = $time * 3600;
962
                break;
963
        }
964
965
        return (int) $time;
966
    }
967
968
    /**
969
     * Data sorting out
970
     *
971
     * @param string|bool $sortOut
972
     * @param array $timePeriods1
973
     * @param array|null $timePeriods2
974
     * @return void
975
     */
976
    protected static function dataSortOut(&$sortOut, &$timePeriods1, &$timePeriods2 = null)
977
    {
978
        // Data sorting out
979
        $sortOut = $sortOut === 'default' ? self::getSortOut() : !!$sortOut;
980
        if ($sortOut) {
981
            $timePeriods1 = self::union($timePeriods1);
982
            if (!is_null($timePeriods2)) {
983
                $timePeriods2 = self::union($timePeriods2);
984
            }
985
        }
986
    }
987
}
988