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