Failed Conditions
Push — develop ( d9bd45...1dfd4d )
by Adrien
22:44
created

Date::isDateTimeFormatCode()   C

Complexity

Conditions 32
Paths 30

Size

Total Lines 65
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 32.016

Importance

Changes 0
Metric Value
cc 32
eloc 40
nc 30
nop 1
dl 0
loc 65
ccs 39
cts 40
cp 0.975
crap 32.016
rs 5.7013
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared;
4
5
use DateTimeInterface;
6
use DateTimeZone;
7
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
8
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
9
use PhpOffice\PhpSpreadsheet\Cell\Cell;
10
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
11
12
class Date
13
{
14
    /** constants */
15
    const CALENDAR_WINDOWS_1900 = 1900; //    Base date of 1st Jan 1900 = 1.0
16
    const CALENDAR_MAC_1904 = 1904; //    Base date of 2nd Jan 1904 = 1.0
17
18
    /*
19
     * Names of the months of the year, indexed by shortname
20
     * Planned usage for locale settings
21
     *
22
     * @public
23
     * @var string[]
24
     */
25
    public static $monthNames = [
26
        'Jan' => 'January',
27
        'Feb' => 'February',
28
        'Mar' => 'March',
29
        'Apr' => 'April',
30
        'May' => 'May',
31
        'Jun' => 'June',
32
        'Jul' => 'July',
33
        'Aug' => 'August',
34
        'Sep' => 'September',
35
        'Oct' => 'October',
36
        'Nov' => 'November',
37
        'Dec' => 'December',
38
    ];
39
40
    /*
41
     * @public
42
     * @var string[]
43
     */
44
    public static $numberSuffixes = [
45
        'st',
46
        'nd',
47
        'rd',
48
        'th',
49
    ];
50
51
    /*
52
     * Base calendar year to use for calculations
53
     * Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904)
54
     *
55
     * @private
56
     * @var int
57
     */
58
    protected static $excelCalendar = self::CALENDAR_WINDOWS_1900;
59
60
    /*
61
     * Default timezone to use for DateTime objects
62
     *
63
     * @private
64
     * @var null|\DateTimeZone
65
     */
66
    protected static $defaultTimeZone;
67
68
    /**
69
     * Set the Excel calendar (Windows 1900 or Mac 1904).
70
     *
71
     * @param int $baseDate Excel base date (1900 or 1904)
72
     *
73
     * @return bool Success or failure
74
     */
75 121
    public static function setExcelCalendar($baseDate)
76
    {
77 121
        if (($baseDate == self::CALENDAR_WINDOWS_1900) ||
78 121
            ($baseDate == self::CALENDAR_MAC_1904)) {
79 120
            self::$excelCalendar = $baseDate;
80
81 120
            return true;
82
        }
83
84 1
        return false;
85
    }
86
87
    /**
88
     * Return the Excel calendar (Windows 1900 or Mac 1904).
89
     *
90
     * @return int Excel base date (1900 or 1904)
91
     */
92 158
    public static function getExcelCalendar()
93
    {
94 158
        return self::$excelCalendar;
95
    }
96
97
    /**
98
     * Set the Default timezone to use for dates.
99
     *
100
     * @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
101
     *
102
     * @throws \Exception
103
     *
104
     * @return bool Success or failure
105
     * @return bool Success or failure
106
     */
107
    public static function setDefaultTimezone($timeZone)
108
    {
109
        if ($timeZone = self::validateTimeZone($timeZone)) {
110
            self::$defaultTimeZone = $timeZone;
111
112
            return true;
113
        }
114
115
        return false;
116
    }
117
118
    /**
119
     * Return the Default timezone being used for dates.
120
     *
121
     * @return DateTimeZone The timezone being used as default for Excel timestamp to PHP DateTime object
122
     */
123 430
    public static function getDefaultTimezone()
124
    {
125 430
        if (self::$defaultTimeZone === null) {
126 20
            self::$defaultTimeZone = new DateTimeZone('UTC');
127
        }
128
129 430
        return self::$defaultTimeZone;
130
    }
131
132
    /**
133
     * Validate a timezone.
134
     *
135
     * @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
136
     *
137
     * @throws \Exception
138
     *
139
     * @return DateTimeZone The timezone as a timezone object
140
     * @return DateTimeZone The timezone as a timezone object
141
     */
142 22
    protected static function validateTimeZone($timeZone)
143
    {
144 22
        if (is_object($timeZone) && $timeZone instanceof DateTimeZone) {
145
            return $timeZone;
146 22
        } elseif (is_string($timeZone)) {
147 22
            return new DateTimeZone($timeZone);
148
        }
149
150
        throw new \Exception('Invalid timezone');
151
    }
152
153
    /**
154
     * Convert a MS serialized datetime value from Excel to a PHP Date/Time object.
155
     *
156
     * @param float|int $excelTimestamp MS Excel serialized date/time value
157
     * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
158
     *                                                                        if you don't want to treat it as a UTC value
159
     *                                                                    Use the default (UST) unless you absolutely need a conversion
160
     *
161
     * @throws \Exception
162
     *
163
     * @return \DateTime PHP date/time object
164
     */
165 452
    public static function excelToDateTimeObject($excelTimestamp, $timeZone = null)
166
    {
167 452
        $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone);
168 452
        if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
169 449
            if ($excelTimestamp < 1.0) {
170
                // Unix timestamp base date
171 49
                $baseDate = new \DateTime('1970-01-01', $timeZone);
172
            } else {
173
                // MS Excel calendar base dates
174 405
                if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
175
                    // Allow adjustment for 1900 Leap Year in MS Excel
176 399
                    $baseDate = ($excelTimestamp < 60) ? new \DateTime('1899-12-31', $timeZone) : new \DateTime('1899-12-30', $timeZone);
177
                } else {
178 449
                    $baseDate = new \DateTime('1904-01-01', $timeZone);
179
                }
180
            }
