Skip to content

Commit c2f4e9d

Browse files
authored
feat(support): add number formatting utils (#1268)
1 parent a986846 commit c2f4e9d

File tree

7 files changed

+578
-5
lines changed

7 files changed

+578
-5
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
"packages/support/src/Math/constants.php",
150150
"packages/support/src/Math/functions.php",
151151
"packages/support/src/Namespace/functions.php",
152+
"packages/support/src/Number/functions.php",
152153
"packages/support/src/Path/functions.php",
153154
"packages/support/src/Random/functions.php",
154155
"packages/support/src/Regex/functions.php",

packages/core/src/AppConfig.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
namespace Tempest\Core;
66

7-
use Tempest\Core\Exceptions\LogExceptionProcessor;
8-
97
use function Tempest\env;
108

119
final class AppConfig
@@ -16,6 +14,7 @@ final class AppConfig
1614

1715
public function __construct(
1816
?Environment $environment = null,
17+
1918
?string $baseUri = null,
2019

2120
/** @var class-string<\Tempest\Core\ExceptionProcessor>[] */

packages/support/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"src/Math/constants.php",
2929
"src/Math/functions.php",
3030
"src/Json/functions.php",
31-
"src/Filesystem/functions.php"
31+
"src/Filesystem/functions.php",
32+
"src/Number/functions.php"
3233
]
3334
},
3435
"autoload-dev": {

packages/support/src/Currency.php

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
namespace Tempest\Support;
4+
5+
/**
6+
* Represents an ISO-4217 currency.
7+
*/
8+
enum Currency
9+
{
10+
case AED;
11+
case AFN;
12+
case ALL;
13+
case AMD;
14+
case AOA;
15+
case ARS;
16+
case AUD;
17+
case AWG;
18+
case AZN;
19+
case BAM;
20+
case BBD;
21+
case BDT;
22+
case BGN;
23+
case BHD;
24+
case BIF;
25+
case BMD;
26+
case BND;
27+
case BOB;
28+
case BRL;
29+
case BSD;
30+
case BTN;
31+
case BWP;
32+
case BYN;
33+
case BZD;
34+
case CAD;
35+
case CDF;
36+
case CHF;
37+
case CLP;
38+
case CNY;
39+
case COP;
40+
case CRC;
41+
case CUP;
42+
case CVE;
43+
case CZK;
44+
case DJF;
45+
case DKK;
46+
case DOP;
47+
case DZD;
48+
case EGP;
49+
case ERN;
50+
case ETB;
51+
case EUR;
52+
case FJD;
53+
case FKP;
54+
case GBP;
55+
case GEL;
56+
case GHS;
57+
case GIP;
58+
case GMD;
59+
case GNF;
60+
case GTQ;
61+
case GYD;
62+
case HKD;
63+
case HNL;
64+
case HTG;
65+
case HUF;
66+
case IDR;
67+
case ILS;
68+
case INR;
69+
case IQD;
70+
case IRR;
71+
case ISK;
72+
case JMD;
73+
case JOD;
74+
case JPY;
75+
case KES;
76+
case KGS;
77+
case KHR;
78+
case KMF;
79+
case KPW;
80+
case KRW;
81+
case KWD;
82+
case KYD;
83+
case KZT;
84+
case LAK;
85+
case LBP;
86+
case LKR;
87+
case LRD;
88+
case LSL;
89+
case LYD;
90+
case MAD;
91+
case MDL;
92+
case MGA;
93+
case MKD;
94+
case MMK;
95+
case MNT;
96+
case MOP;
97+
case MRU;
98+
case MUR;
99+
case MVR;
100+
case MWK;
101+
case MXN;
102+
case MYR;
103+
case MZN;
104+
case NAD;
105+
case NGN;
106+
case NIO;
107+
case NOK;
108+
case NPR;
109+
case NZD;
110+
case OMR;
111+
case PAB;
112+
case PEN;
113+
case PGK;
114+
case PHP;
115+
case PKR;
116+
case PLN;
117+
case PYG;
118+
case QAR;
119+
case RON;
120+
case RSD;
121+
case RUB;
122+
case RWF;
123+
case SAR;
124+
case SBD;
125+
case SCR;
126+
case SDG;
127+
case SEK;
128+
case SGD;
129+
case SHP;
130+
case SLE;
131+
case SOS;
132+
case SRD;
133+
case SSP;
134+
case STN;
135+
case SVC;
136+
case SYP;
137+
case SZL;
138+
case THB;
139+
case TJS;
140+
case TMT;
141+
case TND;
142+
case TOP;
143+
case TRY;
144+
case TTD;
145+
case TWD;
146+
case TZS;
147+
case UAH;
148+
case UGX;
149+
case USD;
150+
case UYU;
151+
case UYW;
152+
case UZS;
153+
case VED;
154+
case VES;
155+
case VND;
156+
case VUV;
157+
case WST;
158+
case XAF;
159+
case XCD;
160+
case XCG;
161+
case XOF;
162+
case XPF;
163+
case YER;
164+
case ZAR;
165+
case ZMW;
166+
case ZWG;
167+
}

packages/support/src/Math/functions.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use function cos as php_cos;
2121
use function count;
2222
use function exp as php_exp;
23+
use function floor as php_floor;
2324
use function intdiv;
2425
use function log as php_log;
2526
use function round as php_round;
@@ -216,8 +217,6 @@ function exp(float $number): float
216217
return php_exp($number);
217218
}
218219

