Skip to content

Commit e642583

Browse files
authored
Merge pull request #299 from bcit-ci/feature/number_helper
Number Helper - Fixes #212
2 parents a8751fc + b13c2ee commit e642583

File tree

5 files changed

+482
-1
lines changed

5 files changed

+482
-1
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ We are not looking for out-of-scope contributions, only those that would be cons
6161
Please read the *Contributing to CodeIgniter* section in the user guide
6262

6363
## Server Requirements
64-
PHP version 7 or higher is required.
64+
PHP version 7 or higher is required, with the following extensions installed:
65+
66+
- intl
67+
6568

6669
## Running CodeIgniter Tests
6770
Information on running CodeIgniter test suite can be found in the [README.md](tests/README.md) file in the tests directory.

system/Helpers/number_helper.php

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
<?php
2+
/**
3+
* CodeIgniter
4+
*
5+
* An open source application development framework for PHP
6+
*
7+
* This content is released under the MIT License (MIT)
8+
*
9+
* Copyright (c) 2014 - 2016, British Columbia Institute of Technology
10+
*
11+
* Permission is hereby granted, free of charge, to any person obtaining a copy
12+
* of this software and associated documentation files (the "Software"), to deal
13+
* in the Software without restriction, including without limitation the rights
14+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
* copies of the Software, and to permit persons to whom the Software is
16+
* furnished to do so, subject to the following conditions:
17+
*
18+
* The above copyright notice and this permission notice shall be included in
19+
* all copies or substantial portions of the Software.
20+
*
21+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27+
* THE SOFTWARE.
28+
*
29+
* @package CodeIgniter
30+
* @author EllisLab Dev Team
31+
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
32+
* @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/)
33+
* @license http://opensource.org/licenses/MIT MIT License
34+
* @link https://codeigniter.com
35+
* @since Version 1.0.0
36+
* @filesource
37+
*/
38+
39+
40+
if ( ! function_exists('number_to_size'))
41+
{
42+
/**
43+
* Formats a numbers as bytes, based on size, and adds the appropriate suffix
44+
*
45+
* @param mixed will be cast as int
46+
* @param int
47+
* @return string
48+
*/
49+
function number_to_size($num, int $precision = 1, string $locale=null)
50+
{
51+
// Strip any formatting
52+
$num = 0 + str_replace(',','',$num);
53+
54+
// Can't work with non-numbers...
55+
if (! is_numeric($num))
56+
{
57+
return false;
58+
}
59+
60+
if ($num >= 1000000000000)
61+
{
62+
$num = round($num / 1099511627776, $precision);
63+
$unit = lang('Number.terabyteAbbr');
64+
}
65+
elseif ($num >= 1000000000)
66+
{
67+
$num = round($num / 1073741824, $precision);
68+
$unit = lang('Number.gigabyteAbbr');
69+
}
70+
elseif ($num >= 1000000)
71+
{
72+
$num = round($num / 1048576, $precision);
73+
$unit = lang('Number.megabyteAbbr');
74+
}
75+
elseif ($num >= 1000)
76+
{
77+
$num = round($num / 1024, $precision);
78+
$unit = lang('Number.kilobyteAbbr');
79+
}
80+
else
81+
{
82+
$unit = lang('Number.bytes');
83+
}
84+
85+
return format_number($num, $precision, $locale, ['after' => ' '.$unit]);
86+
}
87+
}
88+
89+
//--------------------------------------------------------------------
90+
91+
if (! function_exists('number_to_amount'))
92+
{
93+
/**
94+
* Converts numbers to a more readable representation
95+
* when dealing with very large numbers (in the thousands or above),
96+
* up to the quadrillions, because you won't often deal with numbers
97+
* larger than that.
98+
*
99+
* It uses the "short form" numbering system as this is most commonly
100+
* used within most English-speaking countries today.
101+
*
102+
* @see https://simple.wikipedia.org/wiki/Names_for_large_numbers
103+
*
104+
* @param $num
105+
* @param int $precision
106+
* @param string|null $locale
107+
*
108+
* @return bool|string
109+
*/
110+
function number_to_amount($num, int $precision = 0, string $locale = null)
111+
{
112+
// Strip any formatting
113+
$num = 0 + str_replace(',','',$num);
114+
115+
// Can't work with non-numbers...
116+
if (! is_numeric($num))
117+
{
118+
return false;
119+
}
120+
121+
$suffix = '';
122+
123+
if ($num > 1000000000000000)
124+
{
125+
$suffix = lang('Number.quadrillion');
126+
$num = round(($num / 1000000000000000), $precision);
127+
}
128+
elseif ($num > 1000000000000)
129+
{
130+
$suffix = lang('Number.trillion');
131+
$num = round(($num / 1000000000000), $precision);
132+
}
133+
else if ($num > 1000000000)
134+
{
135+
$suffix = lang('Number.billion');
136+
$num = round(($num/1000000000), $precision);
137+
}
138+
else if ($num > 1000000)
139+
{
140+
$suffix = lang('Number.million');
141+
$num = round(($num/1000000), $precision);
142+
}
143+
else if ($num > 1000)
144+
{
145+
$suffix = lang('Number.thousand');
146+
$num = round(($num/1000), $precision);
147+
}
148+
149+
return format_number($num, $precision, $locale, ['after' => $suffix]);
150+
}
151+
}
152+
153+
//--------------------------------------------------------------------
154+
155+
if (! function_exists('number_to_currency'))
156+
{
157+
function number_to_currency($num, string $currency, string $locale = null)
158+
{
159+
return format_number($num, 1, $locale, [
160+
'type' => NumberFormatter::CURRENCY,
161+
'currency' => $currency
162+
]);
163+
}
164+
}
165+
166+
//--------------------------------------------------------------------
167+
168+
if (! function_exists('format_number'))
169+
{
170+
/**
171+
* A general purpose, locale-aware, number_format method.
172+
* Used by all of the functions of the number_helper.
173+
*
174+
* @param $num
175+
* @param int $precision
176+
* @param string|null $locale
177+
* @param array $options
178+
*
179+
* @return string
180+
*/
181+
function format_number($num, int $precision = 1, string $locale = null, array $options=[])
182+
{
183+
// Locale is either passed in here, negotiated with client, or grabbed from our config file.
184+
$locale = $locale ?? \CodeIgniter\Config\Services::request()->getLocale();
185+
186+
// Type can be any of the NumberFormatter options, but provide a default.
187+
$type = isset($options['type'])
188+
? (int)$options['type'] :
189+
NumberFormatter::DECIMAL;
190+
191+
// In order to specify a precision, we'll have to modify
192+
// the pattern used by NumberFormatter.
193+
$pattern = '#,##0.'. str_repeat('#', $precision);
194+
195+
$formatter = new NumberFormatter($locale, $type);
196+
197+
// Try to format it per the locale
198+
if ($type == NumberFormatter::CURRENCY)
199+
{
200+
$output = $formatter->formatCurrency($num, $options['currency']);
201+
}
202+
else
203+
{
204+
$formatter->setPattern($pattern);
205+
$output = $formatter->format($num);
206+
}
207+
208+
// This might lead a trailing period if $precision == 0
209+
$output = trim($output, '. ');
210+
211+
if (intl_is_failure($formatter->getErrorCode()))
212+
{
213+
throw new BadFunctionCallException($formatter->getErrorMessage());
214+
}
215+
216+
// Add on any before/after text.
217+
if (isset($options['before']) && is_string($options['before']))
218+
{
219+
$output = $options['before'].$output;
220+
}
221+
222+
if (isset($options['after']) && is_string($options['after']))
223+
{
224+
$output .= $options['after'];
225+
}
226+
227+
return $output;
228+
}
229+
}
230+
231+
//--------------------------------------------------------------------