181
        } else {
182 9
            $baseDate = new \DateTime('1899-12-30', $timeZone);
183
        }
184
185 452
        $days = floor($excelTimestamp);
186 452
        $partDay = $excelTimestamp - $days;
187 452
        $hours = floor($partDay * 24);
188 452
        $partDay = $partDay * 24 - $hours;
189 452
        $minutes = floor($partDay * 60);
190 452
        $partDay = $partDay * 60 - $minutes;
191 452
        $seconds = round($partDay * 60);
192
193 452
        if ($days >= 0) {
194 451
            $days = '+' . $days;
195
        }
196 452
        $interval = $days . ' days';
197
198 452
        return $baseDate->modify($interval)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $baseDate->modify...rs, $minutes, $seconds) could also return false which is incompatible with the documented return type DateTime. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
199 452
            ->setTime($hours, $minutes, $seconds);
0 ignored issues
show
Bug introduced by
$minutes of type double is incompatible with the type integer expected by parameter $minute of DateTime::setTime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
            ->setTime($hours, /** @scrutinizer ignore-type */ $minutes, $seconds);
Loading history...
Bug introduced by
$hours of type double is incompatible with the type integer expected by parameter $hour of DateTime::setTime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
            ->setTime(/** @scrutinizer ignore-type */ $hours, $minutes, $seconds);
Loading history...
Bug introduced by
$seconds of type double is incompatible with the type integer expected by parameter $second of DateTime::setTime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
            ->setTime($hours, $minutes, /** @scrutinizer ignore-type */ $seconds);
Loading history...
200
    }
201
202
    /**
203
     * Convert a MS serialized datetime value from Excel to a unix timestamp.
204
     *
205
     * @param float|int $excelTimestamp MS Excel serialized date/time value
206
     * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
207
     *                                                                        if you don't want to treat it as a UTC value
208
     *                                                                    Use the default (UST) unless you absolutely need a conversion
209
     *
210
     * @throws \Exception
211
     *
212
     * @return int Unix timetamp for this date/time
213
     */
