NumberFormat   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 518
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 45
eloc 226
dl 0
loc 518
ccs 156
cts 156
cp 1
rs 8.8
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
B fillBuiltInFormatCodes() 0 97 2
A toFormattedString() 0 3 1
A getBuiltInFormatCode() 0 7 2
A setShortDateFormat() 0 3 1
A setBuiltInFormatCode() 0 11 2
A setDateTimeFormat() 0 3 1
A getSharedComponent() 0 6 1
A setTimeFormat() 0 3 1
A getHashCode() 0 10 2
A builtInFormatCodeIndex() 0 11 2
A exportArray1() 0 6 1
A convertSystemFormats() 0 12 6
A getStyleArray() 0 3 1
A getDateTimeFormat() 0 3 1
A getTimeFormat() 0 3 1
A builtInFormatCode() 0 14 2
A getLongDateFormat() 0 3 1
A applyFromArray() 0 11 3
A setLongDateFormat() 0 3 1
A __construct() 0 8 2
A getShortDateFormat() 0 3 1
A setFormatCode() 0 14 3
B getFormatCode() 0 20 7

How to fix   Complexity   

Complex Class

Complex classes like NumberFormat often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NumberFormat, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Style;
4
5
use PhpOffice\PhpSpreadsheet\RichText\RichText;
6
7
class NumberFormat extends Supervisor
8
{
9
    // Pre-defined formats
10
    const FORMAT_GENERAL = 'General';
11
12
    const FORMAT_TEXT = '@';
13
14
    const FORMAT_NUMBER = '0';
15
    const FORMAT_NUMBER_0 = '0.0';
16
    const FORMAT_NUMBER_00 = '0.00';
17
    const FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00';
18
    const FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-';
19
20
    const FORMAT_PERCENTAGE = '0%';
21
    const FORMAT_PERCENTAGE_0 = '0.0%';
22
    const FORMAT_PERCENTAGE_00 = '0.00%';
23
24
    const FORMAT_DATE_YYYYMMDD = 'yyyy-mm-dd';
25
    const FORMAT_DATE_DDMMYYYY = 'dd/mm/yyyy';
26
    const FORMAT_DATE_DMYSLASH = 'd/m/yy';
27
    const FORMAT_DATE_DMYMINUS = 'd-m-yy';
28
    const FORMAT_DATE_DMMINUS = 'd-m';
29
    const FORMAT_DATE_MYMINUS = 'm-yy';
30
    const FORMAT_DATE_XLSX14 = 'mm-dd-yy';
31
    const FORMAT_DATE_XLSX14_ACTUAL = 'm/d/yyyy';
32
    const FORMAT_DATE_XLSX15 = 'd-mmm-yy';
33
    const FORMAT_DATE_XLSX16 = 'd-mmm';
34
    const FORMAT_DATE_XLSX17 = 'mmm-yy';
35
    const FORMAT_DATE_XLSX22 = 'm/d/yy h:mm';
36
    const FORMAT_DATE_XLSX22_ACTUAL = 'm/d/yyyy h:mm';
37
    const FORMAT_DATE_DATETIME = 'd/m/yy h:mm';
38
    const FORMAT_DATE_TIME1 = 'h:mm AM/PM';
39
    const FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM';
40
    const FORMAT_DATE_TIME3 = 'h:mm';
41
    const FORMAT_DATE_TIME4 = 'h:mm:ss';
42
    const FORMAT_DATE_TIME5 = 'mm:ss';
43
    const FORMAT_DATE_TIME6 = 'h:mm:ss';
44
    const FORMAT_DATE_TIME7 = 'i:s.S';
45
    const FORMAT_DATE_TIME8 = 'h:mm:ss;@';
46
    const FORMAT_DATE_YYYYMMDDSLASH = 'yyyy/mm/dd;@';
47
    const FORMAT_DATE_LONG_DATE = 'dddd, mmmm d, yyyy';
48
49
    const DATE_TIME_OR_DATETIME_ARRAY = [
50
        self::FORMAT_DATE_YYYYMMDD,
51
        self::FORMAT_DATE_DDMMYYYY,
52
        self::FORMAT_DATE_DMYSLASH,
53
        self::FORMAT_DATE_DMYMINUS,
54
        self::FORMAT_DATE_DMMINUS,
55
        self::FORMAT_DATE_MYMINUS,
56
        self::FORMAT_DATE_XLSX14,
57
        self::FORMAT_DATE_XLSX14_ACTUAL,
58
        self::FORMAT_DATE_XLSX15,
59
        self::FORMAT_DATE_XLSX16,
60
        self::FORMAT_DATE_XLSX17,
61
        self::FORMAT_DATE_XLSX22,
62
        self::FORMAT_DATE_XLSX22_ACTUAL,
63
        self::FORMAT_DATE_DATETIME,
64
        self::FORMAT_DATE_TIME1,
65
        self::FORMAT_DATE_TIME2,
66
        self::FORMAT_DATE_TIME3,
67
        self::FORMAT_DATE_TIME4,
68
        self::FORMAT_DATE_TIME5,
69
        self::FORMAT_DATE_TIME6,
70
        self::FORMAT_DATE_TIME7,
71
        self::FORMAT_DATE_TIME8,
72
        self::FORMAT_DATE_YYYYMMDDSLASH,
73
        self::FORMAT_DATE_LONG_DATE,
74
    ];
75
    const TIME_OR_DATETIME_ARRAY = [
76
        self::FORMAT_DATE_XLSX22,
77
        self::FORMAT_DATE_DATETIME,
78
        self::FORMAT_DATE_TIME1,
79
        self::FORMAT_DATE_TIME2,
80
        self::FORMAT_DATE_TIME3,
81
        self::FORMAT_DATE_TIME4,
82
        self::FORMAT_DATE_TIME5,
83
        self::FORMAT_DATE_TIME6,
84
        self::FORMAT_DATE_TIME7,
85
        self::FORMAT_DATE_TIME8,
86
    ];
87
88
    const FORMAT_CURRENCY_USD_INTEGER = '$#,##0_-';
89
    const FORMAT_CURRENCY_USD = '$#,##0.00_-';
90
    const FORMAT_CURRENCY_EUR_INTEGER = '#,##0_-[$€]';
91
    const FORMAT_CURRENCY_EUR = '#,##0.00_-[$€]';
92
    const FORMAT_ACCOUNTING_USD = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
93
    const FORMAT_ACCOUNTING_EUR = '_("€"* #,##0.00_);_("€"* \(#,##0.00\);_("€"* "-"??_);_(@_)';
94
95
    const SHORT_DATE_INDEX = 14;
96
    const DATE_TIME_INDEX = 22;
97
    const FORMAT_SYSDATE_X = '[$-x-sysdate]';
98
    const FORMAT_SYSDATE_F800 = '[$-F800]';
99
    const FORMAT_SYSTIME_X = '[$-x-systime]';
100
    const FORMAT_SYSTIME_F400 = '[$-F400]';
101
102
    protected static string $shortDateFormat = self::FORMAT_DATE_XLSX14_ACTUAL;
103
104
    protected static string $longDateFormat = self::FORMAT_DATE_LONG_DATE;
105
106
    protected static string $dateTimeFormat = self::FORMAT_DATE_XLSX22_ACTUAL;
107
108
    protected static string $timeFormat = self::FORMAT_DATE_TIME2;
109
110
    /**
111
     * Excel built-in number formats.
112
     *
113
     * @var string[]
114
     */
115
    protected static array $builtInFormats;
116
117
    /**
118
     * Excel built-in number formats (flipped, for faster lookups).
119
     *
120
     * @var int[]
121
     */
122
    protected static array $flippedBuiltInFormats;
123
124
    /**
125
     * Format Code.
126
     */
127
    protected ?string $formatCode = self::FORMAT_GENERAL;
128
129
    /**
130
     * Built-in format Code.
131
     *
132
     * @var false|int
133
     */
134
    protected $builtInFormatCode = 0;
135
136
    /**
137
     * Create a new NumberFormat.
138
     *
139
     * @param bool $isSupervisor Flag indicating if this is a supervisor or not
140
     *                                    Leave this value at default unless you understand exactly what
141
     *                                        its ramifications are
142
     * @param bool $isConditional Flag indicating if this is a conditional style or not
143
     *                                    Leave this value at default unless you understand exactly what
144
     *                                        its ramifications are
145
     */
146 10596
    public function __construct(bool $isSupervisor = false, bool $isConditional = false)
147
    {
148
        // Supervisor?
149 10596
        parent::__construct($isSupervisor);
150
151 10596
        if ($isConditional) {
152 402
            $this->formatCode = null;
153 402
            $this->builtInFormatCode = false;
154
        }
155
    }
156
157
    /**
158
     * Get the shared style component for the currently active cell in currently active sheet.
159
     * Only used for style supervisor.
160
     */
161 129
    public function getSharedComponent(): self
162
    {
163
        /** @var Style $parent */
164 129
        $parent = $this->parent;
165
166 129
        return $parent->getSharedComponent()->getNumberFormat();
167
    }
168
169
    /**
170
     * Build style array from subcomponents.
171
     *
172
     * @param mixed[] $array
173
     *
174
     * @return array{numberFormat: mixed[]}
175
     */
176 678
    public function getStyleArray(array $array): array
177
    {
178 678
        return ['numberFormat' => $array];
179
    }
180
181
    /**
182
     * Apply styles from array.
183
     *
184
     * <code>
185
     * $spreadsheet->getActiveSheet()->getStyle('B2')->getNumberFormat()->applyFromArray(
186
     *     [
187
     *         'formatCode' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE
188
     *     ]
189
     * );
190
     * </code>
191
     *
192
     * @param string[] $styleArray Array containing style information
193
     *
194
     * @return $this
195
     */
196 716
    public function applyFromArray(array $styleArray): static
197
    {
198 716
        if ($this->isSupervisor) {
199 1
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
200
        } else {
201 716
            if (isset($styleArray['formatCode'])) {
202 716
                $this->setFormatCode($styleArray['formatCode']);
203
            }
204
        }
205
206 716
        return $this;
207
    }
208
209
    /**
210
     * Get Format Code.
211
     */
212 866
    public function getFormatCode(bool $extended = false): ?string
213
    {
214 866
        if ($this->isSupervisor) {
215 129
            return $this->getSharedComponent()->getFormatCode($extended);
216
        }
217 866
        $builtin = $this->getBuiltInFormatCode();
218 866
        if (is_int($builtin)) {
219 448
            if ($extended) {
220 107
                if ($builtin === self::SHORT_DATE_INDEX) {
221 2
                    return self::$shortDateFormat;
222
                }
223 107
                if ($builtin === self::DATE_TIME_INDEX) {
224 2
                    return self::$dateTimeFormat;
225
                }
226
            }
227
228 446
            return self::builtInFormatCode($builtin);
229
        }
230
231 486
        return $extended ? self::convertSystemFormats($this->formatCode) : $this->formatCode;
232
    }
233
234 175
    public static function convertSystemFormats(?string $formatCode): ?string
235
    {
236 175
        if (is_string($formatCode)) {
237 175
            if (stripos($formatCode, self::FORMAT_SYSDATE_F800) !== false || stripos($formatCode, self::FORMAT_SYSDATE_X) !== false) {
238 5
                return self::$longDateFormat;
239
            }
240 173
            if (stripos($formatCode, self::FORMAT_SYSTIME_F400) !== false || stripos($formatCode, self::FORMAT_SYSTIME_X) !== false) {
241 4
                return self::$timeFormat;
242
            }
243
        }
244
245 169
        return $formatCode;
246
    }
247
248
    /**
249
     * Set Format Code.
250
     *
251
     * @param string $formatCode see self::FORMAT_*
252
     *
253
     * @return $this
254
     */
255 1495
    public function setFormatCode(string $formatCode): static
256
    {
257 1495
        if ($formatCode == '') {
258 16
            $formatCode = self::FORMAT_GENERAL;
259
        }
260 1495
        if ($this->isSupervisor) {
261 677
            $styleArray = $this->getStyleArray(['formatCode' => $formatCode]);
262 677
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
263
        } else {
264 1485
            $this->formatCode = $formatCode;
265 1485
            $this->builtInFormatCode = self::builtInFormatCodeIndex($formatCode);
266
        }
267
268 1485
        return $this;
269
    }
270
271
    /**
272
     * Get Built-In Format Code.
273
     *
274
     * @return false|int
275
     */
276 1205
    public function getBuiltInFormatCode()
277
    {
278 1205
        if ($this->isSupervisor) {
279 1
            return $this->getSharedComponent()->getBuiltInFormatCode();
280
        }
281
282 1205
        return $this->builtInFormatCode;
283
    }
284
285
    /**
286
     * Set Built-In Format Code.
287
     *
288
     * @param int $formatCodeIndex Id of the built-in format code to use
289
     *
290
     * @return $this
291
     */
292 3
    public function setBuiltInFormatCode(int $formatCodeIndex): static
293
    {
294 3
        if ($this->isSupervisor) {
295 3
            $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($formatCodeIndex)]);
296 3
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
297
        } else {
298 1
            $this->builtInFormatCode = $formatCodeIndex;
299 1
            $this->formatCode = self::builtInFormatCode($formatCodeIndex);
300
        }
