PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.5.1
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.5.1
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Calculation / DateTime.php

DateTime.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.5.1, at vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php

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