Passed
Branch master (fe4af2)
by Mars
07:24
created

TimePeriodHelper::format()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 8
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
        // 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
                break;
570
            }
571
        }
572
573
        return $opt;
574
    }
575
576
    /**
577
     * Increase the time period of the specified length of time after the last time period
578
     *
579
     * 1. You can specify the smallest unit (from setUnit())
580
     * 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.
581
     * 
582
     * @param array $timePeriods            
583
     * @param number $time
584
     *            Specified length of time (default uint:second)
585
     * @param number $interval
586
     *            Interval with existing time period
587
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
588
     * @return array
589
     */
590
    public static function extend(array $timePeriods, $time, $interval = 0, $sortOut = 'default')
591
    {
592
        // Subject is empty, do nothing
593
        if (empty($timePeriods)) {
594
            return [];
595
        }
596
597
        // Data sorting out
598
        self::dataSortOut($sortOut, $timePeriods);
599
600
        // Convert time by unit
601
        $time = self::time2Second($time);
602
        $interval = self::time2Second($interval);
603
604
        // last time period index
605
        $eIdx = sizeof($timePeriods) - 1;
606
607
        if (!$interval) {
608
            // No gap, Directly extend the end time
609
            $timePeriods[$eIdx][1] = self::extendTime($timePeriods[$eIdx][1], $time);
610
        } else {
611
            // Has gap
612
            $tps = self::extendTime($timePeriods[$eIdx][1], $interval);
613
            $tpe = self::extendTime($tps, $time);
614
            if ($tps != $tpe) {
615
                $timePeriods[] = [$tps, $tpe];
616
            }
617
        }
618
619
        return $timePeriods;
620
    }
621
622
    /**
623
     * Shorten the specified length of time from behind
624
     *
625
     * 1. You can specify the smallest unit (from setUnit())
626
     * 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.
627
     * 
628
     * @param array $timePeriods            
629
     * @param number $time
630
     *            Specified length of time (default uint:second)
631
     * @param bool $crossperiod
632
     *            Whether to shorten across time
633
     * @param bool $sortOut Whether the input needs to be rearranged. Value: true, false, 'default'. If it is 'default', see getSortOut()
634
     * @return array
635
     */
636
    public static function shorten(array $timePeriods, $time, $crossperiod = true, $sortOut = 'default')
637
    {
638
        // Subject is empty, do nothing
639
        if (empty($timePeriods)) {
640
            return [];
641
        }
642
643
        // Data sorting out
644
        self::dataSortOut($sortOut, $timePeriods);
645
646
        // Convert time by unit
647
        $time = self::time2Second($time);
648
649
        // last time period index
650
        $eIdx = sizeof($timePeriods) - 1;
651
652
        for ($i = $eIdx; $i >= 0; $i--) {
653
            $tps = $timePeriods[$i][0];
654
            $tpe = $timePeriods[$i][1];
655
            $tTime = strtotime($tpe) - strtotime($tps);
656
657
            if ($tTime <= $time) {
658
                // Not enough, unset this timeperiod
659
                unset($timePeriods[$i]);
660
                $time -= $tTime;
661
            } else {
662
                // Enough, shorten end time.
663
                $timePeriods[$i][1] = self::extendTime($timePeriods[$i][0], $tTime - $time);
664
                break;
665
            }
666
667
            // End or No cross-period
668
            if ($time <= 0 || !$crossperiod) {
669
                break;
670
            }
671
        }
672
673
        return $timePeriods;
674
    }
675
676
    /**
677
     * Transform format
678
     * 
679
     * @param array $timePeriods
680
     * @param string $unit Time unit, if default,use class options setting
681
     * @return array
682
     */
683
    public static function format(array $timePeriods, $unit = 'default')
684
    {
685
        foreach ($timePeriods as $k => &$tp) {
686
            $tp[0] = self::timeFormatConv($tp[0], $unit);
687
            $tp[1] = self::timeFormatConv($tp[1], $unit);
688
        }
689
690
        return $timePeriods;
691
    }
692
693
    /**
694
     * Validate time period
695
     * 
696
     * Verify format, size, start/end time
697
     * 
698
     * @param mixed|array $timePeriods
699
     * @throws \Exception
700
     * @return bool
701
     */
702
    public static function validate($timePeriods)