301
302 3
        return $this;
303
    }
304
305
    /**
306
     * Fill built-in format codes.
307
     */
308 1706
    private static function fillBuiltInFormatCodes(): void
309
    {
310
        //  [MS-OI29500: Microsoft Office Implementation Information for ISO/IEC-29500 Standard Compliance]
311
        //  18.8.30. numFmt (Number Format)
312
        //
313
        //  The ECMA standard defines built-in format IDs
314
        //      14: "mm-dd-yy"
315
        //      22: "m/d/yy h:mm"
316
        //      37: "#,##0 ;(#,##0)"
317
        //      38: "#,##0 ;[Red](#,##0)"
318
        //      39: "#,##0.00;(#,##0.00)"
319
        //      40: "#,##0.00;[Red](#,##0.00)"
320
        //      47: "mmss.0"
321
        //      KOR fmt 55: "yyyy-mm-dd"
322
        //  Excel defines built-in format IDs
323
        //      14: "m/d/yyyy"
324
        //      22: "m/d/yyyy h:mm"
325
        //      37: "#,##0_);(#,##0)"
326
        //      38: "#,##0_);[Red](#,##0)"
327
        //      39: "#,##0.00_);(#,##0.00)"
328
        //      40: "#,##0.00_);[Red](#,##0.00)"
329
        //      47: "mm:ss.0"
330
        //      KOR fmt 55: "yyyy/mm/dd"
331
332
        // Built-in format codes
333 1706
        if (empty(self::$builtInFormats)) {
334 171
            self::$builtInFormats = [];
335
336
            // General
337 171
            self::$builtInFormats[0] = self::FORMAT_GENERAL;
338 171
            self::$builtInFormats[1] = '0';
339 171
            self::$builtInFormats[2] = '0.00';
340 171
            self::$builtInFormats[3] = '#,##0';
341 171
            self::$builtInFormats[4] = '#,##0.00';
342
343 171
            self::$builtInFormats[9] = '0%';
344 171
            self::$builtInFormats[10] = '0.00%';
345 171
            self::$builtInFormats[11] = '0.00E+00';
346 171
            self::$builtInFormats[12] = '# ?/?';
347 171
            self::$builtInFormats[13] = '# ??/??';
348 171
            self::$builtInFormats[14] = self::FORMAT_DATE_XLSX14_ACTUAL; // Despite ECMA 'mm-dd-yy';
349 171
            self::$builtInFormats[15] = self::FORMAT_DATE_XLSX15;
350 171
            self::$builtInFormats[16] = 'd-mmm';
351 171
            self::$builtInFormats[17] = 'mmm-yy';
352 171
            self::$builtInFormats[18] = 'h:mm AM/PM';
353 171
            self::$builtInFormats[19] = 'h:mm:ss AM/PM';
354 171
            self::$builtInFormats[20] = 'h:mm';
355 171
            self::$builtInFormats[21] = 'h:mm:ss';
356 171
            self::$builtInFormats[22] = self::FORMAT_DATE_XLSX22_ACTUAL; // Despite ECMA 'm/d/yy h:mm';
357
358 171
            self::$builtInFormats[37] = '#,##0_);(#,##0)'; //  Despite ECMA '#,##0 ;(#,##0)';
359 171
            self::$builtInFormats[38] = '#,##0_);[Red](#,##0)'; //  Despite ECMA '#,##0 ;[Red](#,##0)';
360 171
            self::$builtInFormats[39] = '#,##0.00_);(#,##0.00)'; //  Despite ECMA '#,##0.00;(#,##0.00)';
361 171
            self::$builtInFormats[40] = '#,##0.00_);[Red](#,##0.00)'; //  Despite ECMA '#,##0.00;[Red](#,##0.00)';
362
363 171
            self::$builtInFormats[44] = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
364 171
            self::$builtInFormats[45] = 'mm:ss';
365 171
            self::$builtInFormats[46] = '[h]:mm:ss';
366 171
            self::$builtInFormats[47] = 'mm:ss.0'; //  Despite ECMA 'mmss.0';
367 171
            self::$builtInFormats[48] = '##0.0E+0';
368 171
            self::$builtInFormats[49] = '@';
369
370
            // CHT
371 171
            self::$builtInFormats[27] = '[$-404]e/m/d';
372 171
            self::$builtInFormats[30] = 'm/d/yy';
373 171
            self::$builtInFormats[36] = '[$-404]e/m/d';
374 171
            self::$builtInFormats[50] = '[$-404]e/m/d';
375 171
            self::$builtInFormats[57] = '[$-404]e/m/d';
376
377
            // THA
378 171
            self::$builtInFormats[59] = 't0';
379 171
            self::$builtInFormats[60] = 't0.00';
380 171
            self::$builtInFormats[61] = 't#,##0';
381 171
            self::$builtInFormats[62] = 't#,##0.00';
382 171
            self::$builtInFormats[67] = 't0%';
383 171
            self::$builtInFormats[68] = 't0.00%';
384 171
            self::$builtInFormats[69] = 't# ?/?';
385 171
            self::$builtInFormats[70] = 't# ??/??';
386
387
            // JPN
388 171
            self::$builtInFormats[28] = '[$-411]ggge"年"m"月"d"日"';
389 171
            self::$builtInFormats[29] = '[$-411]ggge"年"m"月"d"日"';
390 171
            self::$builtInFormats[31] = 'yyyy"年"m"月"d"日"';
391 171
            self::$builtInFormats[32] = 'h"時"mm"分"';
392 171
            self::$builtInFormats[33] = 'h"時"mm"分"ss"秒"';
393 171
            self::$builtInFormats[34] = 'yyyy"年"m"月"';
394 171
            self::$builtInFormats[35] = 'm"月"d"日"';
395 171
            self::$builtInFormats[51] = '[$-411]ggge"年"m"月"d"日"';
396 171
            self::$builtInFormats[52] = 'yyyy"年"m"月"';
397 171
            self::$builtInFormats[53] = 'm"月"d"日"';
398 171
            self::$builtInFormats[54] = '[$-411]ggge"年"m"月"d"日"';
399 171
            self::$builtInFormats[55] = 'yyyy"年"m"月"';
400 171
            self::$builtInFormats[56] = 'm"月"d"日"';
401 171
            self::$builtInFormats[58] = '[$-411]ggge"年"m"月"d"日"';
402
403
            // Flip array (for faster lookups)
404 171
            self::$flippedBuiltInFormats = array_flip(self::$builtInFormats);
405
        }
406
    }
