• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPOffice / PhpSpreadsheet / 18959589190

31 Oct 2025 01:09AM UTC coverage: 95.947%. Remained the same
18959589190

Pull #4697

github

web-flow
Merge 57985fde8 into 4cb69539a
Pull Request #4697: Unexpected Exception in Php DateTime

17 of 17 new or added lines in 4 files covered. (100.0%)

10 existing lines in 2 files now uncovered.

45359 of 47275 relevant lines covered (95.95%)

372.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.24
/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php
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
    public static function isLeapYear(int|string $year): bool
186✔
21
    {
22
        $year = (int) $year;
186✔
23

24
        return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0);
186✔
25
    }
26

27
    /**
28
     * getDateValue.
29
     *
30
     * @return float Excel date/time serial value
31
     */
32
    public static function getDateValue(mixed $dateValue, bool $allowBool = true): float
2,383✔
33
    {
34
        if (is_object($dateValue)) {
2,383✔
35
            $retval = SharedDateHelper::PHPToExcel($dateValue);
3✔
36
            if (is_bool($retval)) {
3✔
UNCOV
37
                throw new Exception(ExcelError::VALUE());
×
38
            }
39

40
            return $retval;
3✔
41
        }
42

43
        self::nullFalseTrueToNumber($dateValue, $allowBool);
2,380✔
44
        if (!is_numeric($dateValue)) {
2,380✔
45
            $saveReturnDateType = Functions::getReturnDateType();
2,077✔
46
            Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
2,077✔
47
            if (is_string($dateValue)) {
2,077✔
48
                $dateValue = DateValue::fromString($dateValue);
2,071✔
49
            }
50
            Functions::setReturnDateType($saveReturnDateType);
2,077✔
51
            if (!is_numeric($dateValue)) {
2,077✔
52
                throw new Exception(ExcelError::VALUE());
138✔
53
            }
54
        }
55
        if ($dateValue < 0 && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
2,284✔
56
            throw new Exception(ExcelError::NAN());
20✔
57
        }
58

59
        return (float) $dateValue;
2,264✔
60
    }
61

62
    /**
63
     * getTimeValue.
64
     *
65
     * @return float|string Excel date/time serial value, or string if error
66
     */
67
    public static function getTimeValue(string $timeValue): string|float
54✔
68
    {
69
        $saveReturnDateType = Functions::getReturnDateType();
54✔
70
        Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
54✔
71
        /** @var float|string $timeValue */
72
        $timeValue = TimeValue::fromString($timeValue);
54✔
73
        Functions::setReturnDateType($saveReturnDateType);
54✔
74

75
        return $timeValue;
54✔
76
    }
77

78
    /**
79
     * Adjust date by given months.
80
     *
81
     * @param float|int $dateValue date to be adjusted
82
     */
83
    public static function adjustDateByMonths($dateValue = 0, float $adjustmentMonths = 0): DateTime
114✔
84
    {
85
        // Execute function
86
        $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
114✔
87
        $oMonth = (int) $PHPDateObject->format('m');
114✔
88
        $oYear = (int) $PHPDateObject->format('Y');
114✔
89

90
        $adjustmentMonthsString = (string) $adjustmentMonths;
114✔
91
        if ($adjustmentMonths > 0) {
114✔
92
            $adjustmentMonthsString = '+' . $adjustmentMonths;
68✔
93
        }
94
        if ($adjustmentMonths != 0) {
114✔
95
            $PHPDateObject->modify($adjustmentMonthsString . ' months');
97✔
96
        }
97
        $nMonth = (int) $PHPDateObject->format('m');
114✔
98
        $nYear = (int) $PHPDateObject->format('Y');
114✔
99

100
        $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
114✔
101
        if ($monthDiff != $adjustmentMonths) {
114✔
102
            $adjustDays = (int) $PHPDateObject->format('d');
17✔
103
            $adjustDaysString = '-' . $adjustDays . ' days';
17✔
104
            $PHPDateObject->modify($adjustDaysString);
17✔
105
        }
106

107
        return $PHPDateObject;
114✔
108
    }
109

110
    /**
111
     * Help reduce perceived complexity of some tests.
112
     */
113
    public static function replaceIfEmpty(mixed &$value, mixed $altValue): void
2,235✔
114
    {
115
        $value = $value ?: $altValue;
2,235✔
116
    }
117

118
    /**
119
     * Adjust year in ambiguous situations.
120
     */
121
    public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void
1,643✔
122
    {
123
        if (!is_numeric($testVal1) || $testVal1 < 31) {
1,643✔
124
            if (!is_numeric($testVal2) || $testVal2 < 12) {
134✔
125
                if (is_numeric($testVal3) && $testVal3 < 12) {
85✔
126
                    $testVal3 = (string) ($testVal3 + 2000);
4✔
127
                }
128
            }
129
        }
130
    }
131

132
    /**
133
     * Return result in one of three formats.
134
     *
135
     * @param array{year: int, month: int, day: int, hour: int, minute: int, second: int} $dateArray
136
     */
137
    public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false): DateTime|float|int
2,241✔
138
    {
139
        $retType = Functions::getReturnDateType();
2,241✔
140
        if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
2,241✔
141
            return new DateTime(
1✔
142
                $dateArray['year']
1✔
143
                . '-' . $dateArray['month']
1✔
144
                . '-' . $dateArray['day']
1✔
145
                . ' ' . $dateArray['hour']
1✔
146
                . ':' . $dateArray['minute']
1✔
147
                . ':' . $dateArray['second']
1✔
148
            );
1✔
149
        }
150
        $excelDateValue
