Passed
Push — 6.0 ( 4ac4e1...87e1d7 )
by Olivier
01:56
created

Calendar::apply_transform()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
3
namespace ICanBoogie\CLDR\Dates;
4
5
use ArrayObject;
6
use DateTimeInterface;
7
use ICanBoogie\Accessor\AccessorTrait;
8
use ICanBoogie\CLDR\Core\Locale;
9
use ICanBoogie\CLDR\General\Transforms\ContextTransforms;
10
11
/**
12
 * Representation of a locale calendar.
13
 *
14
 * @property-read DateTimeFormatter $datetime_formatter A datetime formatter.
15
 * @property-read DateFormatter $date_formatter A date formatter.
16
 * @property-read TimeFormatter $time_formatter A time formatter.
17
 *
18
 * @property-read string[] $standalone_abbreviated_days     Shortcut to `days/stand-alone/abbreviated`.
19
 * @property-read string[] $standalone_abbreviated_eras     Shortcut to `eras/eraAbbr`.
20
 * @property-read string[] $standalone_abbreviated_months   Shortcut to `months/stand-alone/abbreviated`.
21
 * @property-read string[] $standalone_abbreviated_quarters Shortcut to `quarters/stand-alone/abbreviated`.
22
 * @property-read string[] $standalone_narrow_days          Shortcut to `days/stand-alone/narrow`.
23
 * @property-read string[] $standalone_narrow_eras          Shortcut to `eras/eraNarrow`.
24
 * @property-read string[] $standalone_narrow_months        Shortcut to `months/stand-alone/narrow`.
25
 * @property-read string[] $standalone_narrow_quarters      Shortcut to `quarters/stand-alone/narrow`.
26
 * @property-read string[] $standalone_short_days           Shortcut to `days/stand-alone/short`.
27
 * @property-read string[] $standalone_short_eras           Shortcut to `eras/eraAbbr`.
28
 * @property-read string[] $standalone_short_months         Shortcut to `months/stand-alone/abbreviated`.
29
 * @property-read string[] $standalone_short_quarters       Shortcut to `quarters/stand-alone/abbreviated`.
30
 * @property-read string[] $standalone_wide_days            Shortcut to `days/stand-alone/wide`.
31
 * @property-read string[] $standalone_wide_eras            Shortcut to `eras/eraNames`.
32
 * @property-read string[] $standalone_wide_months          Shortcut to `months/stand-alone/wide`.
33
 * @property-read string[] $standalone_wide_quarters        Shortcut to `quarters/stand-alone/wide`.
34
 * @property-read string[] $abbreviated_days                Shortcut to `days/format/abbreviated`.
35
 * @property-read string[] $abbreviated_eras                Shortcut to `eras/eraAbbr`.
36
 * @property-read string[] $abbreviated_months              Shortcut to `months/format/abbreviated`.
37
 * @property-read string[] $abbreviated_quarters            Shortcut to `quarters/format/abbreviated`.
38
 * @property-read string[] $narrow_days                     Shortcut to `days/format/narrow`.
39
 * @property-read string[] $narrow_eras                     Shortcut to `eras/eraNarrow`.
40
 * @property-read string[] $narrow_months                   Shortcut to `months/format/narrow`.
41
 * @property-read string[] $narrow_quarters                 Shortcut to `quarters/format/narrow`.
42
 * @property-read string[] $short_days                      Shortcut to `days/format/short`.
43
 * @property-read string[] $short_eras                      Shortcut to `eras/eraAbbr`.
44
 * @property-read string[] $short_months                    Shortcut to `months/format/abbreviated`.
45
 * @property-read string[] $short_quarters                  Shortcut to `quarters/format/abbreviated`.
46
 * @property-read string[] $wide_days                       Shortcut to `days/format/wide`.
47
 * @property-read string[] $wide_eras                       Shortcut to `eras/eraNames`.
48
 * @property-read string[] $wide_months                     Shortcut to `months/format/wide`.
49
 * @property-read string[] $wide_quarters                   Shortcut to `quarters/format/wide`.
50
 *
51
 * @extends ArrayObject<string, mixed>
52
 */
