| 1 |
<?php |
| 2 |
|
| 3 |
namespace TablePress\PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; |
| 4 |
|
| 5 |
use DateTime; |
| 6 |
use TablePress\PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; |
| 7 |
use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception; |
| 8 |
use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; |
| 9 |
use TablePress\PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper; |
| 10 |
use TablePress\PhpOffice\PhpSpreadsheet\Shared\StringHelper; |
| 11 |
|
| 12 |
class Date |
| 13 |
{ |
| 14 |
use ArrayEnabled; |
| 15 |
|
| 16 |
/** |
| 17 |
* DATE. |
| 18 |
* |
| 19 |
* The DATE function returns a value that represents a particular date. |
| 20 |
* |
| 21 |
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date |
| 22 |
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. |
| 23 |
* |
| 24 |
* Excel Function: |
| 25 |
* DATE(year,month,day) |
| 26 |
* |
| 27 |
* PhpSpreadsheet is a lot more forgiving than MS Excel when passing non-numeric values to this function. |
| 28 |
* A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted, |
| 29 |
* as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language. |
| 30 |
* |
| 31 |
* @param array<mixed>|float|int|string $year The value of the year argument can include one to four digits. |
| 32 |
* Excel interprets the year argument according to the configured |
| 33 |
* date system: 1900 or 1904. |
| 34 |
* If year is between 0 (zero) and 1899 (inclusive), Excel adds that |
| 35 |
* value to 1900 to calculate the year. For example, DATE(108,1,2) |
| 36 |
* returns January 2, 2008 (1900+108). |
| 37 |
* If year is between 1900 and 9999 (inclusive), Excel uses that |
| 38 |
* value as the year. For example, DATE(2008,1,2) returns January 2, |
| 39 |
* 2008. |
| 40 |
* If year is less than 0 or is 10000 or greater, Excel returns the |
| 41 |
* #NUM! error value. |
| 42 |
* @param array<mixed>|float|int|string $month A positive or negative integer representing the month of the year |
| 43 |
* from 1 to 12 (January to December). |
| 44 |
* If month is greater than 12, month adds that number of months to |
| 45 |
* the first month in the year specified. For example, DATE(2008,14,2) |
| 46 |
* returns the serial number representing February 2, 2009. |
| 47 |
* If month is less than 1, month subtracts the magnitude of that |
| 48 |
* number of months, plus 1, from the first month in the year |
| 49 |
* specified. For example, DATE(2008,-3,2) returns the serial number |
| 50 |
* representing September 2, 2007. |
| 51 |
* @param array<mixed>|float|int|string $day A positive or negative integer representing the day of the month |
| 52 |
* from 1 to 31. |
| 53 |
* If day is greater than the number of days in the month specified, |
| 54 |
* day adds that number of days to the first day in the month. For |
| 55 |
* example, DATE(2008,1,35) returns the serial number representing |
| 56 |
* February 4, 2008. |
| 57 |
* If day is less than 1, day subtracts the magnitude that number of |
| 58 |
* days, plus one, from the first day of the month specified. For |
| 59 |
* example, DATE(2008,1,-15) returns the serial number representing |
| 60 |
* December 16, 2007. |
| 61 |
* |
| 62 |
* @return array<mixed>|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object, |
| 63 |
* depending on the value of the ReturnDateType flag |
| 64 |
* If an array of numbers is passed as the argument, then the returned result will also be an array |
| 65 |
* with the same dimensions |
| 66 |
*/ |
| 67 |
public static function fromYMD($year, $month, $day) |
| 68 |
{ |
| 69 |
if (is_array($year) || is_array($month) || is_array($day)) { |
| 70 |
return self::evaluateArrayArguments([self::class, __FUNCTION__], $year, $month, $day); |
| 71 |
} |
| 72 |
|
| 73 |
$baseYear = SharedDateHelper::getExcelCalendar(); |
| 74 |
|
| 75 |
try { |
| 76 |
$year = self::getYear($year, $baseYear); |
| 77 |
$month = self::getMonth($month); |
| 78 |
$day = self::getDay($day); |
| 79 |
self::adjustYearMonth($year, $month, $baseYear); |
| 80 |
} catch (Exception $e) { |
| 81 |
return $e->getMessage(); |
| 82 |
} |
| 83 |
|
| 84 |
// Execute function |
| 85 |
$excelDateValue = SharedDateHelper::formattedPHPToExcel($year, $month, $day); |
| 86 |
|
| 87 |
return Helpers::returnIn3FormatsFloat($excelDateValue); |
| 88 |
} |
| 89 |
|
| 90 |
/** |
| 91 |
* Convert year from multiple formats to int. |
| 92 |
* @param mixed $year |
| 93 |
*/ |
| 94 |
private static function getYear($year, int $baseYear): int |
| 95 |
{ |
| 96 |
if ($year === null) { |
| 97 |
$year = 0; |
| 98 |
} elseif (is_scalar($year)) { |
| 99 |
$year = StringHelper::testStringAsNumeric((string) $year); |
| 100 |
} |
| 101 |
if (!is_numeric($year)) { |
| 102 |
throw new Exception(ExcelError::VALUE()); |
| 103 |
} |
| 104 |
$year = (int) $year; |
| 105 |
|
| 106 |
if ($year < ($baseYear - 1900)) { |
| 107 |
throw new Exception(ExcelError::NAN()); |
| 108 |
} |
| 109 |
if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) { |
| 110 |
throw new Exception(ExcelError::NAN()); |
| 111 |
} |
| 112 |
|
| 113 |
if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { |
| 114 |
$year += 1900; |
| 115 |
} |
| 116 |
|
| 117 |
return (int) $year; |
| 118 |
} |
| 119 |
|
| 120 |
/** |
| 121 |
* Convert month from multiple formats to int. |
| 122 |
* @param mixed $month |
| 123 |
*/ |
| 124 |
private static function getMonth($month): int |
| 125 |
{ |
| 126 |
if (is_string($month)) { |
| 127 |
if (!is_numeric($month)) { |
| 128 |
$month = SharedDateHelper::monthStringToNumber($month); |
| 129 |
} |
| 130 |
} elseif ($month === null) { |
| 131 |
$month = 0; |
| 132 |
} elseif (is_bool($month)) { |
| 133 |
$month = (int) $month; |
| 134 |
} |
| 135 |
if (!is_numeric($month)) { |
| 136 |
throw new Exception(ExcelError::VALUE()); |
| 137 |
} |
| 138 |
|
| 139 |
return (int) $month; |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Convert day from multiple formats to int. |
| 144 |
* @param mixed $day |
| 145 |
*/ |
| 146 |
private static function getDay($day): int |
| 147 |
{ |
| 148 |
if (is_string($day) && !is_numeric($day)) { |
| 149 |
$day = SharedDateHelper::dayStringToNumber($day); |
| 150 |
} |
| 151 |
|
| 152 |
if ($day === null) { |
| 153 |
$day = 0; |
| 154 |
} elseif (is_scalar($day)) { |
| 155 |
$day = StringHelper::testStringAsNumeric((string) $day); |
| 156 |
} |
| 157 |
if (!is_numeric($day)) { |
| 158 |
throw new Exception(ExcelError::VALUE()); |
| 159 |
} |
| 160 |
|
| 161 |
return (int) $day; |
| 162 |
} |
| 163 |
|
| 164 |
private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void |
| 165 |
{ |
| 166 |
if ($month < 1) { |
| 167 |
// Handle year/month adjustment if month < 1 |
| 168 |
--$month; |
| 169 |
$year += (int) (ceil($month / 12) - 1); |
| 170 |
$month = 13 - abs($month % 12); |
| 171 |
} elseif ($month > 12) { |
| 172 |
// Handle year/month adjustment if month > 12 |
| 173 |
$year += intdiv($month, 12); |
| 174 |
$month = ($month % 12); |
| 175 |
} |
| 176 |
|
| 177 |
// Re-validate the year parameter after adjustments |
| 178 |
if (($year < $baseYear) || ($year >= 10000)) { |
| 179 |
throw new Exception(ExcelError::NAN()); |
| 180 |
} |
| 181 |
} |
| 182 |
} |
| 183 |
|