Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.39% |
61 / 62 |
|
88.89% |
8 / 9 |
CRAP | |
0.00% |
0 / 1 |
Time | |
98.39% |
61 / 62 |
|
88.89% |
8 / 9 |
31 | |
0.00% |
0 / 1 |
getTotalSeconds | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
createFromSeconds | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
2.01 | |||
createISO8601 | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createReadable | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
createConstructor | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
create | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
toStringISO8601 | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
13 | |||
toStringReadable | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
toString | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\Core; |
6 | |
7 | use DateInterval; |
8 | use Exception; |
9 | use PeServer\Core\Collection\Arr; |
10 | use PeServer\Core\Errors\ErrorHandler; |
11 | use PeServer\Core\Throws\ArgumentException; |
12 | use PeServer\Core\Throws\FormatException; |
13 | use PeServer\Core\Throws\Throws; |
14 | |
15 | /** |
16 | * `DateInterval` 処理。 |
17 | */ |
18 | abstract 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 | } |