DateTime.php 62 KB

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