Completed
Push — develop ( aa97bb...91573b )
by
unknown
09:37
created

Date::validateTimeZone()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 9
rs 9.2
1
<?php
2
3
namespace PHPExcel\Shared;
4
5
/**
6
 * \PHPExcel\Shared\Date
7
 *
8
 * Copyright (c) 2006 - 2015 PHPExcel
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with this library; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23
 *
24
 * @category   PHPExcel
25
 * @package    PHPExcel_Shared
26
 * @copyright  Copyright (c) 2006 - 2015 PHPExcel (http://www.codeplex.com/PHPExcel)
27
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
28
 * @version    ##VERSION##, ##DATE##
29
 */
30
class Date
31
{
32
    /** constants */
33
    const CALENDAR_WINDOWS_1900 = 1900;        //    Base date of 1st Jan 1900 = 1.0
34
    const CALENDAR_MAC_1904 = 1904;            //    Base date of 2nd Jan 1904 = 1.0
35
36
    /*
37
     * Names of the months of the year, indexed by shortname
38
     * Planned usage for locale settings
39
     *
40
     * @public
41
     * @var    string[]
42
     */
43
    public static $monthNames = [
44
        'Jan' => 'January',
45
        'Feb' => 'February',
46
        'Mar' => 'March',
47
        'Apr' => 'April',
48
        'May' => 'May',
49
        'Jun' => 'June',
50
        'Jul' => 'July',
51
        'Aug' => 'August',
52
        'Sep' => 'September',
53
        'Oct' => 'October',
54
        'Nov' => 'November',
55
        'Dec' => 'December',
56
    ];
57
58
    /*
59
     * @public
60
     * @var    string[]
61
     */
62
    public static $numberSuffixes = [
63
        'st',
64
        'nd',
65
        'rd',
66
        'th',
67
    ];
68
69
    /*
70
     * Base calendar year to use for calculations
71
     * Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904)
72
     *
73
     * @private
74
     * @var    int
75
     */
76
    protected static $excelCalendar = self::CALENDAR_WINDOWS_1900;
77
78
    /*
79
     * Default timezone to use for DateTime objects
80
     *
81
     * @private
82
     * @var    null|\DateTimeZone
83
     */
84
    protected static $defaultTimeZone;
85
86
    /**
87
     * Set the Excel calendar (Windows 1900 or Mac 1904)
88
     *
89
     * @param     integer    $baseDate           Excel base date (1900 or 1904)
90
     * @return    boolean                        Success or failure
91
     */
92
    public static function setExcelCalendar($baseDate)
93
    {
94
        if (($baseDate == self::CALENDAR_WINDOWS_1900) ||
95
            ($baseDate == self::CALENDAR_MAC_1904)) {
96
            self::$excelCalendar = $baseDate;
97
            return true;
98
        }
99
        return false;
100
    }
101
102
    /**
103
     * Return the Excel calendar (Windows 1900 or Mac 1904)
104
     *
105
     * @return     integer    Excel base date (1900 or 1904)
106
     */
107
    public static function getExcelCalendar()
108
    {
109
        return self::$excelCalendar;
110
    }
111
112
    /**
113
     * Set the Default timezone to use for dates
114
     *
115
     * @param     string|\DateTimeZone    $timezone    The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
0 ignored issues
show
Documentation introduced by
There is no parameter named $timezone. Did you maybe mean $timeZone?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
116
     * @return    boolean                              Success or failure
117
     * @throws    \Exception
118
     */
119
    public static function setDefaultTimezone($timeZone)
120
    {
121
        if ($timeZone = self::validateTimeZone($timeZone)) {
122
            self::$defaultTimeZone = $timeZone;
123
            return true;
124
        }
125
        return false;
126
    }
127
128
    /**
129
     * Return the Default timezone being used for dates
130
     *
131
     * @return     \DateTimeZone    The timezone being used as default for Excel timestamp to PHP DateTime object
132
     */
133
    public static function getDefaultTimezone()
134
    {
135
        if (self::$defaultTimeZone === null) {
136
            self::$defaultTimeZone = new \DateTimeZone('UTC');
137
        }
138
        return self::$defaultTimeZone;
139
    }
140
141
    /**
142
     * Validate a timezone
143
     *
144
     * @param     string|\DateTimeZone    $timezone    The timezone to validate, either as a timezone string or object
0 ignored issues
show
Documentation introduced by
There is no parameter named $timezone. Did you maybe mean $timeZone?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
145
     * @return    \DateTimeZone                        The timezone as a timezone object
146
     * @throws    \Exception
147
     */
148
    protected static function validateTimeZone($timeZone)
149
    {
150
        if (is_object($timeZone) && $timeZone instanceof \DateTimeZone) {
151
            return $timeZone;
152
        } elseif (is_string($timeZone)) {
153
            return new \DateTimeZone($timeZone);
154
        }
155
        throw new \Exception('Invalid timezone');
156
    }
157
158
    /**
159
     * Convert a MS serialized datetime value from Excel to a PHP Date/Time object
160
     *
161
     * @param     integer|float    $dateValue        MS Excel serialized date/time value
0 ignored issues
show
Bug introduced by
There is no parameter named $dateValue. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
162
     * @param     \DateTimeZone|string|null          $timezone            The timezone to assume for the Excel timestamp,
0 ignored issues
show
Documentation introduced by
There is no parameter named $timezone. Did you maybe mean $timeZone?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
163
     *                                                                        if you don't want to treat it as a UTC value
164
     *                                                                    Use the default (UST) unless you absolutely need a conversion
165
     * @return    \DateTime                          PHP date/time object
166
     * @throws    \Exception
167
     */
168
    public static function excelToDateTimeObject($excelTimestamp = 0, $timeZone = null)
169
    {
170
        $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone);
171
        if ($excelTimestamp < 1.0) {
172
            // Unix timestamp base date
173
            $baseDate = new \DateTime('1970-01-01', $timeZone);
174
        } else {
175
            // MS Excel calendar base dates
176
            if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
177
                // Allow adjustment for 1900 Leap Year in MS Excel
178
                $baseDate = ($excelTimestamp < 60) ? new \DateTime('1899-12-31', $timeZone) : new \DateTime('1899-12-30', $timeZone);
179
            } else {
180
                $baseDate = new \DateTime('1904-01-01', $timeZone);
181
            }
182
        }
