1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; |
4
|
|
|
|
5
|
|
|
use DateTime; |
6
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception; |
7
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions; |
8
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; |
9
|
|
|
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper; |
10
|
|
|
|
11
|
|
|
class Helpers |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* Identify if a year is a leap year or not. |
15
|
|
|
* |
16
|
|
|
* @param int|string $year The year to test |
17
|
|
|
* |
18
|
|
|
* @return bool TRUE if the year is a leap year, otherwise FALSE |
19
|
|
|
*/ |
20
|
186 |
|
public static function isLeapYear(int|string $year): bool |
21
|
|
|
{ |
22
|
186 |
|
$year = (int) $year; |
23
|
|
|
|
24
|
186 |
|
return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0); |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* getDateValue. |
29
|
|
|
* |
30
|
|
|
* @return float Excel date/time serial value |
31
|
|
|
*/ |
32
|
2383 |
|
public static function getDateValue(mixed $dateValue, bool $allowBool = true): float |
33
|
|
|
{ |
34
|
2383 |
|
if (is_object($dateValue)) { |
35
|
3 |
|
$retval = SharedDateHelper::PHPToExcel($dateValue); |
36
|
3 |
|
if (is_bool($retval)) { |
37
|
|
|
throw new Exception(ExcelError::VALUE()); |
38
|
|
|
} |
39
|
|
|
|
40
|
3 |
|
return $retval; |
41
|
|
|
} |
42
|
|
|
|
43
|
2380 |
|
self::nullFalseTrueToNumber($dateValue, $allowBool); |
44
|
2380 |
|
if (!is_numeric($dateValue)) { |
45
|
2077 |
|
$saveReturnDateType = Functions::getReturnDateType(); |
46
|
2077 |
|
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); |
47
|
2077 |
|
if (is_string($dateValue)) { |
48
|
2071 |
|
$dateValue = DateValue::fromString($dateValue); |
49
|
|
|
} |
50
|
2077 |
|
Functions::setReturnDateType($saveReturnDateType); |
51
|
2077 |
|
if (!is_numeric($dateValue)) { |
52
|
138 |
|
throw new Exception(ExcelError::VALUE()); |
53
|
|
|
} |
54
|
|
|
} |
55
|
2284 |
|
if ($dateValue < 0 && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) { |
56
|
20 |
|
throw new Exception(ExcelError::NAN()); |
57
|
|
|
} |
58
|
|
|
|
59
|
2264 |
|
return (float) $dateValue; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* getTimeValue. |
64
|
|
|
* |
65
|
|
|
* @return float|string Excel date/time serial value, or string if error |
66
|
|
|
*/ |
67
|
54 |
|
public static function getTimeValue(string $timeValue): string|float |
68
|
|
|
{ |
69
|
54 |
|
$saveReturnDateType = Functions::getReturnDateType(); |
70
|
54 |
|
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); |
71
|
|
|
/** @var float|string $timeValue */ |
72
|
54 |
|
$timeValue = TimeValue::fromString($timeValue); |
73
|
54 |
|
Functions::setReturnDateType($saveReturnDateType); |
74
|
|
|
|
75
|
54 |
|
return $timeValue; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Adjust date by given months. |
80
|
|
|
* |
81
|
|
|
* @param float|int $dateValue date to be adjusted |
82
|
|
|
*/ |
83
|
114 |
|
public static function adjustDateByMonths($dateValue = 0, float $adjustmentMonths = 0): DateTime |
84
|
|
|
{ |
85
|
|
|
// Execute function |
86
|
114 |
|
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); |
87
|
114 |
|
$oMonth = (int) $PHPDateObject->format('m'); |
88
|
114 |
|
$oYear = (int) $PHPDateObject->format('Y'); |
89
|
|
|
|
90
|
114 |
|
$adjustmentMonthsString = (string) $adjustmentMonths; |
91
|
114 |
|
if ($adjustmentMonths > 0) { |
92
|
68 |
|
$adjustmentMonthsString = '+' . $adjustmentMonths; |
93
|
|
|
} |
94
|
114 |
|
if ($adjustmentMonths != 0) { |
95
|
97 |
|
$PHPDateObject->modify($adjustmentMonthsString . ' months'); |
96
|
|
|
} |
97
|
114 |
|
$nMonth = (int) $PHPDateObject->format('m'); |
98
|
114 |
|
$nYear = (int) $PHPDateObject->format('Y'); |
99
|
|
|
|
100
|
114 |
|
$monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); |
101
|
114 |
|
if ($monthDiff != $adjustmentMonths) { |
102
|
16 |
|
$adjustDays = (int) $PHPDateObject->format('d'); |
103
|
16 |
|
$adjustDaysString = '-' . $adjustDays . ' days'; |
104
|
16 |
|
$PHPDateObject->modify($adjustDaysString); |
105
|
|
|
} |
106
|
|
|
|
107
|
114 |
|
return $PHPDateObject; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Help reduce perceived complexity of some tests. |
112
|
|
|
*/ |
113
|
2231 |
|
public static function replaceIfEmpty(mixed &$value, mixed $altValue): void |
114
|
|
|
{ |
115
|
2231 |
|
$value = $value ?: $altValue; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Adjust year in ambiguous situations. |
120
|
|
|
*/ |
121
|
1639 |
|
public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void |
122
|
|
|
{ |
123
|
1639 |
|
if (!is_numeric($testVal1) || $testVal1 < 31) { |
124
|
133 |
|
if (!is_numeric($testVal2) || $testVal2 < 12) { |
125
|
84 |
|
if (is_numeric($testVal3) && $testVal3 < 12) { |
126
|
4 |
|
$testVal3 = (string) ($testVal3 + 2000); |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Return result in one of three formats. |
134
|
|
|
*/ |
135
|
2237 |
|
public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false): DateTime|float|int |
136
|
|
|
{ |
137
|
2237 |
|
$retType = Functions::getReturnDateType(); |
138
|
2237 |
|
if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { |
139
|
1 |
|
return new DateTime( |
140
|
1 |
|
$dateArray['year'] |
141
|
1 |
|
. '-' . $dateArray['month'] |
142
|
1 |
|
. '-' . $dateArray['day'] |
143
|
1 |
|
. ' ' . $dateArray['hour'] |
144
|
1 |
|
. ':' . $dateArray['minute'] |
145
|
1 |
|
. ':' . $dateArray['second'] |
146
|
1 |
|
); |
147
|
|
|
} |
148
|
2236 |
|
$excelDateValue |
149
|
2236 |
|
= SharedDateHelper::formattedPHPToExcel( |
150
|
2236 |
|
$dateArray['year'], |
151
|
2236 |
|
$dateArray['month'], |
152
|
2236 |
|
$dateArray['day'], |
153
|
2236 |
|
$dateArray['hour'], |
154
|
2236 |
|
$dateArray['minute'], |
155
|
2236 |
|
$dateArray['second'] |
156
|
2236 |
|
); |
157
|
2236 |
|
if ($retType === Functions::RETURNDATE_EXCEL) { |
158
|
2235 |
|
return $noFrac ? floor($excelDateValue) : $excelDateValue; |
159
|
|
|
} |
160
|
|
|
// RETURNDATE_UNIX_TIMESTAMP) |
161
|
|
|
|
162
|
1 |
|
return SharedDateHelper::excelToTimestamp($excelDateValue); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Return result in one of three formats. |
167
|
|
|
*/ |
168
|
378 |
|
public static function returnIn3FormatsFloat(float $excelDateValue): float|int|DateTime |
169
|
|
|
{ |
170
|
378 |
|
$retType = Functions::getReturnDateType(); |
171
|
378 |
|
if ($retType === Functions::RETURNDATE_EXCEL) { |
172
|
376 |
|
return $excelDateValue; |
173
|
|
|
} |
174
|
2 |
|
if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { |
175
|
1 |
|
return SharedDateHelper::excelToTimestamp($excelDateValue); |
176
|
|
|
} |
177
|
|
|
// RETURNDATE_PHP_DATETIME_OBJECT |
178
|
|
|
|
179
|
1 |
|
return SharedDateHelper::excelToDateTimeObject($excelDateValue); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Return result in one of three formats. |
184
|
|
|
*/ |
185
|
114 |
|
public static function returnIn3FormatsObject(DateTime $PHPDateObject): DateTime|float|int |
186
|
|
|
{ |
187
|
114 |
|
$retType = Functions::getReturnDateType(); |
188
|
114 |
|
if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { |
189
|
2 |
|
return $PHPDateObject; |
190
|
|
|
} |
191
|
112 |
|
if ($retType === Functions::RETURNDATE_EXCEL) { |
192
|
110 |
|
return (float) SharedDateHelper::PHPToExcel($PHPDateObject); |
193
|
|
|
} |
194
|
|
|
// RETURNDATE_UNIX_TIMESTAMP |
195
|
2 |
|
$stamp = SharedDateHelper::PHPToExcel($PHPDateObject); |
196
|
2 |
|
$stamp = is_bool($stamp) ? ((int) $stamp) : $stamp; |
197
|
|
|
|
198
|
2 |
|
return SharedDateHelper::excelToTimestamp($stamp); |
199
|
|
|
} |
200
|
|
|
|
201
|
2527 |
|
private static function baseDate(): int |
202
|
|
|
{ |
203
|
2527 |
|
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { |
204
|
26 |
|
return 0; |
205
|
|
|
} |
206
|
2501 |
|
if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) { |
207
|
102 |
|
return 0; |
208
|
|
|
} |
209
|
|
|
|
210
|
2399 |
|
return 1; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Many functions accept null/false/true argument treated as 0/0/1. |
215
|
|
|
*/ |
216
|
2527 |
|
public static function nullFalseTrueToNumber(mixed &$number, bool $allowBool = true): void |
217
|
|
|
{ |
218
|
2527 |
|
$number = Functions::flattenSingleValue($number); |
219
|
2527 |
|
$nullVal = self::baseDate(); |
220
|
2527 |
|
if ($number === null) { |
221
|
104 |
|
$number = $nullVal; |
222
|
2526 |
|
} elseif ($allowBool && is_bool($number)) { |
223
|
42 |
|
$number = $nullVal + (int) $number; |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Many functions accept null argument treated as 0. |
229
|
|
|
*/ |
230
|
783 |
|
public static function validateNumericNull(mixed $number): int|float |
231
|
|
|
{ |
232
|
783 |
|
$number = Functions::flattenSingleValue($number); |
233
|
783 |
|
if ($number === null) { |
234
|
2 |
|
return 0; |
235
|
|
|
} |
236
|
781 |
|
if (is_int($number)) { |
237
|
755 |
|
return $number; |
238
|
|
|
} |
239
|
26 |
|
if (is_numeric($number)) { |
240
|
5 |
|
return (float) $number; |
241
|
|
|
} |
242
|
|
|
|
243
|
21 |
|
throw new Exception(ExcelError::VALUE()); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Many functions accept null/false/true argument treated as 0/0/1. |
248
|
|
|
* |
249
|
|
|
* @phpstan-assert float $number |
250
|
|
|
*/ |
251
|
152 |
|
public static function validateNotNegative(mixed $number): float |
252
|
|
|
{ |
253
|
152 |
|
if (!is_numeric($number)) { |
254
|
18 |
|
throw new Exception(ExcelError::VALUE()); |
255
|
|
|
} |
256
|
134 |
|
if ($number >= 0) { |
257
|
125 |
|
return (float) $number; |
258
|
|
|
} |
259
|
|
|
|
260
|
9 |
|
throw new Exception(ExcelError::NAN()); |
261
|
|
|
} |
262
|
|
|
|
263
|
621 |
|
public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void |
264
|
|
|
{ |
265
|
621 |
|
$isoDate = $PHPDateObject->format('c'); |
266
|
621 |
|
if ($isoDate < '1900-03-01') { |
267
|
86 |
|
$PHPDateObject->modify($mod); |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
2418 |
|
public static function dateParse(string $string): array |
272
|
|
|
{ |
273
|
2418 |
|
return self::forceArray(date_parse($string)); |
274
|
|
|
} |
275
|
|
|
|
276
|
2418 |
|
public static function dateParseSucceeded(array $dateArray): bool |
277
|
|
|
{ |
278
|
2418 |
|
return $dateArray['error_count'] === 0; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Despite documentation, date_parse probably never returns false. |
283
|
|
|
* Just in case, this routine helps guarantee it. |
284
|
|
|
* |
285
|
|
|
* @param array|false $dateArray |
286
|
|
|
*/ |
287
|
2418 |
|
private static function forceArray(array|bool $dateArray): array |
288
|
|
|
{ |
289
|
2418 |
|
return is_array($dateArray) ? $dateArray : ['error_count' => 1]; |
290
|
|
|
} |
291
|
|
|
|
292
|
538 |
|
public static function floatOrInt(mixed $value): float|int |
293
|
|
|
{ |
294
|
538 |
|
$result = Functions::scalar($value); |
295
|
|
|
|
296
|
538 |
|
return is_numeric($result) ? ($result + 0) : 0; |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|