214 84
    public static function excelToTimestamp($excelTimestamp, $timeZone = null)
215
    {
216 84
        return (int) self::excelToDateTimeObject($excelTimestamp, $timeZone)
217 84
            ->format('U');
218
    }
219
220
    /**
221
     * Convert a date from PHP to an MS Excel serialized date/time value.
222
     *
223
     * @param mixed $dateValue Unix Timestamp or PHP DateTime object or a string
224
     *
225
     * @return bool|float Excel date/time value
226
     *                                  or boolean FALSE on failure
227
     */
228 65
    public static function PHPToExcel($dateValue)
229
    {
230 65
        if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) {
231 42
            return self::dateTimeToExcel($dateValue);
232 23
        } elseif (is_numeric($dateValue)) {
233 23
            return self::timestampToExcel($dateValue);
234 7
        } elseif (is_string($dateValue)) {
235
            return self::stringToExcel($dateValue);
236
        }
237
238 7
        return false;
239
    }
240
241
    /**
242
     * Convert a PHP DateTime object to an MS Excel serialized date/time value.
243
     *
244
     * @param DateTimeInterface $dateValue PHP DateTime object
245
     *
246
     * @return float MS Excel serialized date/time value
247
     */
248 97
    public static function dateTimeToExcel(DateTimeInterface $dateValue)
249
    {
250 97
        return self::formattedPHPToExcel(
251 97
            $dateValue->format('Y'),
0 ignored issues
show
Bug introduced by
$dateValue->format('Y') of type string is incompatible with the type integer expected by parameter $year of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

251
            /** @scrutinizer ignore-type */ $dateValue->format('Y'),
Loading history...
252 97
            $dateValue->format('m'),
0 ignored issues
show
Bug introduced by
$dateValue->format('m') of type string is incompatible with the type integer expected by parameter $month of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

252
            /** @scrutinizer ignore-type */ $dateValue->format('m'),
Loading history...
253 97
            $dateValue->format('d'),
0 ignored issues
show
Bug introduced by
$dateValue->format('d') of type string is incompatible with the type integer expected by parameter $day of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

253
            /** @scrutinizer ignore-type */ $dateValue->format('d'),
Loading history...
254 97
            $dateValue->format('H'),
0 ignored issues
show
Bug introduced by
$dateValue->format('H') of type string is incompatible with the type integer expected by parameter $hours of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

254
            /** @scrutinizer ignore-type */ $dateValue->format('H'),
Loading history...
255 97
            $dateValue->format('i'),
0 ignored issues
show
Bug introduced by
$dateValue->format('i') of type string is incompatible with the type integer expected by parameter $minutes of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

255
            /** @scrutinizer ignore-type */ $dateValue->format('i'),
Loading history...
256 97
            $dateValue->format('s')
0 ignored issues
show
Bug introduced by
$dateValue->format('s') of type string is incompatible with the type integer expected by parameter $seconds of PhpOffice\PhpSpreadsheet...::formattedPHPToExcel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

256
            /** @scrutinizer ignore-type */ $dateValue->format('s')
Loading history...
257
        );
258
    }
259
260
    /**
261
     * Convert a Unix timestamp to an MS Excel serialized date/time value.
262
     *
263
     * @param DateTimeInterface $dateValue Unix Timestamp
264
     *
265
     * @return float MS Excel serialized date/time value
266
     */
267 40
    public static function timestampToExcel($dateValue)
268
    {
269 40
        if (!is_numeric($dateValue)) {
270
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type double.
Loading history...
271
        }
272
273 40
        return self::dateTimeToExcel(new \DateTime('@' . $dateValue));
274
    }
