| 1441 |
ariadna |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
|
|
4 |
|
|
|
5 |
use DateTime;
|
|
|
6 |
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
|
|
7 |
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
|
|
8 |
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
|
|
9 |
|
|
|
10 |
class WorkDay
|
|
|
11 |
{
|
|
|
12 |
use ArrayEnabled;
|
|
|
13 |
|
|
|
14 |
/**
|
|
|
15 |
* WORKDAY.
|
|
|
16 |
*
|
|
|
17 |
* Returns the date that is the indicated number of working days before or after a date (the
|
|
|
18 |
* starting date). Working days exclude weekends and any dates identified as holidays.
|
|
|
19 |
* Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected
|
|
|
20 |
* delivery times, or the number of days of work performed.
|
|
|
21 |
*
|
|
|
22 |
* Excel Function:
|
|
|
23 |
* WORKDAY(startDate,endDays[,holidays[,holiday[,...]]])
|
|
|
24 |
*
|
|
|
25 |
* @param array|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
|
|
|
26 |
* PHP DateTime object, or a standard date string
|
|
|
27 |
* Or can be an array of date values
|
|
|
28 |
* @param array|int $endDays The number of nonweekend and nonholiday days before or after
|
|
|
29 |
* startDate. A positive value for days yields a future date; a
|
|
|
30 |
* negative value yields a past date.
|
|
|
31 |
* Or can be an array of int values
|
|
|
32 |
* @param null|mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
|
|
|
33 |
*
|
|
|
34 |
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
|
|
|
35 |
* depending on the value of the ReturnDateType flag
|
|
|
36 |
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
|
|
|
37 |
* will also be an array with matching dimensions
|
|
|
38 |
*/
|
|
|
39 |
public static function date(mixed $startDate, array|int|string $endDays, mixed ...$dateArgs): array|float|int|DateTime|string
|
|
|
40 |
{
|
|
|
41 |
if (is_array($startDate) || is_array($endDays)) {
|
|
|
42 |
return self::evaluateArrayArgumentsSubset(
|
|
|
43 |
[self::class, __FUNCTION__],
|
|
|
44 |
2,
|
|
|
45 |
$startDate,
|
|
|
46 |
$endDays,
|
|
|
47 |
...$dateArgs
|
|
|
48 |
);
|
|
|
49 |
}
|
|
|
50 |
|
|
|
51 |
// Retrieve the mandatory start date and days that are referenced in the function definition
|
|
|
52 |
try {
|
|
|
53 |
$startDate = Helpers::getDateValue($startDate);
|
|
|
54 |
$endDays = Helpers::validateNumericNull($endDays);
|
|
|
55 |
$holidayArray = array_map([Helpers::class, 'getDateValue'], Functions::flattenArray($dateArgs));
|
|
|
56 |
} catch (Exception $e) {
|
|
|
57 |
return $e->getMessage();
|
|
|
58 |
}
|
|
|
59 |
|
|
|
60 |
$startDate = (float) floor($startDate);
|
|
|
61 |
$endDays = (int) floor($endDays);
|
|
|
62 |
// If endDays is 0, we always return startDate
|
|
|
63 |
if ($endDays == 0) {
|
|
|
64 |
return $startDate;
|
|
|
65 |
}
|
|
|
66 |
if ($endDays < 0) {
|
|
|
67 |
return self::decrementing($startDate, $endDays, $holidayArray);
|
|
|
68 |
}
|
|
|
69 |
|
|
|
70 |
return self::incrementing($startDate, $endDays, $holidayArray);
|
|
|
71 |
}
|
|
|
72 |
|
|
|
73 |
/**
|
|
|
74 |
* Use incrementing logic to determine Workday.
|
|
|
75 |
*/
|
|
|
76 |
private static function incrementing(float $startDate, int $endDays, array $holidayArray): float|int|DateTime
|
|
|
77 |
{
|
|
|
78 |
// Adjust the start date if it falls over a weekend
|
|
|
79 |
$startDoW = self::getWeekDay($startDate, 3);
|
|
|
80 |
if ($startDoW >= 5) {
|
|
|
81 |
$startDate += 7 - $startDoW;
|
|
|
82 |
--$endDays;
|
|
|
83 |
}
|
|
|
84 |
|
|
|
85 |
// Add endDays
|
|
|
86 |
$endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
|
|
|
87 |
$endDays = $endDays % 5;
|
|
|
88 |
while ($endDays > 0) {
|
|
|
89 |
++$endDate;
|
|
|
90 |
// Adjust the calculated end date if it falls over a weekend
|
|
|
91 |
$endDow = self::getWeekDay($endDate, 3);
|
|
|
92 |
if ($endDow >= 5) {
|
|
|
93 |
$endDate += 7 - $endDow;
|
|
|
94 |
}
|
|
|
95 |
--$endDays;
|
|
|
96 |
}
|
|
|
97 |
|
|
|
98 |
// Test any extra holiday parameters
|
|
|
99 |
if (!empty($holidayArray)) {
|
|
|
100 |
$endDate = self::incrementingArray($startDate, $endDate, $holidayArray);
|
|
|
101 |
}
|
|
|
102 |
|
|
|
103 |
return Helpers::returnIn3FormatsFloat($endDate);
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float
|
|
|
107 |
{
|
|
|
108 |
$holidayCountedArray = $holidayDates = [];
|
|
|
109 |
foreach ($holidayArray as $holidayDate) {
|
|
|
110 |
if (self::getWeekDay($holidayDate, 3) < 5) {
|
|
|
111 |
$holidayDates[] = $holidayDate;
|
|
|
112 |
}
|
|
|
113 |
}
|
|
|
114 |
sort($holidayDates, SORT_NUMERIC);
|
|
|
115 |
foreach ($holidayDates as $holidayDate) {
|
|
|
116 |
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
|
|
|
117 |
if (!in_array($holidayDate, $holidayCountedArray)) {
|
|
|
118 |
++$endDate;
|
|
|
119 |
$holidayCountedArray[] = $holidayDate;
|
|
|
120 |
}
|
|
|
121 |
}
|
|
|
122 |
// Adjust the calculated end date if it falls over a weekend
|
|
|
123 |
$endDoW = self::getWeekDay($endDate, 3);
|
|
|
124 |
if ($endDoW >= 5) {
|
|
|
125 |
$endDate += 7 - $endDoW;
|
|
|
126 |
}
|
|
|
127 |
}
|
|
|
128 |
|
|
|
129 |
return $endDate;
|
|
|
130 |
}
|
|
|
131 |
|
|
|
132 |
/**
|
|
|
133 |
* Use decrementing logic to determine Workday.
|
|
|
134 |
*/
|
|
|
135 |
private static function decrementing(float $startDate, int $endDays, array $holidayArray): float|int|DateTime
|
|
|
136 |
{
|
|
|
137 |
// Adjust the start date if it falls over a weekend
|
|
|
138 |
$startDoW = self::getWeekDay($startDate, 3);
|
|
|
139 |
if ($startDoW >= 5) {
|
|
|
140 |
$startDate += -$startDoW + 4;
|
|
|
141 |
++$endDays;
|
|
|
142 |
}
|
|
|
143 |
|
|
|
144 |
// Add endDays
|
|
|
145 |
$endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
|
|
|
146 |
$endDays = $endDays % 5;
|
|
|
147 |
while ($endDays < 0) {
|
|
|
148 |
--$endDate;
|
|
|
149 |
// Adjust the calculated end date if it falls over a weekend
|
|
|
150 |
$endDow = self::getWeekDay($endDate, 3);
|
|
|
151 |
if ($endDow >= 5) {
|
|
|
152 |
$endDate += 4 - $endDow;
|
|
|
153 |
}
|
|
|
154 |
++$endDays;
|
|
|
155 |
}
|
|
|
156 |
|
|
|
157 |
// Test any extra holiday parameters
|
|
|
158 |
if (!empty($holidayArray)) {
|
|
|
159 |
$endDate = self::decrementingArray($startDate, $endDate, $holidayArray);
|
|
|
160 |
}
|
|
|
161 |
|
|
|
162 |
return Helpers::returnIn3FormatsFloat($endDate);
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float
|
|
|
166 |
{
|
|
|
167 |
$holidayCountedArray = $holidayDates = [];
|
|
|
168 |
foreach ($holidayArray as $holidayDate) {
|
|
|
169 |
if (self::getWeekDay($holidayDate, 3) < 5) {
|
|
|
170 |
$holidayDates[] = $holidayDate;
|
|
|
171 |
}
|
|
|
172 |
}
|
|
|
173 |
rsort($holidayDates, SORT_NUMERIC);
|
|
|
174 |
foreach ($holidayDates as $holidayDate) {
|
|
|
175 |
if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
|
|
|
176 |
if (!in_array($holidayDate, $holidayCountedArray)) {
|
|
|
177 |
--$endDate;
|
|
|
178 |
$holidayCountedArray[] = $holidayDate;
|
|
|
179 |
}
|
|
|
180 |
}
|
|
|
181 |
// Adjust the calculated end date if it falls over a weekend
|
|
|
182 |
$endDoW = self::getWeekDay($endDate, 3);
|
|
|
183 |
/** int $endDoW */
|
|
|
184 |
if ($endDoW >= 5) {
|
|
|
185 |
$endDate += -$endDoW + 4;
|
|
|
186 |
}
|
|
|
187 |
}
|
|
|
188 |
|
|
|
189 |
return $endDate;
|
|
|
190 |
}
|
|
|
191 |
|
|
|
192 |
private static function getWeekDay(float $date, int $wd): int
|
|
|
193 |
{
|
|
|
194 |
$result = Functions::scalar(Week::day($date, $wd));
|
|
|
195 |
|
|
|
196 |
return is_int($result) ? $result : -1;
|
|
|
197 |
}
|
|
|
198 |
}
|