Skip to content

Commit f7884b7

Browse files
ramseyahawkins
andauthored
feat: add formatters for numbers and currency (#37)
* add commit-stage context The commit-stage context (https://circleci.com/docs/2.0/contexts/) includes AWS access keys for pulling (and pushing) images to our ECR repos. Co-authored-by: Adam Hawkins <adam@skillshare.com>
1 parent 5cb8617 commit f7884b7

File tree

57 files changed

+22790
-44
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+22790
-44
lines changed

.circleci/config.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
version: 2
1+
version: 2.1
2+
workflows:
3+
test:
4+
jobs:
5+
- build:
6+
context:
7+
- commit-stage
8+
29
jobs:
310
build:
411
docker:
5-
- image: cimg/php:7.4
12+
- image: 289698421666.dkr.ecr.us-east-1.amazonaws.com/skillshare/circleci:0.9.3
613

714
steps:
815
- checkout

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## 0.5.0 - 2022-02-01
9+
10+
### Added
11+
12+
- Provide functionality for formatting numbers and currency through `Intl\NumberFormat`, as well as `FormatPHP::formatNumber()` and `FormatPHP::formatCurrency()` convenience methods.
13+
- Add `UnableToFormatNumberException` thrown when we're unable to format a number string.
14+
15+
### Changed
16+
17+
- Nothing.
18+
19+
### Deprecated
20+
21+
- Nothing.
22+
23+
### Removed
24+
25+
- Nothing.
26+
27+
### Fixed
28+
29+
- Nothing.
30+
831
## 0.4.1 - 2022-01-21
932

1033
### Added

README.md

Lines changed: 181 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -60,28 +60,171 @@ use FormatPHP\MessageCollection;
6060

6161
// Translated messages in Spanish with matching IDs to what you declared.
6262
$messagesInSpanish = new MessageCollection([
63-
new Message('hello', '¡Hola {name}! Hoy es {ts, date, ::yyyyMMdd}.'),
63+
new Message('hello', '¡Hola {name}! Hoy es {today}.'),
6464
]);
6565

6666
$config = new Config(
6767
// Locale of the application (or of the user using the application).
68-
locale: new Intl\Locale('es'),
68+
new Intl\Locale('es-ES'),
6969
);
7070

71-
$formatphp = new FormatPHP(
72-
config: $config,
73-
messages: $messagesInSpanish,
74-
);
71+
$formatphp = new FormatPHP($config, $messagesInSpanish);
7572

7673
echo $formatphp->formatMessage([
7774
'id' => 'hello',
78-
'defaultMessage' => 'Hello, {name}! Today is {ts, date, ::yyyyMMdd}.',
75+
'defaultMessage' => 'Hello, {name}! Today is {today}.',
7976
], [
8077
'name' => 'Arwen',
81-
'ts' => time(),
82-
]);
78+
'today' => $formatphp->formatDate(time()),
79+
]); // e.g., ¡Hola Arwen! Hoy es 31/1/22.
80+
```
81+
82+
### Formatting Numbers and Currency
83+
84+
You may use the methods `formatNumber()` and `formatCurrency()` for format
85+
numbers and currency, according to the locale.
86+
87+
```php
88+
use FormatPHP\Config;
89+
use FormatPHP\FormatPHP;
90+
use FormatPHP\Intl;
91+
92+
$config = new Config(new Intl\Locale('es-ES'));
93+
$formatphp = new FormatPHP($config);
94+
95+
$number = -12_345.678;
96+
97+
echo $formatphp->formatNumber($number); // e.g., "-12.345,678"
98+
echo $formatphp->formatCurrency($number, 'USD'); // e.g., "-12.345,68 $"
8399
```
84100

101+
#### Using Intl\NumberFormatOptions with formatNumber() and formatCurrency()
102+
103+
Fine-tune number and currency formatting with `Intl\NumberFormatOptions`.
104+
105+
```php
106+
echo $formatphp->formatNumber($number, new Intl\NumberFormatOptions([
107+
'style' => 'unit',
108+
'unit' => 'meter',
109+
'unitDisplay' => 'long',
110+
])); // e.g., "-12.345,678 metros"
111+
112+
echo $formatphp->formatCurrency($number, 'USD', new Intl\NumberFormatOptions([
113+
'currencySign' => 'accounting',
114+
'currencyDisplay' => 'long',
115+
])); // e.g., "-12.345,68 US$"
116+
```
117+
118+
`NumberFormatOptions` accepts the following options to specify the style and
119+
type of notation desired:
120+
121+
* `notation`: The number formatting to use. Possible values include: `standard`,
122+
`scientific`, `engineering`, and `compact`. The default is `standard`.
123+
* `style`: The number formatting style to use. Possible values include:
124+
`decimal`, `currency`, `percent`, and `unit`. The default is `decimal` when
125+
using `formatNumber()`. When using `formatCurrency()`, this value will always
126+
be `currency` no matter what value is set on the `NumberFormatOptions` instance.
127+
128+
All notations support the following properties to provide more granular control
129+
over the formatting of numbers:
130+
131+
* `signDisplay`: Controls when to display the sign for the number. Defaults to
132+
`auto`. Possible values include:
133+
* `always`: Always display the sign.
134+
* `auto`: Use the locale to determine when to display the sign.
135+
* `exceptZero`: Display the sign for positive and negative numbers, but never
136+
display the size for zero.
137+
* `never`: Never display the sign.
138+
* `roundingMode`: Controls rounding rules for the number. The default is
139+
`halfEven`. Possible values include:
140+
* `ceil`: All values are rounded towards positive infinity (+∞).
141+
* `floor`: All values are rounded towards negative infinity (-∞).
142+
* `expand`: All values are rounded away from zero.
143+
* `trunc`: All values are rounded towards zero.
144+
* `halfCeil`: Values exactly on the 0.5 (half) mark are rounded towards
145+
positive infinity (+∞).
146+
* `halfFloor`: Values exactly on the 0.5 (half) mark are rounded towards
147+
negative infinity (-∞).
148+
* `halfExpand`: Values exactly on the 0.5 (half) mark are rounded away from zero.
149+
* `halfTrunc`: Values exactly on the 0.5 (half) mark are rounded towards zero.
150+
* `halfEven`: Values exactly on the 0.5 (half) mark are rounded to the nearest
151+
even digit. This is often called Banker’s Rounding because it is, on average,
152+
free of bias.
153+
* `halfOdd`: Similar to `halfEven`, but rounds ties to the nearest odd number
154+
instead of even number.
155+
* `unnecessary`: This mode doesn't perform any rounding but will throw an
156+
exception if the value cannot be represented exactly without rounding.
157+
* `useGrouping`: Controls display of grouping separators, such as thousand
158+
separators or thousand/lakh/crore separators. The default is `auto`. Possible
159+
values include:
160+
* `always`: Always display grouping separators, even if the locale prefers otherwise.
161+
* `auto`: Use the locale's preference for grouping separators.
162+
* `false`: Do not display grouping separators. Please note this is a string
163+
value and not a boolean `false` value.
164+
* `min2`: Display grouping separators when there are at least two digits in a group.
165+
* `true`: This is an alias for `always`. Please note this is a string value
166+
and not a boolean `true` value.
167+
* `scale`: A scale by which to multiply the number before formatting it. For
168+
example, a scale value of 100 will multiply the number by 100 first, then
169+
apply other formatting options.
170+
* `minimumIntegerDigits`: Specifies the minimum number of integer digits to use.
171+
The default is 1.
172+
* `minimumFractionDigits` and `maximumFractionDigits`: Specifies the minimum and
173+
maximum number of fraction digits to use.
174+
* `minimumSignificantDigits` and `maximumSignificantDigits`: Specifies the
175+
minimum and maximum number of significant digits to use.
176+
* `numberingSystem`: Specifies a [numbering system](https://cldr.unicode.org/translation/core-data/numbering-systems)
177+
to use when representing numeric values. You may specify any [numbering system
178+
defined within Unicode CLDR](https://github.com/unicode-org/cldr/blob/main/common/bcp47/number.xml)
179+
and bundled in the ICU library version that is available on your platform.
180+
However, numbering systems featuring algorithmic numbers do not yet work.
181+
Possible values include (but are not limited to): `adlm`, `ahom`, `arab`,
182+
`arabext`, `bali`, `beng`, `bhks`, `brah`, `cakm`, `cham`, `deva`, `fullwide`,
183+
`gong`, `gonm`, `gujr`, `guru`, `hanidec`, `hmng`, `java`, `kali`, `khmr`,
184+
`knda`, `lana`, `lanatham`, `laoo`, `latn`, `lepc`, `limb`, `mathbold`,
185+
`mathdbl`, `mathmono`, `mathsanb`, `mathsans`, `mlym`, `modi`, `mong`, `mroo`,
186+
`mtei`, `mymr`, `mymrshan`, `mymrtlng`, `newa`, `nkoo`, `olck`, `orya`, `osma`,
187+
`rohg`, `saur`, `shrd`, `sind`, `sora`, `sund`, `takr`, `talu`, `tamldec`,
188+
`telu`, `thai`, `tibt`, `tirh`, `vaii`, `wara`, and `wcho`.
189+
190+
#### Formatting Fractions
191+
192+
The following properties affect the formatting of fractional digits (e.g., when
193+
using `minimumFractionDigits` or `maximumFractionDigits`).
194+
195+
* `trailingZeroDisplay`: Controls the display of trailing zeros when formatting
196+
numbers. The default is `auto`.
197+
* `auto`: Keep the trailing zeros according to the rules defined in
198+
`minimumFractionDigits` and `maximumFractionDigits`.
199+
* `stripIfInteger`: If the formatted number is a whole integer, do not display
200+
trailing zeros.
201+
* `roundingPriority`: Specifies how to resolve conflicts between maximum fraction
202+
digits and maximum significant digits. The default is `auto`.
203+
* `auto`: The significant digits always win a conflict.
204+
* `morePrecision`: The result with more precision wins the conflict.
205+
* `lessPrecision`: The result with less precision wins the conflict.
206+
207+
#### Formatting Currency
208+
209+
When formatting currency, you may use the following properties.
210+
211+
* `currencySign`: In accounting, many locales format negative currency values
212+
using parentheses rather than the minus sign. You may enable this behavior by
213+
setting this property to `accounting`. The default value is `standard`.
214+
* `currencyDisplay`: How to display the currency. Possible values include:
215+
* `symbol`: Use a localized currency symbol when formatting the currency. This
216+
is the default.
217+
* `narrowSymbol`: Use a narrow format for the currency symbol. For example, in
218+
some locales (e.g., en-GB), USD currency will default to display as "US$100."
219+
When using `narrowSymbol`, it will display as "$100."
220+
* `code`: Use the ISO currency code when formatting currency (e.g., "USD 100").
221+
* `name`: Use a localized name for the currency (e.g., "100 US dollars").
222+
223+
#### Compact Notation
224+
225+
If `notation` is `compact`, then you may specify the `compactDisplay` property
226+
with the value `short` or `long`. The default is `short`.
227+
85228
### Formatting Dates and Times
86229

87230
You may use the methods `formatDate()` and `formatTime()` to format dates and
@@ -92,13 +235,13 @@ use FormatPHP\Config;
92235
use FormatPHP\FormatPHP;
93236
use FormatPHP\Intl;
94237

95-
$config = new Config(new Intl\Locale('es'));
238+
$config = new Config(new Intl\Locale('es-ES'));
96239
$formatphp = new FormatPHP($config);
97240

98241
$date = new DateTimeImmutable();
99242

100-
echo $formatphp->formatDate($date); // e.g. "21/1/22"
101-
echo $formatphp->formatTime($date); // e.g. "16:58"
243+
echo $formatphp->formatDate($date); // e.g., "31/1/22"
244+
echo $formatphp->formatTime($date); // e.g., "16:58"
102245
```
103246