219-
use function floor as php_floor;
220-
221220
/**
222221
* Return the largest integer value less then or equal to the given number.
223222
*/
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
namespace Tempest\Support\Number;
4+
5+
use NumberFormatter;
6+
use Tempest\Support\Currency;
7+
use Tempest\Support\Language\Locale;
8+
use Tempest\Support\Math;
9+
10+
/**
11+
* Formats the given number.
12+
*
13+
* @see https://www.php.net/manual/en/class.numberformatter.php
14+
*/
15+
function format(int|float $number, ?int $precision = null, ?int $maxPrecision = null, ?Locale $locale = null): string|false
16+
{
17+
$locale ??= Locale::ENGLISH;
18+
$formatter = new NumberFormatter($locale->value, NumberFormatter::DECIMAL);
19+
20+
if (! is_null($maxPrecision)) {
21+
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $maxPrecision);
22+
} elseif (! is_null($precision)) {
23+
$formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $precision);
24+
}
25+
26+
return $formatter->format($number);
27+
}
28+
29+
/**
30+
* Spells out the given number in the given locale.
31+
*
32+
* @see https://www.php.net/manual/en/class.numberformatter.php
33+
*/
34+
function spell_out(int|float $number, ?Locale $locale = null, ?int $after = null, ?int $until = null): string|false
35+
{
36+
$locale ??= Locale::ENGLISH;
37+
38+
if (! is_null($after) && $number <= $after) {
39+
return namespace\format($number, locale: $locale);
40+
}
41+
42+
if (! is_null($until) && $number >= $until) {
43+
return namespace\format($number, locale: $locale);
44+
}
45+
46+
$formatter = new NumberFormatter($locale->value, NumberFormatter::SPELLOUT);
47+
48+
return $formatter->format($number);
49+
}
50+
51+
/**
52+
* Converts the given number to ordinal form.
53+
*
54+
* @see https://www.php.net/manual/en/class.numberformatter.php
55+
*/
56+
function to_ordinal(int|float $number, ?Locale $locale = null): string|false
57+
{
58+
$locale ??= Locale::ENGLISH;
59+
$formatter = new NumberFormatter($locale->value, NumberFormatter::ORDINAL);
60+
61+
return $formatter->format($number);
62+
}
63+
64+
/**
65+
* Spells out the given number in the given locale in ordinal form.
66+
*
67+
* @see https://www.php.net/manual/en/class.numberformatter.php
68+
*/
69+
function to_spelled_ordinal(int|float $number, ?Locale $locale = null): string|false
70+
{
71+
$locale ??= Locale::ENGLISH;
72+
$formatter = new NumberFormatter($locale->value, NumberFormatter::SPELLOUT);
73+
$formatter->setTextAttribute(NumberFormatter::DEFAULT_RULESET, '%spellout-ordinal');
74+
75+
return $formatter->format($number);
76+
}
77+
78+
/**
79+
* Converts the given number to its percentage equivalent.
80+
*/
81+
function to_percentage(int|float $number, int $precision = 0, ?int $maxPrecision = null, ?Locale $locale = null): string|false
82+
{
83+
$locale ??= Locale::ENGLISH;
84+
$formatter = new NumberFormatter($locale->value, NumberFormatter::PERCENT);
85+
86+
if (! is_null($maxPrecision)) {
87+
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $maxPrecision);
88+
} else {
89+
$formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $precision);
90+
}
91+
92+
return $formatter->format($number / 100);
93+
}
94+
95+
/**
96+
* Converts the given number to its currency equivalent.
97+
*/
98+
function currency(int|float $number, Currency $currency, ?Locale $locale = null, ?int $precision = null): string|false
99+
{
100+
$locale ??= Locale::ENGLISH;
101+
$formatter = new NumberFormatter($locale->value, NumberFormatter::CURRENCY);
102+
103+
if (! is_null($precision)) {
104+
$formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $precision);
105+
}
106+
107+
return $formatter->formatCurrency($number, $currency->name);
108+
}
109+
110+
/**
111+
* Converts the given number of bytes to its human-readable file size equivalent.
112+
*/
113+
function to_file_size(int|float $bytes, int $precision = 0, ?int $maxPrecision = null, bool $useBinaryPrefix = false): string
114+
{
115+
$base = $useBinaryPrefix ? 1024 : 1000;
116+
$units = $useBinaryPrefix
117+
? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', 'RiB', 'QiB']
118+
: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'RB', 'QB'];
119+
120+
for ($i = 0; ($bytes / $base) > 0.9 && $i < (count($units) - 1); $i++) {
121+
$bytes /= $base;
122+
}
123+
124+
return sprintf('%s %s', namespace\format($bytes, $precision, $maxPrecision), $units[$i]);
125+
}
126+
127+
/**
128+
* Converts the number to its human-readable equivalent.
129+
*/
130+
function to_human_readable(int|float $number, int $precision = 0, ?int $maxPrecision = null, array $units = []): string|false
131+
{
132+
if ($units === []) {
133+
$units = [
134+
3 => 'K',
135+
6 => 'M',
136+
9 => 'B',
137+
12 => 'T',
138+
15 => 'Q',
139+
];
140+
}
141+
142+
switch (true) {
143+
case floatval($number) === 0.0:
144+
return $precision > 0 ? namespace\format(0, $precision, $maxPrecision) : '0';
145+
case $number < 0:
146+
return sprintf('-%s', namespace\to_human_readable(Math\abs($number), $precision, $maxPrecision, $units));
147+
case $number >= 1e15:
148+
return sprintf('%s' . end($units), namespace\to_human_readable($number / 1e15, $precision, $maxPrecision, $units));
149+
}
150+
151+
$numberExponent = Math\floor(Math\log($number, base: 10));
152+
$displayExponent = $numberExponent - ($numberExponent % 3);
153+
$number /= 10 ** $displayExponent;
154+
155+
return trim(sprintf('%s%s', namespace\format($number, $precision, $maxPrecision), $units[$displayExponent] ?? ''));
156+
}

0 commit comments

Comments
 (0)