Skip to content

Commit

Permalink
Merge pull request #4351 from oleibman/datetext
Browse files Browse the repository at this point in the history
Fix TEXT and TIMEVALUE Functions Master Branch
  • Loading branch information
oleibman authored Feb 11, 2025
2 parents 33f8fc1 + a974933 commit cf58236
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 10 deletions.
31 changes: 24 additions & 7 deletions src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;

use Composer\Pcre\Preg;
use Datetime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
Expand All @@ -12,6 +13,19 @@ class TimeValue
{
use ArrayEnabled;

private const EXTRACT_TIME = '/\b'
. '(\d+)' // match[1] - hour
. '(:' // start of match[2] (rest of string) - colon
. '(\d+' // start of match[3] - minute
. '(:\d+' // start of match[4] - colon and seconds
. '([.]\d+)?' // match[5] - optional decimal point followed by fractional seconds
. ')?' // end of match[4], which is optional
. ')' // end of match 3
// Excel does not require 'm' to trail 'a' or 'p'; Php does
. '(\s*(a|p))?' // match[6] optional whitespace followed by optional match[7] a or p
. ')' // end of match[2]
. '/i';

/**
* TIMEVALUE.
*
Expand Down Expand Up @@ -43,17 +57,20 @@ public static function fromString(null|array|string|int|bool|float $timeValue):
}

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

$timeValue = trim((string) $timeValue, '"');
$timeValue = str_replace(['/', '.'], '-', $timeValue);

$arraySplit = preg_split('/[\/:\-\s]/', $timeValue) ?: [];
if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) {
$arraySplit[0] = ((int) $arraySplit[0] % 24);
$timeValue = implode(':', $arraySplit);
if (Preg::isMatch(self::EXTRACT_TIME, $timeValue, $matches)) {
if (empty($matches[6])) { // am/pm
$hour = (int) $matches[0];
$timeValue = ($hour % 24) . $matches[2];
} elseif ($matches[6] === $matches[7]) { // Excel wants space before am/pm
return ExcelError::VALUE();
} else {
$timeValue = $matches[0] . 'm';
}
}

$PHPDateArray = Helpers::dateParse($timeValue);
Expand Down
7 changes: 4 additions & 3 deletions src/PhpSpreadsheet/Calculation/TextData/Format.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;

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

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

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

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

if (!is_numeric($value)) {
$decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches, PREG_OFFSET_CAPTURE);
$decimalPositions = Preg::matchAllWithOffsets('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches);
if ($decimalPositions > 1) {
return ExcelError::VALUE();
}
Expand Down
50 changes: 50 additions & 0 deletions tests/data/Calculation/TextData/TEXT.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,56 @@
'2014-02-15 16:17',
'dd-mmm-yyyy HH:MM:SS AM/PM',
],
'datetime integer' => [
'1900-01-06 00:00',
6,
'yyyy-mm-dd hh:mm',
],
'datetime integer as string' => [
'1900-01-06 00:00',
'6',
'yyyy-mm-dd hh:mm',
],
'datetime 2 integers without date delimiters' => [
'5 6',
'5 6',
'yyyy-mm-dd hh:mm',
],
'datetime 2 integers separated by hyphen' => [
(new DateTimeImmutable())->format('Y') . '-05-13 00:00',
'5-13',
'yyyy-mm-dd hh:mm',
],
'datetime string date only' => [
'1951-01-23 00:00',
'January 23, 1951',
'yyyy-mm-dd hh:mm',
],
'datetime string time followed by date' => [
'1952-05-02 03:54',
'3:54 May 2, 1952',
'yyyy-mm-dd hh:mm',
],
'datetime string date followed by time pm' => [
'1952-05-02 15:54',
'May 2, 1952 3:54 pm',
'yyyy-mm-dd hh:mm',
],
'datetime string date followed by time p' => [
'1952-05-02 15:54',
'May 2, 1952 3:54 p',
'yyyy-mm-dd hh:mm',
],
'datetime decimal string interpreted as time' => [
'1900-01-02 12:00',
'2.5',
'yyyy-mm-dd hh:mm',
],
'datetime unparseable string' => [
'xyz',
'xyz',
'yyyy-mm-dd hh:mm',
],
[
'1 3/4',
1.75,
Expand Down

0 comments on commit cf58236

Please sign in to comment.