407
408
    /**
409
     * Get built-in format code.
410
     */
411 1172
    public static function builtInFormatCode(int $index): string
412
    {
413
        // Clean parameter
414 1172
        $index = (int) $index;
415
416
        // Ensure built-in format codes are available
417 1172
        self::fillBuiltInFormatCodes();
418
419
        // Lookup format code
420 1172
        if (isset(self::$builtInFormats[$index])) {
421 1172
            return self::$builtInFormats[$index];
422
        }
423
424 20
        return '';
425
    }
426
427
    /**
428
     * Get built-in format code index.
429
     *
430
     * @return false|int
431
     */
432 1485
    public static function builtInFormatCodeIndex(string $formatCodeIndex)
433
    {
434
        // Ensure built-in format codes are available
435 1485
        self::fillBuiltInFormatCodes();
436
437
        // Lookup format code
438 1485
        if (array_key_exists($formatCodeIndex, self::$flippedBuiltInFormats)) {
439 1083
            return self::$flippedBuiltInFormats[$formatCodeIndex];
440
        }
441
442 768
        return false;
443
    }
444
445
    /**
446
     * Get hash code.
447
     *
448
     * @return string Hash code
449
     */
450 1257
    public function getHashCode(): string
451
    {
452 1257
        if ($this->isSupervisor) {
453 3
            return $this->getSharedComponent()->getHashCode();
454
        }
455
456 1257
        return md5(
457 1257
            $this->formatCode
458 1257
            . $this->builtInFormatCode
459 1257
            . __CLASS__
460 1257
        );
461
    }