703
    {
704
        // If not array, return.
705
        if (!is_array($timePeriods)) {
706
            throw new \Exception('Time periods format error !', 400);
707
        }
708
709
        foreach ($timePeriods as $k => $tp) {
710
            // filter format, number
711
            if (!is_array($tp) || sizeof($tp) != 2) {
712
                    throw new \Exception('Time periods format error !', 400);
713
            }
714
            // filter time period
715
            if ($tp[0] >= $tp[1]) {
716
                    throw new \Exception('Time periods format error !', 400);
717
            }
718
            // filter time format
719
            if (self::getFilterDatetime() && (!self::isDatetime($tp[0]) || !self::isDatetime($tp[1]))) {
720
                    throw new \Exception('Time periods format error !', 400);
721
            }
722
        }
723
724
        return true;
725
    }
726
727
    /**
728
     * Remove invalid time period
729
     * 
730
     * 1. Verify format, size, start/end time, and remove invalid.
731
     * 2. time carry problem processing, e.g. 2019-01-01 24:00:00 => 2019-01-02 00:00:00
732
     * 
733
     * @param mixed|array $timePeriods
734
     * @throws \Exception
735
     * @return array
736
     */
737
    public static function filter($timePeriods)
738
    {
739
        // If not array, return.
740
        if (!is_array($timePeriods)) {
741
            return [];
742
        }
743
744
        foreach ($timePeriods as $k => $tp) {
745
            // filter format, number
746
            if (!is_array($tp) || sizeof($tp) != 2) {
747
                unset($timePeriods[$k]);
748
                continue;
749
            }
750
            // filter time period
751
            if ($tp[0] >= $tp[1]) {
752
                unset($timePeriods[$k]);
753
                continue;
754
            }
755
            // filter time format
756
            if (self::getFilterDatetime() && (!self::isDatetime($tp[0]) || !self::isDatetime($tp[1]))) {
757
                unset($timePeriods[$k]);
758
                continue;
759
            }
760
761
            // Time carry
762
            $timeLen = strlen($tp[0]);
763
            if ($timeLen >= 13) {
764
                if (substr($tp[0], 11, 2) == '24') {
765
                    $timePeriods[$k][0] = self::extendTime($timePeriods[$k][0], 0);
766
                }
767
                if (substr($tp[1], 11, 2) == '24') {
768
                    $timePeriods[$k][1] = self::extendTime($timePeriods[$k][1], 0);
769
                }
770
            }
771
        }
772
773
        return $timePeriods;
774
    }
775
776
777
    /**
778
     * **********************************************
779
     * ************** Options Function **************
780
     * **********************************************
781
     */
782
783
784
    /**
785
     * Specify the minimum unit of calculation
786
     * 
787
     * 1. Scope: Global
788
     * 2. hour,minute,second
789
     * 
790
     * @param string $unit time unit. e.g. hour, minute, second.
791
     * @param string $target Specify function,or all functions
792
     * @throws \Exception
793
     * @return $this
794
     */
795
    public static function setUnit(string $unit, string $target = 'all')
796
    {
797
        /*** Arguments prepare ***/
798
        if (!isset(self::$_options['unitMap'][$unit])) {
799
            throw new \Exception('Error Unit: ' . $unit, 400);
800
        }
801
        // conv unit
802
        $unit = self::$_options['unitMap'][$unit];
803
804
        if ($target != 'all' && !isset(self::$_options['unit'][$target])) {
805
            throw new \Exception('Error Target: ' . $target, 400);
806
        }
807
808
        /* Setting */
809
        if ($target != 'all') {
810
            self::$_options['unit'][$target] = $unit;
811
        } else {
812
            foreach (self::$_options['unit'] as $tar => &$value) {
813
                $value = $unit;
814
            }
815
        }
816
817
        return new static();
818
    }
819
820
    /**
821
     * Get the unit used by the specified function
822
     * 
823
     * @param string $target Specify function's unit
824
     * @throws \Exception
825
     * @return string
826
     */
827
    public static function getUnit(string $target)
828
    {
829
        if (isset(self::$_options['unit'][$target])) {
830
            return self::$_options['unit'][$target];
831
        } else {
832
            throw new \Exception('Error Target: ' . $target, 400);
833
        }
834
    }
835
836
    /**
837
     * If neet filter datetime : Set option
838
     * 
839
     * 1. Scope: Global
840
     * 2. If you do not want to filter the datetime format, set it to false.  
841
     * 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.
842
     * 4. Effect function: filter(), validate()
843
     * 
844
     * @param bool $bool
845
     * @return $this
846
     */
