Passed
Pull Request — master (#25)
by Alexander
14:50
created

FormatConverterHelper   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 8
eloc 220
c 2
b 0
f 0
dl 0
loc 290
rs 10

2 Methods

Rating   Name   Duplication   Size   Complexity  
B convertDatePhpToIcu() 0 94 1
B convertDateIcuToPhp() 0 127 7
1
<?php
2
namespace Yiisoft\Validator;
3
4
use IntlDateFormatter;
5
6
/**
7
 * FormatConverterHelper provides concrete implementation for [[FormatConverter]].
8
 *
9
 * Do not use BaseFormatConverter. Use [[FormatConverter]] instead.
10
 */
11
class FormatConverterHelper
12
{
13
    /**
14
     * @var array the php fallback definition to use for the ICU short patterns `short`, `medium`, `long` and `full`.
15
     * This is used as fallback when the intl extension is not installed.
16
     */
17
    private static $phpFallbackDatePatterns = [
18
        'short' => [
19
            'date' => 'n/j/y',
20
            'time' => 'H:i',
21
            'datetime' => 'n/j/y H:i',
22
        ],
23
        'medium' => [
24
            'date' => 'M j, Y',
25
            'time' => 'g:i:s A',
26
            'datetime' => 'M j, Y g:i:s A',
27
        ],
28
        'long' => [
29
            'date' => 'F j, Y',
30
            'time' => 'g:i:sA',
31
            'datetime' => 'F j, Y g:i:sA',
32
        ],
33
        'full' => [
34
            'date' => 'l, F j, Y',
35
            'time' => 'g:i:sA T',
36
            'datetime' => 'l, F j, Y g:i:sA T',
37
        ],
38
    ];
39
40
    private static $icuShortFormats = [
41
        'short' => 3, // IntlDateFormatter::SHORT,
42
        'medium' => 2, // IntlDateFormatter::MEDIUM,
43
        'long' => 1, // IntlDateFormatter::LONG,
44
        'full' => 0, // IntlDateFormatter::FULL,
45
    ];
46
47
48
    /**
49
     * Converts a date format pattern from [ICU format][] to [php date() function format][].
50
     *
51
     * The conversion is limited to date patterns that do not use escaped characters.
52
     * Patterns like `d 'of' MMMM yyyy` which will result in a date like `1 of December 2014` may not be converted correctly
53
     * because of the use of escaped characters.
54
     *
55
     * Pattern constructs that are not supported by the PHP format will be removed.
56
     *
57
     * [php date() function format]: https://secure.php.net/manual/en/function.date.php
58
     * [ICU format]: http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
59
     *
60
     * @param string $pattern date format pattern in ICU format.
61
     * @param string $type 'date', 'time', or 'datetime'.
62
     * @param string $locale the locale to use for converting ICU short patterns `short`, `medium`, `long` and `full`.
63
     * If not given, `Yii::$app->language` will be used.
64
     * @return string The converted date format pattern.
65
     */
66
    public static function convertDateIcuToPhp($pattern, $type = 'date', $locale): string
67
    {
68
        if (isset(self::$icuShortFormats[$pattern])) {
69
            if (extension_loaded('intl')) {
70
                if ($type === 'date') {
71
                    $formatter = new IntlDateFormatter($locale, self::$icuShortFormats[$pattern], IntlDateFormatter::NONE);
72
                } elseif ($type === 'time') {
73
                    $formatter = new IntlDateFormatter($locale, IntlDateFormatter::NONE, self::$icuShortFormats[$pattern]);
74
                } else {
75
                    $formatter = new IntlDateFormatter($locale, self::$icuShortFormats[$pattern], self::$icuShortFormats[$pattern]);
76
                }
77
                $pattern = $formatter->getPattern();
78
            } else {
79
                return static::$phpFallbackDatePatterns[$pattern][$type];
0 ignored issues
show
Bug introduced by
Since $phpFallbackDatePatterns is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $phpFallbackDatePatterns to at least protected.
Loading history...
80
            }
81
        }
82
        // http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
83
        // escaped text
84
        $escaped = [];
85
        if (preg_match_all('/(?<!\')\'(.*?[^\'])\'(?!\')/', $pattern, $matches, PREG_SET_ORDER)) {
86
            foreach ($matches as $match) {
87
                $match[1] = str_replace('\'\'', '\'', $match[1]);
88
                $escaped[$match[0]] = '\\' . implode('\\', preg_split('//u', $match[1], -1, PREG_SPLIT_NO_EMPTY));
0 ignored issues
show
Bug introduced by
It seems like preg_split('//u', $match...or\PREG_SPLIT_NO_EMPTY) can also be of type false; 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

88
                $escaped[$match[0]] = '\\' . implode('\\', /** @scrutinizer ignore-type */ preg_split('//u', $match[1], -1, PREG_SPLIT_NO_EMPTY));
Loading history...
89
            }
90
        }
91
92
        return strtr($pattern, array_merge($escaped, [
93
            "''" => "\\'",  // two single quotes produce one
94
            'G' => '',      // era designator like (Anno Domini)
95
            'Y' => 'o',     // 4digit year of "Week of Year"
96
            'y' => 'Y',     // 4digit year e.g. 2014
97
            'yyyy' => 'Y',  // 4digit year e.g. 2014
98
            'yy' => 'y',    // 2digit year number eg. 14
99
            'u' => '',      // extended year e.g. 4601
100
            'U' => '',      // cyclic year name, as in Chinese lunar calendar
101
            'r' => '',      // related Gregorian year e.g. 1996
102
            'Q' => '',      // number of quarter
103
            'QQ' => '',     // number of quarter '02'
104
            'QQQ' => '',    // quarter 'Q2'
105
            'QQQQ' => '',   // quarter '2nd quarter'
106
            'QQQQQ' => '',  // number of quarter '2'
107
            'q' => '',      // number of Stand Alone quarter
108
            'qq' => '',     // number of Stand Alone quarter '02'
109
            'qqq' => '',    // Stand Alone quarter 'Q2'
110
            'qqqq' => '',   // Stand Alone quarter '2nd quarter'
111
            'qqqqq' => '',  // number of Stand Alone quarter '2'
112
            'M' => 'n',     // Numeric representation of a month, without leading zeros
113
            'MM' => 'm',    // Numeric representation of a month, with leading zeros
114
            'MMM' => 'M',   // A short textual representation of a month, three letters
115
            'MMMM' => 'F',  // A full textual representation of a month, such as January or March
116
            'MMMMM' => '',
117
            'L' => 'n',     // Stand alone month in year
118
            'LL' => 'm',    // Stand alone month in year
119
            'LLL' => 'M',   // Stand alone month in year
120
            'LLLL' => 'F',  // Stand alone month in year
121
            'LLLLL' => '',  // Stand alone month in year
122
            'w' => 'W',     // ISO-8601 week number of year
123
            'ww' => 'W',    // ISO-8601 week number of year
124
            'W' => '',      // week of the current month
125
            'd' => 'j',     // day without leading zeros
126
            'dd' => 'd',    // day with leading zeros
127
            'D' => 'z',     // day of the year 0 to 365
128
            'F' => '',      // Day of Week in Month. eg. 2nd Wednesday in July
129
            'g' => '',      // Modified Julian day. This is different from the conventional Julian day number in two regards.
130
            'E' => 'D',     // day of week written in short form eg. Sun
131
            'EE' => 'D',
132
            'EEE' => 'D',
133
            'EEEE' => 'l',  // day of week fully written eg. Sunday
134
            'EEEEE' => '',
135
            'EEEEEE' => '',
136
            'e' => 'N',     // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun
137
            'ee' => 'N',    // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year
138
            'eee' => 'D',
139
            'eeee' => 'l',
140
            'eeeee' => '',
141
            'eeeeee' => '',
142
            'c' => 'N',     // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun
143
            'cc' => 'N',    // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year
144
            'ccc' => 'D',
145
            'cccc' => 'l',
146
            'ccccc' => '',
147
            'cccccc' => '',
148
            'a' => 'A',     // AM/PM marker
149
            'h' => 'g',     // 12-hour format of an hour without leading zeros 1 to 12h
150
            'hh' => 'h',    // 12-hour format of an hour with leading zeros, 01 to 12 h
151
            'H' => 'G',     // 24-hour format of an hour without leading zeros 0 to 23h
152
            'HH' => 'H',    // 24-hour format of an hour with leading zeros, 00 to 23 h
153
            'k' => '',      // hour in day (1~24)
154
            'kk' => '',     // hour in day (1~24)
155
            'K' => '',      // hour in am/pm (0~11)
156
            'KK' => '',     // hour in am/pm (0~11)
157
            'm' => 'i',     // Minutes without leading zeros, not supported by php but we fallback
158
            'mm' => 'i',    // Minutes with leading zeros
159
            's' => 's',     // Seconds, without leading zeros, not supported by php but we fallback
160
            'ss' => 's',    // Seconds, with leading zeros
161
            'S' => '',      // fractional second
162
            'SS' => '',     // fractional second
163
            'SSS' => '',    // fractional second
164
            'SSSS' => '',   // fractional second
165
            'A' => '',      // milliseconds in day
166
            'z' => 'T',     // Timezone abbreviation
167
            'zz' => 'T',    // Timezone abbreviation
168
            'zzz' => 'T',   // Timezone abbreviation
169
            'zzzz' => 'T',  // Timezone full name, not supported by php but we fallback
170
            'Z' => 'O',     // Difference to Greenwich time (GMT) in hours
171
            'ZZ' => 'O',    // Difference to Greenwich time (GMT) in hours
172
            'ZZZ' => 'O',   // Difference to Greenwich time (GMT) in hours
173
            'ZZZZ' => '\G\M\TP', // Time Zone: long localized GMT (=OOOO) e.g. GMT-08:00
174
            'ZZZZZ' => '',  //  TIme Zone: ISO8601 extended hms? (=XXXXX)
175
            'O' => '',      // Time Zone: short localized GMT e.g. GMT-8
176
            'OOOO' => '\G\M\TP', //  Time Zone: long localized GMT (=ZZZZ) e.g. GMT-08:00
177
            'v' => '\G\M\TP', // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here
178
            'vvvv' => '\G\M\TP', // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here
179
            'V' => '',      // Time Zone: short time zone ID
180
            'VV' => 'e',    // Time Zone: long time zone ID
181
            'VVV' => '',    // Time Zone: time zone exemplar city
182
            'VVVV' => '\G\M\TP', // Time Zone: generic location (falls back to OOOO) using the ICU defined fallback here
183
            'X' => '',      // Time Zone: ISO8601 basic hm?, with Z for 0, e.g. -08, +0530, Z
184
            'XX' => 'O, \Z', // Time Zone: ISO8601 basic hm, with Z, e.g. -0800, Z
185
            'XXX' => 'P, \Z',    // Time Zone: ISO8601 extended hm, with Z, e.g. -08:00, Z
186
            'XXXX' => '',   // Time Zone: ISO8601 basic hms?, with Z, e.g. -0800, -075258, Z
187
            'XXXXX' => '',  // Time Zone: ISO8601 extended hms?, with Z, e.g. -08:00, -07:52:58, Z
188
            'x' => '',      // Time Zone: ISO8601 basic hm?, without Z for 0, e.g. -08, +0530
189
            'xx' => 'O',    // Time Zone: ISO8601 basic hm, without Z, e.g. -0800
190
            'xxx' => 'P',   // Time Zone: ISO8601 extended hm, without Z, e.g. -08:00
191
            'xxxx' => '',   // Time Zone: ISO8601 basic hms?, without Z, e.g. -0800, -075258
192
            'xxxxx' => '',  // Time Zone: ISO8601 extended hms?, without Z, e.g. -08:00, -07:52:58
193
        ]));
194
    }
195
196
    /**
197
     * Converts a date format pattern from [php date() function format][] to [ICU format][].
198
     *
199
     * Pattern constructs that are not supported by the ICU format will be removed.
200
     *
201
     * [php date() function format]: https://secure.php.net/manual/en/function.date.php
202
     * [ICU format]: http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
203
     *
204
     * @param string $pattern date format pattern in php date()-function format.
205
     * @return string The converted date format pattern.
206
     */
207
    public static function convertDatePhpToIcu(string $pattern): string
208
    {
209
        // https://secure.php.net/manual/en/function.date.php
210
        $result = strtr($pattern, [
211
            "'" => "''''",  // single `'` should be encoded as `''`, which internally should be encoded as `''''`
212
            // Day
213
            '\d' => "'d'",
214
            'd' => 'dd',    // Day of the month, 2 digits with leading zeros 	01 to 31
215
            '\D' => "'D'",
216
            'D' => 'eee',   // A textual representation of a day, three letters 	Mon through Sun
217
            '\j' => "'j'",
218
            'j' => 'd',     // Day of the month without leading zeros 	1 to 31
219
            '\l' => "'l'",
220
            'l' => 'eeee',  // A full textual representation of the day of the week 	Sunday through Saturday
221
            '\N' => "'N'",
222
            'N' => 'e',     // ISO-8601 numeric representation of the day of the week, 1 (for Monday) through 7 (for Sunday)
223
            '\S' => "'S'",
224
            'S' => '',      // English ordinal suffix for the day of the month, 2 characters 	st, nd, rd or th. Works well with j
225
            '\w' => "'w'",
226
            'w' => '',      // Numeric representation of the day of the week 	0 (for Sunday) through 6 (for Saturday)
227
            '\z' => "'z'",
228
            'z' => 'D',     // The day of the year (starting from 0) 	0 through 365
229
            // Week
230
            '\W' => "'W'",
231
            '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)
232
            // Month
233
            '\F' => "'F'",
234
            'F' => 'MMMM',  // A full textual representation of a month, January through December
235
            '\m' => "'m'",
236
            'm' => 'MM',    // Numeric representation of a month, with leading zeros 	01 through 12
237
            '\M' => "'M'",
238
            'M' => 'MMM',   // A short textual representation of a month, three letters 	Jan through Dec
239
            '\n' => "'n'",
240
            'n' => 'M',     // Numeric representation of a month, without leading zeros 	1 through 12, not supported by ICU but we fallback to "with leading zero"
241
            '\t' => "'t'",
242
            't' => '',      // Number of days in the given month 	28 through 31
243
            // Year
244
            '\L' => "'L'",
245
            'L' => '',      // Whether it's a leap year, 1 if it is a leap year, 0 otherwise.
246
            '\o' => "'o'",
247
            '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.
248
            '\Y' => "'Y'",
249
            'Y' => 'yyyy',  // A full numeric representation of a year, 4 digits 	Examples: 1999 or 2003
250
            '\y' => "'y'",
251
            'y' => 'yy',    // A two digit representation of a year 	Examples: 99 or 03
252
            // Time
253
            '\a' => "'a'",
254
            'a' => 'a',     // Lowercase Ante meridiem and Post meridiem, am or pm
255
            '\A' => "'A'",
256
            'A' => 'a',     // Uppercase Ante meridiem and Post meridiem, AM or PM, not supported by ICU but we fallback to lowercase
257
            '\B' => "'B'",
258
            'B' => '',      // Swatch Internet time 	000 through 999
259
            '\g' => "'g'",
260
            'g' => 'h',     // 12-hour format of an hour without leading zeros 	1 through 12
261
            '\G' => "'G'",
262
            'G' => 'H',     // 24-hour format of an hour without leading zeros 0 to 23h
263
            '\h' => "'h'",
264
            'h' => 'hh',    // 12-hour format of an hour with leading zeros, 01 to 12 h
265
            '\H' => "'H'",
266
            'H' => 'HH',    // 24-hour format of an hour with leading zeros, 00 to 23 h
267
            '\i' => "'i'",
268
            'i' => 'mm',    // Minutes with leading zeros 	00 to 59
269
            '\s' => "'s'",
270
            's' => 'ss',    // Seconds, with leading zeros 	00 through 59
271
            '\u' => "'u'",
272
            'u' => '',      // Microseconds. Example: 654321
273
            // Timezone
274
            '\e' => "'e'",
275
            'e' => 'VV',    // Timezone identifier. Examples: UTC, GMT, Atlantic/Azores
276
            '\I' => "'I'",
277
            'I' => '',      // Whether or not the date is in daylight saving time, 1 if Daylight Saving Time, 0 otherwise.
278
            '\O' => "'O'",
279
            'O' => 'xx',    // Difference to Greenwich time (GMT) in hours, Example: +0200
280
            '\P' => "'P'",
281
            'P' => 'xxx',   // Difference to Greenwich time (GMT) with colon between hours and minutes, Example: +02:00
282
            '\T' => "'T'",
283
            'T' => 'zzz',   // Timezone abbreviation, Examples: EST, MDT ...
284
            '\Z' => "'Z'",
285
            '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
286
            // Full Date/Time
287
            '\c' => "'c'",
288
            'c' => "yyyy-MM-dd'T'HH:mm:ssxxx", // ISO 8601 date, e.g. 2004-02-12T15:19:21+00:00
289
            '\r' => "'r'",
290
            'r' => 'eee, dd MMM yyyy HH:mm:ss xx', // RFC 2822 formatted date, Example: Thu, 21 Dec 2000 16:01:07 +0200
291
            '\U' => "'U'",
292
            'U' => '',      // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
293
            '\\\\' => '\\',
294
        ]);
295
296
        // remove `''` - the're result of consecutive escaped chars (`\A\B` will be `'A''B'`, but should be `'AB'`)
297
        // real `'` are encoded as `''''`
298
        return strtr($result, [
299
            "''''" => "''",
300
            "''" => '',
301
        ]);
302
    }
303
}
304