275
276
    /**
277
     * formattedPHPToExcel.
278
     *
279
     * @param int $year
280
     * @param int $month
281
     * @param int $day
282
     * @param int $hours
283
     * @param int $minutes
284
     * @param int $seconds
285
     *
286
     * @return float Excel date/time value
287
     */
288 587
    public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0)
289
    {
290 587
        if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
291
            //
292
            //    Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel
293
            //    This affects every date following 28th February 1900
294
            //
295 580
            $excel1900isLeapYear = true;
296 580
            if (($year == 1900) && ($month <= 2)) {
297 55
                $excel1900isLeapYear = false;
298
            }
299 580
            $myexcelBaseDate = 2415020;
300
        } else {
301 7
            $myexcelBaseDate = 2416481;
302 7
            $excel1900isLeapYear = false;
303
        }
304
305
        //    Julian base date Adjustment
306 587
        if ($month > 2) {
307 362
            $month -= 3;
308
        } else {
309 376
            $month += 9;
310 376
            --$year;
311
        }
312
313
        //    Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0)
314 587
        $century = substr($year, 0, 2);
315 587
        $decade = substr($year, 2, 2);
316 587
        $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear;
317
318 587
        $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
319
320 587
        return (float) $excelDate + $excelTime;
321
    }
322
323
    /**
324
     * Is a given cell a date/time?
325
     *
326
     * @param Cell $pCell
327
     *
328
     * @return bool
329
     */
330
    public static function isDateTime(Cell $pCell)
331
    {
332
        return self::isDateTimeFormat(
333
            $pCell->getWorksheet()->getStyle(
334
                $pCell->getCoordinate()
335
            )->getNumberFormat()
336
        );
337
    }
338
339
    /**
340
     * Is a given number format a date/time?
341
     *
342
     * @param NumberFormat $pFormat
343
     *
344
     * @return bool
345
     */
346
    public static function isDateTimeFormat(NumberFormat $pFormat)
347
    {
348
        return self::isDateTimeFormatCode($pFormat->getFormatCode());
349
    }
350
351
    private static $possibleDateFormatCharacters = 'eymdHs';
352
353
    /**
354
     * Is a given number format code a date/time?
355
     *
356
     * @param string $pFormatCode
357
     *
358
     * @return bool
359
     */
360 38
    public static function isDateTimeFormatCode($pFormatCode)
361
    {
362 38
        if (strtolower($pFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) {
363
            //    "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check)
364 1
            return false;
365
        }
366 37
        if (preg_match('/[0#]E[+-]0/i', $pFormatCode)) {
367
            //    Scientific format
368
            return false;
369
        }
370
371
        // Switch on formatcode
372
        switch ($pFormatCode) {
373
            //    Explicitly defined date formats
374 37
            case NumberFormat::FORMAT_DATE_YYYYMMDD:
375 36
            case NumberFormat::FORMAT_DATE_YYYYMMDD2:
376 33
            case NumberFormat::FORMAT_DATE_DDMMYYYY:
377 32
            case NumberFormat::FORMAT_DATE_DMYSLASH:
378 32
            case NumberFormat::FORMAT_DATE_DMYMINUS:
379 32
            case NumberFormat::FORMAT_DATE_DMMINUS:
380 31
            case NumberFormat::FORMAT_DATE_MYMINUS:
381 31
            case NumberFormat::FORMAT_DATE_DATETIME:
382 31
            case NumberFormat::FORMAT_DATE_TIME1:
383 30
            case NumberFormat::FORMAT_DATE_TIME2:
384 29
            case NumberFormat::FORMAT_DATE_TIME3:
385 28
            case NumberFormat::FORMAT_DATE_TIME4:
386 26
            case NumberFormat::FORMAT_DATE_TIME5:
387 25
            case NumberFormat::FORMAT_DATE_TIME6:
388 25
            case NumberFormat::FORMAT_DATE_TIME7:
389 24
            case NumberFormat::FORMAT_DATE_TIME8:
390 23
            case NumberFormat::FORMAT_DATE_YYYYMMDDSLASH:
391 22
            case NumberFormat::FORMAT_DATE_XLSX14:
392 21
            case NumberFormat::FORMAT_DATE_XLSX15:
393 20
            case NumberFormat::FORMAT_DATE_XLSX16:
394 19
            case NumberFormat::FORMAT_DATE_XLSX17:
395 18
            case NumberFormat::FORMAT_DATE_XLSX22:
396 20
                return true;
397
        }
398
399
        //    Typically number, currency or accounting (or occasionally fraction) formats
400 17
        if ((substr($pFormatCode, 0, 1) == '_') || (substr($pFormatCode, 0, 2) == '0 ')) {
401 1
            return false;
402
        }
403
        // Try checking for any of the date formatting characters that don't appear within square braces
404 16
        if (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $pFormatCode)) {
405
            //    We might also have a format mask containing quoted strings...
406
            //        we don't want to test for any of our characters within the quoted blocks
407 5
            if (strpos($pFormatCode, '"') !== false) {
408 1
                $segMatcher = false;
409 1
                foreach (explode('"', $pFormatCode) as $subVal) {
410
                    //    Only test in alternate array entries (the non-quoted blocks)
411 1
                    if (($segMatcher = !$segMatcher) &&
412 1
                        (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $subVal))) {
413 1
                        return true;
414
                    }
415
                }
416
417 1
                return false;
418
            }
419
420 4
            return true;
421
        }