847
    public static function setFilterDatetime($bool)
848
    {
849
        self::$_options['filter']['isDatetime'] = !!$bool;
850
851
        return new static();
852
    }
853
854
    /**
855
     * If neet filter datetime : Get option
856
     * 
857
     * @return bool
858
     */
859
    public static function getFilterDatetime()
860
    {
861
        return self::$_options['filter']['isDatetime'];
862
    }
863
864
    /**
865
     * Auto sort out $timePeriods : Set option
866
     *
867
     * 1. Scope: Global
868
     * 2. Before the function is processed, union() will be used to organize $timePeriods format.
869
     * 
870
     * @param bool $bool default true
871
     * @return $this
872
     */
873
    public static function setSortOut($bool = true)
874
    {
875
        self::$_options['sortOut'] = !!$bool;
876
877
        return new static();
878
    }
879
880
    /**
881
     * Auto sort out $timePeriods : Get option
882
     *
883
     * @return bool
884
     */
885
    public static function getSortOut()
886
    {
887
        return self::$_options['sortOut'];
888
    }
889
890
    /**
891
     * ********************************************
892
     * ************** Tools Function **************
893
     * ********************************************
894
     */
895
896
    /**
897
     * Check datetime fast
898
     * 
899
     * Only check format,no check for reasonableness
900
     * 
901
     * @param string $datetime
902
     * @return boolean
903
     */
904
    public static function isDatetime(string $datetime)
905
    {
906
        return (bool) preg_match('|^[0-9]{4}\-[0-9]{2}\-[0-9]{2}\ [0-9]{2}\:[0-9]{2}\:[0-9]{2}$|', $datetime);
907
    }
908
909
    /**
910
     * Time format convert
911
     * 
912
     * format:Y-m-d H:i:s
913
     * When the length is insufficient, it will add the missing
914
     * 
915
     * @param string $datetime
916
     * @param string $unit Time unit, if default,use self::$_options setting
917
     * @return string
918
     */
919
    public static function timeFormatConv(string $datetime, $unit = 'default')
920
    {
921
        // fill format
922
        $strlen = strlen($datetime);
923
        $datetime .= substr(' 00:00:00', $strlen - 10);
924
925
        $unit = !isset(self::$_options['unitMap'][$unit]) ? self::$_options['unit']['format'] : self::$_options['unitMap'][$unit];
926
        // replace
927
        if ($unit == 'minute') {
928
            $datetime = substr_replace($datetime, "00", 17, 2);
929
        } elseif ($unit == 'hour') {
930
            $datetime = substr_replace($datetime, "00:00", 14, 5);
931
        }
932
933
        return $datetime;
934
    }
935
936
    /**
937
     * Extend time
938
     * 
939
     * @param string $datetime
940
     * @param int $timeLen 
941
     * @return string
942
     */
943
    protected static function extendTime(String $datetime, $timeLen)
944
    {
945
        $tout = date('Y-m-d H:i:s', strtotime($datetime) + $timeLen);
946
        return substr($tout, 0, strlen($datetime));
947
    }
948
949
    /**
950
     * Time Conversion frm unit to second
951
     * 
952
     * @param number $time
953
     * @param string $unit Time unit, if default,use self::$_options setting
954
     * @return int
955
     */
956
    public static function time2Second($time, $unit = 'default')
957
    {
958
        // Git time unit
959
        $unit = !isset(self::$_options['unitMap'][$unit]) ? self::getUnit('time') : self::$_options['unitMap'][$unit];
960
961
        // Convert
962
        switch ($unit) {
963
            case 'minute':
964
                $time = $time * 60;
965
                break;
966
            case 'hour':
967
                $time = $time * 3600;
968
                break;
969
        }
970
971
        return (int) $time;
972
    }
973
974
    /**
975
     * Data sorting out
976
     *
977
     * @param string|bool $sortOut
978
     * @param array $timePeriods1
979
     * @param array|null $timePeriods2
980
     * @return void
981
     */
982
    protected static function dataSortOut(&$sortOut, &$timePeriods1, &$timePeriods2 = null)
983
    {
984
        // Data sorting out
985
        $sortOut = $sortOut === 'default' ? self::getSortOut() : !!$sortOut;
986
        if ($sortOut) {
987
            $timePeriods1 = self::union($timePeriods1);
988
            if (!is_null($timePeriods2)) {
989
                $timePeriods2 = self::union($timePeriods2);
990
            }
991
        }
992
    }
993
}
994