Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.39% covered (success)
98.39%
61 / 62
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Time
98.39% covered (success)
98.39%
61 / 62
88.89% covered (warning)
88.89%
8 / 9
31
0.00% covered (danger)
0.00%
0 / 1
 getTotalSeconds
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 createFromSeconds
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 createISO8601
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createReadable
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 createConstructor
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 create
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 toStringISO8601
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
13
 toStringReadable
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 toString
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core;
6
7use DateInterval;
8use Exception;
9use PeServer\Core\Collection\Arr;
10use PeServer\Core\Errors\ErrorHandler;
11use PeServer\Core\Throws\ArgumentException;
12use PeServer\Core\Throws\FormatException;
13use PeServer\Core\Throws\Throws;
14
15/**
16 * `DateInterval` 処理。
17 */
18abstract class Time
19{
20    #region define
21
22    /** ISO 8601 書式 */
23    public const FORMAT_ISO8601 = 0;
24    /** D.HH:MM:SS */
25    public const FORMAT_READABLE = 1;
26
27    #endregion
28
29    #region function
30
31    /**
32     * 全体秒を取得。
33     *
34     * @param DateInterval $time
35     * @return int
36     */
37    public static function getTotalSeconds(DateInterval $time): int
38    {
39        $totalSeconds
40            = ($time->s)
41            + ($time->i * 60)
42            + ($time->h * 60 * 60)
43            + ($time->d * 60 * 60 * 24)
44            + ($time->m * 60 * 60 * 24 * 30)
45            + ($time->y * 60 * 60 * 24 * 365);
46
47        return $totalSeconds;
48    }
49
50    /**
51     * 全体秒から時間を生成。
52     *
53     * @param int $totalSeconds 全体秒。
54     * @return DateInterval
55     */
56    public static function createFromSeconds(int $totalSeconds): DateInterval
57    {
58        $interval = new DateInterval('PT' . abs($totalSeconds) . 'S');
59        $current = Utc::create();
60
61        $diffValue = 0 < $totalSeconds
62            ? $current->sub($interval)
63            : $current->add($interval);
64
65        $timeSpan = $diffValue->diff($current);
66
67        return $timeSpan;
68    }
69
70    private static function createISO8601(string $time): DateInterval
71    {
72        return Throws::wrap(Exception::class, FormatException::class, fn () => new DateInterval($time));
73    }
74
75    private static function createReadable(string $time, ?Encoding $encoding = null): DateInterval
76    {
77        //TODO: 秒未満未対応かぁ~
78
79        $regex = new Regex($encoding);
80        $matches = $regex->matches($time, '/\A((?<DAY>\d+)\.)?(?<H>\d+):(?<M>\d+):(?<S>\d+)\z/');
81
82        if (Arr::isNullOrEmpty($matches)) {
83            throw new FormatException($time);
84        }
85
86        $totalSeconds
87            = ((int)$matches['S'])
88            + ((int)$matches['M'] * 60)
89            + ((int)$matches['H'] * 60 * 60)
90            + (isset($matches['DAY']) ? (int)$matches['DAY'] * 60 * 60 * 24 : 0);
91
92        $timeSpan = self::createFromSeconds($totalSeconds);
93
94        return $timeSpan;
95    }
96
97    private static function createConstructor(string $time): DateInterval
98    {
99        $result = ErrorHandler::trap(fn () => DateInterval::createFromDateString($time));
100        if ($result->isFailureOrFalse()) {
101            throw new FormatException($time);
102        }
103
104        return $result->value;
105    }
106
107    /**
108     * 文字列から時間を生成。
109     *
110     * @param string $time 時間を表す文字列
111     *               1. `ISO8601`
112     *               2. `DAY.HH:MM:SS`
113     *               3. `DateInterval::__constructor`
114     * @param Encoding|null $encoding HH:MM:SS 形式の場合のエンコーディング。 **指定する必要なし**。
115     * @return DateInterval
116     * @throws ArgumentException 引数が空。
117     * @throws FormatException 書式が腐ってる。
118     */
119    public static function create(string $time, ?Encoding $encoding = null): DateInterval
120    {
121        if (Text::isNullOrWhiteSpace($time)) {
122            throw new ArgumentException('$time');
123        }
124
125        if ($time[0] === 'P') {
126            return self::createISO8601($time);
127        }
128
129        if (Text::contains($time, ':', false)) {
130            return self::createReadable($time, $encoding);
131        }
132
133        return self::createConstructor($time);
134    }
135
136    private static function toStringISO8601(DateInterval $time): string
137    {
138        $buffer = 'P';
139
140        $hasDate = $time->y || $time->m || $time->d;
141        if ($hasDate) {
142            $buffer .= $time->y ? $time->y . 'Y' : '';
143            $buffer .= $time->m ? $time->m . 'M' : '';
144            $buffer .= $time->d ? $time->d . 'D' : '';
145        }
146
147        $hasTime = $time->h || $time->i || $time->s;
148        if ($hasTime) {
149            $buffer .= 'T';
150            $buffer .= $time->h ? $time->h . 'H' : '';
151            $buffer .= $time->i ? $time->i . 'M' : '';
152            $buffer .= $time->s ? $time->s . 'S' : '';
153        }
154
155        return $buffer;
156    }
157
158    private static function toStringReadable(DateInterval $time): string
159    {
160        $totalSeconds = self::getTotalSeconds($time);
161
162        $timeSpan = self::createFromSeconds($totalSeconds);
163
164        $buffer = '';
165        if ($timeSpan->days) {
166            $buffer .= $timeSpan->days . '.';
167        }
168
169        $buffer .= $timeSpan->format('%H:%I:%S');
170
171        return $buffer;
172    }
173
174    /**
175     * `DateInterval` の文字列化。
176     *
177     * @param DateInterval $time
178     * @param int $format
179     * @phpstan-param self::FORMAT_* $format
180     * @return string
181     */
182    public static function toString(DateInterval $time, int $format): string
183    {
184        return match ($format) {
185            self::FORMAT_ISO8601 => self::toStringISO8601($time),
186            self::FORMAT_READABLE => self::toStringReadable($time),
187        };
188    }
189
190    #endregion
191}