[!!!][FEATURE] Support IEC/SI units in file size formatting 91/40291/12
authorPierrick Caillon <pierrick.caillon@plan-net.fr>
Tue, 16 Jun 2015 11:35:01 +0000 (13:35 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Wed, 17 Jun 2015 16:51:40 +0000 (18:51 +0200)
The new labels "iec" and "si" are added to GeneralUtility::formatSize,
where "iec" is the new default label.

A new parameter "base" has been added for specifying the base unit
multiplier expecting 1000 and 1024 as value and defaulting to 1024
when using the old labels.

Resolves: #22175
Releases: master
Change-Id: Iaa255f639b0421e508f561557a6c6b2e2b226bc9
Reviewed-on: http://review.typo3.org/40291
Reviewed-by: Alexander Opitz <opitz.alexander@googlemail.com>
Tested-by: Alexander Opitz <opitz.alexander@googlemail.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/backend/Tests/Unit/Form/Element/NoneElementTest.php
typo3/sysext/core/Classes/Utility/GeneralUtility.php
typo3/sysext/core/Documentation/Changelog/master/Feature-22175-SupportIecSiUnitsInFileSizeFormatting.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php

index 1b1a243..52cd624 100644 (file)
@@ -209,7 +209,7 @@ class NoneElementTest extends UnitTestCase {
                                        'format' => 'filesize',
                                ),
                                '100000',
-                               '98 K'
+                               '98 Ki'
                        ),
                        'format to filesize with empty value' => array(
                                array(
@@ -226,7 +226,7 @@ class NoneElementTest extends UnitTestCase {
                                        ),
                                ),
                                '100000',
-                               '98 K (100000)'
+                               '98 Ki (100000)'
                        ),
                );
        }
