1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Utility; |
4: | |
5: | use Salient\Contract\Core\Jsonable; |
6: | use DateTimeInterface; |
7: | use InvalidArgumentException; |
8: | use JsonException; |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | final class Format extends AbstractUtility |
16: | { |
17: | private const BINARY_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; |
18: | private const DECIMAL_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'RB', 'QB']; |
19: | |
20: | |
21: | |
22: | |
23: | |
24: | |
25: | |
26: | |
27: | |
28: | |
29: | public static function list( |
30: | ?array $list, |
31: | string $format = "- %s\n", |
32: | int $indent = 2, |
33: | ?string $trim = null |
34: | ): string { |
35: | if ($list === null || !$list) { |
36: | return ''; |
37: | } |
38: | $indent = $indent > 0 ? str_repeat(' ', $indent) : ''; |
39: | $string = ''; |
40: | foreach ($list as $value) { |
41: | $value = self::value($value); |
42: | if ($indent !== '') { |
43: | $value = str_replace("\n", "\n" . $indent, $value); |
44: | } |
45: | $string .= sprintf($format, $value); |
46: | } |
47: | return $trim === null |
48: | ? $string |
49: | : rtrim($string, $trim); |
50: | } |
51: | |
52: | |
53: | |
54: | |
55: | |
56: | |
57: | |
58: | |
59: | |
60: | |
61: | public static function array( |
62: | ?array $array, |
63: | string $format = "%s: %s\n", |
64: | int $indent = 4, |
65: | ?string $trim = null |
66: | ): string { |
67: | if ($array === null || !$array) { |
68: | return ''; |
69: | } |
70: | $indent = $indent > 0 ? str_repeat(' ', $indent) : ''; |
71: | $string = ''; |
72: | foreach ($array as $key => $value) { |
73: | $value = self::value($value); |
74: | if ($indent !== '') { |
75: | $value = str_replace("\n", "\n" . $indent, $value, $count); |
76: | if ($count) { |
77: | $value = "\n" . $indent . $value; |
78: | } |
79: | } |
80: | $string .= sprintf($format, $key, $value); |
81: | } |
82: | return $trim === null |
83: | ? $string |
84: | : rtrim($string, $trim); |
85: | } |
86: | |
87: | |
88: | |
89: | |
90: | |
91: | |
92: | public static function value($value): string |
93: | { |
94: | if ($value === null) { |
95: | return ''; |
96: | } |
97: | if (Test::isStringable($value)) { |
98: | return Str::setEol((string) $value); |
99: | } |
100: | if (is_bool($value)) { |
101: | return self::bool($value); |
102: | } |
103: | if (is_scalar($value)) { |
104: | return (string) $value; |
105: | } |
106: | if ($value instanceof Jsonable) { |
107: | return $value->toJson(Json::ENCODE_FLAGS); |
108: | } |
109: | try { |
110: | return Json::stringify($value); |
111: | } catch (JsonException $ex) { |
112: | return '<' . Get::type($value) . '>'; |
113: | } |
114: | } |
115: | |
116: | |
117: | |
118: | |
119: | public static function bool(?bool $value): string |
120: | { |
121: | return $value === null ? '' : ($value ? 'true' : 'false'); |
122: | } |
123: | |
124: | |
125: | |
126: | |
127: | public static function yn(?bool $value): string |
128: | { |
129: | return $value === null ? '' : ($value ? 'yes' : 'no'); |
130: | } |
131: | |
132: | |
133: | |
134: | |
135: | public static function date( |
136: | ?DateTimeInterface $date, |
137: | string $before = '[', |
138: | ?string $after = ']', |
139: | ?string $thisYear = null |
140: | ): string { |
141: | if ($date === null) { |
142: | return ''; |
143: | } |
144: | |
145: | $thisYear ??= date('Y'); |
146: | |
147: | $date = Date::maybeSetTimezone($date); |
148: | |
149: | |
150: | |
151: | |
152: | $format = 'D j M'; |
153: | if ($date->format('Y') !== $thisYear) { |
154: | $format .= ' Y'; |
155: | } |
156: | if ($date->format('H:i:s') !== '00:00:00') { |
157: | $format .= ' H:i:s T'; |
158: | } |
159: | |
160: | return Str::enclose($date->format($format), $before, $after); |
161: | } |
162: | |
163: | |
164: | |
165: | |
166: | public static function dateRange( |
167: | ?DateTimeInterface $from, |
168: | ?DateTimeInterface $to, |
169: | string $delimiter = '–', |
170: | string $before = '[', |
171: | ?string $after = ']', |
172: | ?string $thisYear = null |
173: | ): string { |
174: | if ($from === null && $to === null) { |
175: | return ''; |
176: | } |
177: | |
178: | $thisYear ??= date('Y'); |
179: | |
180: | if ($from === null || $to === null) { |
181: | return sprintf( |
182: | '%s%s%s', |
183: | self::date($from, $before, $after, $thisYear), |
184: | $delimiter, |
185: | self::date($to, $before, $after, $thisYear), |
186: | ); |
187: | } |
188: | |
189: | $from = Date::maybeSetTimezone($from); |
190: | $to = Date::maybeSetTimezone($to); |
191: | |
192: | [$fromTimezone, $fromYear, $fromTime] = |
193: | [$from->format('T'), $from->format('Y'), $from->format('H:i:s')]; |
194: | [$toTimezone, $toYear, $toTime] = |
195: | [$to->format('T'), $to->format('Y'), $to->format('H:i:s')]; |
196: | |
197: | |
198: | |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | $fromFormat = $toFormat = 'D j M'; |
205: | if ($fromYear !== $toYear) { |
206: | $fromFormat = $toFormat .= ' Y'; |
207: | } elseif ($fromYear !== $thisYear) { |
208: | $toFormat .= ' Y'; |
209: | } |
210: | if ($fromTime !== '00:00:00' || $toTime !== '00:00:00') { |
211: | $fromFormat .= ' H:i:s'; |
212: | $toFormat .= ' H:i:s T'; |
213: | if ($fromTimezone !== $toTimezone) { |
214: | $fromFormat .= ' T'; |
215: | } |
216: | } |
217: | |
218: | return sprintf( |
219: | '%s%s%s', |
220: | Str::enclose($from->format($fromFormat), $before, $after), |
221: | $delimiter, |
222: | Str::enclose($to->format($toFormat), $before, $after), |
223: | ); |
224: | } |
225: | |
226: | |
227: | |
228: | |
229: | |
230: | public static function bytes( |
231: | ?int $bytes, |
232: | int $precision = 3, |
233: | bool $binary = true |
234: | ): string { |
235: | if ($bytes === null) { |
236: | return ''; |
237: | } |
238: | if ($bytes < 0) { |
239: | throw new InvalidArgumentException('$bytes cannot be less than zero'); |
240: | } |
241: | if ($precision < 0) { |
242: | throw new InvalidArgumentException('$precision cannot be less than zero'); |
243: | } |
244: | |
245: | [$base, $units] = $binary |
246: | ? [1024, self::BINARY_UNITS] |
247: | : [1000, self::DECIMAL_UNITS]; |
248: | $maxPower = count($units) - 1; |
249: | $power = $bytes |
250: | ? min($maxPower, (int) (log($bytes) / log($base))) |
251: | : 0; |
252: | $bytes = $bytes / $base ** $power; |
253: | |
254: | if ($bytes >= 1000 && $precision && $power < $maxPower) { |
255: | $power++; |
256: | $bytes /= $base; |
257: | } |
258: | |
259: | return sprintf( |
260: | $precision && $power ? "%.{$precision}f%s" : '%d%s', |
261: | $precision ? (int) ($bytes * 10 ** $precision) / 10 ** $precision : (int) $bytes, |
262: | $units[$power], |
263: | ); |
264: | } |
265: | } |
266: | |