Passed
Push — master ( be44e4...bf46b0 )
by Mars
01:51
created

TimePeriodHelper::timeFormatConv()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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