index 780ea3b..6de0cb7 100755 (executable)
@@ -905,46 +905,48 @@ class GeneralUtility {
         * Formats the input integer $sizeInBytes as bytes/kilobytes/megabytes (-/K/M)
         *
         * @param int $sizeInBytes Number of bytes to format.
-        * @param string $labels Labels for bytes, kilo, mega and giga separated by vertical bar (|) and possibly encapsulated in "". Eg: " | K| M| G" (which is the default value)
+        * @param string $labels Binary unit name "iec", decimal unit name "si" or labels for bytes, kilo, mega, giga, and so on separated by vertical bar (|) and possibly encapsulated in "". Eg: " | K| M| G". Defaults to "iec".
+        * @param int $base The unit base if not using a unit name. Defaults to 1024.
         * @return string Formatted representation of the byte number, for output.
         */
-       static public function formatSize($sizeInBytes, $labels = '') {
-               // Set labels:
-               if ($labels === '') {
-                       $labels = ' | K| M| G';
-               } else {
-                       $labels = str_replace('"', '', $labels);
-               }
-               $labelArr = explode('|', $labels);
-               // Find size:
-               if ($sizeInBytes > 900) {
-                       // @todo find out which locale is used for current BE user to cover the BE case as well
-                       $locale = is_object($GLOBALS['TSFE']) ? $GLOBALS['TSFE']->config['config']['locale_all'] : '';
-                       $oldLocale = setlocale(LC_NUMERIC, 0);
-                       if ($locale) {
-                               setlocale(LC_NUMERIC, $locale);
-                       }
-                       $localeInfo = localeconv();
-                       if ($locale) {
-                               setlocale(LC_NUMERIC, $oldLocale);
-                       }
-                       // GB
-                       if ($sizeInBytes > 900000000) {
-                               $val = $sizeInBytes / (1024 * 1024 * 1024);
-                               return number_format($val, ($val < 20 ? 1 : 0), $localeInfo['decimal_point'], '') . $labelArr[3];
-                       } elseif ($sizeInBytes > 900000) {
-                               // MB
-                               $val = $sizeInBytes / (1024 * 1024);
-                               return number_format($val, ($val < 20 ? 1 : 0), $localeInfo['decimal_point'], '') . $labelArr[2];
-                       } else {
-                               // KB
-                               $val = $sizeInBytes / 1024;
-                               return number_format($val, ($val < 20 ? 1 : 0), $localeInfo['decimal_point'], '') . $labelArr[1];
-                       }
-               } else {
-                       // Bytes
-                       return $sizeInBytes . $labelArr[0];
+       static public function formatSize($sizeInBytes, $labels = '', $base = 0) {
+               $defaultFormats = array(
+                       'iec' => array('base' => 1024, 'labels' => array(' ', ' Ki', ' Mi', ' Gi', ' Ti', ' Pi', ' Ei', ' Zi', ' Yi')),
+                       'si' => array('base' => 1000, 'labels' => array(' ', ' k', ' M', ' G', ' T', ' P', ' E', ' Z', ' Y')),
+               );
+               // Set labels and base:
+               if (empty($labels)) {
+                       $labels = 'iec';
                }
+               if (isset($defaultFormats[$labels])) {
+                       $base = $defaultFormats[$labels]['base'];
+                       $labelArr = $defaultFormats[$labels]['labels'];
+               } else {
+                       $base = (int)$base;
+                       if ($base !== 1000 && $base !== 1024) {
+                               $base = 1024;
+                       }
+                       $labelArr = explode('|', str_replace('"', '', $labels));
+               }
+               // @todo find out which locale is used for current BE user to cover the BE case as well
+               $oldLocale = setlocale(LC_NUMERIC, 0);
+               $newLocale = is_object($GLOBALS['TSFE']) ? $GLOBALS['TSFE']->config['config']['locale_all'] : '';
+               if ($newLocale) {
+                       setlocale(LC_NUMERIC, $newLocale);
+               }
+               $localeInfo = localeconv();
+               if ($newLocale) {
+                       setlocale(LC_NUMERIC, $oldLocale);
+               }
+               $sizeInBytes = max($sizeInBytes, 0);
+               $multiplier = floor(($sizeInBytes ? log($sizeInBytes) : 0) / log($base));
+               $sizeInUnits = $sizeInBytes / pow($base, $multiplier);
+               if ($sizeInUnits > ($base * .9)) {
+                       $multiplier++;
+               }
+               $multiplier = min($multiplier, count($labelArr) - 1);
+               $sizeInUnits = $sizeInBytes / pow($base, $multiplier);
+               return number_format($sizeInUnits, (($multiplier > 0) && ($sizeInUnits < 20)) ? 2 : 0, $localeInfo['decimal_point'], '') . $labelArr[$multiplier];
        }
 
        /**
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-22175-SupportIecSiUnitsInFileSizeFormatting.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-22175-SupportIecSiUnitsInFileSizeFormatting.rst
new file mode 100644 (file)
index 0000000..dacd7ed
--- /dev/null
@@ -0,0 +1,37 @@
+==============================================================
+Feature: #22175 - Support IEC/SI units in file size formatting
+==============================================================
+
+Description
+===========
+
+Size formatting supports two keywords additionally to the list of labels:
+- iec: uses the Ki, Mi, etc prefixes and binary base (power of two, 1024)
+- si: uses the k, M, etc prefixes and decimal base (power of ten, 1000)
+
+The default formatting is set to "iec" base size calculations on the same base as before.
+The fractional part, when present, is changed to two numbers instead of only one.
+
+The list of labels is still supported and defaults to using binary base. It is also
+possible to explicitly choose between binary or decimal base when it is used.
+
+
+Impact
+======
+
+Default formatted output of file sizes changes, see example below.
+
+TypoScript ``stdWrap`` property ``bytes`` defaults to a different label set.
+``bytes.labels = iec``, a specifically defined label string with pipe separated
+label keywords is obsolete, but can still be used if required. The keyword
+``iec`` resolves to `` | Ki| Mi| Gi| Ti| Pi| Ei| Zi| Yi`` (binary base) and ``si`` resolves
+to `` | k| M| G| T| P| E| Z| Y`` (based on ten).
+
+
+Example
+=======
+
+.. codeblock:: php
+       echo GeneralUtility::formatSize(85123);
+       // => Before "83.1 K"
+       // => Now "83.13 Ki"
index abba45d..8967040 100644 (file)
@@ -727,8 +727,8 @@ class GeneralUtilityTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         * @dataProvider formatSizeDataProvider
         */
-       public function formatSizeTranslatesBytesToHigherOrderRepresentation($size, $label, $expected) {
-               $this->assertEquals($expected, Utility\GeneralUtility::formatSize($size, $label));
+       public function formatSizeTranslatesBytesToHigherOrderRepresentation($size, $labels, $base, $expected) {
+               $this->assertEquals($expected, Utility\GeneralUtility::formatSize($size, $labels, $base));
        }
 
        /**
@@ -738,18 +738,39 @@ class GeneralUtilityTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         */
        public function formatSizeDataProvider() {
                return array(
-                       'Bytes keep beeing bytes (min)' => array(1, '', '1 '),
-                       'Bytes keep beeing bytes (max)' => array(899, '', '899 '),
-                       'Kilobytes are detected' => array(1024, '', '1.0 K'),
-                       'Megabytes are detected' => array(1048576, '', '1.0 M'),
-                       'Gigabytes are detected' => array(1073741824, '', '1.0 G'),
-                       'Decimal is omitted for large kilobytes' => array(31080, '', '30 K'),
-                       'Decimal is omitted for large megabytes' => array(31458000, '', '30 M'),
-                       'Decimal is omitted for large gigabytes' => array(32212254720, '', '30 G'),
-                       'Label for bytes can be exchanged' => array(1, ' Foo|||', '1 Foo'),
-                       'Label for kilobytes can be exchanged' => array(1024, '| Foo||', '1.0 Foo'),
-                       'Label for megabyes can be exchanged' => array(1048576, '|| Foo|', '1.0 Foo'),
-                       'Label for gigabytes can be exchanged' => array(1073741824, '||| Foo', '1.0 Foo')
+                       'IEC Bytes stay bytes (min)' => array(1, '', 0, '1 '),
+                       'IEC Bytes stay bytes (max)' => array(921, '', 0, '921 '),
+                       'IEC Kilobytes are used (min)' => array(922, '', 0, '0.90 Ki'),
+                       'IEC Kilobytes are used (max)' => array(943718, '', 0, '922 Ki'),
+                       'IEC Megabytes are used (min)' => array(943719, '', 0, '0.90 Mi'),
+                       'IEC Megabytes are used (max)' => array(966367641, '', 0, '922 Mi'),
+                       'IEC Gigabytes are used (min)' => array(966367642, '', 0, '0.90 Gi'),
+                       'IEC Gigabytes are used (max)' => array(989560464998, '', 0, '922 Gi'),
+                       'IEC Decimal is omitted for large kilobytes' => array(31080, '', 0, '30 Ki'),
+                       'IEC Decimal is omitted for large megabytes' => array(31458000, '', 0, '30 Mi'),
+                       'IEC Decimal is omitted for large gigabytes' => array(32212254720, '', 0, '30 Gi'),
+                       'SI Bytes stay bytes (min)' => array(1, 'si', 0, '1 '),
+                       'SI Bytes stay bytes (max)' => array(899, 'si', 0, '899 '),
+                       'SI Kilobytes are used (min)' => array(901, 'si', 0, '0.90 k'),
+                       'SI Kilobytes are used (max)' => array(900000, 'si', 0, '900 k'),
+                       'SI Megabytes are used (min)' => array(900001, 'si', 0, '0.90 M'),
+                       'SI Megabytes are used (max)' => array(900000000, 'si', 0, '900 M'),
+                       'SI Gigabytes are used (min)' => array(900000001, 'si', 0, '0.90 G'),
+                       'SI Gigabytes are used (max)' => array(900000000000, 'si', 0, '900 G'),
+                       'SI Decimal is omitted for large kilobytes' => array(30000, 'si', 0, '30 k'),
+                       'SI Decimal is omitted for large megabytes' => array(30000000, 'si', 0, '30 M'),
+                       'SI Decimal is omitted for large gigabytes' => array(30000000000, 'si', 0, '30 G'),
+                       'Label for bytes can be exchanged (binary unit)' => array(1, ' Foo|||', 0, '1 Foo'),
+                       'Label for kilobytes can be exchanged (binary unit)' => array(1024, '| Foo||', 0, '1.00 Foo'),
+                       'Label for megabyes can be exchanged (binary unit)' => array(1048576, '|| Foo|', 0, '1.00 Foo'),
+                       'Label for gigabytes can be exchanged (binary unit)' => array(1073741824, '||| Foo', 0, '1.00 Foo'),
+                       'Label for bytes can be exchanged (decimal unit)' => array(1, ' Foo|||', 1000, '1 Foo'),
+                       'Label for kilobytes can be exchanged (decimal unit)' => array(1000, '| Foo||', 1000, '1.00 Foo'),
+                       'Label for megabyes can be exchanged (decimal unit)' => array(1000000, '|| Foo|', 1000, '1.00 Foo'),
+                       'Label for gigabytes can be exchanged (decimal unit)' => array(1000000000, '||| Foo', 1000, '1.00 Foo'),
+                       'IEC Base is ignored' => array(1024, 'iec', 1000, '1.00 Ki'),
+                       'SI Base is ignored' => array(1000, 'si', 1024, '1.00 k'),
+                       'Use binary base for unexpected base' => array(2048, '| Bar||', 512, '2.00 Bar')
                );
        }