422
423
        // No date...
424 11
        return false;
425
    }
426
427
    /**
428
     * Convert a date/time string to Excel time.
429
     *
430
     * @param string $dateValue Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10'
431
     *
432
     * @return false|float Excel date/time serial value
433
     */
434 2
    public static function stringToExcel($dateValue)
435
    {
436 2
        if (strlen($dateValue) < 2) {
437 1
            return false;
438
        }
439 2
        if (!preg_match('/^(\d{1,4}[ \.\/\-][A-Z]{3,9}([ \.\/\-]\d{1,4})?|[A-Z]{3,9}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?|\d{1,4}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?)( \d{1,2}:\d{1,2}(:\d{1,2})?)?$/iu', $dateValue)) {
440 2
            return false;
441
        }
442
443 2
        $dateValueNew = DateTime::DATEVALUE($dateValue);
444
445 2
        if ($dateValueNew === Functions::VALUE()) {
446
            return false;
447
        }
448
449 2 View Code Duplication
        if (strpos($dateValue, ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
450 1
            $timeValue = DateTime::TIMEVALUE($dateValue);
451 1
            if ($timeValue === Functions::VALUE()) {
452
                return false;
453
            }
454 1
            $dateValueNew += $timeValue;
455
        }
456
457 2
        return $dateValueNew;
458
    }
459
460
    /**
461
     * Converts a month name (either a long or a short name) to a month number.
462
     *
463
     * @param string $month Month name or abbreviation
464
     *
465
     * @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name
466
     */
467 3
    public static function monthStringToNumber($month)
468
    {
469 3
        $monthIndex = 1;
470 3
        foreach (self::$monthNames as $shortMonthName => $longMonthName) {
471 3
            if (($month === $longMonthName) || ($month === $shortMonthName)) {
472 2
                return $monthIndex;
473
            }
474 3
            ++$monthIndex;
475
        }
476
477 1
        return $month;
478
    }
479
480
    /**
481
     * Strips an ordinal froma numeric value.
482
     *
483
     * @param string $day Day number with an ordinal
484
     *
485
     * @return int|string The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric
486
     */
487 3
    public static function dayStringToNumber($day)
488
    {
489 3
        $strippedDayValue = (str_replace(self::$numberSuffixes, '', $day));
490 3
        if (is_numeric($strippedDayValue)) {
491 2
            return (int) $strippedDayValue;
492
        }
493
494 1
        return $day;
495
    }
496
}
497