104247
#### Using Intl\DateTimeFormatOptions with formatDate() and formatTime()
@@ -107,11 +250,12 @@ Fine-tune date and time formatting with `Intl\DateTimeFormatOptions`.
107250

108251
```php
109252
echo $formatphp->formatDate($date, new Intl\DateTimeFormatOptions([
110-
'dateStyle' => 'medium',
111-
])); // e.g. "21 ene 2022"
253+
'dateStyle' => 'medium',
254+
])); // e.g., "31 ene 2022"
255+
112256
echo $formatphp->formatTime($date, new Intl\DateTimeFormatOptions([
113-
'timeStyle' => 'long',
114-
])); // e.g. "16:58:31 UTC"
257+
'timeStyle' => 'long',
258+
])); // e.g., "16:58:31 UTC"
115259
```
116260

117261
`DateTimeFormatOptions` accepts the following general options for formatting
@@ -193,15 +337,20 @@ be broken up into chunks. These are not HTML or XML tags, and attributes are
193337
not supported.
194338

195339
```php
196-
$formatphp->formatMessage([
340+
echo $formatphp->formatMessage([
197341
'id' => 'priceMessage',
198342
'defaultMessage' => <<<'EOD'
199-
Our price is <boldThis>{price, number, ::currency/USD precision-integer}</boldThis>
200-
with <link>{pct, number, ::percent} discount</link>
343+
Our price is <boldThis>{price}</boldThis>
344+
with <link>{discount} discount</link>
201345
EOD,
202346
], [
203-
'price' => 29.99,
204-
'pct' => 2.5,
347+
'price' => $formatphp->formatCurrency(29.99, 'USD', new Intl\NumberFormatOptions([
348+
'maximumFractionDigits' => 0,
349+
])),
350+
'discount' => $formatphp->formatNumber(.025, new Intl\NumberFormatOptions([
351+
'style' => 'percent',
352+
'minimumFractionDigits' => 1,
353+
])),
205354
'boldThis' => fn ($text) => "<strong>$text</strong>",
206355
'link' => fn ($text) => "<a href=\"/discounts/1234\">$text</a>",
207356
]);
@@ -216,8 +365,9 @@ of tag names to rich text formatting functions, when configuring FormatPHP.
216365

217366
```php
218367
$config = new Config(
219-
locale: new Intl\Locale('en-US'),
220-
defaultRichTextElements: [
368+
new Intl\Locale('en-US'),
369+
null,
370+
[
221371
'em' => fn ($text) => "<em class=\"myClass\">$text</em>",
222372
'strong' => fn ($text) => "<strong class=\"myClass\">$text</strong>",
223373
],
@@ -249,9 +399,9 @@ use FormatPHP\MessageLoader;
249399

250400
$messageLoader = new MessageLoader(
251401
// The path to your locale JSON files (i.e., en.json, es.json, etc.).
252-
messagesDirectory: '/path/to/app/locales',
402+
'/path/to/app/locales',
253403
// The configuration object created earlier.
254-
config: $config,
404+
$config,
255405
);
256406

257407
$messagesInSpanish = $messageLoader->loadMessages();
@@ -264,7 +414,7 @@ contents:
264414
```json
265415
{
266416
"hello": {
267-
"defaultMessage": "Hello, {name}! Today is {ts, date, ::yyyyMMdd}."
417+
"defaultMessage": "Hello, {name}! Today is {today}."
268418
}
269419
}
270420
```
@@ -274,7 +424,7 @@ and an `es.json` file with these contents:
274424
```json
275425
{
276426
"hello": {
277-
"defaultMessage": "¡Hola {name}! Hoy es {ts, date, ::yyyyMMdd}."
427+
"defaultMessage": "¡Hola {name}! Hoy es {today}."
278428
}
279429
}
280430
```
@@ -380,7 +530,7 @@ particular TMS.
380530
| [SimpleLocalize](https://simplelocalize.io/docs/integrations/format-js-cli/) | `simple` |
381531
| [Smartling ICU JSON](https://help.smartling.com/hc/en-us/articles/360008000733-JSON) | `smartling` |
382532

383-
Our default formatter is `formatphp`, which mirrors the output of default
533+
Our default formatter is `formatphp`, which mirrors the output of the default
384534
formatter for FormatJS.
385535

386536
### Custom Formatters
@@ -431,11 +581,11 @@ To use a custom reader with the message loader:
431581
```php
432582
$messageLoader = new MessageLoader(
433583
// The path to your locale JSON files (i.e., en.json, es.json, etc.).
434-
messagesDirectory: '/path/to/app/locales',
584+
'/path/to/app/locales',
435585
// The configuration object created earlier.
436-
config: $config,
586+
$config,
437587
// Pass your custom reader through the formatReader parameter.
438-
formatReader: MyCustomReader::class,
588+
MyCustomReader::class,
439589
);
440590
```
441591

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
},
3232
"require-dev": {
3333
"ramsey/devtools": "^1.7",
34+
"ramsey/devtools-lib": "1.3.0",
3435
"spatie/phpunit-snapshot-assertions": "^4.2"
3536
},
3637
"minimum-stability": "dev",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/**
4+
* This file is part of skillshare/formatphp
5+
*
6+
* skillshare/formatphp is open source software: you can distribute
7+
* it and/or modify it under the terms of the MIT License
8+
* (the "License"). You may not use this file except in
9+
* compliance with the License.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14+
* implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*
17+
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
18+
* @license https://opensource.org/licenses/MIT MIT License
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace FormatPHP\Exception;
24+
25+
/**
26+
* Thrown when we are unable to format a number
27+
*/
28+
class UnableToFormatNumberException extends UnableToFormatStringException
29+
{
30+
}

0 commit comments

Comments
 (0)