Fix time format for duration was incorrect

When using format `[h]:mm` it should convert to the "total hours:minutes"

Closes #666
Fixes #664
Fixes #446
Fixes #342
This commit is contained in:
GreatHumorist 2018-09-08 01:32:17 +08:00 committed by Adrien Crivelli
parent 39b573b29d
commit 699da09176
No known key found for this signature in database
GPG Key ID: B182FD79DC6DE92E
3 changed files with 117 additions and 85 deletions

View File

@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Improved performance when loading large spreadsheets - [#824](https://github.com/PHPOffice/PhpSpreadsheet/pull/824)
- Fix color from CSS when reading from HTML - [#831](https://github.com/PHPOffice/PhpSpreadsheet/pull/831)
- Fix infinite loop when reading invalid ODS files - [#832](https://github.com/PHPOffice/PhpSpreadsheet/pull/832)
- Fix time format for duration is incorrect - [#666](https://github.com/PHPOffice/PhpSpreadsheet/pull/666)
## [1.5.2] - 2018-11-25

View File

@ -424,9 +424,9 @@ class NumberFormat extends Supervisor
* @var array
*/
private static $dateFormatReplacements24 = [
'hh' => 'H',
'h' => 'G',
];
'hh' => 'H',
'h' => 'G',
];
/**
* Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
@ -434,9 +434,9 @@ class NumberFormat extends Supervisor
* @var array
*/
private static $dateFormatReplacements12 = [
'hh' => 'h',
'h' => 'g',
];
'hh' => 'h',
'h' => 'g',
];
private static function setLowercaseCallback($matches)
{
@ -467,6 +467,13 @@ class NumberFormat extends Supervisor
$block = strtr($block, self::$dateFormatReplacements);
if (!strpos($block, 'A')) {
// 24-hour time format
// when [h]:mm format, the [h] should replace to the hours of the value * 24
if (false !== strpos($block, '[h]')) {
$hours = (int) ($value * 24);
$block = str_replace('[h]', $hours, $block);
continue;
}
$block = strtr($block, self::$dateFormatReplacements24);
} else {
// 12-hour time format
@ -636,104 +643,105 @@ class NumberFormat extends Supervisor
// Save format with color information for later use below
$formatColor = $format;
// Strip color information
$color_regex = '/^\\[[a-zA-Z]+\\]/';
$format = preg_replace($color_regex, '', $format);
// Let's begin inspecting the format and converting the value to a formatted string
// Check for date/time characters (not inside quotes)
if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) {
// datetime format
self::formatAsDate($value, $format);
} elseif (preg_match('/%$/', $format)) {
// % number format
self::formatAsPercentage($value, $format);
} else {
if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
$value = 'EUR ' . sprintf('%1.2f', $value);
// Strip color information
$color_regex = '/^\\[[a-zA-Z]+\\]/';
$format = preg_replace($color_regex, '', $format);
if (preg_match('/%$/', $format)) {
// % number format
self::formatAsPercentage($value, $format);
} else {
// Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
$format = str_replace(['"', '*'], '', $format);
// Find out if we need thousands separator
// This is indicated by a comma enclosed by a digit placeholder:
// #,# or 0,0
$useThousands = preg_match('/(#,#|0,0)/', $format);
if ($useThousands) {
$format = preg_replace('/0,0/', '00', $format);
$format = preg_replace('/#,#/', '##', $format);
}
// Scale thousands, millions,...
// This is indicated by a number of commas after a digit placeholder:
// #, or 0.0,,
$scale = 1; // same as no scale
$matches = [];
if (preg_match('/(#|0)(,+)/', $format, $matches)) {
$scale = pow(1000, strlen($matches[2]));
// strip the commas
$format = preg_replace('/0,+/', '0', $format);
$format = preg_replace('/#,+/', '#', $format);
}
if (preg_match('/#?.*\?\/\?/', $format, $m)) {
if ($value != (int) $value) {
self::formatAsFraction($value, $format);
}
if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
$value = 'EUR ' . sprintf('%1.2f', $value);
} else {
// Handle the number itself
// Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
$format = str_replace(['"', '*'], '', $format);
// scale number
$value = $value / $scale;
// Find out if we need thousands separator
// This is indicated by a comma enclosed by a digit placeholder:
// #,# or 0,0
$useThousands = preg_match('/(#,#|0,0)/', $format);
if ($useThousands) {
$format = preg_replace('/0,0/', '00', $format);
$format = preg_replace('/#,#/', '##', $format);
}
// Strip #
$format = preg_replace('/\\#/', '0', $format);
// Scale thousands, millions,...
// This is indicated by a number of commas after a digit placeholder:
// #, or 0.0,,
$scale = 1; // same as no scale
$matches = [];
if (preg_match('/(#|0)(,+)/', $format, $matches)) {
$scale = pow(1000, strlen($matches[2]));
// Remove locale code [$-###]
$format = preg_replace('/\[\$\-.*\]/', '', $format);
// strip the commas
$format = preg_replace('/0,+/', '0', $format);
$format = preg_replace('/#,+/', '#', $format);
}
$n = '/\\[[^\\]]+\\]/';
$m = preg_replace($n, '', $format);
$number_regex = '/(0+)(\\.?)(0*)/';
if (preg_match($number_regex, $m, $matches)) {
$left = $matches[1];
$dec = $matches[2];
$right = $matches[3];
if (preg_match('/#?.*\?\/\?/', $format, $m)) {
if ($value != (int) $value) {
self::formatAsFraction($value, $format);
}
} else {
// Handle the number itself
// minimun width of formatted number (including dot)
$minWidth = strlen($left) + strlen($dec) + strlen($right);
if ($useThousands) {
$value = number_format(
$value,
strlen($right),
StringHelper::getDecimalSeparator(),
StringHelper::getThousandsSeparator()
);
$value = preg_replace($number_regex, $value, $format);
} else {
if (preg_match('/[0#]E[+-]0/i', $format)) {
// Scientific format
$value = sprintf('%5.2E', $value);
} elseif (preg_match('/0([^\d\.]+)0/', $format)) {
$value = self::complexNumberFormatMask($value, $format);
} else {
$sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
$value = sprintf($sprintf_pattern, $value);
// scale number
$value = $value / $scale;
// Strip #
$format = preg_replace('/\\#/', '0', $format);
// Remove locale code [$-###]
$format = preg_replace('/\[\$\-.*\]/', '', $format);
$n = '/\\[[^\\]]+\\]/';
$m = preg_replace($n, '', $format);
$number_regex = '/(0+)(\\.?)(0*)/';
if (preg_match($number_regex, $m, $matches)) {
$left = $matches[1];
$dec = $matches[2];
$right = $matches[3];
// minimun width of formatted number (including dot)
$minWidth = strlen($left) + strlen($dec) + strlen($right);
if ($useThousands) {
$value = number_format(
$value,
strlen($right),
StringHelper::getDecimalSeparator(),
StringHelper::getThousandsSeparator()
);
$value = preg_replace($number_regex, $value, $format);
} else {
if (preg_match('/[0#]E[+-]0/i', $format)) {
// Scientific format
$value = sprintf('%5.2E', $value);
} elseif (preg_match('/0([^\d\.]+)0/', $format)) {
$value = self::complexNumberFormatMask($value, $format);
} else {
$sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
$value = sprintf($sprintf_pattern, $value);
$value = preg_replace($number_regex, $value, $format);
}
}
}
}
}
if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
// Currency or Accounting
$currencyCode = $m[1];
list($currencyCode) = explode('-', $currencyCode);
if ($currencyCode == '') {
$currencyCode = StringHelper::getCurrencyCode();
if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
// Currency or Accounting
$currencyCode = $m[1];
list($currencyCode) = explode('-', $currencyCode);
if ($currencyCode == '') {
$currencyCode = StringHelper::getCurrencyCode();
}
$value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value);
}
$value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value);
}
}
}

View File

@ -72,4 +72,27 @@ return [
43332,
'[$-1010409]m/d/yyyy',
],
[
'27:15',
1.1354166666667,
'[h]:mm',
],
// Percentage
[
'12%',
0.12,
'0%',
],
// Simple color
[
'12345',
12345,
'[Green]General',
],
// Multiple colors
[
'12345',
12345,
'[Blue]0;[Red]0',
],
];