PluginProbe
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin / 6.5.1.7
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin v6.5.1.7
6.5.1.7 6.5.1.6 6.5.1.5 6.5.1.4 6.5.1.3 6.5.1.2 6.5.1.1 6.5.0.9 6.5.0.8 6.5.0.7 6.5.0.6 trunk 3.4.2.40 3.4.2.41 3.4.2.42 3.4.2.43 3.4.2.44 3.4.2.45 3.4.2.46 3.4.2.47 3.4.2.48 3.4.2.49 3.4.2.50 6.3.2 6.3.3.1 All 47 releases
wpdatatables / lib / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Shared / Date.php

Date.php in wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin 6.5.1.7, at lib/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php

557 lines 18.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace PhpOffice\PhpSpreadsheet\Shared;
4
5 use DateTime;
6 use DateTimeInterface;
7 use DateTimeZone;
8 use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
9 use PhpOffice\PhpSpreadsheet\Calculation\Functions;
10 use PhpOffice\PhpSpreadsheet\Cell\Cell;
11 use PhpOffice\PhpSpreadsheet\Exception;
12 use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
13 use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
14 use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
15
16 class Date
17 {
18 /** constants */
19 const CALENDAR_WINDOWS_1900 = 1900; // Base date of 1st Jan 1900 = 1.0
20 const CALENDAR_MAC_1904 = 1904; // Base date of 2nd Jan 1904 = 1.0
21
22 /**
23 * Names of the months of the year, indexed by shortname
24 * Planned usage for locale settings.
25 *
26 * @var string[]
27 */
28 public static $monthNames = [
29 'Jan' => 'January',
30 'Feb' => 'February',
31 'Mar' => 'March',
32 'Apr' => 'April',
33 'May' => 'May',
34 'Jun' => 'June',
35 'Jul' => 'July',
36 'Aug' => 'August',
37 'Sep' => 'September',
38 'Oct' => 'October',
39 'Nov' => 'November',
40 'Dec' => 'December',
41 ];
42
43 /**
44 * @var string[]
45 */
46 public static $numberSuffixes = [
47 'st',
48 'nd',
49 'rd',
50 'th',
51 ];
52
53 /**
54 * Base calendar year to use for calculations
55 * Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904).
56 *
57 * @var int
58 */
59 protected static $excelCalendar = self::CALENDAR_WINDOWS_1900;
60
61 /**
62 * Default timezone to use for DateTime objects.
63 *
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 $baseYear Excel base date (1900 or 1904)
72 *
73 * @return bool Success or failure
74 */
75 public static function setExcelCalendar($baseYear)
76 {
77 if (
78 ($baseYear == self::CALENDAR_WINDOWS_1900) ||
79 ($baseYear == self::CALENDAR_MAC_1904)
80 ) {
81 self::$excelCalendar = $baseYear;
82
83 return true;
84 }
85
86 return false;
87 }
88
89 /**
90 * Return the Excel calendar (Windows 1900 or Mac 1904).
91 *
92 * @return int Excel base date (1900 or 1904)
93 */
94 public static function getExcelCalendar()
95 {
96 return self::$excelCalendar;
97 }
98
99 /**
100 * Set the Default timezone to use for dates.
101 *
102 * @param null|DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
103 *
104 * @return bool Success or failure
105 */
106 public static function setDefaultTimezone($timeZone)
107 {
108 try {
109 $timeZone = self::validateTimeZone($timeZone);
110 self::$defaultTimeZone = $timeZone;
111 $retval = true;
112 } catch (PhpSpreadsheetException $e) {
113 $retval = false;
114 }
115
116 return $retval;
117 }
118
119 /**
120 * Return the Default timezone, or UTC if default not set.
121 */
122 public static function getDefaultTimezone(): DateTimeZone
123 {
124 return self::$defaultTimeZone ?? new DateTimeZone('UTC');
125 }
126
127 /**
128 * Return the Default timezone, or local timezone if default is not set.
129 */
130 public static function getDefaultOrLocalTimezone(): DateTimeZone
131 {
132 return self::$defaultTimeZone ?? new DateTimeZone(date_default_timezone_get());
133 }
134
135 /**
136 * Return the Default timezone even if null.
137 */
138 public static function getDefaultTimezoneOrNull(): ?DateTimeZone
139 {
140 return self::$defaultTimeZone;
141 }
142
143 /**
144 * Validate a timezone.
145 *
146 * @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
147 *
148 * @return ?DateTimeZone The timezone as a timezone object
149 */
150 private static function validateTimeZone($timeZone)
151 {
152 if ($timeZone instanceof DateTimeZone || $timeZone === null) {
153 return $timeZone;
154 }
155 if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) {
156 return new DateTimeZone($timeZone);
157 }
158
159 throw new PhpSpreadsheetException('Invalid timezone');
160 }
161
162 /**
163 * @param mixed $value Converts a date/time in ISO-8601 standard format date string to an Excel
164 * serialized timestamp.
165 * See https://en.wikipedia.org/wiki/ISO_8601 for details of the ISO-8601 standard format.
166 *
167 * @return float|int
168 */
169 public static function convertIsoDate($value)
170 {
171 if (!is_string($value)) {
172 throw new Exception('Non-string value supplied for Iso Date conversion');
173 }
174
175 $date = new DateTime($value);
176 $dateErrors = DateTime::getLastErrors();
177
178 if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) {
179 throw new Exception("Invalid string $value supplied for datatype Date");
180 }
181
182 $newValue = SharedDate::PHPToExcel($date);
183 if ($newValue === false) {
184 throw new Exception("Invalid string $value supplied for datatype Date");
185 }
186
187 if (preg_match('/^\s*\d?\d:\d\d(:\d\d([.]\d+)?)?\s*(am|pm)?\s*$/i', $value) == 1) {
188 $newValue = fmod($newValue, 1.0);
189 }
190
191 return $newValue;
192 }
193
194 /**
195 * Convert a MS serialized datetime value from Excel to a PHP Date/Time object.
196 *
197 * @param float|int $excelTimestamp MS Excel serialized date/time value
198 * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
199 * if you don't want to treat it as a UTC value
200 * Use the default (UTC) unless you absolutely need a conversion
201 *
202 * @return DateTime PHP date/time object
203 */
204 public static function excelToDateTimeObject($excelTimestamp, $timeZone = null)
205 {
206 $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone);
207 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
208 if ($excelTimestamp < 1 && self::$excelCalendar === self::CALENDAR_WINDOWS_1900) {
209 // Unix timestamp base date
210 $baseDate = new DateTime('1970-01-01', $timeZone);
211 } else {
212 // MS Excel calendar base dates
213 if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
214 // Allow adjustment for 1900 Leap Year in MS Excel
215 $baseDate = ($excelTimestamp < 60) ? new DateTime('1899-12-31', $timeZone) : new DateTime('1899-12-30', $timeZone);
216 } else {
217 $baseDate = new DateTime('1904-01-01', $timeZone);
218 }
219 }
220 } else {
221 $baseDate = new DateTime('1899-12-30', $timeZone);
222 }
223
224 $days = floor($excelTimestamp);
225 $partDay = $excelTimestamp - $days;
226 $hours = floor($partDay * 24);
227 $partDay = $partDay * 24 - $hours;
228 $minutes = floor($partDay * 60);
229 $partDay = $partDay * 60 - $minutes;
230 $seconds = round($partDay * 60);
231
232 if ($days >= 0) {
233 $days = '+' . $days;
234 }
235 $interval = $days . ' days';
236
237 return $baseDate->modify($interval)
238 ->setTime((int) $hours, (int) $minutes, (int) $seconds);
239 }
240
241 /**
242 * Convert a MS serialized datetime value from Excel to a unix timestamp.
243 * The use of Unix timestamps, and therefore this function, is discouraged.
244 * They are not Y2038-safe on a 32-bit system, and have no timezone info.
245 *
246 * @param float|int $excelTimestamp MS Excel serialized date/time value
247 * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
248 * if you don't want to treat it as a UTC value
249 * Use the default (UTC) unless you absolutely need a conversion
250 *
251 * @return int Unix timetamp for this date/time
252 */
253 public static function excelToTimestamp($excelTimestamp, $timeZone = null)
254 {
255 return (int) self::excelToDateTimeObject($excelTimestamp, $timeZone)
256 ->format('U');
257 }
258
259 /**
260 * Convert a date from PHP to an MS Excel serialized date/time value.
261 *
262 * @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged;
263 * not Y2038-safe on a 32-bit system, and no timezone info
264 *
265 * @return false|float Excel date/time value
266 * or boolean FALSE on failure
267 */
268 public static function PHPToExcel($dateValue)
269 {
270 if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) {
271 return self::dateTimeToExcel($dateValue);
272 } elseif (is_numeric($dateValue)) {
273 return self::timestampToExcel($dateValue);
274 } elseif (is_string($dateValue)) {
275 return self::stringToExcel($dateValue);
276 }
277
278 return false;
279 }
280
281 /**
282 * Convert a PHP DateTime object to an MS Excel serialized date/time value.
283 *
284 * @param DateTimeInterface $dateValue PHP DateTime object
285 *
286 * @return float MS Excel serialized date/time value
287 */
288 public static function dateTimeToExcel(DateTimeInterface $dateValue)
289 {
290 return self::formattedPHPToExcel(
291 (int) $dateValue->format('Y'),
292 (int) $dateValue->format('m'),
293 (int) $dateValue->format('d'),
294 (int) $dateValue->format('H'),
295 (int) $dateValue->format('i'),
296 (int) $dateValue->format('s')
297 );
298 }
299
300 /**
301 * Convert a Unix timestamp to an MS Excel serialized date/time value.
302 * The use of Unix timestamps, and therefore this function, is discouraged.
303 * They are not Y2038-safe on a 32-bit system, and have no timezone info.
304 *
305 * @param float|int|string $unixTimestamp Unix Timestamp
306 *
307 * @return false|float MS Excel serialized date/time value
308 */
309 public static function timestampToExcel($unixTimestamp)
310 {
311 if (!is_numeric($unixTimestamp)) {
312 return false;
313 }
314
315 return self::dateTimeToExcel(new DateTime('@' . $unixTimestamp));
316 }
317
318 /**
319 * formattedPHPToExcel.
320 *
321 * @param int $year
322 * @param int $month
323 * @param int $day
324 * @param int $hours
325 * @param int $minutes
326 * @param int $seconds
327 *
328 * @return float Excel date/time value
329 */
330 public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0)
331 {
332 if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
333 //
334 // Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel
335 // This affects every date following 28th February 1900
336 //
337 $excel1900isLeapYear = true;
338 if (($year == 1900) && ($month <= 2)) {
339 $excel1900isLeapYear = false;
340 }
341 $myexcelBaseDate = 2415020;
342 } else {
343 $myexcelBaseDate = 2416481;
344 $excel1900isLeapYear = false;
345 }
346
347 // Julian base date Adjustment
348 if ($month > 2) {
349 $month -= 3;
350 } else {
351 $month += 9;
352 --$year;
353 }
354
355 // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0)
356 $century = (int) substr((string) $year, 0, 2);
357 $decade = (int) substr((string) $year, 2, 2);
358 $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear;
359
360 $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
361
362 return (float) $excelDate + $excelTime;
363 }
364
365 /**
366 * Is a given cell a date/time?
367 *
368 * @param mixed $value
369 *
370 * @return bool
371 */
372 public static function isDateTime(Cell $cell, $value = null, bool $dateWithoutTimeOkay = true)
373 {
374 $result = false;
375 $worksheet = $cell->getWorksheetOrNull();
376 $spreadsheet = ($worksheet === null) ? null : $worksheet->getParent();
377 if ($worksheet !== null && $spreadsheet !== null) {
378 $index = $spreadsheet->getActiveSheetIndex();
379 $selected = $worksheet->getSelectedCells();
380
381 try {
382 $result = is_numeric($value ?? $cell->getCalculatedValue()) &&
383 self::isDateTimeFormat(
384 $worksheet->getStyle(
385 $cell->getCoordinate()
386 )->getNumberFormat(),
387 $dateWithoutTimeOkay
388 );
389 } catch (Exception $e) {
390 // Result is already false, so no need to actually do anything here
391 }
392 $worksheet->setSelectedCells($selected);
393 $spreadsheet->setActiveSheetIndex($index);
394 }
395
396 return $result;
397 }
398
399 /**
400 * Is a given NumberFormat code a date/time format code?
401 *
402 * @return bool
403 */
404 public static function isDateTimeFormat(NumberFormat $excelFormatCode, bool $dateWithoutTimeOkay = true)
405 {
406 return self::isDateTimeFormatCode((string) $excelFormatCode->getFormatCode(), $dateWithoutTimeOkay);
407 }
408
409 private const POSSIBLE_DATETIME_FORMAT_CHARACTERS = 'eymdHs';
410 private const POSSIBLE_TIME_FORMAT_CHARACTERS = 'Hs'; // note - no 'm' due to ambiguity
411
412 /**
413 * Is a given number format code a date/time?
414 *
415 * @param string $excelFormatCode
416 *
417 * @return bool
418 */
419 public static function isDateTimeFormatCode($excelFormatCode, bool $dateWithoutTimeOkay = true)
420 {
421 if (strtolower($excelFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) {
422 // "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check)
423 return false;
424 }
425 if (preg_match('/[0#]E[+-]0/i', $excelFormatCode)) {
426 // Scientific format
427 return false;
428 }
429
430 // Switch on formatcode
431 if (in_array($excelFormatCode, NumberFormat::DATE_TIME_OR_DATETIME_ARRAY, true)) {
432 return $dateWithoutTimeOkay || in_array($excelFormatCode, NumberFormat::TIME_OR_DATETIME_ARRAY);
433 }
434
435 // Typically number, currency or accounting (or occasionally fraction) formats
436 if ((substr($excelFormatCode, 0, 1) == '_') || (substr($excelFormatCode, 0, 2) == '0 ')) {
437 return false;
438 }
439 // Some "special formats" provided in German Excel versions were detected as date time value,
440 // so filter them out here - "\C\H\-00000" (Switzerland) and "\D-00000" (Germany).
441 if (\strpos($excelFormatCode, '-00000') !== false) {
442 return false;
443 }
444 $possibleFormatCharacters = $dateWithoutTimeOkay ? self::POSSIBLE_DATETIME_FORMAT_CHARACTERS : self::POSSIBLE_TIME_FORMAT_CHARACTERS;
445 // Try checking for any of the date formatting characters that don't appear within square braces
446 if (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $excelFormatCode)) {
447 // We might also have a format mask containing quoted strings...
448 // we don't want to test for any of our characters within the quoted blocks
449 if (strpos($excelFormatCode, '"') !== false) {
450 $segMatcher = false;
451 foreach (explode('"', $excelFormatCode) as $subVal) {
452 // Only test in alternate array entries (the non-quoted blocks)
453 $segMatcher = $segMatcher === false;
454 if (
455 $segMatcher &&
456 (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $subVal))
457 ) {
458 return true;
459 }
460 }
461
462 return false;
463 }
464
465 return true;
466 }
467
468 // No date...
469 return false;
470 }
471
472 /**
473 * Convert a date/time string to Excel time.
474 *
475 * @param string $dateValue Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10'
476 *
477 * @return false|float Excel date/time serial value
478 */
479 public static function stringToExcel($dateValue)
480 {
481 if (strlen($dateValue) < 2) {
482 return false;
483 }
484 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)) {
485 return false;
486 }
487
488 $dateValueNew = DateTimeExcel\DateValue::fromString($dateValue);
489
490 if (!is_float($dateValueNew)) {
491 return false;
492 }
493
494 if (strpos($dateValue, ':') !== false) {
495 $timeValue = DateTimeExcel\TimeValue::fromString($dateValue);
496 if (!is_float($timeValue)) {
497 return false;
498 }
499 $dateValueNew += $timeValue;
500 }
501
502 return $dateValueNew;
503 }
504
505 /**
506 * Converts a month name (either a long or a short name) to a month number.
507 *
508 * @param string $monthName Month name or abbreviation
509 *
510 * @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name
511 */
512 public static function monthStringToNumber($monthName)
513 {
514 $monthIndex = 1;
515 foreach (self::$monthNames as $shortMonthName => $longMonthName) {
516 if (($monthName === $longMonthName) || ($monthName === $shortMonthName)) {
517 return $monthIndex;
518 }
519 ++$monthIndex;
520 }
521
522 return $monthName;
523 }
524
525 /**
526 * Strips an ordinal from a numeric value.
527 *
528 * @param string $day Day number with an ordinal
529 *
530 * @return int|string The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric
531 */
532 public static function dayStringToNumber($day)
533 {
534 $strippedDayValue = (str_replace(self::$numberSuffixes, '', $day));
535 if (is_numeric($strippedDayValue)) {
536 return (int) $strippedDayValue;
537 }
538
539 return $day;
540 }
541
542 public static function dateTimeFromTimestamp(string $date, ?DateTimeZone $timeZone = null): DateTime
543 {
544 $dtobj = DateTime::createFromFormat('U', $date) ?: new DateTime();
545 $dtobj->setTimeZone($timeZone ?? self::getDefaultOrLocalTimezone());
546
547 return $dtobj;
548 }
549
550 public static function formattedDateTimeFromTimestamp(string $date, string $format, ?DateTimeZone $timeZone = null): string
551 {
552 $dtobj = self::dateTimeFromTimestamp($date, $timeZone);
553
554 return $dtobj->format($format);
555 }
556 }
557