Passed
Pull Request — 2.2 (#20024)
by Wilmer
06:15
created

BaseFormatConverter::createFormatter()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 1
b 0
f 0
nc 6
nop 3
dl 0
loc 15
ccs 9
cts 9
cp 1
crap 4
rs 9.9666
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\helpers;
9
10
use IntlDateFormatter;
11
use Yii;
12
13
/**
14
 * BaseFormatConverter provides concrete implementation for [[FormatConverter]].
15
 *
16
 * Do not use BaseFormatConverter. Use [[FormatConverter]] instead.
17
 *
18
 * @author Carsten Brandt <[email protected]>
19
 * @author Enrica Ruedin <[email protected]>
20
 * @since 2.0
21
 */
22
class BaseFormatConverter
23
{
24
    /**
25
     * @var array the php fallback definition to use for the ICU short patterns `short`, `medium`, `long` and `full`.
26
     * This is used as fallback when the `intl` extension is not installed.
27
     */
28
    public static $phpFallbackDatePatterns = [
29
        'short' => [
30
            'date' => 'n/j/y',
31
            'time' => 'H:i',
32
            'datetime' => 'n/j/y H:i',
33
        ],
34
        'medium' => [
35
            'date' => 'M j, Y',
36
            'time' => 'g:i:s A',
37
            'datetime' => 'M j, Y g:i:s A',
38
        ],
39
        'long' => [
40
            'date' => 'F j, Y',
41
            'time' => 'g:i:sA',
42
            'datetime' => 'F j, Y g:i:sA',
43
        ],
44
        'full' => [
45
            'date' => 'l, F j, Y',
46
            'time' => 'g:i:sA T',
47
            'datetime' => 'l, F j, Y g:i:sA T',
48
        ],
49
    ];
50
    /**
51
     * @var array the jQuery UI fallback definition to use for the ICU short patterns `short`, `medium`, `long` and `full`.
52
     * This is used as fallback when the `intl` extension is not installed.
53
     */
54
    public static $juiFallbackDatePatterns = [
55
        'short' => [
56
            'date' => 'd/m/y',
57
            'time' => '',
58
            'datetime' => 'd/m/y',
59
        ],
60
        'medium' => [
61
            'date' => 'M d, yy',
62
            'time' => '',
63
            'datetime' => 'M d, yy',
64
        ],
65
        'long' => [
66
            'date' => 'MM d, yy',
67
            'time' => '',
68
            'datetime' => 'MM d, yy',
69
        ],
70
        'full' => [
71
            'date' => 'DD, MM d, yy',
72
            'time' => '',
73
            'datetime' => 'DD, MM d, yy',
74
        ],
75
    ];
76
77
    private static $_icuShortFormats = [
78
        'short' => 3, // IntlDateFormatter::SHORT,
79
        'medium' => 2, // IntlDateFormatter::MEDIUM,
80
        'long' => 1, // IntlDateFormatter::LONG,
81
        'full' => 0, // IntlDateFormatter::FULL,
82
    ];
83
84
85
    /**
86
     * Converts a date format pattern from [ICU format](https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax)
87
     * to [PHP `date()` function format](https://www.php.net/manual/en/function.date).
88
     *
89
     * The conversion is limited to date patterns that do not use escaped characters.
90
     * Patterns like `d 'of' MMMM yyyy` which will result in a date like `1 of December 2014` may not be converted correctly
91
     * because of the use of escaped characters.
92
     *
93
     * Pattern constructs that are not supported by the PHP format will be removed.
94
     *
95
     * @param string $pattern date format pattern in ICU format.
96
     * @param string $type 'date', 'time', or 'datetime'.
97
     * @param string|null $locale the locale to use for converting ICU short patterns `short`, `medium`, `long` and `full`.
98
     * If not given, `Yii::$app->language` will be used.
99
     * @return string The converted date format pattern.
100
     * @throws \Exception
101
     */
102 245
    public static function convertDateIcuToPhp($pattern, $type = 'date', $locale = null)
103
    {
104 245
        if (isset(self::$_icuShortFormats[$pattern])) {
105 9
            if (extension_loaded('intl')) {
106 4
                $pattern = self::createFormatter($locale, $type, $pattern);
107
            } else {
108 5
                return static::$phpFallbackDatePatterns[$pattern][$type];
109
            }
110
        }
111
        // https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
112
        // escaped text
113 241
        $escaped = [];
114 241
        if (preg_match_all('/(?<!\')\'(.*?[^\'])\'(?!\')/', $pattern, $matches, PREG_SET_ORDER)) {
115 2
            foreach ($matches as $match) {
116 2
                $match[1] = str_replace('\'\'', '\'', $match[1]);
117 2
                $escaped[$match[0]] = '\\' . implode('\\', preg_split('//u', $match[1], -1, PREG_SPLIT_NO_EMPTY));
118
            }
119
        }
120
121 241
        return strtr($pattern, array_merge($escaped, [
122 241
            "''" => "\\'",  // two single quotes produce one
123 241
            'G' => '',      // era designator like (Anno Domini)
124 241
            'Y' => 'o',     // 4digit year of "Week of Year"
125 241
            'y' => 'Y',     // 4digit year e.g. 2014
126 241
            'yyyy' => 'Y',  // 4digit year e.g. 2014
127 241
            'yy' => 'y',    // 2digit year number eg. 14
128 241
            'u' => '',      // extended year e.g. 4601
129 241
            'U' => '',      // cyclic year name, as in Chinese lunar calendar
130 241
            'r' => '',      // related Gregorian year e.g. 1996
131 241
            'Q' => '',      // number of quarter
132 241
            'QQ' => '',     // number of quarter '02'
133 241
            'QQQ' => '',    // quarter 'Q2'
134 241
            'QQQQ' => '',   // quarter '2nd quarter'
135 241
            'QQQQQ' => '',  // number of quarter '2'
136 241
            'q' => '',      // number of Stand Alone quarter
137 241
            'qq' => '',     // number of Stand Alone quarter '02'
138 241
            'qqq' => '',    // Stand Alone quarter 'Q2'
139 241
            'qqqq' => '',   // Stand Alone quarter '2nd quarter'
140 241
            'qqqqq' => '',  // number of Stand Alone quarter '2'
141 241
            'M' => 'n',     // Numeric representation of a month, without leading zeros
142 241
            'MM' => 'm',    // Numeric representation of a month, with leading zeros
143 241
            'MMM' => 'M',   // A short textual representation of a month, three letters
144 241
            'MMMM' => 'F',  // A full textual representation of a month, such as January or March
145 241
            'MMMMM' => '',
146 241
            'L' => 'n',     // Stand alone month in year
147 241
            'LL' => 'm',    // Stand alone month in year
148 241
            'LLL' => 'M',   // Stand alone month in year
149 241
            'LLLL' => 'F',  // Stand alone month in year
150 241
            'LLLLL' => '',  // Stand alone month in year
151 241
            'w' => 'W',     // ISO-8601 week number of year
152 241
            'ww' => 'W',    // ISO-8601 week number of year
153 241
            'W' => '',      // week of the current month
154 241
            'd' => 'j',     // day without leading zeros
155 241
            'dd' => 'd',    // day with leading zeros
156 241
            'D' => 'z',     // day of the year 0 to 365
157 241
            'F' => '',      // Day of Week in Month. eg. 2nd Wednesday in July
158 241
            'g' => '',      // Modified Julian day. This is different from the conventional Julian day number in two regards.
159 241
            'E' => 'D',     // day of week written in short form eg. Sun
160 241
            'EE' => 'D',
161 241
            'EEE' => 'D',
162 241
            'EEEE' => 'l',  // day of week fully written eg. Sunday
163 241
            'EEEEE' => '',
164 241
            'EEEEEE' => '',
165 241
            'e' => 'N',     // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun
166 241
            'ee' => 'N',    // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year
167 241
            'eee' => 'D',
168 241
            'eeee' => 'l',
169 241
            'eeeee' => '',
170 241
            'eeeeee' => '',
171 241
            'c' => 'N',     // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun
172 241
            'cc' => 'N',    // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year
173 241
            'ccc' => 'D',
174 241
            'cccc' => 'l',
175 241
            'ccccc' => '',
176 241
            'cccccc' => '',
177 241
            'a' => 'A',     // AM/PM marker
178 241
            'h' => 'g',     // 12-hour format of an hour without leading zeros 1 to 12h
179 241
            'hh' => 'h',    // 12-hour format of an hour with leading zeros, 01 to 12 h
180 241
            'H' => 'G',     // 24-hour format of an hour without leading zeros 0 to 23h
181 241
            'HH' => 'H',    // 24-hour format of an hour with leading zeros, 00 to 23 h
182 241
            'k' => '',      // hour in day (1~24)
183 241
            'kk' => '',     // hour in day (1~24)
184 241
            'K' => '',      // hour in am/pm (0~11)
185 241
            'KK' => '',     // hour in am/pm (0~11)
186 241
            'm' => 'i',     // Minutes without leading zeros, not supported by php but we fallback
187 241
            'mm' => 'i',    // Minutes with leading zeros
188 241
            's' => 's',     // Seconds, without leading zeros, not supported by php but we fallback
189 241
            'ss' => 's',    // Seconds, with leading zeros
190 241
            'S' => '',      // fractional second
191 241
            'SS' => '',     // fractional second
192 241
            'SSS' => '',    // fractional second
193 241
            'SSSS' => '',   // fractional second
194 241
            'A' => '',      // milliseconds in day
195 241
            'z' => 'T',     // Timezone abbreviation
196 241
            'zz' => 'T',    // Timezone abbreviation
197 241
            'zzz' => 'T',   // Timezone abbreviation
198 241
            'zzzz' => 'T',  // Timezone full name, not supported by php but we fallback
199 241
            'Z' => 'O',     // Difference to Greenwich time (GMT) in hours
200 241
            'ZZ' => 'O',    // Difference to Greenwich time (GMT) in hours
201 241
            'ZZZ' => 'O',   // Difference to Greenwich time (GMT) in hours
202 241
            'ZZZZ' => '\G\M\TP', // Time Zone: long localized GMT (=OOOO) e.g. GMT-08:00
203 241
            'ZZZZZ' => '',  //  TIme Zone: ISO8601 extended hms? (=XXXXX)
204 241
            'O' => '',      // Time Zone: short localized GMT e.g. GMT-8
205 241
            'OOOO' => '\G\M\TP', //  Time Zone: long localized GMT (=ZZZZ) e.g. GMT-08:00
206 241
            'v' => '\G\M\TP', // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here
207 241
            'vvvv' => '\G\M\TP', // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here
208 241
            'V' => '',      // Time Zone: short time zone ID
209 241
            'VV' => 'e',    // Time Zone: long time zone ID
210 241
            'VVV' => '',    // Time Zone: time zone exemplar city
211 241
            'VVVV' => '\G\M\TP', // Time Zone: generic location (falls back to OOOO) using the ICU defined fallback here
212 241
            'X' => '',      // Time Zone: ISO8601 basic hm?, with Z for 0, e.g. -08, +0530, Z
213 241
            'XX' => 'O, \Z', // Time Zone: ISO8601 basic hm, with Z, e.g. -0800, Z
214 241
            'XXX' => 'P, \Z',    // Time Zone: ISO8601 extended hm, with Z, e.g. -08:00, Z
215 241
            'XXXX' => '',   // Time Zone: ISO8601 basic hms?, with Z, e.g. -0800, -075258, Z
216 241
            'XXXXX' => '',  // Time Zone: ISO8601 extended hms?, with Z, e.g. -08:00, -07:52:58, Z
217 241
            'x' => '',      // Time Zone: ISO8601 basic hm?, without Z for 0, e.g. -08, +0530
218 241
            'xx' => 'O',    // Time Zone: ISO8601 basic hm, without Z, e.g. -0800
219 241
            'xxx' => 'P',   // Time Zone: ISO8601 extended hm, without Z, e.g. -08:00
220 241
            'xxxx' => '',   // Time Zone: ISO8601 basic hms?, without Z, e.g. -0800, -075258
221 241
            'xxxxx' => '',  // Time Zone: ISO8601 extended hms?, without Z, e.g. -08:00, -07:52:58
222 241
        ]));
223
    }
224
225
    /**
226
     * Converts a date format pattern from [PHP `date()` function format](https://www.php.net/manual/en/function.date)
227
     * to [ICU format](https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax).
228
     *
229
     * Pattern constructs that are not supported by the ICU format will be removed.
230
     *
231
     * Since 2.0.13 it handles escaped characters correctly.
232
     *
233
     * @param string $pattern date format pattern in PHP `date()` function format.
234
     * @return string The converted date format pattern.
235
     */
236 85
    public static function convertDatePhpToIcu($pattern)
237
    {
238
        // https://www.php.net/manual/en/function.date
239 85
        $result = strtr($pattern, [
240 85
            "'" => "''''",  // single `'` should be encoded as `''`, which internally should be encoded as `''''`
241
            // Day
242 85
            '\d' => "'d'",
243 85
            'd' => 'dd',    // Day of the month, 2 digits with leading zeros 	01 to 31
244 85
            '\D' => "'D'",
245 85
            'D' => 'eee',   // A textual representation of a day, three letters 	Mon through Sun
246 85
            '\j' => "'j'",
247 85
            'j' => 'd',     // Day of the month without leading zeros 	1 to 31
248 85
            '\l' => "'l'",
249 85
            'l' => 'eeee',  // A full textual representation of the day of the week 	Sunday through Saturday
250 85
            '\N' => "'N'",
251 85
            'N' => 'e',     // ISO-8601 numeric representation of the day of the week, 1 (for Monday) through 7 (for Sunday)
252 85
            '\S' => "'S'",
253 85
            'S' => '',      // English ordinal suffix for the day of the month, 2 characters 	st, nd, rd or th. Works well with j
254 85
            '\w' => "'w'",
255 85
            'w' => '',      // Numeric representation of the day of the week 	0 (for Sunday) through 6 (for Saturday)
256 85
            '\z' => "'z'",
257 85
            'z' => 'D',     // The day of the year (starting from 0) 	0 through 365
258
            // Week
259 85
            '\W' => "'W'",
260 85
            'W' => 'w',     // ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0) 	Example: 42 (the 42nd week in the year)
261
            // Month
262 85
            '\F' => "'F'",
263 85
            'F' => 'MMMM',  // A full textual representation of a month, January through December
264 85
            '\m' => "'m'",
265 85
            'm' => 'MM',    // Numeric representation of a month, with leading zeros 	01 through 12
266 85
            '\M' => "'M'",
267 85
            'M' => 'MMM',   // A short textual representation of a month, three letters 	Jan through Dec
268 85
            '\n' => "'n'",
269 85
            'n' => 'M',     // Numeric representation of a month, without leading zeros 	1 through 12, not supported by ICU but we fallback to "with leading zero"
270 85
            '\t' => "'t'",
271 85
            't' => '',      // Number of days in the given month 	28 through 31
272
            // Year
273 85
            '\L' => "'L'",
274 85
            'L' => '',      // Whether it's a leap year, 1 if it is a leap year, 0 otherwise.
275 85
            '\o' => "'o'",
276 85
            'o' => 'Y',     // ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead.
277 85
            '\Y' => "'Y'",
278 85
            'Y' => 'yyyy',  // A full numeric representation of a year, 4 digits 	Examples: 1999 or 2003
279 85
            '\y' => "'y'",
280 85
            'y' => 'yy',    // A two digit representation of a year 	Examples: 99 or 03
281
            // Time
282 85
            '\a' => "'a'",
283 85
            'a' => 'a',     // Lowercase Ante meridiem and Post meridiem, am or pm
284 85
            '\A' => "'A'",
285 85
            'A' => 'a',     // Uppercase Ante meridiem and Post meridiem, AM or PM, not supported by ICU but we fallback to lowercase
286 85
            '\B' => "'B'",
287 85
            'B' => '',      // Swatch Internet time 	000 through 999
288 85
            '\g' => "'g'",
289 85
            'g' => 'h',     // 12-hour format of an hour without leading zeros 	1 through 12
290 85
            '\G' => "'G'",
291 85
            'G' => 'H',     // 24-hour format of an hour without leading zeros 0 to 23h
292 85
            '\h' => "'h'",
293 85
            'h' => 'hh',    // 12-hour format of an hour with leading zeros, 01 to 12 h
294 85
            '\H' => "'H'",
295 85
            'H' => 'HH',    // 24-hour format of an hour with leading zeros, 00 to 23 h
296 85
            '\i' => "'i'",
297 85
            'i' => 'mm',    // Minutes with leading zeros 	00 to 59
298 85
            '\s' => "'s'",
299 85
            's' => 'ss',    // Seconds, with leading zeros 	00 through 59
300 85
            '\u' => "'u'",
301 85
            'u' => '',      // Microseconds. Example: 654321
302
            // Timezone
303 85
            '\e' => "'e'",
304 85
            'e' => 'VV',    // Timezone identifier. Examples: UTC, GMT, Atlantic/Azores
305 85
            '\I' => "'I'",
306 85
            'I' => '',      // Whether or not the date is in daylight saving time, 1 if Daylight Saving Time, 0 otherwise.
307 85
            '\O' => "'O'",
308 85
            'O' => 'xx',    // Difference to Greenwich time (GMT) in hours, Example: +0200
309 85
            '\P' => "'P'",
310 85
            'P' => 'xxx',   // Difference to Greenwich time (GMT) with colon between hours and minutes, Example: +02:00
311 85
            '\T' => "'T'",
312 85
            'T' => 'zzz',   // Timezone abbreviation, Examples: EST, MDT ...
313 85
            '\Z' => "'Z'",
314 85
            'Z' => '',      // Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. -43200 through 50400
315
            // Full Date/Time
316 85
            '\c' => "'c'",
317 85
            'c' => "yyyy-MM-dd'T'HH:mm:ssxxx", // ISO 8601 date, e.g. 2004-02-12T15:19:21+00:00
318 85
            '\r' => "'r'",
319 85
            'r' => 'eee, dd MMM yyyy HH:mm:ss xx', // RFC 2822 formatted date, Example: Thu, 21 Dec 2000 16:01:07 +0200
320 85
            '\U' => "'U'",
321 85
            'U' => '',      // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
322 85
            '\\\\' => '\\',
323 85
        ]);
324
325
        // remove `''` - they're result of consecutive escaped chars (`\A\B` will be `'A''B'`, but should be `'AB'`)
326
        // real `'` are encoded as `''''`
327 85
        return strtr($result, [
328 85
            "''''" => "''",
329 85
            "''" => '',
330 85
        ]);
331
    }
332
333
    /**
334
     * Converts a date format pattern from [ICU format](https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax)
335
     * to [jQuery UI date format](https://api.jqueryui.com/datepicker/#utility-formatDate).
336
     *
337
     * Pattern constructs that are not supported by the jQuery UI format will be removed.
338
     *
339
     * @param string $pattern date format pattern in ICU format.
340
     * @param string $type 'date', 'time', or 'datetime'.
341
     * @param string|null $locale the locale to use for converting ICU short patterns `short`, `medium`, `long` and `full`.
342
     * If not given, `Yii::$app->language` will be used.
343
     * @return string The converted date format pattern.
344
     * @throws \Exception
345
     */
346 105
    public static function convertDateIcuToJui($pattern, $type = 'date', $locale = null)
347
    {
348 105
        if (isset(self::$_icuShortFormats[$pattern])) {
349 4
            if (extension_loaded('intl')) {
350 4
                $pattern = self::createFormatter($locale, $type, $pattern);
351
            } else {
352
                return static::$juiFallbackDatePatterns[$pattern][$type];
353
            }
354
        }
355
        // https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
356
        // escaped text
357 105
        $escaped = [];
358 105
        if (preg_match_all('/(?<!\')\'.*?[^\']\'(?!\')/', $pattern, $matches)) {
359 2
            foreach ($matches[0] as $match) {
360 2
                $escaped[$match] = $match;
361
            }
362
        }
363
364 105
        return strtr($pattern, array_merge($escaped, [
365 105
            'G' => '',      // era designator like (Anno Domini)
366 105
            'Y' => '',      // 4digit year of "Week of Year"
367 105
            'y' => 'yy',    // 4digit year e.g. 2014
368 105
            'yyyy' => 'yy', // 4digit year e.g. 2014
369 105
            'yy' => 'y',    // 2digit year number eg. 14
370 105
            'u' => '',      // extended year e.g. 4601
371 105
            'U' => '',      // cyclic year name, as in Chinese lunar calendar
372 105
            'r' => '',      // related Gregorian year e.g. 1996
373 105
            'Q' => '',      // number of quarter
374 105
            'QQ' => '',     // number of quarter '02'
375 105
            'QQQ' => '',    // quarter 'Q2'
376 105
            'QQQQ' => '',   // quarter '2nd quarter'
377 105
            'QQQQQ' => '',  // number of quarter '2'
378 105
            'q' => '',      // number of Stand Alone quarter
379 105
            'qq' => '',     // number of Stand Alone quarter '02'
380 105
            'qqq' => '',    // Stand Alone quarter 'Q2'
381 105
            'qqqq' => '',   // Stand Alone quarter '2nd quarter'
382 105
            'qqqqq' => '',  // number of Stand Alone quarter '2'
383 105
            'M' => 'm',     // Numeric representation of a month, without leading zeros
384 105
            'MM' => 'mm',   // Numeric representation of a month, with leading zeros
385 105
            'MMM' => 'M',   // A short textual representation of a month, three letters
386 105
            'MMMM' => 'MM', // A full textual representation of a month, such as January or March
387 105
            'MMMMM' => '',
388 105
            'L' => 'm',     // Stand alone month in year
389 105
            'LL' => 'mm',   // Stand alone month in year
390 105
            'LLL' => 'M',   // Stand alone month in year
391 105
            'LLLL' => 'MM', // Stand alone month in year
392 105
            'LLLLL' => '',  // Stand alone month in year
393 105
            'w' => '',      // ISO-8601 week number of year
394 105
            'ww' => '',     // ISO-8601 week number of year
395 105
            'W' => '',      // week of the current month
396 105
            'd' => 'd',     // day without leading zeros
397 105
            'dd' => 'dd',   // day with leading zeros
398 105
            'D' => 'o',     // day of the year 0 to 365
399 105
            'F' => '',      // Day of Week in Month. eg. 2nd Wednesday in July
400 105
            'g' => '',      // Modified Julian day. This is different from the conventional Julian day number in two regards.
401 105
            'E' => 'D',     // day of week written in short form eg. Sun
402 105
            'EE' => 'D',
403 105
            'EEE' => 'D',
404 105
            'EEEE' => 'DD', // day of week fully written eg. Sunday
405 105
            'EEEEE' => '',
406 105
            'EEEEEE' => '',
407 105
            'e' => '',      // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun
408 105
            'ee' => '',     // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year
409 105
            'eee' => 'D',
410 105
            'eeee' => '',
411 105
            'eeeee' => '',
412 105
            'eeeeee' => '',
413 105
            'c' => '',      // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun
414 105
            'cc' => '',     // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year
415 105
            'ccc' => 'D',
416 105
            'cccc' => 'DD',
417 105
            'ccccc' => '',
418 105
            'cccccc' => '',
419 105
            'a' => '',      // am/pm marker
420 105
            'h' => '',      // 12-hour format of an hour without leading zeros 1 to 12h
421 105
            'hh' => '',     // 12-hour format of an hour with leading zeros, 01 to 12 h
422 105
            'H' => '',      // 24-hour format of an hour without leading zeros 0 to 23h
423 105
            'HH' => '',     // 24-hour format of an hour with leading zeros, 00 to 23 h
424 105
            'k' => '',      // hour in day (1~24)
425 105
            'kk' => '',     // hour in day (1~24)
426 105
            'K' => '',      // hour in am/pm (0~11)
427 105
            'KK' => '',     // hour in am/pm (0~11)
428 105
            'm' => '',      // Minutes without leading zeros, not supported by php but we fallback
429 105
            'mm' => '',     // Minutes with leading zeros
430 105
            's' => '',      // Seconds, without leading zeros, not supported by php but we fallback
431 105
            'ss' => '',     // Seconds, with leading zeros
432 105
            'S' => '',      // fractional second
433 105
            'SS' => '',     // fractional second
434 105
            'SSS' => '',    // fractional second
435 105
            'SSSS' => '',   // fractional second
436 105
            'A' => '',      // milliseconds in day
437 105
            'z' => '',      // Timezone abbreviation
438 105
            'zz' => '',     // Timezone abbreviation
439 105
            'zzz' => '',    // Timezone abbreviation
440 105
            'zzzz' => '',   // Timezone full name, not supported by php but we fallback
441 105
            'Z' => '',      // Difference to Greenwich time (GMT) in hours
442 105
            'ZZ' => '',     // Difference to Greenwich time (GMT) in hours
443 105
            'ZZZ' => '',    // Difference to Greenwich time (GMT) in hours
444 105
            'ZZZZ' => '',   // Time Zone: long localized GMT (=OOOO) e.g. GMT-08:00
445 105
            'ZZZZZ' => '',  // Time Zone: ISO8601 extended hms? (=XXXXX)
446 105
            'O' => '',      // Time Zone: short localized GMT e.g. GMT-8
447 105
            'OOOO' => '',   // Time Zone: long localized GMT (=ZZZZ) e.g. GMT-08:00
448 105
            'v' => '',      // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here
449 105
            'vvvv' => '',   // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here
450 105
            'V' => '',      // Time Zone: short time zone ID
451 105
            'VV' => '',     // Time Zone: long time zone ID
452 105
            'VVV' => '',    // Time Zone: time zone exemplar city
453 105
            'VVVV' => '',   // Time Zone: generic location (falls back to OOOO) using the ICU defined fallback here
454 105
            'X' => '',      // Time Zone: ISO8601 basic hm?, with Z for 0, e.g. -08, +0530, Z
455 105
            'XX' => '',     // Time Zone: ISO8601 basic hm, with Z, e.g. -0800, Z
456 105
            'XXX' => '',    // Time Zone: ISO8601 extended hm, with Z, e.g. -08:00, Z
457 105
            'XXXX' => '',   // Time Zone: ISO8601 basic hms?, with Z, e.g. -0800, -075258, Z
458 105
            'XXXXX' => '',  // Time Zone: ISO8601 extended hms?, with Z, e.g. -08:00, -07:52:58, Z
459 105
            'x' => '',      // Time Zone: ISO8601 basic hm?, without Z for 0, e.g. -08, +0530
460 105
            'xx' => '',     // Time Zone: ISO8601 basic hm, without Z, e.g. -0800
461 105
            'xxx' => '',    // Time Zone: ISO8601 extended hm, without Z, e.g. -08:00
462 105
            'xxxx' => '',   // Time Zone: ISO8601 basic hms?, without Z, e.g. -0800, -075258
463 105
            'xxxxx' => '',  // Time Zone: ISO8601 extended hms?, without Z, e.g. -08:00, -07:52:58
464 105
        ]));
465
    }
466
467
    /**
468
     * Converts a date format pattern from [PHP `date()` function format](https://www.php.net/manual/en/function.date)
469
     * to [jQuery UI date format](https://api.jqueryui.com/datepicker/#utility-formatDate).
470
     *
471
     * The conversion is limited to date patterns that do not use escaped characters.
472
     * Patterns like `jS \o\f F Y` which will result in a date like `1st of December 2014` may not be converted correctly
473
     * because of the use of escaped characters.
474
     *
475
     * Pattern constructs that are not supported by the jQuery UI format will be removed.
476
     *
477
     * @param string $pattern date format pattern in PHP `date()` function format.
478
     * @return string The converted date format pattern.
479
     */
480 38
    public static function convertDatePhpToJui($pattern)
481
    {
482
        // https://www.php.net/manual/en/function.date
483 38
        return strtr($pattern, [
484
            // Day
485 38
            'd' => 'dd',    // Day of the month, 2 digits with leading zeros 	01 to 31
486 38
            'D' => 'D',     // A textual representation of a day, three letters 	Mon through Sun
487 38
            'j' => 'd',     // Day of the month without leading zeros 	1 to 31
488 38
            'l' => 'DD',    // A full textual representation of the day of the week 	Sunday through Saturday
489 38
            'N' => '',      // ISO-8601 numeric representation of the day of the week, 1 (for Monday) through 7 (for Sunday)
490 38
            'S' => '',      // English ordinal suffix for the day of the month, 2 characters 	st, nd, rd or th. Works well with j
491 38
            'w' => '',      // Numeric representation of the day of the week 	0 (for Sunday) through 6 (for Saturday)
492 38
            'z' => 'o',     // The day of the year (starting from 0) 	0 through 365
493
            // Week
494 38
            'W' => '',      // ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0) 	Example: 42 (the 42nd week in the year)
495
            // Month
496 38
            'F' => 'MM',    // A full textual representation of a month, January through December
497 38
            'm' => 'mm',    // Numeric representation of a month, with leading zeros 	01 through 12
498 38
            'M' => 'M',     // A short textual representation of a month, three letters 	Jan through Dec
499 38
            'n' => 'm',     // Numeric representation of a month, without leading zeros 	1 through 12
500 38
            't' => '',      // Number of days in the given month 	28 through 31
501
            // Year
502 38
            'L' => '',      // Whether it's a leap year, 1 if it is a leap year, 0 otherwise.
503 38
            'o' => '',      // ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead.
504 38
            'Y' => 'yy',    // A full numeric representation of a year, 4 digits 	Examples: 1999 or 2003
505 38
            'y' => 'y',     // A two digit representation of a year 	Examples: 99 or 03
506
            // Time
507 38
            'a' => '',      // Lowercase Ante meridiem and Post meridiem, am or pm
508 38
            'A' => '',      // Uppercase Ante meridiem and Post meridiem, AM or PM, not supported by ICU but we fallback to lowercase
509 38
            'B' => '',      // Swatch Internet time 	000 through 999
510 38
            'g' => '',      // 12-hour format of an hour without leading zeros 	1 through 12
511 38
            'G' => '',      // 24-hour format of an hour without leading zeros 0 to 23h
512 38
            'h' => '',      // 12-hour format of an hour with leading zeros, 01 to 12 h
513 38
            'H' => '',      // 24-hour format of an hour with leading zeros, 00 to 23 h
514 38
            'i' => '',      // Minutes with leading zeros 	00 to 59
515 38
            's' => '',      // Seconds, with leading zeros 	00 through 59
516 38
            'u' => '',      // Microseconds. Example: 654321
517
            // Timezone
518 38
            'e' => '',      // Timezone identifier. Examples: UTC, GMT, Atlantic/Azores
519 38
            'I' => '',      // Whether or not the date is in daylight saving time, 1 if Daylight Saving Time, 0 otherwise.
520 38
            'O' => '',      // Difference to Greenwich time (GMT) in hours, Example: +0200
521 38
            'P' => '',      // Difference to Greenwich time (GMT) with colon between hours and minutes, Example: +02:00
522 38
            'T' => '',      // Timezone abbreviation, Examples: EST, MDT ...
523 38
            'Z' => '',      // Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. -43200 through 50400
524
            // Full Date/Time
525 38
            'c' => 'yyyy-MM-dd', // ISO 8601 date, e.g. 2004-02-12T15:19:21+00:00, skipping the time here because it is not supported
526 38
            'r' => 'D, d M yy', // RFC 2822 formatted date, Example: Thu, 21 Dec 2000 16:01:07 +0200, skipping the time here because it is not supported
527 38
            'U' => '@',     // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
528 38
        ]);
529
    }
530
531
    /**
532
     * Creates a date/time formatter based on the given parameters.
533
     *
534
     * @param string|null $locale The locale to be used. If null, the application's current language will be used.
535
     * @param string $type The type of formatter ('date', 'time', etc.)
536
     * @param string $pattern The pattern for the IntlDateFormatter.
537
     *
538
     * @return string The resulting pattern after formatter creation.
539
     *
540
     * @throws \Exception If the 'intl' extension is not loaded.
541
     */
542 8
    private static function createFormatter($locale, $type, $pattern)
543
    {
544 8
        if ($locale === null) {
545 2
            $locale = Yii::$app->language;
546
        }
547
548 8
        if ($type === 'date') {
549 4
            $formatter = new IntlDateFormatter($locale, self::$_icuShortFormats[$pattern], IntlDateFormatter::NONE);
550 4
        } elseif ($type === 'time') {
551 2
            $formatter = new IntlDateFormatter($locale, IntlDateFormatter::NONE, self::$_icuShortFormats[$pattern]);
552
        } else {
553 2
            $formatter = new IntlDateFormatter($locale, self::$_icuShortFormats[$pattern], self::$_icuShortFormats[$pattern]);
554
        }
555
556 8
        return $formatter->getPattern();
557
    }
558
}
559