Skip to content

Commit

Permalink
Fix time format for duration was incorrect
Browse files Browse the repository at this point in the history
When using format `[h]:mm` it should convert to the "total hours:minutes"

Closes PHPOffice#666
Fixes PHPOffice#664
Fixes PHPOffice#446
Fixes PHPOffice#342
  • Loading branch information
yasar-luo authored and guillaume-ro-fr committed Jun 12, 2019
1 parent 7ca97b3 commit ec617ca
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 89 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
186 changes: 97 additions & 89 deletions src/PhpSpreadsheet/Style/NumberFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -424,19 +424,19 @@ public function getHashCode()
* @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).
*
* @var array
*/
private static $dateFormatReplacements12 = [
'hh' => 'h',
'h' => 'g',
];
'hh' => 'h',
'h' => 'g',
];

private static function setLowercaseCallback($matches)
{
Expand Down Expand Up @@ -467,6 +467,13 @@ private static function formatAsDate(&$value, &$format)
$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
Expand Down Expand Up @@ -636,104 +643,105 @@ public static function toFormattedString($value, $format, $callBack = null)
// 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 ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
$value = 'EUR ' . sprintf('%1.2f', $value);
} 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);
}

if (preg_match('/#?.*\?\/\?/', $format, $m)) {
if ($value != (int) $value) {
self::formatAsFraction($value, $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);
}
} else {
// Handle the number itself

// 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);

if (preg_match('/#?.*\?\/\?/', $format, $m)) {
if ($value != (int) $value) {
self::formatAsFraction($value, $format);
}
} else {
// Handle the number itself

// 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);
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions tests/data/Style/NumberFormatDates.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,27 @@
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',
],
];

0 comments on commit ec617ca

Please sign in to comment.