183
        $days = floor($excelTimestamp);
184
        $partDay = $excelTimestamp - $days;
185
        $hours = floor($partDay * 24);
186
        $partDay = $partDay * 24 - $hours;
187
        $minutes = floor($partDay * 60);
188
        $partDay = $partDay * 60 - $minutes;
189
        $seconds = round($partDay * 60);
190
191
        $interval = '+' . $days . ' days';
192
        return $baseDate->modify($interval)
193
            ->setTime($hours, $minutes, $seconds);
194
    }
195
196
    /**
197
     * Convert a MS serialized datetime value from Excel to a unix timestamp
198
     *
199
     * @param     integer|float    $dateValue        MS Excel serialized date/time value
0 ignored issues
show
Bug introduced by
There is no parameter named $dateValue. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
200
     * @param     \DateTimeZone|string|null          $timezone            The timezone to assume for the Excel timestamp,
0 ignored issues
show
Documentation introduced by
There is no parameter named $timezone. Did you maybe mean $timeZone?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
201
     *                                                                        if you don't want to treat it as a UTC value
202
     *                                                                    Use the default (UST) unless you absolutely need a conversion
203
     * @return    integer                            Unix timetamp for this date/time
204
     * @throws    \Exception
205
     */
206
    public static function excelToTimestamp($excelTimestamp = 0, $timeZone = null)
207
    {
208
        return (int) self::excelToDateTimeObject($excelTimestamp, $timeZone)
209
            ->format('U');
210
    }
211
212
    /**
213
     *    Convert a date from PHP to an MS Excel serialized date/time value
214
     *
215
     *    @param    mixed            $dateValue            PHP serialized date/time or date object
216
     *    @return   float|boolean    Excel date/time value
217
     *                                  or boolean FALSE on failure
218
     */
219
    public static function PHPToExcel($dateValue = 0)
