Issues (662)

src/Supplemental/Units/Units.php (7 issues)

1
<?php
2
3
namespace ICanBoogie\CLDR\Supplemental\Units;
4
5
use ICanBoogie\CLDR\Core\Locale;
6
use ICanBoogie\CLDR\General\Lists\ListType;
7
use ICanBoogie\CLDR\Supplemental\Plurals;
8
use ICanBoogie\CLDR\UTF8Helpers;
9
use ICanBoogie\PropertyNotDefined;
10
use LogicException;
11
12
use function is_string;
13
use function str_replace;
14
use function strtr;
15
16
class Units
17
{
18
    use UnitsCompanion;
19
20
    public const DEFAULT_LENGTH = UnitLength::LONG;
21
    public const COUNT_PREFIX = 'unitPattern-count-';
22
23
    private static function length_to_unit_type(UnitLength $length): ListType
24
    {
25
        return match ($length) {
26
            UnitLength::LONG => ListType::UNIT,
27
            UnitLength::SHORT => ListType::UNIT_SHORT,
28
            UnitLength::NARROW => ListType::UNIT_NARROW,
29
        };
30
    }
31
32
    /**
33
     * @phpstan-ignore-next-line
34
     */
35
    private readonly array $data;
36
37
    public readonly Sequence $sequence;
38
39
    public function __construct(
40
        public readonly Locale $locale
41
    ) {
42
        $this->data = $locale['units'];
43
        $this->sequence = new Sequence($this);
44
    }
45
46
    /**
47
     * @var array<string, Unit>
48
     *     Where _key_ is a unit name.
49
     */
50
    private array $units = [];
51
52
    public function __get(string $property): Unit
53
    {
54
        $unit = strtr($property, '_', '-');
55
56
        if (isset($this->data[self::DEFAULT_LENGTH->value][$unit])) {
57
            return $this->units[$property] ??= new Unit($this, $unit);
58
        }
59
60
        throw new PropertyNotDefined(property: $property, container: $this);
61
    }
62
63
    /**
64
     * @throws LogicException if the specified unit is not defined.
65
     */
66
    public function assert_is_unit(string $unit): void
67
    {
68
            $this->data[self::DEFAULT_LENGTH->value][$unit]
69
            ?? throw new LogicException("No such unit: $unit");
70
    }
71
72
    public function name_for(string $unit, UnitLength $length = self::DEFAULT_LENGTH): string
73
    {
74
        $unit = strtr($unit, '_', '-');
75
76
        return $this->data[$length->value][$unit]['displayName'];
77
    }
78
79
    /**
80
     * @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...
81
     */
82
    public function format(float|int|string $number, string $unit, UnitLength $length = self::DEFAULT_LENGTH): string
83
    {
84
        $pattern = $this->pattern_for_unit($unit, $number, $length);
85
        $number = $this->ensure_number_if_formatted($number);
86
87
        return strtr($pattern, [ '{0}' => $number ]);
88
    }
89
90
    /**
91
     * Format a combination of units is X per Y, such as _miles per hour_ or _liters per second_.
92
     *
93
     * @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...
94
     *
95
     * @link https://www.unicode.org/reports/tr35/tr35-72/tr35-general.html#compound-units
96
     */
97
    public function format_compound(
98
        float|int|string $number,
99
        string $number_unit,
100
        string $per_unit,
101
        UnitLength $length = self::DEFAULT_LENGTH
102
    ): string {
103
        $formatted = $this->format($number, $number_unit, $length);
104
        $data = $this->data[$length->value][$per_unit];
105
106
        if (isset($data['perUnitPattern'])) {
107
            return strtr($data['perUnitPattern'], [
108
109
                '{0}' => $formatted
110
111
            ]);
112
        }
113
114
        $denominator = $this->pattern_for_denominator($per_unit, $number, $length);
115
        $pattern = $this->pattern_for_combination($length);
116
117
        return strtr($pattern, [
118
119
            '{0}' => $formatted,
120
            '{1}' => $denominator
121
122
        ]);
123
    }
124
125
    /**
126
     * Units may be used in composed sequences, such as 5° 30′ for 5 degrees 30 minutes,
127
     * or 3 ft 2 in. For that purpose, the appropriate width of the unit listPattern can be used
128
     * to compose the units in a sequence.
129
     *
130
     * @param array<string, int|float|numeric-string> $units_and_numbers
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, int|float|numeric-string> at position 8 could not be parsed: Unknown type name 'numeric-string' at position 8 in array<string, int|float|numeric-string>.
Loading history...
131
     *
132
     * @link https://www.unicode.org/reports/tr35/tr35-72/tr35-general.html#Unit_Sequences
133
     */
134
    public function format_sequence(array $units_and_numbers, UnitLength $length = self::DEFAULT_LENGTH): string
135
    {
136
        $list = [];
137
138
        foreach ($units_and_numbers as $unit => $number) {
139
            $list[] = $this->format($number, $unit, $length);
140
        }
141
142
        return $this->locale->format_list($list, self::length_to_unit_type($length));
143
    }
144
145
    /**
146
     * @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...
147
     */
148
    private function pattern_for_unit(string $unit, float|int|string $number, UnitLength $length): string
149
    {
150
        $this->assert_is_unit($unit);
151
152
        $count = $this->count_for($number);
153
154
        return $this->data[$length->value][$unit][self::COUNT_PREFIX . $count];
155
    }
156
157
    /**
158
     * @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...
159
     */
160
    private function pattern_for_denominator(string $unit, float|int|string $number, UnitLength $length): string
161
    {
162
        $pattern = $this->pattern_for_unit($unit, $number, $length);
163
164
        return UTF8Helpers::trim(str_replace('{0}', '', $pattern));
165
    }
166
167
    private function pattern_for_combination(UnitLength $length): string
168
    {
169
        return $this->data[$length->value]['per']['compoundUnitPattern'];
170
    }
171
172
    private Plurals $plurals;
173
174
    /**
175
     * @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...
176
     */
177
    private function count_for(float|int|string $number): string
178
    {
179
        $plurals = $this->plurals ??= $this->locale->repository->plurals;
180
181
        return $plurals->rule_for($number, $this->locale->language);
182
    }
183
184
    /**
185
     * @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...
186
     */
187
    private function ensure_number_if_formatted(float|int|string $number): string
188
    {
189
        if (is_string($number)) {
190
            return $number;
191
        }
192
193
        return $this->locale->format_number($number);
194
    }
195
}
196