system/Language/en/Number.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
/**
3+
* CodeIgniter
4+
*
5+
* An open source application development framework for PHP
6+
*
7+
* This content is released under the MIT License (MIT)
8+
*
9+
* Copyright (c) 2014 - 2016, British Columbia Institute of Technology
10+
*
11+
* Permission is hereby granted, free of charge, to any person obtaining a copy
12+
* of this software and associated documentation files (the "Software"), to deal
13+
* in the Software without restriction, including without limitation the rights
14+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
* copies of the Software, and to permit persons to whom the Software is
16+
* furnished to do so, subject to the following conditions:
17+
*
18+
* The above copyright notice and this permission notice shall be included in
19+
* all copies or substantial portions of the Software.
20+
*
21+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27+
* THE SOFTWARE.
28+
*
29+
* @package CodeIgniter
30+
* @author EllisLab Dev Team
31+
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
32+
* @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/)
33+
* @license http://opensource.org/licenses/MIT MIT License
34+
* @link https://codeigniter.com
35+
* @since Version 1.0.0
36+
* @filesource
37+
*/
38+
return [
39+
'terabyteAbbr' => 'TB',
40+
'gigabyteAbbr' => 'GB',
41+
'megabyteAbbr' => 'MB',
42+
'kilobyteAbbr' => 'KB',
43+
'bytes' => 'Bytes',
44+
// don't forget the space in front of these!
45+
'thousand' => ' thousand',
46+
'million' => ' million',
47+
'billion' => ' billion',
48+
'trillion' => ' trillion',
49+
'quadrillion' => ' quadrillion',
50+
];
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php namespace CodeIgniter\HTTP;
2+
3+
class numberHelperTest extends \CIUnitTestCase
4+
{
5+
public function __construct(...$params)
6+
{
7+
parent::__construct(...$params);
8+
9+
helper('number');
10+
}
11+
12+
//--------------------------------------------------------------------
13+
14+
public function test_format_number()
15+
{
16+
$this->assertEquals('123,456', format_number(123456, 0, 'en_US'));
17+
}
18+
19+
public function test_format_number_with_precision()
20+
{
21+
$this->assertEquals('123,456.8', format_number(123456.789, 1, 'en_US'));
22+
$this->assertEquals('123,456.79', format_number(123456.789, 2, 'en_US'));
23+
}
24+
25+
public function test_number_to_size()
26+
{
27+
$this->assertEquals('456 Bytes', number_to_size(456));
28+
}
29+
30+
public function test_kb_format()
31+
{
32+
$this->assertEquals('4.5 KB', number_to_size(4567));
33+
}
34+
35+
public function test_kb_format_medium()
36+
{
37+
$this->assertEquals('44.6 KB', number_to_size(45678));
38+
}
39+
40+
public function test_kb_format_large()
41+
{
42+
$this->assertEquals('446.1 KB', number_to_size(456789));
43+
}
44+
45+
public function test_mb_format()
46+
{
47+
$this->assertEquals('3.3 MB', number_to_size(3456789));
48+
}
49+
50+
public function test_gb_format()
51+
{
52+
$this->assertEquals('1.8 GB', number_to_size(1932735283.2));
53+
}
54+
55+
public function test_tb_format()
56+
{
57+
$this->assertEquals('112,283.3 TB', number_to_size(123456789123456789));
58+
}
59+
60+
public function test_thousands()
61+
{
62+
$this->assertEquals('123 thousand', number_to_amount('123,000', 0, 'en_US'));
63+
}
64+
65+
public function test_millions()
66+
{
67+
$this->assertEquals('123.4 million', number_to_amount('123,400,000', 1, 'en_US'));
68+
}
69+
70+
public function test_billions()
71+
{
72+
$this->assertEquals('123.46 billion', number_to_amount('123,456,000,000', 2, 'en_US'));
73+
}
74+
75+
public function test_trillions()
76+
{
77+
$this->assertEquals('123.457 trillion', number_to_amount('123,456,700,000,000', 3, 'en_US'));
78+
}
79+
80+
public function test_quadrillions()
81+
{
82+
$this->assertEquals('123.5 quadrillion', number_to_amount('123,456,700,000,000,000', 1, 'en_US'));
83+
}
84+
85+
/**
86+
* @group single
87+
*/
88+
public function test_currency_current_locale()
89+
{
90+
$this->assertEquals('$1,234.56', number_to_currency(1234.56, 'USD', 'en_US'));
91+
$this->assertEquals('£1,234.56', number_to_currency(1234.56, 'GBP', 'en_GB'));
92+
}
93+
94+
}

0 commit comments

Comments
 (0)