Changes to WEEKNUM and YEARFRAC (#1316)
* Changes to WEEKNUM and YEARFRAC The optional second parameter for WEEKNUM can take any of 10 values (1, 2, 11-17, and 21), but currently only 1 and 2 are supported. This change adds support for the other 8 possibilities. YEARFRAC in Excel does not require that end date be before start date, but PhpSpreadsheet was returning an error in that situation. YEARFRAC third parameter (method) of 1 was not correctly implemented. I was able to find a description of the algorithm, and documented that location in the code, and implemented according to that spec. PHPExcel had a (failing) test to assert the result of YEARFRAC("1960-12-19", "2008-06-28", 1). This test had been dropped from PhpSpreadsheet, and is now restored; several new tests have been added, and verified against Excel. * Add YEARFRAC Tests Scrutinizer reported a very mysterious failure with no details. project.metric_change("scrutinizer.test_coverage", < 0), without even a link to explain what it is reporting. It is possible that it was a complaint about code coverage. If so, I have added some tests which will, I hope, eliminate the problem. * Make Array Constant Responding to review from Mark Baker. * Merge with PR 1362 Bugfix 1161 Travis CI reported problem with Calculation.php (which is not part of this change). That was changed in master a few days ago (delete some unused code). Perhaps the lack of that change is the problem here. Merging it manually.
This commit is contained in:
parent
0c52f173aa
commit
cb18163a1d
|
@ -878,6 +878,8 @@ class DateTime
|
||||||
*
|
*
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* YEARFRAC(startDate,endDate[,method])
|
* YEARFRAC(startDate,endDate[,method])
|
||||||
|
* See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html
|
||||||
|
* for description of algorithm used in Excel
|
||||||
*
|
*
|
||||||
* @category Date/Time Functions
|
* @category Date/Time Functions
|
||||||
*
|
*
|
||||||
|
@ -906,6 +908,11 @@ class DateTime
|
||||||
if (is_string($endDate = self::getDateValue($endDate))) {
|
if (is_string($endDate = self::getDateValue($endDate))) {
|
||||||
return Functions::VALUE();
|
return Functions::VALUE();
|
||||||
}
|
}
|
||||||
|
if ($startDate > $endDate) {
|
||||||
|
$temp = $startDate;
|
||||||
|
$startDate = $endDate;
|
||||||
|
$endDate = $temp;
|
||||||
|
}
|
||||||
|
|
||||||
if (((is_numeric($method)) && (!is_string($method))) || ($method == '')) {
|
if (((is_numeric($method)) && (!is_string($method))) || ($method == '')) {
|
||||||
switch ($method) {
|
switch ($method) {
|
||||||
|
@ -916,46 +923,43 @@ class DateTime
|
||||||
$startYear = self::YEAR($startDate);
|
$startYear = self::YEAR($startDate);
|
||||||
$endYear = self::YEAR($endDate);
|
$endYear = self::YEAR($endDate);
|
||||||
$years = $endYear - $startYear + 1;
|
$years = $endYear - $startYear + 1;
|
||||||
$leapDays = 0;
|
|
||||||
if ($years == 1) {
|
|
||||||
if (self::isLeapYear($endYear)) {
|
|
||||||
$startMonth = self::MONTHOFYEAR($startDate);
|
|
||||||
$endMonth = self::MONTHOFYEAR($endDate);
|
|
||||||
$endDay = self::DAYOFMONTH($endDate);
|
|
||||||
if (($startMonth < 3) ||
|
|
||||||
(($endMonth * 100 + $endDay) >= (2 * 100 + 29))) {
|
|
||||||
$leapDays += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for ($year = $startYear; $year <= $endYear; ++$year) {
|
|
||||||
if ($year == $startYear) {
|
|
||||||
$startMonth = self::MONTHOFYEAR($startDate);
|
$startMonth = self::MONTHOFYEAR($startDate);
|
||||||
$startDay = self::DAYOFMONTH($startDate);
|
$startDay = self::DAYOFMONTH($startDate);
|
||||||
if ($startMonth < 3) {
|
|
||||||
$leapDays += (self::isLeapYear($year)) ? 1 : 0;
|
|
||||||
}
|
|
||||||
} elseif ($year == $endYear) {
|
|
||||||
$endMonth = self::MONTHOFYEAR($endDate);
|
$endMonth = self::MONTHOFYEAR($endDate);
|
||||||
$endDay = self::DAYOFMONTH($endDate);
|
$endDay = self::DAYOFMONTH($endDate);
|
||||||
if (($endMonth * 100 + $endDay) >= (2 * 100 + 29)) {
|
$startMonthDay = 100 * $startMonth + $startDay;
|
||||||
$leapDays += (self::isLeapYear($year)) ? 1 : 0;
|
$endMonthDay = 100 * $endMonth + $endDay;
|
||||||
|
if ($years == 1) {
|
||||||
|
if (self::isLeapYear($endYear)) {
|
||||||
|
$tmpCalcAnnualBasis = 366;
|
||||||
|
} else {
|
||||||
|
$tmpCalcAnnualBasis = 365;
|
||||||
|
}
|
||||||
|
} elseif ($years == 2 && $startMonthDay >= $endMonthDay) {
|
||||||
|
if (self::isLeapYear($startYear)) {
|
||||||
|
if ($startMonthDay <= 229) {
|
||||||
|
$tmpCalcAnnualBasis = 366;
|
||||||
|
} else {
|
||||||
|
$tmpCalcAnnualBasis = 365;
|
||||||
|
}
|
||||||
|
} elseif (self::isLeapYear($endYear)) {
|
||||||
|
if ($endMonthDay >= 229) {
|
||||||
|
$tmpCalcAnnualBasis = 366;
|
||||||
|
} else {
|
||||||
|
$tmpCalcAnnualBasis = 365;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$leapDays += (self::isLeapYear($year)) ? 1 : 0;
|
$tmpCalcAnnualBasis = 365;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$tmpCalcAnnualBasis = 0;
|
||||||
|
for ($year = $startYear; $year <= $endYear; ++$year) {
|
||||||
|
$tmpCalcAnnualBasis += self::isLeapYear($year) ? 366 : 365;
|
||||||
}
|
}
|
||||||
if ($years == 2) {
|
$tmpCalcAnnualBasis /= $years;
|
||||||
if (($leapDays == 0) && (self::isLeapYear($startYear)) && ($days > 365)) {
|
|
||||||
$leapDays = 1;
|
|
||||||
} elseif ($days < 366) {
|
|
||||||
$years = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$leapDays /= $years;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $days / (365 + $leapDays);
|
return $days / $tmpCalcAnnualBasis;
|
||||||
case 2:
|
case 2:
|
||||||
return self::DATEDIF($startDate, $endDate) / 360;
|
return self::DATEDIF($startDate, $endDate) / 360;
|
||||||
case 3:
|
case 3:
|
||||||
|
@ -1273,6 +1277,36 @@ class DateTime
|
||||||
return $DoW;
|
return $DoW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STARTWEEK_SUNDAY = 1;
|
||||||
|
const STARTWEEK_MONDAY = 2;
|
||||||
|
const STARTWEEK_MONDAY_ALT = 11;
|
||||||
|
const STARTWEEK_TUESDAY = 12;
|
||||||
|
const STARTWEEK_WEDNESDAY = 13;
|
||||||
|
const STARTWEEK_THURSDAY = 14;
|
||||||
|
const STARTWEEK_FRIDAY = 15;
|
||||||
|
const STARTWEEK_SATURDAY = 16;
|
||||||
|
const STARTWEEK_SUNDAY_ALT = 17;
|
||||||
|
const DOW_SUNDAY = 1;
|
||||||
|
const DOW_MONDAY = 2;
|
||||||
|
const DOW_TUESDAY = 3;
|
||||||
|
const DOW_WEDNESDAY = 4;
|
||||||
|
const DOW_THURSDAY = 5;
|
||||||
|
const DOW_FRIDAY = 6;
|
||||||
|
const DOW_SATURDAY = 7;
|
||||||
|
const STARTWEEK_MONDAY_ISO = 21;
|
||||||
|
const METHODARR = [
|
||||||
|
self::STARTWEEK_SUNDAY => self::DOW_SUNDAY,
|
||||||
|
self::DOW_MONDAY,
|
||||||
|
self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY,
|
||||||
|
self::DOW_TUESDAY,
|
||||||
|
self::DOW_WEDNESDAY,
|
||||||
|
self::DOW_THURSDAY,
|
||||||
|
self::DOW_FRIDAY,
|
||||||
|
self::DOW_SATURDAY,
|
||||||
|
self::DOW_SUNDAY,
|
||||||
|
self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO,
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WEEKNUM.
|
* WEEKNUM.
|
||||||
*
|
*
|
||||||
|
@ -1291,41 +1325,51 @@ class DateTime
|
||||||
* @param int $method Week begins on Sunday or Monday
|
* @param int $method Week begins on Sunday or Monday
|
||||||
* 1 or omitted Week begins on Sunday.
|
* 1 or omitted Week begins on Sunday.
|
||||||
* 2 Week begins on Monday.
|
* 2 Week begins on Monday.
|
||||||
|
* 11 Week begins on Monday.
|
||||||
|
* 12 Week begins on Tuesday.
|
||||||
|
* 13 Week begins on Wednesday.
|
||||||
|
* 14 Week begins on Thursday.
|
||||||
|
* 15 Week begins on Friday.
|
||||||
|
* 16 Week begins on Saturday.
|
||||||
|
* 17 Week begins on Sunday.
|
||||||
|
* 21 ISO (Jan. 4 is week 1, begins on Monday).
|
||||||
*
|
*
|
||||||
* @return int|string Week Number
|
* @return int|string Week Number
|
||||||
*/
|
*/
|
||||||
public static function WEEKNUM($dateValue = 1, $method = 1)
|
public static function WEEKNUM($dateValue = 1, $method = self::STARTWEEK_SUNDAY)
|
||||||
{
|
{
|
||||||
$dateValue = Functions::flattenSingleValue($dateValue);
|
$dateValue = Functions::flattenSingleValue($dateValue);
|
||||||
$method = Functions::flattenSingleValue($method);
|
$method = Functions::flattenSingleValue($method);
|
||||||
|
|
||||||
if (!is_numeric($method)) {
|
if (!is_numeric($method)) {
|
||||||
return Functions::VALUE();
|
return Functions::VALUE();
|
||||||
} elseif (($method < 1) || ($method > 2)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
}
|
||||||
$method = floor($method);
|
$method = (int) $method;
|
||||||
|
if (!array_key_exists($method, self::METHODARR)) {
|
||||||
|
return Functions::NaN();
|
||||||
|
}
|
||||||
|
$method = self::METHODARR[$method];
|
||||||
|
|
||||||
if ($dateValue === null) {
|
$dateValue = self::getDateValue($dateValue);
|
||||||
$dateValue = 1;
|
if (is_string($dateValue)) {
|
||||||
} elseif (is_string($dateValue = self::getDateValue($dateValue))) {
|
|
||||||
return Functions::VALUE();
|
return Functions::VALUE();
|
||||||
} elseif ($dateValue < 0.0) {
|
}
|
||||||
|
if ($dateValue < 0.0) {
|
||||||
return Functions::NAN();
|
return Functions::NAN();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute function
|
// Execute function
|
||||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||||
|
if ($method == self::STARTWEEK_MONDAY_ISO) {
|
||||||
|
return (int) $PHPDateObject->format('W');
|
||||||
|
}
|
||||||
$dayOfYear = $PHPDateObject->format('z');
|
$dayOfYear = $PHPDateObject->format('z');
|
||||||
$PHPDateObject->modify('-' . $dayOfYear . ' days');
|
$PHPDateObject->modify('-' . $dayOfYear . ' days');
|
||||||
$firstDayOfFirstWeek = $PHPDateObject->format('w');
|
$firstDayOfFirstWeek = $PHPDateObject->format('w');
|
||||||
$daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7;
|
$daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7;
|
||||||
$interval = $dayOfYear - $daysInFirstWeek;
|
$daysInFirstWeek += 7 * !$daysInFirstWeek;
|
||||||
$weekOfYear = floor($interval / 7) + 1;
|
$endFirstWeek = $daysInFirstWeek - 1;
|
||||||
|
$weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7);
|
||||||
if ($daysInFirstWeek) {
|
|
||||||
++$weekOfYear;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int) $weekOfYear;
|
return (int) $weekOfYear;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,10 @@ return [
|
||||||
'#NUM!',
|
'#NUM!',
|
||||||
'3/7/1977', 0,
|
'3/7/1977', 0,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
'3/7/1977', -1,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#VALUE!',
|
'#VALUE!',
|
||||||
'Invalid', 1,
|
'Invalid', 1,
|
||||||
|
@ -61,4 +65,112 @@ return [
|
||||||
'#NUM!',
|
'#NUM!',
|
||||||
-1,
|
-1,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
53,
|
||||||
|
'2019-12-29', 1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
'2019-12-29', 2,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
'2019-12-29', 3,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
'2019-12-29', 10,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
'2019-12-29', 11,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
'2019-12-29', 12,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53,
|
||||||
|
'2019-12-29', 13,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53,
|
||||||
|
'2019-12-29', 14,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53,
|
||||||
|
'2019-12-29', 15,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53,
|
||||||
|
'2019-12-29', 16,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53,
|
||||||
|
'2019-12-29', 17,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
'2019-12-29', 18,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
'2019-12-29', 20,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
'2019-12-29', 22,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
'2019-12-29', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53,
|
||||||
|
'2020-12-29', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
'2021-12-29', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
'2022-12-29', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
'2020-01-01', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53,
|
||||||
|
'2021-01-01', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
'2022-01-01', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
'2023-01-01', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
'2020-01-08', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
'2021-01-08', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
'2022-01-08', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
'2023-01-08', 21,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
'2025-12-29', 21,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -7,6 +7,12 @@ return [
|
||||||
'2007-1-10',
|
'2007-1-10',
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
0.025,
|
||||||
|
'2007-1-10',
|
||||||
|
'2007-1-1',
|
||||||
|
0,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
0.024657534246580001,
|
0.024657534246580001,
|
||||||
'2007-1-1',
|
'2007-1-1',
|
||||||
|
@ -337,6 +343,12 @@ return [
|
||||||
'2008-6-28',
|
'2008-6-28',
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
47.52162252765670,
|
||||||
|
'1960-12-19',
|
||||||
|
'2008-6-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
48.216666666666697,
|
48.216666666666697,
|
||||||
'1960-12-19',
|
'1960-12-19',
|
||||||
|
@ -385,4 +397,167 @@ return [
|
||||||
'2008-6-28',
|
'2008-6-28',
|
||||||
4,
|
4,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
0.163934426,
|
||||||
|
'1960-01-01',
|
||||||
|
'1960-03-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.161643836,
|
||||||
|
'1961-01-01',
|
||||||
|
'1961-03-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.161643836,
|
||||||
|
'1963-03-01',
|
||||||
|
'1963-01-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.086183311,
|
||||||
|
'1960-01-01',
|
||||||
|
'1961-02-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.084931507,
|
||||||
|
'1961-01-01',
|
||||||
|
'1962-02-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.083447332,
|
||||||
|
'1963-01-01',
|
||||||
|
'1964-02-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.162790698,
|
||||||
|
'1963-01-01',
|
||||||
|
'1964-03-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.841530055,
|
||||||
|
'2020-02-28',
|
||||||
|
'2021-01-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.764383562,
|
||||||
|
'2020-03-28',
|
||||||
|
'2021-01-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.841530055,
|
||||||
|
'2023-04-28',
|
||||||
|
'2024-03-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.838797814,
|
||||||
|
'2023-04-28',
|
||||||
|
'2024-02-29',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.838356164,
|
||||||
|
'2023-04-28',
|
||||||
|
'2024-02-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.753424658,
|
||||||
|
'2023-04-28',
|
||||||
|
'2024-01-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.753424658,
|
||||||
|
'2022-04-28',
|
||||||
|
'2023-01-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
'2020-01-01',
|
||||||
|
'2021-01-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.99726776,
|
||||||
|
'2020-02-28',
|
||||||
|
'2021-02-27',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.764383562,
|
||||||
|
'2020-03-28',
|
||||||
|
'2021-01-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.841530055,
|
||||||
|
'2023-04-28',
|
||||||
|
'2024-03-01',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.838797814,
|
||||||
|
'2023-04-28',
|
||||||
|
'2024-02-29',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.838356164,
|
||||||
|
'2023-04-28',
|
||||||
|
'2024-02-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.753424658,
|
||||||
|
'2023-04-28',
|
||||||
|
'2024-01-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.753424658,
|
||||||
|
'2022-04-28',
|
||||||
|
'2023-01-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.082191781,
|
||||||
|
'2022-04-28',
|
||||||
|
'2023-05-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.002739726,
|
||||||
|
'2022-04-27',
|
||||||
|
'2023-04-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.084699454,
|
||||||
|
'2024-04-27',
|
||||||
|
'2024-05-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.084931507,
|
||||||
|
'2023-04-27',
|
||||||
|
'2023-05-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2.085766423,
|
||||||
|
'2023-04-27',
|
||||||
|
'2025-05-28',
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in New Issue