2,240✔
151
            = SharedDateHelper::formattedPHPToExcel(
2,240✔
152
                $dateArray['year'],
2,240✔
153
                $dateArray['month'],
2,240✔
154
                $dateArray['day'],
2,240✔
155
                $dateArray['hour'],
2,240✔
156
                $dateArray['minute'],
2,240✔
157
                $dateArray['second']
2,240✔
158
            );
2,240✔
159
        if ($retType === Functions::RETURNDATE_EXCEL) {
2,240✔
160
            return $noFrac ? floor($excelDateValue) : $excelDateValue;
2,239✔
161
        }
162
        // RETURNDATE_UNIX_TIMESTAMP)
163

164
        return SharedDateHelper::excelToTimestamp($excelDateValue);
1✔
165
    }
166

167
    /**
168
     * Return result in one of three formats.
169
     */
170
    public static function returnIn3FormatsFloat(float $excelDateValue): float|int|DateTime
380✔
171
    {
172
        $retType = Functions::getReturnDateType();
380✔
173
        if ($retType === Functions::RETURNDATE_EXCEL) {
380✔
174
            return $excelDateValue;
378✔
175
        }
176
        if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
2✔
177
            return SharedDateHelper::excelToTimestamp($excelDateValue);
1✔
178
        }
179
        // RETURNDATE_PHP_DATETIME_OBJECT
180

181
        return SharedDateHelper::excelToDateTimeObject($excelDateValue);
1✔
182
    }
183

184
    /**
185
     * Return result in one of three formats.
186
     */
187
    public static function returnIn3FormatsObject(DateTime $PHPDateObject): DateTime|float|int
114✔
188
    {
189
        $retType = Functions::getReturnDateType();
114✔
190
        if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
114✔
191
            return $PHPDateObject;
2✔
192
        }
193
        if ($retType === Functions::RETURNDATE_EXCEL) {
112✔
194
            return (float) SharedDateHelper::PHPToExcel($PHPDateObject);
110✔
195
        }
196
        // RETURNDATE_UNIX_TIMESTAMP
197
        $stamp = SharedDateHelper::PHPToExcel($PHPDateObject);
2✔
198
        $stamp = is_bool($stamp) ? ((int) $stamp) : $stamp;
2✔
199

200
        return SharedDateHelper::excelToTimestamp($stamp);
2✔
201
    }
202

203
    private static function baseDate(): int
2,527✔
204
    {
205
        if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
2,527✔
206
            return 0;
26✔
207
        }
208
        if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) {
2,501✔
209
            return 0;
102✔
210
        }
211

212
        return 1;
2,399✔
213
    }
214

215
    /**
216
     * Many functions accept null/false/true argument treated as 0/0/1.
217
     */
218
    public static function nullFalseTrueToNumber(mixed &$number, bool $allowBool = true): void
2,527✔
219
    {
220
        $number = Functions::flattenSingleValue($number);
2,527✔
221
        $nullVal = self::baseDate();
2,527✔
222
        if ($number === null) {
2,527✔
223
            $number = $nullVal;
104✔
224
        } elseif ($allowBool && is_bool($number)) {
2,526✔
225
            $number = $nullVal + (int) $number;
42✔
226
        }
227
    }
228

229
    /**
230
     * Many functions accept null argument treated as 0.
231
     */
232
    public static function validateNumericNull(mixed $number): int|float
783✔
233
    {
234
        $number = Functions::flattenSingleValue($number);
783✔
235
        if ($number === null) {
783✔
236
            return 0;
2✔
237
        }
238
        if (is_int($number)) {
781✔
239
            return $number;
755✔
240
        }
241
        if (is_numeric($number)) {
26✔
242
            return (float) $number;
5✔
243
        }
244

245
        throw new Exception(ExcelError::VALUE());
21✔
246
    }
247

248
    /**
249
     * Many functions accept null/false/true argument treated as 0/0/1.
250
     *
251
     * @phpstan-assert float $number
252
     */
253
    public static function validateNotNegative(mixed $number): float
152✔
254
    {
255
        if (!is_numeric($number)) {
152✔
256
            throw new Exception(ExcelError::VALUE());
18✔
257
        }
258
        if ($number >= 0) {
134✔
259
            return (float) $number;
125✔
260
        }
261

262
        throw new Exception(ExcelError::NAN());
9✔
263
    }
264

265
    public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void
621✔
266
    {
267
        $isoDate = $PHPDateObject->format('c');
621✔
268
        if ($isoDate < '1900-03-01') {
621✔
269
            $PHPDateObject->modify($mod);
86✔
270
        }
271
    }
272

273
    /** @return array{year: int, month: int, day: int, hour: int, minute: int, second: int} */
274
    public static function dateParse(string $string): array
2,422✔
275
    {
276
        /** @var array{year: int, month: int, day: int, hour: int, minute: int, second: int} */
277
        $temp = self::forceArray(date_parse($string));
2,422✔
278

279
        return $temp;
2,422✔
280
    }
281

282
    /** @param mixed[] $dateArray */
283
    public static function dateParseSucceeded(array $dateArray): bool
2,422✔
284
    {
285
        return $dateArray['error_count'] === 0;
2,422✔
286
    }
287

288
    /**
289
     * Despite documentation, date_parse probably never returns false.
290
     * Just in case, this routine helps guarantee it.
291
     *
292
     * @param array<mixed>|false $dateArray
293
     *
294
     * @return mixed[]
295
     */
296
    private static function forceArray(array|bool $dateArray): array
2,422✔
297
    {
298
        return is_array($dateArray) ? $dateArray : ['error_count' => 1];
2,422✔
299
    }
300

301
    public static function floatOrInt(mixed $value): float|int
538✔
302
    {
303
        $result = Functions::scalar($value);
538✔
304

305
        return is_numeric($result) ? ($result + 0) : 0;
538✔
306
    }
307
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc