1 | <?php |
||||
2 | |||||
3 | namespace ICanBoogie\CLDR\Numbers; |
||||
4 | |||||
5 | use function abs; |
||||
6 | use function implode; |
||||
7 | use function ltrim; |
||||
8 | use function round; |
||||
9 | use function str_pad; |
||||
10 | use function str_split; |
||||
11 | use function strlen; |
||||
12 | use function strpos; |
||||
13 | use function substr; |
||||
14 | |||||
15 | use const STR_PAD_LEFT; |
||||
16 | |||||
17 | /** |
||||
18 | * Representation of a number pattern. |
||||
19 | */ |
||||
20 | final class NumberPattern |
||||
21 | { |
||||
22 | public static function from(string $pattern): NumberPattern |
||||
23 | { |
||||
24 | static $instances; |
||||
25 | |||||
26 | return $instances[$pattern] ??= self::do_from($pattern); |
||||
27 | } |
||||
28 | |||||
29 | private static function do_from(string $pattern): self |
||||
30 | { |
||||
31 | $parsed_pattern = NumberPatternParser::parse($pattern); |
||||
32 | |||||
33 | return new self($pattern, ...$parsed_pattern); |
||||
34 | } |
||||
35 | |||||
36 | /** |
||||
37 | * @param string $pattern |
||||
38 | * @param string $positive_prefix |
||||
39 | * Prefix to a positive number. |
||||
40 | * @param string $positive_suffix |
||||
41 | * Suffix to a positive number. |
||||
42 | * @param string $negative_prefix |
||||
43 | * Prefix to a negative number. |
||||
44 | * @param string $negative_suffix |
||||
45 | * Suffix to negative number. |
||||
46 | * @param int $multiplier |
||||
47 | * 100 for percent, 1000 for per mille. |
||||
48 | * @param int $decimal_digits |
||||
49 | * The number of required digits after the decimal point. |
||||
50 | * The string is padded with zeros if there aren't enough digits. |
||||
51 | * `-1` means the decimal point should be dropped. |
||||
52 | * @param int $max_decimal_digits |
||||
53 | * The maximum number of digits after the decimal point. |
||||
54 | * Additional digits will be truncated. |
||||
55 | * @param int $integer_digits |
||||
56 | * The number of required digits before the decimal point. |
||||
57 | * The string is padded with zeros if there aren't enough digits. |
||||
58 | * @param int $group_size1 |
||||
59 | * The primary grouping size. `0` means no grouping. |
||||
60 | * @param int $group_size2 |
||||
61 | * The secondary grouping size. `0` means no secondary grouping. |
||||
62 | */ |
||||
63 | private function __construct( |
||||
64 | public readonly string $pattern, |
||||
65 | public readonly string $positive_prefix, |
||||
66 | public readonly string $positive_suffix, |
||||
67 | public readonly string $negative_prefix, |
||||
68 | public readonly string $negative_suffix, |
||||
69 | public readonly int $multiplier, |
||||
70 | public readonly int $decimal_digits, |
||||
71 | public readonly int $max_decimal_digits, |
||||
72 | public readonly int $integer_digits, |
||||
73 | public readonly int $group_size1, |
||||
74 | public readonly int $group_size2 |
||||
75 | ) { |
||||
76 | } |
||||
77 | |||||
78 | public function __toString(): string |
||||
79 | { |
||||
80 | return $this->pattern; |
||||
81 | } |
||||
82 | |||||
83 | /** |
||||
84 | * Parses a number according to the pattern and return its integer and decimal parts. |
||||
85 | * |
||||
86 | * @param float|int|numeric-string $number |
||||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||||
87 | * |
||||
88 | * @return array{ 0: int, 1: string} |
||||
0 ignored issues
–
show
|
|||||
89 | * Where `0` is the integer part and `1` the decimal part. |
||||
90 | */ |
||||
91 | public function parse_number(float|int|string $number): array |
||||
92 | { |
||||
93 | $number = abs($number * $this->multiplier); |
||||
94 | |||||
95 | if ($this->max_decimal_digits >= 0) { |
||||
96 | $number = round($number, $this->max_decimal_digits); |
||||
97 | } |
||||
98 | |||||
99 | $number = "$number"; |
||||
100 | $pos = strpos($number, '.'); |
||||
101 | |||||
102 | if ($pos !== false) { |
||||
103 | return [ (int)substr($number, 0, $pos), substr($number, $pos + 1) ]; |
||||
104 | } |
||||
105 | |||||
106 | return [ (int)$number, '' ]; |
||||
107 | } |
||||
108 | |||||
109 | /** |
||||
110 | * Formats an integer according to a group pattern. |
||||
111 | */ |
||||
112 | public function format_integer_with_group(int $integer, string $group_symbol): string |
||||
113 | { |
||||
114 | $integer = str_pad((string)$integer, $this->integer_digits, '0', STR_PAD_LEFT); |
||||
115 | $group_size1 = $this->group_size1; |
||||
116 | |||||
117 | if ($group_size1 < 1 || strlen($integer) <= $this->group_size1) { |
||||
118 | return $integer; |
||||
119 | } |
||||
120 | |||||
121 | $group_size2 = $this->group_size2; |
||||
122 | |||||
123 | $str1 = substr($integer, 0, -$group_size1); |
||||
124 | $str2 = substr($integer, -$group_size1); |
||||
125 | $size = $group_size2 > 0 ? $group_size2 : $group_size1; |
||||
126 | $str1 = str_pad($str1, (int)((strlen($str1) + $size - 1) / $size) * $size, ' ', STR_PAD_LEFT); |
||||
127 | |||||
128 | return ltrim(implode($group_symbol, str_split($str1, $size))) . $group_symbol . $str2; |
||||
0 ignored issues
–
show
It seems like
str_split($str1, $size) can also be of type true ; however, parameter $pieces of implode() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
129 | } |
||||
130 | |||||
131 | /** |
||||
132 | * Formats an integer with a decimal. |
||||
133 | * |
||||
134 | * @param int|string $integer |
||||
135 | * An integer, or a formatted integer as returned by {@see format_integer_with_group}. |
||||
136 | */ |
||||
137 | public function format_integer_with_decimal(int|string $integer, string $decimal, string $decimal_symbol): string |
||||
138 | { |
||||
139 | if ($decimal === '0') { |
||||
140 | $decimal = ''; |
||||
141 | } |
||||
142 | |||||
143 | if ($this->decimal_digits > strlen($decimal)) { |
||||
144 | $decimal = str_pad($decimal, $this->decimal_digits, '0'); |
||||
145 | } |
||||
146 | |||||
147 | if (strlen($decimal)) { |
||||
148 | $decimal = $decimal_symbol . $decimal; |
||||
149 | } |
||||
150 | |||||
151 | return "$integer" . $decimal; |
||||
152 | } |
||||
153 | } |
||||
154 |