Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Calculation; 4 5 use DateTimeImmutable; 6 use DateTimeInterface; 7 use PhpOffice\PhpSpreadsheet\Shared\Date; 8 use PhpOffice\PhpSpreadsheet\Shared\StringHelper; 9 10 class DateTime 11 { 12 /** 13 * Identify if a year is a leap year or not. 14 * 15 * @param int|string $year The year to test 16 * 17 * @return bool TRUE if the year is a leap year, otherwise FALSE 18 */ 19 public static function isLeapYear($year) 20 { 21 return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0); 22 } 23 24 /** 25 * Return the number of days between two dates based on a 360 day calendar. 26 * 27 * @param int $startDay Day of month of the start date 28 * @param int $startMonth Month of the start date 29 * @param int $startYear Year of the start date 30 * @param int $endDay Day of month of the start date 31 * @param int $endMonth Month of the start date 32 * @param int $endYear Year of the start date 33 * @param bool $methodUS Whether to use the US method or the European method of calculation 34 * 35 * @return int Number of days between the start date and the end date 36 */ 37 private static function dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, $methodUS) 38 { 39 if ($startDay == 31) { 40 --$startDay; 41 } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !self::isLeapYear($startYear))))) { 42 $startDay = 30; 43 } 44 if ($endDay == 31) { 45 if ($methodUS && $startDay != 30) { 46 $endDay = 1; 47 if ($endMonth == 12) { 48 ++$endYear; 49 $endMonth = 1; 50 } else { 51 ++$endMonth; 52 } 53 } else { 54 $endDay = 30; 55 } 56 } 57 58 return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; 59 } 60 61 /** 62 * getDateValue. 63 * 64 * @param mixed $dateValue 65 * 66 * @return mixed Excel date/time serial value, or string if error 67 */ 68 public static function getDateValue($dateValue) 69 { 70 if (!is_numeric($dateValue)) { 71 if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) { 72 $dateValue = Date::PHPToExcel($dateValue); 73 } else { 74 $saveReturnDateType = Functions::getReturnDateType(); 75 Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); 76 $dateValue = self::DATEVALUE($dateValue); 77 Functions::setReturnDateType($saveReturnDateType); 78 } 79 } 80 81 return $dateValue; 82 } 83 84 /** 85 * getTimeValue. 86 * 87 * @param string $timeValue 88 * 89 * @return mixed Excel date/time serial value, or string if error 90 */ 91 private static function getTimeValue($timeValue) 92 { 93 $saveReturnDateType = Functions::getReturnDateType(); 94 Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); 95 $timeValue = self::TIMEVALUE($timeValue); 96 Functions::setReturnDateType($saveReturnDateType); 97 98 return $timeValue; 99 } 100 101 private static function adjustDateByMonths($dateValue = 0, $adjustmentMonths = 0) 102 { 103 // Execute function 104 $PHPDateObject = Date::excelToDateTimeObject($dateValue); 105 $oMonth = (int) $PHPDateObject->format('m'); 106 $oYear = (int) $PHPDateObject->format('Y'); 107 108 $adjustmentMonthsString = (string) $adjustmentMonths; 109 if ($adjustmentMonths > 0) { 110 $adjustmentMonthsString = '+' . $adjustmentMonths; 111 } 112 if ($adjustmentMonths != 0) { 113 $PHPDateObject->modify($adjustmentMonthsString . ' months'); 114 } 115 $nMonth = (int) $PHPDateObject->format('m'); 116 $nYear = (int) $PHPDateObject->format('Y'); 117 118 $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); 119 if ($monthDiff != $adjustmentMonths) { 120 $adjustDays = (int) $PHPDateObject->format('d'); 121 $adjustDaysString = '-' . $adjustDays . ' days'; 122 $PHPDateObject->modify($adjustDaysString); 123 } 124 125 return $PHPDateObject; 126 } 127 128 /** 129 * DATETIMENOW. 130 * 131 * Returns the current date and time. 132 * The NOW function is useful when you need to display the current date and time on a worksheet or 133 * calculate a value based on the current date and time, and have that value updated each time you 134 * open the worksheet. 135 * 136 * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date 137 * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. 138 * 139 * Excel Function: 140 * NOW() 141 * 142 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 143 * depending on the value of the ReturnDateType flag 144 */ 145 public static function DATETIMENOW() 146 { 147 $saveTimeZone = date_default_timezone_get(); 148 date_default_timezone_set('UTC'); 149 $retValue = false; 150 switch (Functions::getReturnDateType()) { 151 case Functions::RETURNDATE_EXCEL: 152 $retValue = (float) Date::PHPToExcel(time()); 153 154 break; 155 case Functions::RETURNDATE_UNIX_TIMESTAMP: 156 $retValue = (int) time(); 157 158 break; 159 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 160 $retValue = new \DateTime(); 161 162 break; 163 } 164 date_default_timezone_set($saveTimeZone); 165 166 return $retValue; 167 } 168 169 /** 170 * DATENOW. 171 * 172 * Returns the current date. 173 * The NOW function is useful when you need to display the current date and time on a worksheet or 174 * calculate a value based on the current date and time, and have that value updated each time you 175 * open the worksheet. 176 * 177 * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date 178 * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. 179 * 180 * Excel Function: 181 * TODAY() 182 * 183 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 184 * depending on the value of the ReturnDateType flag 185 */ 186 public static function DATENOW() 187 { 188 $saveTimeZone = date_default_timezone_get(); 189 date_default_timezone_set('UTC'); 190 $retValue = false; 191 $excelDateTime = floor(Date::PHPToExcel(time())); 192 switch (Functions::getReturnDateType()) { 193 case Functions::RETURNDATE_EXCEL: 194 $retValue = (float) $excelDateTime; 195 196 break; 197 case Functions::RETURNDATE_UNIX_TIMESTAMP: 198 $retValue = (int) Date::excelToTimestamp($excelDateTime); 199 200 break; 201 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 202 $retValue = Date::excelToDateTimeObject($excelDateTime); 203 204 break; 205 } 206 date_default_timezone_set($saveTimeZone); 207 208 return $retValue; 209 } 210 211 /** 212 * DATE. 213 * 214 * The DATE function returns a value that represents a particular date. 215 * 216 * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date 217 * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. 218 * 219 * Excel Function: 220 * DATE(year,month,day) 221 * 222 * PhpSpreadsheet is a lot more forgiving than MS Excel when passing non numeric values to this function. 223 * A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted, 224 * as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language. 225 * 226 * @param int $year The value of the year argument can include one to four digits. 227 * Excel interprets the year argument according to the configured 228 * date system: 1900 or 1904. 229 * If year is between 0 (zero) and 1899 (inclusive), Excel adds that 230 * value to 1900 to calculate the year. For example, DATE(108,1,2) 231 * returns January 2, 2008 (1900+108). 232 * If year is between 1900 and 9999 (inclusive), Excel uses that 233 * value as the year. For example, DATE(2008,1,2) returns January 2, 234 * 2008. 235 * If year is less than 0 or is 10000 or greater, Excel returns the 236 * #NUM! error value. 237 * @param int $month A positive or negative integer representing the month of the year 238 * from 1 to 12 (January to December). 239 * If month is greater than 12, month adds that number of months to 240 * the first month in the year specified. For example, DATE(2008,14,2) 241 * returns the serial number representing February 2, 2009. 242 * If month is less than 1, month subtracts the magnitude of that 243 * number of months, plus 1, from the first month in the year 244 * specified. For example, DATE(2008,-3,2) returns the serial number 245 * representing September 2, 2007. 246 * @param int $day A positive or negative integer representing the day of the month 247 * from 1 to 31. 248 * If day is greater than the number of days in the month specified, 249 * day adds that number of days to the first day in the month. For 250 * example, DATE(2008,1,35) returns the serial number representing 251 * February 4, 2008. 252 * If day is less than 1, day subtracts the magnitude that number of 253 * days, plus one, from the first day of the month specified. For 254 * example, DATE(2008,1,-15) returns the serial number representing 255 * December 16, 2007. 256 * 257 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 258 * depending on the value of the ReturnDateType flag 259 */ 260 public static function DATE($year = 0, $month = 1, $day = 1) 261 { 262 $year = Functions::flattenSingleValue($year); 263 $month = Functions::flattenSingleValue($month); 264 $day = Functions::flattenSingleValue($day); 265 266 if (($month !== null) && (!is_numeric($month))) { 267 $month = Date::monthStringToNumber($month); 268 } 269 270 if (($day !== null) && (!is_numeric($day))) { 271 $day = Date::dayStringToNumber($day); 272 } 273 274 $year = ($year !== null) ? StringHelper::testStringAsNumeric($year) : 0; 275 $month = ($month !== null) ? StringHelper::testStringAsNumeric($month) : 0; 276 $day = ($day !== null) ? StringHelper::testStringAsNumeric($day) : 0; 277 if ( 278 (!is_numeric($year)) || 279 (!is_numeric($month)) || 280 (!is_numeric($day)) 281 ) { 282 return Functions::VALUE(); 283 } 284 $year = (int) $year; 285 $month = (int) $month; 286 $day = (int) $day; 287 288 $baseYear = Date::getExcelCalendar(); 289 // Validate parameters 290 if ($year < ($baseYear - 1900)) { 291 return Functions::NAN(); 292 } 293 if ((($baseYear - 1900) != 0) && ($year < $baseYear) && ($year >= 1900)) { 294 return Functions::NAN(); 295 } 296 297 if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { 298 $year += 1900; 299 } 300 301 if ($month < 1) { 302 // Handle year/month adjustment if month < 1 303 --$month; 304 $year += ceil($month / 12) - 1; 305 $month = 13 - abs($month % 12); 306 } elseif ($month > 12) { 307 // Handle year/month adjustment if month > 12 308 $year += floor($month / 12); 309 $month = ($month % 12); 310 } 311 312 // Re-validate the year parameter after adjustments 313 if (($year < $baseYear) || ($year >= 10000)) { 314 return Functions::NAN(); 315 } 316 317 // Execute function 318 $excelDateValue = Date::formattedPHPToExcel($year, $month, $day); 319 switch (Functions::getReturnDateType()) { 320 case Functions::RETURNDATE_EXCEL: 321 return (float) $excelDateValue; 322 case Functions::RETURNDATE_UNIX_TIMESTAMP: 323 return (int) Date::excelToTimestamp($excelDateValue); 324 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 325 return Date::excelToDateTimeObject($excelDateValue); 326 } 327 } 328 329 /** 330 * TIME. 331 * 332 * The TIME function returns a value that represents a particular time. 333 * 334 * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time 335 * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. 336 * 337 * Excel Function: 338 * TIME(hour,minute,second) 339 * 340 * @param int $hour A number from 0 (zero) to 32767 representing the hour. 341 * Any value greater than 23 will be divided by 24 and the remainder 342 * will be treated as the hour value. For example, TIME(27,0,0) = 343 * TIME(3,0,0) = .125 or 3:00 AM. 344 * @param int $minute A number from 0 to 32767 representing the minute. 345 * Any value greater than 59 will be converted to hours and minutes. 346 * For example, TIME(0,750,0) = TIME(12,30,0) = .520833 or 12:30 PM. 347 * @param int $second A number from 0 to 32767 representing the second. 348 * Any value greater than 59 will be converted to hours, minutes, 349 * and seconds. For example, TIME(0,0,2000) = TIME(0,33,22) = .023148 350 * or 12:33:20 AM 351 * 352 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 353 * depending on the value of the ReturnDateType flag 354 */ 355 public static function TIME($hour = 0, $minute = 0, $second = 0) 356 { 357 $hour = Functions::flattenSingleValue($hour); 358 $minute = Functions::flattenSingleValue($minute); 359 $second = Functions::flattenSingleValue($second); 360 361 if ($hour == '') { 362 $hour = 0; 363 } 364 if ($minute == '') { 365 $minute = 0; 366 } 367 if ($second == '') { 368 $second = 0; 369 } 370 371 if ((!is_numeric($hour)) || (!is_numeric($minute)) || (!is_numeric($second))) { 372 return Functions::VALUE(); 373 } 374 $hour = (int) $hour; 375 $minute = (int) $minute; 376 $second = (int) $second; 377 378 if ($second < 0) { 379 $minute += floor($second / 60); 380 $second = 60 - abs($second % 60); 381 if ($second == 60) { 382 $second = 0; 383 } 384 } elseif ($second >= 60) { 385 $minute += floor($second / 60); 386 $second = $second % 60; 387 } 388 if ($minute < 0) { 389 $hour += floor($minute / 60); 390 $minute = 60 - abs($minute % 60); 391 if ($minute == 60) { 392 $minute = 0; 393 } 394 } elseif ($minute >= 60) { 395 $hour += floor($minute / 60); 396 $minute = $minute % 60; 397 } 398 399 if ($hour > 23) { 400 $hour = $hour % 24; 401 } elseif ($hour < 0) { 402 return Functions::NAN(); 403 } 404 405 // Execute function 406 switch (Functions::getReturnDateType()) { 407 case Functions::RETURNDATE_EXCEL: 408 $date = 0; 409 $calendar = Date::getExcelCalendar(); 410 if ($calendar != Date::CALENDAR_WINDOWS_1900) { 411 $date = 1; 412 } 413 414 return (float) Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); 415 case Functions::RETURNDATE_UNIX_TIMESTAMP: 416 return (int) Date::excelToTimestamp(Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 417 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 418 $dayAdjust = 0; 419 if ($hour < 0) { 420 $dayAdjust = floor($hour / 24); 421 $hour = 24 - abs($hour % 24); 422 if ($hour == 24) { 423 $hour = 0; 424 } 425 } elseif ($hour >= 24) { 426 $dayAdjust = floor($hour / 24); 427 $hour = $hour % 24; 428 } 429 $phpDateObject = new \DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); 430 if ($dayAdjust != 0) { 431 $phpDateObject->modify($dayAdjust . ' days'); 432 } 433 434 return $phpDateObject; 435 } 436 } 437 438 /** 439 * DATEVALUE. 440 * 441 * Returns a value that represents a particular date. 442 * Use DATEVALUE to convert a date represented by a text string to an Excel or PHP date/time stamp 443 * value. 444 * 445 * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date 446 * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. 447 * 448 * Excel Function: 449 * DATEVALUE(dateValue) 450 * 451 * @param string $dateValue Text that represents a date in a Microsoft Excel date format. 452 * For example, "1/30/2008" or "30-Jan-2008" are text strings within 453 * quotation marks that represent dates. Using the default date 454 * system in Excel for Windows, date_text must represent a date from 455 * January 1, 1900, to December 31, 9999. Using the default date 456 * system in Excel for the Macintosh, date_text must represent a date 457 * from January 1, 1904, to December 31, 9999. DATEVALUE returns the 458 * #VALUE! error value if date_text is out of this range. 459 * 460 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 461 * depending on the value of the ReturnDateType flag 462 */ 463 public static function DATEVALUE($dateValue = 1) 464 { 465 $dateValue = trim(Functions::flattenSingleValue($dateValue), '"'); 466 // Strip any ordinals because they're allowed in Excel (English only) 467 $dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue); 468 // Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany) 469 $dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue); 470 471 $yearFound = false; 472 $t1 = explode(' ', $dateValue); 473 foreach ($t1 as &$t) { 474 if ((is_numeric($t)) && ($t > 31)) { 475 if ($yearFound) { 476 return Functions::VALUE(); 477 } 478 if ($t < 100) { 479 $t += 1900; 480 } 481 $yearFound = true; 482 } 483 } 484 if ((count($t1) == 1) && (strpos($t, ':') !== false)) { 485 // We've been fed a time value without any date 486 return 0.0; 487 } elseif (count($t1) == 2) { 488 // We only have two parts of the date: either day/month or month/year 489 if ($yearFound) { 490 array_unshift($t1, 1); 491 } else { 492 if (is_numeric($t1[1]) && $t1[1] > 29) { 493 $t1[1] += 1900; 494 array_unshift($t1, 1); 495 } else { 496 $t1[] = date('Y'); 497 } 498 } 499 } 500 unset($t); 501 $dateValue = implode(' ', $t1); 502 503 $PHPDateArray = date_parse($dateValue); 504 if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { 505 $testVal1 = strtok($dateValue, '- '); 506 if ($testVal1 !== false) { 507 $testVal2 = strtok('- '); 508 if ($testVal2 !== false) { 509 $testVal3 = strtok('- '); 510 if ($testVal3 === false) { 511 $testVal3 = strftime('%Y'); 512 } 513 } else { 514 return Functions::VALUE(); 515 } 516 } else { 517 return Functions::VALUE(); 518 } 519 if ($testVal1 < 31 && $testVal2 < 12 && $testVal3 < 12 && strlen($testVal3) == 2) { 520 $testVal3 += 2000; 521 } 522 $PHPDateArray = date_parse($testVal1 . '-' . $testVal2 . '-' . $testVal3); 523 if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { 524 $PHPDateArray = date_parse($testVal2 . '-' . $testVal1 . '-' . $testVal3); 525 if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { 526 return Functions::VALUE(); 527 } 528 } 529 } 530 531 if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { 532 // Execute function 533 if ($PHPDateArray['year'] == '') { 534 $PHPDateArray['year'] = strftime('%Y'); 535 } 536 if ($PHPDateArray['year'] < 1900) { 537 return Functions::VALUE(); 538 } 539 if ($PHPDateArray['month'] == '') { 540 $PHPDateArray['month'] = strftime('%m'); 541 } 542 if ($PHPDateArray['day'] == '') { 543 $PHPDateArray['day'] = strftime('%d'); 544 } 545 if (!checkdate($PHPDateArray['month'], $PHPDateArray['day'], $PHPDateArray['year'])) { 546 return Functions::VALUE(); 547 } 548 $excelDateValue = floor( 549 Date::formattedPHPToExcel( 550 $PHPDateArray['year'], 551 $PHPDateArray['month'], 552 $PHPDateArray['day'], 553 $PHPDateArray['hour'], 554 $PHPDateArray['minute'], 555 $PHPDateArray['second'] 556 ) 557 ); 558 switch (Functions::getReturnDateType()) { 559 case Functions::RETURNDATE_EXCEL: 560 return (float) $excelDateValue; 561 case Functions::RETURNDATE_UNIX_TIMESTAMP: 562 return (int) Date::excelToTimestamp($excelDateValue); 563 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 564 return new \DateTime($PHPDateArray['year'] . '-' . $PHPDateArray['month'] . '-' . $PHPDateArray['day'] . ' 00:00:00'); 565 } 566 } 567 568 return Functions::VALUE(); 569 } 570 571 /** 572 * TIMEVALUE. 573 * 574 * Returns a value that represents a particular time. 575 * Use TIMEVALUE to convert a time represented by a text string to an Excel or PHP date/time stamp 576 * value. 577 * 578 * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time 579 * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. 580 * 581 * Excel Function: 582 * TIMEVALUE(timeValue) 583 * 584 * @param string $timeValue A text string that represents a time in any one of the Microsoft 585 * Excel time formats; for example, "6:45 PM" and "18:45" text strings 586 * within quotation marks that represent time. 587 * Date information in time_text is ignored. 588 * 589 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 590 * depending on the value of the ReturnDateType flag 591 */ 592 public static function TIMEVALUE($timeValue) 593 { 594 $timeValue = trim(Functions::flattenSingleValue($timeValue), '"'); 595 $timeValue = str_replace(['/', '.'], '-', $timeValue); 596 597 $arraySplit = preg_split('/[\/:\-\s]/', $timeValue); 598 if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) { 599 $arraySplit[0] = ($arraySplit[0] % 24); 600 $timeValue = implode(':', $arraySplit); 601 } 602 603 $PHPDateArray = date_parse($timeValue); 604 if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { 605 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { 606 $excelDateValue = Date::formattedPHPToExcel( 607 $PHPDateArray['year'], 608 $PHPDateArray['month'], 609 $PHPDateArray['day'], 610 $PHPDateArray['hour'], 611 $PHPDateArray['minute'], 612 $PHPDateArray['second'] 613 ); 614 } else { 615 $excelDateValue = Date::formattedPHPToExcel(1900, 1, 1, $PHPDateArray['hour'], $PHPDateArray['minute'], $PHPDateArray['second']) - 1; 616 } 617 618 switch (Functions::getReturnDateType()) { 619 case Functions::RETURNDATE_EXCEL: 620 return (float) $excelDateValue; 621 case Functions::RETURNDATE_UNIX_TIMESTAMP: 622 return (int) $phpDateValue = Date::excelToTimestamp($excelDateValue + 25569) - 3600; 623 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 624 return new \DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']); 625 } 626 } 627 628 return Functions::VALUE(); 629 } 630 631 /** 632 * DATEDIF. 633 * 634 * @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object 635 * or a standard date string 636 * @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object 637 * or a standard date string 638 * @param string $unit 639 * 640 * @return int|string Interval between the dates 641 */ 642 public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') 643 { 644 $startDate = Functions::flattenSingleValue($startDate); 645 $endDate = Functions::flattenSingleValue($endDate); 646 $unit = strtoupper(Functions::flattenSingleValue($unit)); 647 648 if (is_string($startDate = self::getDateValue($startDate))) { 649 return Functions::VALUE(); 650 } 651 if (is_string($endDate = self::getDateValue($endDate))) { 652 return Functions::VALUE(); 653 } 654 655 // Validate parameters 656 if ($startDate > $endDate) { 657 return Functions::NAN(); 658 } 659 660 // Execute function 661 $difference = $endDate - $startDate; 662 663 $PHPStartDateObject = Date::excelToDateTimeObject($startDate); 664 $startDays = $PHPStartDateObject->format('j'); 665 $startMonths = $PHPStartDateObject->format('n'); 666 $startYears = $PHPStartDateObject->format('Y'); 667 668 $PHPEndDateObject = Date::excelToDateTimeObject($endDate); 669 $endDays = $PHPEndDateObject->format('j'); 670 $endMonths = $PHPEndDateObject->format('n'); 671 $endYears = $PHPEndDateObject->format('Y'); 672 673 $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); 674 675 switch ($unit) { 676 case 'D': 677 $retVal = (int) $difference; 678 679 break; 680 case 'M': 681 $retVal = (int) 12 * $PHPDiffDateObject->format('%y') + $PHPDiffDateObject->format('%m'); 682 683 break; 684 case 'Y': 685 $retVal = (int) $PHPDiffDateObject->format('%y'); 686 687 break; 688 case 'MD': 689 if ($endDays < $startDays) { 690 $retVal = $endDays; 691 $PHPEndDateObject->modify('-' . $endDays . ' days'); 692 $adjustDays = $PHPEndDateObject->format('j'); 693 $retVal += ($adjustDays - $startDays); 694 } else { 695 $retVal = (int) $PHPDiffDateObject->format('%d'); 696 } 697 698 break; 699 case 'YM': 700 $retVal = (int) $PHPDiffDateObject->format('%m'); 701 702 break; 703 case 'YD': 704 $retVal = (int) $difference; 705 if ($endYears > $startYears) { 706 $isLeapStartYear = $PHPStartDateObject->format('L'); 707 $wasLeapEndYear = $PHPEndDateObject->format('L'); 708 709 // Adjust end year to be as close as possible as start year 710 while ($PHPEndDateObject >= $PHPStartDateObject) { 711 $PHPEndDateObject->modify('-1 year'); 712 $endYears = $PHPEndDateObject->format('Y'); 713 } 714 $PHPEndDateObject->modify('+1 year'); 715 716 // Get the result 717 $retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days; 718 719 // Adjust for leap years cases 720 $isLeapEndYear = $PHPEndDateObject->format('L'); 721 $limit = new \DateTime($PHPEndDateObject->format('Y-02-29')); 722 if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { 723 --$retVal; 724 } 725 } 726 727 break; 728 default: 729 $retVal = Functions::VALUE(); 730 } 731 732 return $retVal; 733 } 734 735 /** 736 * DAYS. 737 * 738 * Returns the number of days between two dates 739 * 740 * Excel Function: 741 * DAYS(endDate, startDate) 742 * 743 * @param DateTimeImmutable|float|int|string $endDate Excel date serial value (float), 744 * PHP date timestamp (integer), PHP DateTime object, or a standard date string 745 * @param DateTimeImmutable|float|int|string $startDate Excel date serial value (float), 746 * PHP date timestamp (integer), PHP DateTime object, or a standard date string 747 * 748 * @return int|string Number of days between start date and end date or an error 749 */ 750 public static function DAYS($endDate = 0, $startDate = 0) 751 { 752 $startDate = Functions::flattenSingleValue($startDate); 753 $endDate = Functions::flattenSingleValue($endDate); 754 755 $startDate = self::getDateValue($startDate); 756 if (is_string($startDate)) { 757 return Functions::VALUE(); 758 } 759 760 $endDate = self::getDateValue($endDate); 761 if (is_string($endDate)) { 762 return Functions::VALUE(); 763 } 764 765 // Execute function 766 $PHPStartDateObject = Date::excelToDateTimeObject($startDate); 767 $PHPEndDateObject = Date::excelToDateTimeObject($endDate); 768 769 $diff = $PHPStartDateObject->diff($PHPEndDateObject); 770 $days = $diff->days; 771 772 if ($diff->invert) { 773 $days = -$days; 774 } 775 776 return $days; 777 } 778 779 /** 780 * DAYS360. 781 * 782 * Returns the number of days between two dates based on a 360-day year (twelve 30-day months), 783 * which is used in some accounting calculations. Use this function to help compute payments if 784 * your accounting system is based on twelve 30-day months. 785 * 786 * Excel Function: 787 * DAYS360(startDate,endDate[,method]) 788 * 789 * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), 790 * PHP DateTime object, or a standard date string 791 * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), 792 * PHP DateTime object, or a standard date string 793 * @param bool $method US or European Method 794 * FALSE or omitted: U.S. (NASD) method. If the starting date is 795 * the last day of a month, it becomes equal to the 30th of the 796 * same month. If the ending date is the last day of a month and 797 * the starting date is earlier than the 30th of a month, the 798 * ending date becomes equal to the 1st of the next month; 799 * otherwise the ending date becomes equal to the 30th of the 800 * same month. 801 * TRUE: European method. Starting dates and ending dates that 802 * occur on the 31st of a month become equal to the 30th of the 803 * same month. 804 * 805 * @return int|string Number of days between start date and end date 806 */ 807 public static function DAYS360($startDate = 0, $endDate = 0, $method = false) 808 { 809 $startDate = Functions::flattenSingleValue($startDate); 810 $endDate = Functions::flattenSingleValue($endDate); 811 812 if (is_string($startDate = self::getDateValue($startDate))) { 813 return Functions::VALUE(); 814 } 815 if (is_string($endDate = self::getDateValue($endDate))) { 816 return Functions::VALUE(); 817 } 818 819 if (!is_bool($method)) { 820 return Functions::VALUE(); 821 } 822 823 // Execute function 824 $PHPStartDateObject = Date::excelToDateTimeObject($startDate); 825 $startDay = $PHPStartDateObject->format('j'); 826 $startMonth = $PHPStartDateObject->format('n'); 827 $startYear = $PHPStartDateObject->format('Y'); 828 829 $PHPEndDateObject = Date::excelToDateTimeObject($endDate); 830 $endDay = $PHPEndDateObject->format('j'); 831 $endMonth = $PHPEndDateObject->format('n'); 832 $endYear = $PHPEndDateObject->format('Y'); 833 834 return self::dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, !$method); 835 } 836 837 /** 838 * YEARFRAC. 839 * 840 * Calculates the fraction of the year represented by the number of whole days between two dates 841 * (the start_date and the end_date). 842 * Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or 843 * obligations to assign to a specific term. 844 * 845 * Excel Function: 846 * YEARFRAC(startDate,endDate[,method]) 847 * See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html 848 * for description of algorithm used in Excel 849 * 850 * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), 851 * PHP DateTime object, or a standard date string 852 * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), 853 * PHP DateTime object, or a standard date string 854 * @param int $method Method used for the calculation 855 * 0 or omitted US (NASD) 30/360 856 * 1 Actual/actual 857 * 2 Actual/360 858 * 3 Actual/365 859 * 4 European 30/360 860 * 861 * @return float|string fraction of the year, or a string containing an error 862 */ 863 public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) 864 { 865 $startDate = Functions::flattenSingleValue($startDate); 866 $endDate = Functions::flattenSingleValue($endDate); 867 $method = Functions::flattenSingleValue($method); 868 869 if (is_string($startDate = self::getDateValue($startDate))) { 870 return Functions::VALUE(); 871 } 872 if (is_string($endDate = self::getDateValue($endDate))) { 873 return Functions::VALUE(); 874 } 875 if ($startDate > $endDate) { 876 $temp = $startDate; 877 $startDate = $endDate; 878 $endDate = $temp; 879 } 880 881 if (((is_numeric($method)) && (!is_string($method))) || ($method == '')) { 882 switch ($method) { 883 case 0: 884 return self::DAYS360($startDate, $endDate) / 360; 885 case 1: 886 $days = self::DATEDIF($startDate, $endDate); 887 $startYear = self::YEAR($startDate); 888 $endYear = self::YEAR($endDate); 889 $years = $endYear - $startYear + 1; 890 $startMonth = self::MONTHOFYEAR($startDate); 891 $startDay = self::DAYOFMONTH($startDate); 892 $endMonth = self::MONTHOFYEAR($endDate); 893 $endDay = self::DAYOFMONTH($endDate); 894 $startMonthDay = 100 * $startMonth + $startDay; 895 $endMonthDay = 100 * $endMonth + $endDay; 896 if ($years == 1) { 897 if (self::isLeapYear($endYear)) { 898 $tmpCalcAnnualBasis = 366; 899 } else { 900 $tmpCalcAnnualBasis = 365; 901 } 902 } elseif ($years == 2 && $startMonthDay >= $endMonthDay) { 903 if (self::isLeapYear($startYear)) { 904 if ($startMonthDay <= 229) { 905 $tmpCalcAnnualBasis = 366; 906 } else { 907 $tmpCalcAnnualBasis = 365; 908 } 909 } elseif (self::isLeapYear($endYear)) { 910 if ($endMonthDay >= 229) { 911 $tmpCalcAnnualBasis = 366; 912 } else { 913 $tmpCalcAnnualBasis = 365; 914 } 915 } else { 916 $tmpCalcAnnualBasis = 365; 917 } 918 } else { 919 $tmpCalcAnnualBasis = 0; 920 for ($year = $startYear; $year <= $endYear; ++$year) { 921 $tmpCalcAnnualBasis += self::isLeapYear($year) ? 366 : 365; 922 } 923 $tmpCalcAnnualBasis /= $years; 924 } 925 926 return $days / $tmpCalcAnnualBasis; 927 case 2: 928 return self::DATEDIF($startDate, $endDate) / 360; 929 case 3: 930 return self::DATEDIF($startDate, $endDate) / 365; 931 case 4: 932 return self::DAYS360($startDate, $endDate, true) / 360; 933 } 934 } 935 936 return Functions::VALUE(); 937 } 938 939 /** 940 * NETWORKDAYS. 941 * 942 * Returns the number of whole working days between start_date and end_date. Working days 943 * exclude weekends and any dates identified in holidays. 944 * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days 945 * worked during a specific term. 946 * 947 * Excel Function: 948 * NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]]) 949 * 950 * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), 951 * PHP DateTime object, or a standard date string 952 * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), 953 * PHP DateTime object, or a standard date string 954 * 955 * @return int|string Interval between the dates 956 */ 957 public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs) 958 { 959 // Retrieve the mandatory start and end date that are referenced in the function definition 960 $startDate = Functions::flattenSingleValue($startDate); 961 $endDate = Functions::flattenSingleValue($endDate); 962 // Get the optional days 963 $dateArgs = Functions::flattenArray($dateArgs); 964 965 // Validate the start and end dates 966 if (is_string($startDate = $sDate = self::getDateValue($startDate))) { 967 return Functions::VALUE(); 968 } 969 $startDate = (float) floor($startDate); 970 if (is_string($endDate = $eDate = self::getDateValue($endDate))) { 971 return Functions::VALUE(); 972 } 973 $endDate = (float) floor($endDate); 974 975 if ($sDate > $eDate) { 976 $startDate = $eDate; 977 $endDate = $sDate; 978 } 979 980 // Execute function 981 $startDoW = 6 - self::WEEKDAY($startDate, 2); 982 if ($startDoW < 0) { 983 $startDoW = 0; 984 } 985 $endDoW = self::WEEKDAY($endDate, 2); 986 if ($endDoW >= 6) { 987 $endDoW = 0; 988 } 989 990 $wholeWeekDays = floor(($endDate - $startDate) / 7) * 5; 991 $partWeekDays = $endDoW + $startDoW; 992 if ($partWeekDays > 5) { 993 $partWeekDays -= 5; 994 } 995 996 // Test any extra holiday parameters 997 $holidayCountedArray = []; 998 foreach ($dateArgs as $holidayDate) { 999 if (is_string($holidayDate = self::getDateValue($holidayDate))) { 1000 return Functions::VALUE(); 1001 } 1002 if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { 1003 if ((self::WEEKDAY($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { 1004 --$partWeekDays; 1005 $holidayCountedArray[] = $holidayDate; 1006 } 1007 } 1008 } 1009 1010 if ($sDate > $eDate) { 1011 return 0 - ($wholeWeekDays + $partWeekDays); 1012 } 1013 1014 return $wholeWeekDays + $partWeekDays; 1015 } 1016 1017 /** 1018 * WORKDAY. 1019 * 1020 * Returns the date that is the indicated number of working days before or after a date (the 1021 * starting date). Working days exclude weekends and any dates identified as holidays. 1022 * Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected 1023 * delivery times, or the number of days of work performed. 1024 * 1025 * Excel Function: 1026 * WORKDAY(startDate,endDays[,holidays[,holiday[,...]]]) 1027 * 1028 * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), 1029 * PHP DateTime object, or a standard date string 1030 * @param int $endDays The number of nonweekend and nonholiday days before or after 1031 * startDate. A positive value for days yields a future date; a 1032 * negative value yields a past date. 1033 * 1034 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 1035 * depending on the value of the ReturnDateType flag 1036 */ 1037 public static function WORKDAY($startDate, $endDays, ...$dateArgs) 1038 { 1039 // Retrieve the mandatory start date and days that are referenced in the function definition 1040 $startDate = Functions::flattenSingleValue($startDate); 1041 $endDays = Functions::flattenSingleValue($endDays); 1042 // Get the optional days 1043 $dateArgs = Functions::flattenArray($dateArgs); 1044 1045 if ((is_string($startDate = self::getDateValue($startDate))) || (!is_numeric($endDays))) { 1046 return Functions::VALUE(); 1047 } 1048 $startDate = (float) floor($startDate); 1049 $endDays = (int) floor($endDays); 1050 // If endDays is 0, we always return startDate 1051 if ($endDays == 0) { 1052 return $startDate; 1053 } 1054 1055 $decrementing = $endDays < 0; 1056 1057 // Adjust the start date if it falls over a weekend 1058 1059 $startDoW = self::WEEKDAY($startDate, 3); 1060 if (self::WEEKDAY($startDate, 3) >= 5) { 1061 $startDate += ($decrementing) ? -$startDoW + 4 : 7 - $startDoW; 1062 ($decrementing) ? $endDays++ : $endDays--; 1063 } 1064 1065 // Add endDays 1066 $endDate = (float) $startDate + ((int) ($endDays / 5) * 7) + ($endDays % 5); 1067 1068 // Adjust the calculated end date if it falls over a weekend 1069 $endDoW = self::WEEKDAY($endDate, 3); 1070 if ($endDoW >= 5) { 1071 $endDate += ($decrementing) ? -$endDoW + 4 : 7 - $endDoW; 1072 } 1073 1074 // Test any extra holiday parameters 1075 if (!empty($dateArgs)) { 1076 $holidayCountedArray = $holidayDates = []; 1077 foreach ($dateArgs as $holidayDate) { 1078 if (($holidayDate !== null) && (trim($holidayDate) > '')) { 1079 if (is_string($holidayDate = self::getDateValue($holidayDate))) { 1080 return Functions::VALUE(); 1081 } 1082 if (self::WEEKDAY($holidayDate, 3) < 5) { 1083 $holidayDates[] = $holidayDate; 1084 } 1085 } 1086 } 1087 if ($decrementing) { 1088 rsort($holidayDates, SORT_NUMERIC); 1089 } else { 1090 sort($holidayDates, SORT_NUMERIC); 1091 } 1092 foreach ($holidayDates as $holidayDate) { 1093 if ($decrementing) { 1094 if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { 1095 if (!in_array($holidayDate, $holidayCountedArray)) { 1096 --$endDate; 1097 $holidayCountedArray[] = $holidayDate; 1098 } 1099 } 1100 } else { 1101 if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { 1102 if (!in_array($holidayDate, $holidayCountedArray)) { 1103 ++$endDate; 1104 $holidayCountedArray[] = $holidayDate; 1105 } 1106 } 1107 } 1108 // Adjust the calculated end date if it falls over a weekend 1109 $endDoW = self::WEEKDAY($endDate, 3); 1110 if ($endDoW >= 5) { 1111 $endDate += ($decrementing) ? -$endDoW + 4 : 7 - $endDoW; 1112 } 1113 } 1114 } 1115 1116 switch (Functions::getReturnDateType()) { 1117 case Functions::RETURNDATE_EXCEL: 1118 return (float) $endDate; 1119 case Functions::RETURNDATE_UNIX_TIMESTAMP: 1120 return (int) Date::excelToTimestamp($endDate); 1121 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 1122 return Date::excelToDateTimeObject($endDate); 1123 } 1124 } 1125 1126 /** 1127 * DAYOFMONTH. 1128 * 1129 * Returns the day of the month, for a specified date. The day is given as an integer 1130 * ranging from 1 to 31. 1131 * 1132 * Excel Function: 1133 * DAY(dateValue) 1134 * 1135 * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), 1136 * PHP DateTime object, or a standard date string 1137 * 1138 * @return int|string Day of the month 1139 */ 1140 public static function DAYOFMONTH($dateValue = 1) 1141 { 1142 $dateValue = Functions::flattenSingleValue($dateValue); 1143 1144 if ($dateValue === null) { 1145 $dateValue = 1; 1146 } elseif (is_string($dateValue = self::getDateValue($dateValue))) { 1147 return Functions::VALUE(); 1148 } 1149 1150 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { 1151 if ($dateValue < 0.0) { 1152 return Functions::NAN(); 1153 } elseif ($dateValue < 1.0) { 1154 return 0; 1155 } 1156 } 1157 1158 // Execute function 1159 $PHPDateObject = Date::excelToDateTimeObject($dateValue); 1160 1161 return (int) $PHPDateObject->format('j'); 1162 } 1163 1164 /** 1165 * WEEKDAY. 1166 * 1167 * Returns the day of the week for a specified date. The day is given as an integer 1168 * ranging from 0 to 7 (dependent on the requested style). 1169 * 1170 * Excel Function: 1171 * WEEKDAY(dateValue[,style]) 1172 * 1173 * @param int $dateValue Excel date serial value (float), PHP date timestamp (integer), 1174 * PHP DateTime object, or a standard date string 1175 * @param int $style A number that determines the type of return value 1176 * 1 or omitted Numbers 1 (Sunday) through 7 (Saturday). 1177 * 2 Numbers 1 (Monday) through 7 (Sunday). 1178 * 3 Numbers 0 (Monday) through 6 (Sunday). 1179 * 1180 * @return int|string Day of the week value 1181 */ 1182 public static function WEEKDAY($dateValue = 1, $style = 1) 1183 { 1184 $dateValue = Functions::flattenSingleValue($dateValue); 1185 $style = Functions::flattenSingleValue($style); 1186 1187 if (!is_numeric($style)) { 1188 return Functions::VALUE(); 1189 } elseif (($style < 1) || ($style > 3)) { 1190 return Functions::NAN(); 1191 } 1192 $style = floor($style); 1193 1194 if ($dateValue === null) { 1195 $dateValue = 1; 1196 } elseif (is_string($dateValue = self::getDateValue($dateValue))) { 1197 return Functions::VALUE(); 1198 } elseif ($dateValue < 0.0) { 1199 return Functions::NAN(); 1200 } 1201 1202 // Execute function 1203 $PHPDateObject = Date::excelToDateTimeObject($dateValue); 1204 $DoW = (int) $PHPDateObject->format('w'); 1205 1206 $firstDay = 1; 1207 switch ($style) { 1208 case 1: 1209 ++$DoW; 1210 1211 break; 1212 case 2: 1213 if ($DoW === 0) { 1214 $DoW = 7; 1215 } 1216 1217 break; 1218 case 3: 1219 if ($DoW === 0) { 1220 $DoW = 7; 1221 } 1222 $firstDay = 0; 1223 --$DoW; 1224 1225 break; 1226 } 1227 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { 1228 // Test for Excel's 1900 leap year, and introduce the error as required 1229 if (($PHPDateObject->format('Y') == 1900) && ($PHPDateObject->format('n') <= 2)) { 1230 --$DoW; 1231 if ($DoW < $firstDay) { 1232 $DoW += 7; 1233 } 1234 } 1235 } 1236 1237 return $DoW; 1238 } 1239 1240 const STARTWEEK_SUNDAY = 1; 1241 const STARTWEEK_MONDAY = 2; 1242 const STARTWEEK_MONDAY_ALT = 11; 1243 const STARTWEEK_TUESDAY = 12; 1244 const STARTWEEK_WEDNESDAY = 13; 1245 const STARTWEEK_THURSDAY = 14; 1246 const STARTWEEK_FRIDAY = 15; 1247 const STARTWEEK_SATURDAY = 16; 1248 const STARTWEEK_SUNDAY_ALT = 17; 1249 const DOW_SUNDAY = 1; 1250 const DOW_MONDAY = 2; 1251 const DOW_TUESDAY = 3; 1252 const DOW_WEDNESDAY = 4; 1253 const DOW_THURSDAY = 5; 1254 const DOW_FRIDAY = 6; 1255 const DOW_SATURDAY = 7; 1256 const STARTWEEK_MONDAY_ISO = 21; 1257 const METHODARR = [ 1258 self::STARTWEEK_SUNDAY => self::DOW_SUNDAY, 1259 self::DOW_MONDAY, 1260 self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY, 1261 self::DOW_TUESDAY, 1262 self::DOW_WEDNESDAY, 1263 self::DOW_THURSDAY, 1264 self::DOW_FRIDAY, 1265 self::DOW_SATURDAY, 1266 self::DOW_SUNDAY, 1267 self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO, 1268 ]; 1269 1270 /** 1271 * WEEKNUM. 1272 * 1273 * Returns the week of the year for a specified date. 1274 * The WEEKNUM function considers the week containing January 1 to be the first week of the year. 1275 * However, there is a European standard that defines the first week as the one with the majority 1276 * of days (four or more) falling in the new year. This means that for years in which there are 1277 * three days or less in the first week of January, the WEEKNUM function returns week numbers 1278 * that are incorrect according to the European standard. 1279 * 1280 * Excel Function: 1281 * WEEKNUM(dateValue[,style]) 1282 * 1283 * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), 1284 * PHP DateTime object, or a standard date string 1285 * @param int $method Week begins on Sunday or Monday 1286 * 1 or omitted Week begins on Sunday. 1287 * 2 Week begins on Monday. 1288 * 11 Week begins on Monday. 1289 * 12 Week begins on Tuesday. 1290 * 13 Week begins on Wednesday. 1291 * 14 Week begins on Thursday. 1292 * 15 Week begins on Friday. 1293 * 16 Week begins on Saturday. 1294 * 17 Week begins on Sunday. 1295 * 21 ISO (Jan. 4 is week 1, begins on Monday). 1296 * 1297 * @return int|string Week Number 1298 */ 1299 public static function WEEKNUM($dateValue = 1, $method = self::STARTWEEK_SUNDAY) 1300 { 1301 $dateValue = Functions::flattenSingleValue($dateValue); 1302 $method = Functions::flattenSingleValue($method); 1303 1304 if (!is_numeric($method)) { 1305 return Functions::VALUE(); 1306 } 1307 $method = (int) $method; 1308 if (!array_key_exists($method, self::METHODARR)) { 1309 return Functions::NaN(); 1310 } 1311 $method = self::METHODARR[$method]; 1312 1313 $dateValue = self::getDateValue($dateValue); 1314 if (is_string($dateValue)) { 1315 return Functions::VALUE(); 1316 } 1317 if ($dateValue < 0.0) { 1318 return Functions::NAN(); 1319 } 1320 1321 // Execute function 1322 $PHPDateObject = Date::excelToDateTimeObject($dateValue); 1323 if ($method == self::STARTWEEK_MONDAY_ISO) { 1324 return (int) $PHPDateObject->format('W'); 1325 } 1326 $dayOfYear = $PHPDateObject->format('z'); 1327 $PHPDateObject->modify('-' . $dayOfYear . ' days'); 1328 $firstDayOfFirstWeek = $PHPDateObject->format('w'); 1329 $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; 1330 $daysInFirstWeek += 7 * !$daysInFirstWeek; 1331 $endFirstWeek = $daysInFirstWeek - 1; 1332 $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7); 1333 1334 return (int) $weekOfYear; 1335 } 1336 1337 /** 1338 * ISOWEEKNUM. 1339 * 1340 * Returns the ISO 8601 week number of the year for a specified date. 1341 * 1342 * Excel Function: 1343 * ISOWEEKNUM(dateValue) 1344 * 1345 * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), 1346 * PHP DateTime object, or a standard date string 1347 * 1348 * @return int|string Week Number 1349 */ 1350 public static function ISOWEEKNUM($dateValue = 1) 1351 { 1352 $dateValue = Functions::flattenSingleValue($dateValue); 1353 1354 if ($dateValue === null) { 1355 $dateValue = 1; 1356 } elseif (is_string($dateValue = self::getDateValue($dateValue))) { 1357 return Functions::VALUE(); 1358 } elseif ($dateValue < 0.0) { 1359 return Functions::NAN(); 1360 } 1361 1362 // Execute function 1363 $PHPDateObject = Date::excelToDateTimeObject($dateValue); 1364 1365 return (int) $PHPDateObject->format('W'); 1366 } 1367 1368 /** 1369 * MONTHOFYEAR. 1370 * 1371 * Returns the month of a date represented by a serial number. 1372 * The month is given as an integer, ranging from 1 (January) to 12 (December). 1373 * 1374 * Excel Function: 1375 * MONTH(dateValue) 1376 * 1377 * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), 1378 * PHP DateTime object, or a standard date string 1379 * 1380 * @return int|string Month of the year 1381 */ 1382 public static function MONTHOFYEAR($dateValue = 1) 1383 { 1384 $dateValue = Functions::flattenSingleValue($dateValue); 1385 1386 if (empty($dateValue)) { 1387 $dateValue = 1; 1388 } 1389 if (is_string($dateValue = self::getDateValue($dateValue))) { 1390 return Functions::VALUE(); 1391 } elseif ($dateValue < 0.0) { 1392 return Functions::NAN(); 1393 } 1394 1395 // Execute function 1396 $PHPDateObject = Date::excelToDateTimeObject($dateValue); 1397 1398 return (int) $PHPDateObject->format('n'); 1399 } 1400 1401 /** 1402 * YEAR. 1403 * 1404 * Returns the year corresponding to a date. 1405 * The year is returned as an integer in the range 1900-9999. 1406 * 1407 * Excel Function: 1408 * YEAR(dateValue) 1409 * 1410 * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), 1411 * PHP DateTime object, or a standard date string 1412 * 1413 * @return int|string Year 1414 */ 1415 public static function YEAR($dateValue = 1) 1416 { 1417 $dateValue = Functions::flattenSingleValue($dateValue); 1418 1419 if ($dateValue === null) { 1420 $dateValue = 1; 1421 } elseif (is_string($dateValue = self::getDateValue($dateValue))) { 1422 return Functions::VALUE(); 1423 } elseif ($dateValue < 0.0) { 1424 return Functions::NAN(); 1425 } 1426 1427 // Execute function 1428 $PHPDateObject = Date::excelToDateTimeObject($dateValue); 1429 1430 return (int) $PHPDateObject->format('Y'); 1431 } 1432 1433 /** 1434 * HOUROFDAY. 1435 * 1436 * Returns the hour of a time value. 1437 * The hour is given as an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.). 1438 * 1439 * Excel Function: 1440 * HOUR(timeValue) 1441 * 1442 * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), 1443 * PHP DateTime object, or a standard time string 1444 * 1445 * @return int|string Hour 1446 */ 1447 public static function HOUROFDAY($timeValue = 0) 1448 { 1449 $timeValue = Functions::flattenSingleValue($timeValue); 1450 1451 if (!is_numeric($timeValue)) { 1452 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { 1453 $testVal = strtok($timeValue, '/-: '); 1454 if (strlen($testVal) < strlen($timeValue)) { 1455 return Functions::VALUE(); 1456 } 1457 } 1458 $timeValue = self::getTimeValue($timeValue); 1459 if (is_string($timeValue)) { 1460 return Functions::VALUE(); 1461 } 1462 } 1463 // Execute function 1464 if ($timeValue >= 1) { 1465 $timeValue = fmod($timeValue, 1); 1466 } elseif ($timeValue < 0.0) { 1467 return Functions::NAN(); 1468 } 1469 $timeValue = Date::excelToTimestamp($timeValue); 1470 1471 return (int) gmdate('G', $timeValue); 1472 } 1473 1474 /** 1475 * MINUTE. 1476 * 1477 * Returns the minutes of a time value. 1478 * The minute is given as an integer, ranging from 0 to 59. 1479 * 1480 * Excel Function: 1481 * MINUTE(timeValue) 1482 * 1483 * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), 1484 * PHP DateTime object, or a standard time string 1485 * 1486 * @return int|string Minute 1487 */ 1488 public static function MINUTE($timeValue = 0) 1489 { 1490 $timeValue = $timeTester = Functions::flattenSingleValue($timeValue); 1491 1492 if (!is_numeric($timeValue)) { 1493 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { 1494 $testVal = strtok($timeValue, '/-: '); 1495 if (strlen($testVal) < strlen($timeValue)) { 1496 return Functions::VALUE(); 1497 } 1498 } 1499 $timeValue = self::getTimeValue($timeValue); 1500 if (is_string($timeValue)) { 1501 return Functions::VALUE(); 1502 } 1503 } 1504 // Execute function 1505 if ($timeValue >= 1) { 1506 $timeValue = fmod($timeValue, 1); 1507 } elseif ($timeValue < 0.0) { 1508 return Functions::NAN(); 1509 } 1510 $timeValue = Date::excelToTimestamp($timeValue); 1511 1512 return (int) gmdate('i', $timeValue); 1513 } 1514 1515 /** 1516 * SECOND. 1517 * 1518 * Returns the seconds of a time value. 1519 * The second is given as an integer in the range 0 (zero) to 59. 1520 * 1521 * Excel Function: 1522 * SECOND(timeValue) 1523 * 1524 * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), 1525 * PHP DateTime object, or a standard time string 1526 * 1527 * @return int|string Second 1528 */ 1529 public static function SECOND($timeValue = 0) 1530 { 1531 $timeValue = Functions::flattenSingleValue($timeValue); 1532 1533 if (!is_numeric($timeValue)) { 1534 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { 1535 $testVal = strtok($timeValue, '/-: '); 1536 if (strlen($testVal) < strlen($timeValue)) { 1537 return Functions::VALUE(); 1538 } 1539 } 1540 $timeValue = self::getTimeValue($timeValue); 1541 if (is_string($timeValue)) { 1542 return Functions::VALUE(); 1543 } 1544 } 1545 // Execute function 1546 if ($timeValue >= 1) { 1547 $timeValue = fmod($timeValue, 1); 1548 } elseif ($timeValue < 0.0) { 1549 return Functions::NAN(); 1550 } 1551 $timeValue = Date::excelToTimestamp($timeValue); 1552 1553 return (int) gmdate('s', $timeValue); 1554 } 1555 1556 /** 1557 * EDATE. 1558 * 1559 * Returns the serial number that represents the date that is the indicated number of months 1560 * before or after a specified date (the start_date). 1561 * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month 1562 * as the date of issue. 1563 * 1564 * Excel Function: 1565 * EDATE(dateValue,adjustmentMonths) 1566 * 1567 * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), 1568 * PHP DateTime object, or a standard date string 1569 * @param int $adjustmentMonths The number of months before or after start_date. 1570 * A positive value for months yields a future date; 1571 * a negative value yields a past date. 1572 * 1573 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 1574 * depending on the value of the ReturnDateType flag 1575 */ 1576 public static function EDATE($dateValue = 1, $adjustmentMonths = 0) 1577 { 1578 $dateValue = Functions::flattenSingleValue($dateValue); 1579 $adjustmentMonths = Functions::flattenSingleValue($adjustmentMonths); 1580 1581 if (!is_numeric($adjustmentMonths)) { 1582 return Functions::VALUE(); 1583 } 1584 $adjustmentMonths = floor($adjustmentMonths); 1585 1586 if (is_string($dateValue = self::getDateValue($dateValue))) { 1587 return Functions::VALUE(); 1588 } 1589 1590 // Execute function 1591 $PHPDateObject = self::adjustDateByMonths($dateValue, $adjustmentMonths); 1592 1593 switch (Functions::getReturnDateType()) { 1594 case Functions::RETURNDATE_EXCEL: 1595 return (float) Date::PHPToExcel($PHPDateObject); 1596 case Functions::RETURNDATE_UNIX_TIMESTAMP: 1597 return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); 1598 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 1599 return $PHPDateObject; 1600 } 1601 } 1602 1603 /** 1604 * EOMONTH. 1605 * 1606 * Returns the date value for the last day of the month that is the indicated number of months 1607 * before or after start_date. 1608 * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. 1609 * 1610 * Excel Function: 1611 * EOMONTH(dateValue,adjustmentMonths) 1612 * 1613 * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), 1614 * PHP DateTime object, or a standard date string 1615 * @param int $adjustmentMonths The number of months before or after start_date. 1616 * A positive value for months yields a future date; 1617 * a negative value yields a past date. 1618 * 1619 * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, 1620 * depending on the value of the ReturnDateType flag 1621 */ 1622 public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) 1623 { 1624 $dateValue = Functions::flattenSingleValue($dateValue); 1625 $adjustmentMonths = Functions::flattenSingleValue($adjustmentMonths); 1626 1627 if (!is_numeric($adjustmentMonths)) { 1628 return Functions::VALUE(); 1629 } 1630 $adjustmentMonths = floor($adjustmentMonths); 1631 1632 if (is_string($dateValue = self::getDateValue($dateValue))) { 1633 return Functions::VALUE(); 1634 } 1635 1636 // Execute function 1637 $PHPDateObject = self::adjustDateByMonths($dateValue, $adjustmentMonths + 1); 1638 $adjustDays = (int) $PHPDateObject->format('d'); 1639 $adjustDaysString = '-' . $adjustDays . ' days'; 1640 $PHPDateObject->modify($adjustDaysString); 1641 1642 switch (Functions::getReturnDateType()) { 1643 case Functions::RETURNDATE_EXCEL: 1644 return (float) Date::PHPToExcel($PHPDateObject); 1645 case Functions::RETURNDATE_UNIX_TIMESTAMP: 1646 return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); 1647 case Functions::RETURNDATE_PHP_DATETIME_OBJECT: 1648 return $PHPDateObject; 1649 } 1650 } 1651 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body