Issues (662)

src/Numbers/NumberPattern.php (3 issues)

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
The doc comment float|int|numeric-string at position 4 could not be parsed: Unknown type name 'numeric-string' at position 4 in float|int|numeric-string.
Loading history...
87
     *
88
     * @return array{ 0: int, 1: string}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{ at position 2 could not be parsed: the token is null at position 2.
Loading history...
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 ignore-type  annotation

128
        return ltrim(implode($group_symbol, /** @scrutinizer ignore-type */ str_split($str1, $size))) . $group_symbol . $str2;
Loading history...
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