Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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
}