Skip to content

Commit cf58236

Browse files
authored
Merge pull request #4351 from oleibman/datetext
Fix TEXT and TIMEVALUE Functions Master Branch
2 parents 33f8fc1 + a974933 commit cf58236

File tree

3 files changed

+78
-10
lines changed

3 files changed

+78
-10
lines changed

src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
44

5+
use Composer\Pcre\Preg;
56
use Datetime;
67
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
78
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
@@ -12,6 +13,19 @@ class TimeValue
1213
{
1314
use ArrayEnabled;
1415

16+
private const EXTRACT_TIME = '/\b'
17+
. '(\d+)' // match[1] - hour
18+
. '(:' // start of match[2] (rest of string) - colon
19+
. '(\d+' // start of match[3] - minute
20+
. '(:\d+' // start of match[4] - colon and seconds
21+
. '([.]\d+)?' // match[5] - optional decimal point followed by fractional seconds
22+
. ')?' // end of match[4], which is optional
23+
. ')' // end of match 3
24+
// Excel does not require 'm' to trail 'a' or 'p'; Php does
25+
. '(\s*(a|p))?' // match[6] optional whitespace followed by optional match[7] a or p
26+
. ')' // end of match[2]
27+
. '/i';
28+
1529
/**
1630
* TIMEVALUE.
1731
*
@@ -43,17 +57,20 @@ public static function fromString(null|array|string|int|bool|float $timeValue):
4357
}
4458

4559
// try to parse as time iff there is at least one digit
46-
if (is_string($timeValue) && preg_match('/\d/', $timeValue) !== 1) {
60+
if (is_string($timeValue) && !Preg::isMatch('/\d/', $timeValue)) {
4761
return ExcelError::VALUE();
4862
}
4963

5064
$timeValue = trim((string) $timeValue, '"');
51-
$timeValue = str_replace(['/', '.'], '-', $timeValue);
52-
53-
$arraySplit = preg_split('/[\/:\-\s]/', $timeValue) ?: [];
54-
if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) {
55-
$arraySplit[0] = ((int) $arraySplit[0] % 24);
56-
$timeValue = implode(':', $arraySplit);
65+
if (Preg::isMatch(self::EXTRACT_TIME, $timeValue, $matches)) {
66+
if (empty($matches[6])) { // am/pm
67+
$hour = (int) $matches[0];
68+
$timeValue = ($hour % 24) . $matches[2];
69+
} elseif ($matches[6] === $matches[7]) { // Excel wants space before am/pm
70+
return ExcelError::VALUE();
71+
} else {
72+
$timeValue = $matches[0] . 'm';
73+
}
5774
}
5875

5976
$PHPDateArray = Helpers::dateParse($timeValue);

src/PhpSpreadsheet/Calculation/TextData/Format.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
44

5+
use Composer\Pcre\Preg;
56
use DateTimeInterface;
67
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
78
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
@@ -133,11 +134,11 @@ public static function TEXTFORMAT(mixed $value, mixed $format): array|string
133134

134135
$format = (string) NumberFormat::convertSystemFormats($format);
135136

136-
if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
137+
if (!is_numeric($value) && Date::isDateTimeFormatCode($format) && !Preg::isMatch('/^\s*\d+(\s+\d+)+\s*$/', $value)) {
137138
$value1 = DateTimeExcel\DateValue::fromString($value);
138139
$value2 = DateTimeExcel\TimeValue::fromString($value);
139140
/** @var float|int|string */
140-
$value = (is_numeric($value1) && is_numeric($value2)) ? ($value1 + $value2) : (is_numeric($value1) ? $value2 : $value1);
141+
$value = (is_numeric($value1) && is_numeric($value2)) ? ($value1 + $value2) : (is_numeric($value1) ? $value1 : (is_numeric($value2) ? $value2 : $value));
141142
}
142143

143144
return (string) NumberFormat::toFormattedString($value, $format);
@@ -293,7 +294,7 @@ public static function NUMBERVALUE(mixed $value = '', mixed $decimalSeparator =
293294
}
294295

295296
if (!is_numeric($value)) {
296-
$decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches, PREG_OFFSET_CAPTURE);
297+
$decimalPositions = Preg::matchAllWithOffsets('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches);
297298
if ($decimalPositions > 1) {
298299
return ExcelError::VALUE();
299300
}

tests/data/Calculation/TextData/TEXT.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,56 @@
7373
'2014-02-15 16:17',
7474
'dd-mmm-yyyy HH:MM:SS AM/PM',
7575
],
76+
'datetime integer' => [
77+
'1900-01-06 00:00',
78+
6,
79+
'yyyy-mm-dd hh:mm',
80+
],
81+
'datetime integer as string' => [
82+
'1900-01-06 00:00',
83+
'6',
84+
'yyyy-mm-dd hh:mm',
85+
],
86+
'datetime 2 integers without date delimiters' => [
87+
'5 6',
88+
'5 6',
89+
'yyyy-mm-dd hh:mm',
90+
],
91+
'datetime 2 integers separated by hyphen' => [
92+
(new DateTimeImmutable())->format('Y') . '-05-13 00:00',
93+
'5-13',
94+
'yyyy-mm-dd hh:mm',
95+
],
96+
'datetime string date only' => [
97+
'1951-01-23 00:00',
98+
'January 23, 1951',
99+
'yyyy-mm-dd hh:mm',
100+
],
101+
'datetime string time followed by date' => [
102+
'1952-05-02 03:54',
103+
'3:54 May 2, 1952',
104+
'yyyy-mm-dd hh:mm',
105+
],
106+
'datetime string date followed by time pm' => [
107+
'1952-05-02 15:54',
108+
'May 2, 1952 3:54 pm',
109+
'yyyy-mm-dd hh:mm',
110+
],
111+
'datetime string date followed by time p' => [
112+
'1952-05-02 15:54',
113+
'May 2, 1952 3:54 p',
114+
'yyyy-mm-dd hh:mm',
115+
],
116+
'datetime decimal string interpreted as time' => [
117+
'1900-01-02 12:00',
118+
'2.5',
119+
'yyyy-mm-dd hh:mm',
120+
],
121+
'datetime unparseable string' => [
122+
'xyz',
123+
'xyz',
124+
'yyyy-mm-dd hh:mm',
125+
],
76126
[
77127
'1 3/4',
78128
1.75,

0 commit comments

Comments
 (0)