220
    {
221
        if ((is_object($dateValue)) && ($dateValue instanceof \DateTimeInterface)) {
222
            return self::DateTimeToExcel($dateValue);
223
        } elseif (is_numeric($dateValue)) {
224
            return self::TimestampToExcel($dateValue);
225
        } elseif (is_string($dateValue)) {
226
            return self::stringToExcel($dateValue);
227
        }
228
229
        return false;
230
    }
231
232
    /**
233
     *    Convert a DateTime object to an MS Excel serialized date/time value
234
     *
235
     *    @param    \DateTimeInterface    $dateValue            PHP DateTime object
236
     *    @return   float                 MS Excel serialized date/time value
237
     */
238
    public static function dateTimeToExcel(\DateTimeInterface $dateValue = null)
239
    {
240
        return self::formattedPHPToExcel(
241
            $dateValue->format('Y'),
0 ignored issues
show
Bug introduced by
It seems like $dateValue is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
242
            $dateValue->format('m'),
243
            $dateValue->format('d'),
244
            $dateValue->format('H'),
245
            $dateValue->format('i'),
246
            $dateValue->format('s')
247
        );
248
    }
249
250
    /**
251
     *    Convert a Unix timestamp to an MS Excel serialized date/time value
252
     *
253
     *    @param    \DateTimeInterface    $dateValue            PHP DateTime object
254
     *    @return   float                 MS Excel serialized date/time value
255
     */
256
    public static function timestampToExcel($dateValue = 0)
257
    {
258
        if (!is_numeric($dateValue)) {
259
            return false;
260
        }
261
        return self::DateTimeToExcel(new \DateTime('@' . $dateValue));
262
    }
263
264
    /**
265
     * formattedPHPToExcel
266
     *
267
     * @param    integer    $year
268
     * @param    integer    $month
269
     * @param    integer    $day
270
     * @param    integer    $hours
271
     * @param    integer    $minutes
272
     * @param    integer    $seconds
273
     * @return   integer    Excel date/time value
274
     */
275
    public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0)
276
    {
277
        if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
278
            //
279
            //    Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel
280
            //    This affects every date following 28th February 1900
281
            //
282
            $excel1900isLeapYear = true;
283
            if (($year == 1900) && ($month <= 2)) {
284
                $excel1900isLeapYear = false;
285
            }
286
            $myexcelBaseDate = 2415020;
287
        } else {
288
            $myexcelBaseDate = 2416481;
289
            $excel1900isLeapYear = false;
290
        }
291
292
        //    Julian base date Adjustment
293
        if ($month > 2) {
294
            $month -= 3;
295
        } else {
296
            $month += 9;
297
            --$year;
298
        }
299
300
        //    Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0)
301
        $century = substr($year, 0, 2);
302
        $decade = substr($year, 2, 2);
303
        $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear;
304
305
        $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
306
307
        return (float) $excelDate + $excelTime;
308
    }
309
310
311
    /**
312
     * Is a given cell a date/time?
313
     *
314
     * @param   \PHPExcel\Cell    $pCell
315
     * @return  boolean
316
     */
317
    public static function isDateTime(\PHPExcel\Cell $pCell)
318
    {
319
        return self::isDateTimeFormat(
320
            $pCell->getWorksheet()->getStyle(
321
                $pCell->getCoordinate()
322
            )->getNumberFormat()
323
        );
324
    }
325
326
327
    /**
328
     * Is a given number format a date/time?
329
     *
330
     * @param   \PHPExcel\Style\NumberFormat    $pFormat
331
     * @return  boolean
332
     */
333
    public static function isDateTimeFormat(\PHPExcel\Style\NumberFormat $pFormat)
334
    {
335
        return self::isDateTimeFormatCode($pFormat->getFormatCode());
336
    }
337
338
339
    private static $possibleDateFormatCharacters = 'eymdHs';
340
341
    /**
342
     * Is a given number format code a date/time?
343
     *
344
     * @param     string    $pFormatCode
345
     * @return     boolean
346
     */
347
    public static function isDateTimeFormatCode($pFormatCode = '')
348
    {
349
        if (strtolower($pFormatCode) === strtolower(\PHPExcel\Style\NumberFormat::FORMAT_GENERAL)) {
350
            //    "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check)
351
            return false;
352
        }
353
        if (preg_match('/[0#]E[+-]0/i', $pFormatCode)) {
354
            //    Scientific format
355
            return false;
356
        }
357
358
        // Switch on formatcode
359
        switch ($pFormatCode) {
360
            //    Explicitly defined date formats
361
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_YYYYMMDD:
362
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_YYYYMMDD2:
363
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_DDMMYYYY:
364
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_DMYSLASH:
365
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_DMYMINUS:
366
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_DMMINUS:
367
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_MYMINUS:
368
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_DATETIME:
369
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_TIME1:
370
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_TIME2:
371
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_TIME3:
372
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_TIME4:
373
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_TIME5:
374
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_TIME6:
375
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_TIME7:
376
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_TIME8:
377
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_YYYYMMDDSLASH:
378
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_XLSX14:
379
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_XLSX15:
380
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_XLSX16:
381
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_XLSX17:
382
            case \PHPExcel\Style\NumberFormat::FORMAT_DATE_XLSX22:
383
                return true;
384
        }
385
386
        //    Typically number, currency or accounting (or occasionally fraction) formats
387
        if ((substr($pFormatCode, 0, 1) == '_') || (substr($pFormatCode, 0, 2) == '0 ')) {
388
            return false;
389
        }
390
        // Try checking for any of the date formatting characters that don't appear within square braces
391
        if (preg_match('/(^|\])[^\[]*['.self::$possibleDateFormatCharacters.']/i', $pFormatCode)) {
392
            //    We might also have a format mask containing quoted strings...
393
            //        we don't want to test for any of our characters within the quoted blocks
394
            if (strpos($pFormatCode, '"') !== false) {
395
                $segMatcher = false;
396
                foreach (explode('"', $pFormatCode) as $subVal) {
397
                    //    Only test in alternate array entries (the non-quoted blocks)
398
                    if (($segMatcher = !$segMatcher) &&
399
                        (preg_match('/(^|\])[^\[]*['.self::$possibleDateFormatCharacters.']/i', $subVal))) {
400
                        return true;
401
                    }
402
                }
403
                return false;
404
            }
405
            return true;
406
        }
407
408
        // No date...
409
        return false;
410
    }
411
412
413
    /**
414
     * Convert a date/time string to Excel time
415
     *
416
     * @param    string    $dateValue        Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10'
417
     * @return    float|FALSE        Excel date/time serial value
418
     */
419
    public static function stringToExcel($dateValue = '')
420
    {
421
        if (strlen($dateValue) < 2) {
422
            return false;
423
        }
424
        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)) {
425
            return false;
426
        }
427
428
        $dateValueNew = \PHPExcel\Calculation\DateTime::DATEVALUE($dateValue);
429
430
        if ($dateValueNew === \PHPExcel\Calculation\Functions::VALUE()) {
431
            return false;
432
        }
433
434 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...
435
            $timeValue = \PHPExcel\Calculation\DateTime::TIMEVALUE($dateValue);
436
            if ($timeValue === \PHPExcel\Calculation\Functions::VALUE()) {
437
                return false;
438
            }
439
            $dateValueNew += $timeValue;
440
        }
441
        return $dateValueNew;
442
    }
443
444
    /**
445
     * Converts a month name (either a long or a short name) to a month number
446
     *
447
     * @param     string    $month    Month name or abbreviation
448
     * @return    integer|string     Month number (1 - 12), or the original string argument if it isn't a valid month name
449
     */
450
    public static function monthStringToNumber($month)
451
    {
452
        $monthIndex = 1;
453
        foreach (self::$monthNames as $shortMonthName => $longMonthName) {
454
            if (($month === $longMonthName) || ($month === $shortMonthName)) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $month (string) and $shortMonthName (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
455
                return $monthIndex;
456
            }
457
            ++$monthIndex;
458
        }
459
        return $month;
460
    }
461
462
    /**
463
     * Strips an ordinal froma numeric value
464
     *
465
     * @param     string    $day      Day number with an ordinal
466
     * @return    integer|string      The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric
467
     */
468
    public static function dayStringToNumber($day)
469
    {
470
        $strippedDayValue = (str_replace(self::$numberSuffixes, '', $day));
471
        if (is_numeric($strippedDayValue)) {
472
            return (integer) $strippedDayValue;
473
        }
474
        return $day;
475
    }
476
}
477