53
final class Calendar extends ArrayObject
54
{
55
    /**
56
     * @uses get_datetime_formatter
57
     * @uses get_date_formatter
58
     * @uses get_time_formatter
59
     */
60
    use AccessorTrait;
61
62
    public const SHORTHANDS_REGEX = '#^(standalone_)?(abbreviated|narrow|short|wide)_(days|eras|months|quarters)$#';
63
64
    public const WIDTH_ABBR = 'abbreviated';
65
    public const WIDTH_NARROW = 'narrow';
66
    public const WIDTH_SHORT = 'short';
67
    public const WIDTH_WIDE = 'wide';
68
69
    public const ERA_NAMES = 'eraNames';
70
    public const ERA_ABBR = 'eraAbbr';
71
    public const ERA_NARROW = 'eraNarrow';
72
73
    public const CALENDAR_MONTHS = 'months';
74
    public const CALENDAR_DAYS = 'days';
75
    public const CALENDAR_QUARTERS = 'quarters';
76
    public const CALENDAR_ERAS = 'eras';
77
78
    public const CONTEXT_FORMAT = 'format';
79
    public const CONTEXT_STAND_ALONE = 'stand-alone';
80
81
    /**
82
     * @var array<string, string>
83
     */
84
    private static array $era_widths_mapping = [
85
86
        self::WIDTH_ABBR => self::ERA_ABBR,
87
        self::WIDTH_NARROW => self::ERA_NARROW,
88
        self::WIDTH_SHORT => self::ERA_ABBR,
89
        self::WIDTH_WIDE => self::ERA_NAMES
90
91
    ];
92
93
    private DateTimeFormatter $datetime_formatter;
94
95
    private function get_datetime_formatter(): DateTimeFormatter
96
    {
97
        return $this->datetime_formatter ??= new DateTimeFormatter($this);
98
    }
99
100
    private DateFormatter $date_formatter;
101
102
    private function get_date_formatter(): DateFormatter
103
    {
104
        return $this->date_formatter ??= new DateFormatter($this);
105
    }
106
107
    private TimeFormatter $time_formatter;
108
109
    private function get_time_formatter(): TimeFormatter
110
    {
111
        return $this->time_formatter ??= new TimeFormatter($this);
112
    }
113
114
    private readonly ContextTransforms $context_transforms;
115
116
    /**
117
     * @param array<string, mixed> $data
118
     */
119
    public function __construct(
120
        public readonly Locale $locale,
121
        array $data
122
    ) {
123
        $this->context_transforms = $locale->context_transforms;
0 ignored issues
show
Bug introduced by
The property context_transforms is declared read-only in ICanBoogie\CLDR\Dates\Calendar.
Loading history...
124
125
        $data = $this->transform_data($data);
126
127
        parent::__construct($data);
128
    }
129
130
    /**
131
     * @phpstan-ignore-next-line
132
     */
133
    private array $shortcuts = [];
134
135
    /**
136
     * @return mixed
137
     */
138
    public function __get(string $property)
139
    {
140
        if (!preg_match(self::SHORTHANDS_REGEX, $property, $matches)) {
141
            return $this->accessor_get($property);
142
        }
143
144
        $make = function () use ($matches) {
145
            [ , $standalone, $width, $type ] = $matches;
146
147
            $data = $this[$type];
148
149
            if ($type === self::CALENDAR_ERAS) {
150
                return $data[self::$era_widths_mapping[$width]];
151
            }
152
153
            $data = $data[$standalone ? self::CONTEXT_STAND_ALONE : self::CONTEXT_FORMAT];
154
155
            if ($width === self::WIDTH_SHORT && empty($data[$width])) {
156
                $width = self::WIDTH_ABBR;
157
            }
158
159
            return $data[$width];
160
        };
161
162
        return $this->shortcuts[$property] ??= $make();
163
    }
164
165
    /**
166
     * @see DateTimeFormatter::format
167
     */
168
    public function format_datetime(
169
        DateTimeInterface|int|string $datetime,
170
        string|DateTimeFormatLength $pattern_or_length_or_skeleton
171
    ): string {
172
        return $this->get_datetime_formatter()->format($datetime, $pattern_or_length_or_skeleton);
173
    }
174
175
    /**
176
     * @see DateFormatter::format
177
     */
178
    public function format_date(
179
        DateTimeInterface|int|string $datetime,
180
        string|DateTimeFormatLength $pattern_or_length_or_skeleton
181
    ): string {
182
        return $this->get_date_formatter()->format($datetime, $pattern_or_length_or_skeleton);
183
    }
184
185
    /**
186
     * @see TimeFormatter::format
187
     */
188
    public function format_time(
189
        DateTimeInterface|int|string $datetime,
190
        string|DateTimeFormatLength $pattern_or_length_or_skeleton
191
    ): string {
192
        return $this->get_time_formatter()->format($datetime, $pattern_or_length_or_skeleton);
193
    }
194
195
    /**
196
     * Transforms calendar data according to context transforms rules.
197
     *
198
     * @param array<string, mixed> $data
199
     *
200
     * @return array<string, mixed>
201
     *
202
     * @uses transform_months
203
     * @uses transform_days
204
     * @uses transform_quarters
205
     */
206
    private function transform_data(array $data): array
207
    {
208
        static $transformable = [
209
210
            self::CALENDAR_MONTHS,
211
            self::CALENDAR_DAYS,
212
            self::CALENDAR_QUARTERS
213
214
        ];
215
216
        foreach ($transformable as $name) {
217
            array_walk($data[$name], function (array &$data, string $context) use ($name): void {
218
                $is_stand_alone = self::CONTEXT_STAND_ALONE === $context;
219
220
                array_walk($data, function (array &$names, string $width) use ($name, $is_stand_alone): void {
221
                    $names = $this->{'transform_' . $name}($names, $width, $is_stand_alone);
222
                });
223
            });
224
        }
225
226
        if (isset($data[self::CALENDAR_ERAS])) {
227
            array_walk($data[self::CALENDAR_ERAS], function (array &$names, string $width): void {
228
                $names = $this->transform_eras($names, $width);
229
            });
230
        }
231
232
        return $data;
233
    }
234
235
    /**
236
     * Transforms month names according to context transforms rules.
237
     *
238
     * @param string[] $names
239
     *
240
     * @return string[]
241
     */
242
    private function transform_months(array $names, string $width, bool $standalone): array
243
    {
244
        return $this->transform_months_or_days(
245
            $names,
246
            $width,
247
            $standalone,
248
            ContextTransforms::USAGE_MONTH_STANDALONE_EXCEPT_NARROW
249
        );
250
    }
251
252
    /**
253
     * Transforms day names according to context transforms rules.
254
     *
255
     * @param string[] $names
256
     *
257
     * @return string[]
258
     */
259
    private function transform_days(array $names, string $width, bool $standalone): array
260
    {
261
        return $this->transform_months_or_days(
262
            $names,
263
            $width,
264
            $standalone,
265
            ContextTransforms::USAGE_DAY_STANDALONE_EXCEPT_NARROW
266
        );
267
    }
268
269
    /**
270
     * Transforms day names according to context transforms rules.
271
     *
272
     * @param string[] $names
273
     *
274
     * @return string[]
275
     */
276
    private function transform_months_or_days(array $names, string $width, bool $standalone, string $usage): array
277
    {
278
        if ($width === self::WIDTH_NARROW || !$standalone) {
279
            return $names;
280
        }
281
282
        return $this->apply_transform(
283
            $names,
284
            $usage,
285
            ContextTransforms::TYPE_STAND_ALONE
286
        );
287
    }
288
289
    /**
290
     * Transforms era names according to context transforms rules.
291
     *
292
     * @param string[] $names
293
     *
294
     * @return string[]
295
     */
296
    private function transform_eras(array $names, string $width): array
297
    {
298
        return match ($width) {
299
            self::ERA_ABBR => $this->apply_transform(
300
                $names,
301
                ContextTransforms::USAGE_ERA_ABBR,
302
                ContextTransforms::TYPE_STAND_ALONE
303
            ),
304
            self::ERA_NAMES => $this->apply_transform(
305
                $names,
306
                ContextTransforms::USAGE_ERA_NAME,
307
                ContextTransforms::TYPE_STAND_ALONE
308
            ),
309
            self::ERA_NARROW => $this->apply_transform(
310
                $names,
311
                ContextTransforms::USAGE_ERA_NARROW,
312
                ContextTransforms::TYPE_STAND_ALONE
313
            ),
314
            default => $names,
315
        };
316
    }
317
318
    /**
319
     * Transforms quarters names according to context transforms rules.
320
     *
321
     * @param string[] $names
322
     *
323
     * @return string[]
324
     */
325
    private function transform_quarters(array $names, string $width, bool $standalone): array
326
    {
327
        if ($standalone) {
328
            if ($width !== self::WIDTH_WIDE) {
329
                return $names;
330
            }
331
332
            return $this->apply_transform(
333
                $names,
334
                ContextTransforms::USAGE_QUARTER_STANDALONE_WIDE,
335
                ContextTransforms::TYPE_STAND_ALONE
336
            );
337
        }
338
339
        return match ($width) {
340
            self::WIDTH_ABBR => $this->apply_transform(
341
                $names,
342
                ContextTransforms::USAGE_QUARTER_ABBREVIATED,
343
                ContextTransforms::TYPE_STAND_ALONE
344
            ),
345
            self::WIDTH_WIDE => $this->apply_transform(
346
                $names,
347
                ContextTransforms::USAGE_QUARTER_FORMAT_WIDE,
348
                ContextTransforms::TYPE_STAND_ALONE
349
            ),
350
            self::WIDTH_NARROW => $this->apply_transform(
351
                $names,
352
                ContextTransforms::USAGE_QUARTER_NARROW,
353
                ContextTransforms::TYPE_STAND_ALONE
354
            ),
355
            default => $names,
356
        };
357
        // @codeCoverageIgnore
358
    }
359
360
    /**
361
     * Applies transformation to names.
362
     *
363
     * @param string[] $names
364
     *
365
     * @return string[]
366
     */
367
    private function apply_transform(array $names, string $usage, string $type): array
368
    {
369
        $context_transforms = $this->context_transforms;
370
371
        return array_map(
372
            static fn(string $str): string => $context_transforms->transform($str, $usage, $type),
373
            $names
374
        );
375
    }
376
}
377