462
463
    /**
464
     * Convert a value in a pre-defined format to a PHP string.
465
     *
466
     * @param null|bool|float|int|RichText|string $value Value to format
467
     * @param string $format Format code: see = self::FORMAT_* for predefined values;
468
     *                          or can be any valid MS Excel custom format string
469
     * @param ?mixed[] $callBack Callback function for additional formatting of string
470
     *
471
     * @return string Formatted string
472
     */
473 1262
    public static function toFormattedString(mixed $value, string $format, ?array $callBack = null): string
474
    {
475 1262
        return NumberFormat\Formatter::toFormattedString($value, $format, $callBack);
476
    }
477
478
    /** @return mixed[] */
479 14
    protected function exportArray1(): array
480
    {
481 14
        $exportedArray = [];
482 14
        $this->exportArray2($exportedArray, 'formatCode', $this->getFormatCode());
483
484 14
        return $exportedArray;
485
    }
486
487 2
    public static function getShortDateFormat(): string
488
    {
489 2
        return self::$shortDateFormat;
490
    }
491
492 2
    public static function setShortDateFormat(string $shortDateFormat): void
493
    {
494 2
        self::$shortDateFormat = $shortDateFormat;
495
    }
496
497 2
    public static function getLongDateFormat(): string
498
    {
499 2
        return self::$longDateFormat;
500
    }
501
502 2
    public static function setLongDateFormat(string $longDateFormat): void
503
    {
504 2
        self::$longDateFormat = $longDateFormat;
505
    }
506
507 2
    public static function getDateTimeFormat(): string
508
    {
509 2
        return self::$dateTimeFormat;
510
    }
511
512 2
    public static function setDateTimeFormat(string $dateTimeFormat): void
513
    {
514 2
        self::$dateTimeFormat = $dateTimeFormat;
515
    }
516
517 2
    public static function getTimeFormat(): string
518
    {
519 2
        return self::$timeFormat;
520
    }
521
522 2
    public static function setTimeFormat(string $timeFormat): void
523
    {
524 2
        self::$timeFormat = $timeFormat;
525
    }
526
}
527