Skip to content

Commit cc4befc

Browse files
committed
Implement formatters for common units
Includes formatters that perform unit promotion. We DO NOT do unit conversion (for example, inches to centimeters), we only choose within the appropriate system (metric or imperial) the best representation for a given value. For example, saying `10cm` is much better than saying `0.1m`. Both are still within the metric system. Considerations made during this change: - We only use unit symbols, not the full names. This simplifies aspects such as translations, since most units are universal but their localized full names are not. There is some space for later accepting full names (new MetricFormatter('meter')) and deciding the output representation automatically. This is also the reason why PHP enums were not used as the source of truth for supported units. - Time unit symbols are *not universal*, but they were considered for inclusion for their value in technical systems (simplifying a log entry or measure). - The SI (colloquially known as "metric system") is the widest adopted standard, so only "Imperial" was prefixed. We used the "Metric" name to denote SI Length units.
1 parent abb8a25 commit cc4befc

22 files changed

+1039
-9
lines changed

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,19 @@ echo f::create()
3737

3838
## Formatters
3939

40-
| Formatter | Description |
41-
| ---------------------------------------------------- | --------------------------------------------------- |
42-
| [DateFormatter](docs/DateFormatter.md) | Date and time formatting with flexible parsing |
43-
| [MaskFormatter](docs/MaskFormatter.md) | Range-based string masking with Unicode support |
44-
| [NumberFormatter](docs/NumberFormatter.md) | Number formatting with thousands and decimal separators |
45-
| [PatternFormatter](docs/PatternFormatter.md) | Pattern-based string filtering with placeholders |
46-
| [PlaceholderFormatter](docs/PlaceholderFormatter.md) | Template interpolation with placeholder replacement |
40+
| Formatter | Description |
41+
| ---------------------------------------------------------- | ---------------------------------------------------------------- |
42+
| [DateFormatter](docs/DateFormatter.md) | Date and time formatting with flexible parsing |
43+
| [ImperialAreaFormatter](docs/ImperialAreaFormatter.md) | Imperial area promotion (in², ft², yd², ac, mi²) |
44+
| [ImperialLengthFormatter](docs/ImperialLengthFormatter.md) | Imperial length promotion (in, ft, yd, mi) |
45+
| [ImperialMassFormatter](docs/ImperialMassFormatter.md) | Imperial mass promotion (oz, lb, st, ton) |
46+
| [MaskFormatter](docs/MaskFormatter.md) | Range-based string masking with Unicode support |
47+
| [MassFormatter](docs/MassFormatter.md) | Metric mass promotion (mg, g, kg, t) |
48+
| [MetricFormatter](docs/MetricFormatter.md) | Metric length promotion (mm, cm, m, km) |
49+
| [NumberFormatter](docs/NumberFormatter.md) | Number formatting with thousands and decimal separators |
50+
| [PatternFormatter](docs/PatternFormatter.md) | Pattern-based string filtering with placeholders |
51+
| [PlaceholderFormatter](docs/PlaceholderFormatter.md) | Template interpolation with placeholder replacement |
52+
| [TimeFormatter](docs/TimeFormatter.md) | Time promotion (mil, c, dec, y, mo, w, d, h, min, s, ms, us, ns) |
4753

4854
## Contributing
4955

docs/ImperialAreaFormatter.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!--
2+
SPDX-FileCopyrightText: (c) Respect Project Contributors
3+
SPDX-License-Identifier: ISC
4+
SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
5+
-->
6+
7+
# ImperialAreaFormatter
8+
9+
The `ImperialAreaFormatter` promotes imperial area values between `in²`, `ft²`, `yd²`, `ac`, and `mi²`.
10+
11+
- Non-numeric input is returned unchanged.
12+
- Promotion is based on magnitude.
13+
- Output uses symbols only (no spaces), e.g. `1ft²`, `2ac`.
14+
15+
## Usage
16+
17+
```php
18+
use Respect\StringFormatter\ImperialAreaFormatter;
19+
20+
$formatter = new ImperialAreaFormatter('ft²');
21+
22+
echo $formatter->format('43560');
23+
// Outputs: 1ac
24+
```
25+
26+
## API
27+
28+
### `ImperialAreaFormatter::__construct`
29+
30+
- `__construct(string $unit = 'ft²')`
31+
32+
The `$unit` is the input unit (the unit you are providing values in).
33+
34+
Accepted units: `in²`, `ft²`, `yd²`, `ac`, `mi²`.

docs/ImperialLengthFormatter.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!--
2+
SPDX-FileCopyrightText: (c) Respect Project Contributors
3+
SPDX-License-Identifier: ISC
4+
SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
5+
-->
6+
7+
# ImperialLengthFormatter
8+
9+
The `ImperialLengthFormatter` promotes imperial length values between `in`, `ft`, `yd`, and `mi`.
10+
11+
- Non-numeric input is returned unchanged.
12+
- Promotion is based on magnitude.
13+
- Output uses symbols only (no spaces), e.g. `1ft`, `2yd`.
14+
15+
## Usage
16+
17+
```php
18+
use Respect\StringFormatter\ImperialLengthFormatter;
19+
20+
$formatter = new ImperialLengthFormatter('in');
21+
22+
echo $formatter->format('12');
23+
// Outputs: 1ft
24+
```
25+
26+
## API
27+
28+
### `ImperialLengthFormatter::__construct`
29+
30+
- `__construct(string $unit = 'in')`
31+
32+
The `$unit` is the input unit (the unit you are providing values in).
33+
34+
Accepted units: `in`, `ft`, `yd`, `mi`.

docs/ImperialMassFormatter.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!--
2+
SPDX-FileCopyrightText: (c) Respect Project Contributors
3+
SPDX-License-Identifier: ISC
4+
SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
5+
-->
6+
7+
# ImperialMassFormatter
8+
9+
The `ImperialMassFormatter` promotes imperial mass values between `oz`, `lb`, `st`, and `ton`.
10+
11+
- Non-numeric input is returned unchanged.
12+
- Promotion is based on magnitude.
13+
- Output uses symbols only (no spaces), e.g. `1lb`, `8oz`.
14+
15+
## Usage
16+
17+
```php
18+
use Respect\StringFormatter\ImperialMassFormatter;
19+
20+
$formatter = new ImperialMassFormatter('oz');
21+
22+
echo $formatter->format('16');
23+
// Outputs: 1lb
24+
```
25+
26+
## API
27+
28+
### `ImperialMassFormatter::__construct`
29+
30+
- `__construct(string $unit = 'lb')`
31+
32+
The `$unit` is the input unit (the unit you are providing values in).
33+
34+
Accepted units: `oz`, `lb`, `st`, `ton`.
35+
36+
## Notes
37+
38+
- `ton` represents the imperial long ton (`2240lb`).

docs/MassFormatter.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
````markdown
2+
<!--
3+
SPDX-FileCopyrightText: (c) Respect Project Contributors
4+
SPDX-License-Identifier: ISC
5+
SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
6+
-->
7+
8+
# MassFormatter
9+
10+
The `MassFormatter` promotes metric *mass* values between `mg`, `g`, `kg`, and `t`.
11+
12+
- Non-numeric input is returned unchanged.
13+
- Promotion is based on magnitude.
14+
- Output uses symbols only (no spaces), e.g. `1kg`, `500mg`.
15+
16+
## Usage
17+
18+
```php
19+
use Respect\StringFormatter\MassFormatter;
20+
21+
$formatter = new MassFormatter('g');
22+
23+
echo $formatter->format('1000');
24+
// Outputs: 1kg
25+
26+
echo $formatter->format('0.001');
27+
// Outputs: 1mg
28+
```
29+
30+
## API
31+
32+
### `MassFormatter::__construct`
33+
34+
- `__construct(string $unit = 'g')`
35+
36+
The `$unit` is the input unit (the unit you are providing values in).
37+
38+
Accepted units: `mg`, `g`, `kg`, `t`.
39+
40+
### `format`
41+
42+
- `format(string $input): string`
43+
44+
If the input is numeric, it is promoted to the closest appropriate metric scale and returned with the corresponding symbol.
45+
46+
````

docs/MetricFormatter.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
````markdown
2+
<!--
3+
SPDX-FileCopyrightText: (c) Respect Project Contributors
4+
SPDX-License-Identifier: ISC
5+
SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
6+
-->
7+
8+
# MetricFormatter
9+
10+
The `MetricFormatter` promotes metric *length* values between `mm`, `cm`, `m`, and `km`.
11+
12+
- Non-numeric input is returned unchanged.
13+
- Promotion is based on magnitude.
14+
- Output uses symbols only (no spaces), e.g. `1km`, `10cm`.
15+
16+
## Usage
17+
18+
```php
19+
use Respect\StringFormatter\MetricFormatter;
20+
21+
$formatter = new MetricFormatter('m');
22+
23+
echo $formatter->format('1000');
24+
// Outputs: 1km
25+
26+
echo $formatter->format('0.1');
27+
// Outputs: 10cm
28+
```
29+
30+
## API
31+
32+
### `MetricFormatter::__construct`
33+
34+
- `__construct(string $unit = 'm')`
35+
36+
The `$unit` is the input unit (the unit you are providing values in).
37+
38+
Accepted units: `mm`, `cm`, `m`, `km`.
39+
40+
### `format`
41+
42+
- `format(string $input): string`
43+
44+
If the input is numeric, it is promoted to the closest appropriate metric scale and returned with the corresponding symbol.
45+
46+
## Behavior
47+
48+
### Promotion rule
49+
50+
The formatter chooses a unit where the promoted value is in the range $[1, 1000)$ when possible. If not possible, it uses the smallest (`mm`) or largest (`km`) unit as needed.
51+
52+
### No rounding
53+
54+
Values are not rounded. Trailing fractional zeros are trimmed:
55+
56+
```php
57+
$formatter = new MetricFormatter('m');
58+
59+
echo $formatter->format('1.23000');
60+
// Outputs: 1.23m
61+
```
62+
63+
````

docs/TimeFormatter.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!--
2+
SPDX-FileCopyrightText: (c) Respect Project Contributors
3+
SPDX-License-Identifier: ISC
4+
SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
5+
-->
6+
7+
# TimeFormatter
8+
9+
The `TimeFormatter` promotes time values between multiple units.
10+
11+
- Non-numeric input is returned unchanged.
12+
- Promotion is based on magnitude.
13+
- Output uses symbols only (no spaces), e.g. `1h`, `500ms`.
14+
15+
## Usage
16+
17+
```php
18+
use Respect\StringFormatter\TimeFormatter;
19+
20+
$formatter = new TimeFormatter('s');
21+
22+
echo $formatter->format('60');
23+
// Outputs: 1min
24+
25+
echo $formatter->format('0.001');
26+
// Outputs: 1ms
27+
```
28+
29+
## API
30+
31+
### `TimeFormatter::__construct`
32+
33+
- `__construct(string $unit = 's')`
34+
35+
The `$unit` is the input unit (the unit you are providing values in).
36+
37+
Accepted symbols:
38+
39+
- `ns`, `us`, `ms`, `s`, `min`, `h`, `d`, `w`, `mo`, `y`, `dec`, `c`, `mil`
40+
41+
## Notes
42+
43+
- `y` uses a fixed year of 365 days.
44+
- `mo` uses 1/12 of a fixed year (approx. 30.41 days).
45+
- `w` uses 7 days.
46+
- `dec`, `c`, and `mil` are based on that fixed year.

src/ImperialAreaFormatter.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
5+
* SPDX-License-Identifier: ISC
6+
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\StringFormatter;
12+
13+
use Respect\StringFormatter\Internal\UnitPromoter;
14+
15+
final readonly class ImperialAreaFormatter implements Formatter
16+
{
17+
use UnitPromoter;
18+
19+
private const array UNIT_RATIOS = [
20+
'mi²' => [4_014_489_600, 1],
21+
'ac' => [6_272_640, 1],
22+
'yd²' => [1_296, 1],
23+
'ft²' => [144, 1],
24+
'in²' => [1, 1],
25+
];
26+
27+
public function __construct(private string $unit)
28+
{
29+
if (!isset(self::UNIT_RATIOS[$unit])) {
30+
throw new InvalidFormatterException('Unsupported imperial area unit');
31+
}
32+
}
33+
}

src/ImperialLengthFormatter.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
5+
* SPDX-License-Identifier: ISC
6+
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\StringFormatter;
12+
13+
use Respect\StringFormatter\Internal\UnitPromoter;
14+
15+
final readonly class ImperialLengthFormatter implements Formatter
16+
{
17+
use UnitPromoter;
18+
19+
private const array UNIT_RATIOS = [
20+
'mi' => [63_360, 1],
21+
'yd' => [36, 1],
22+
'ft' => [12, 1],
23+
'in' => [1, 1],
24+
];
25+
26+
public function __construct(private string $unit)
27+
{
28+
if (!isset(self::UNIT_RATIOS[$unit])) {
29+
throw new InvalidFormatterException('Unsupported imperial length unit');
30+
}
31+
}
32+
}

src/ImperialMassFormatter.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* SPDX-FileCopyrightText: (c) Respect Project Contributors
5+
* SPDX-License-Identifier: ISC
6+
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Respect\StringFormatter;
12+
13+
use Respect\StringFormatter\Internal\UnitPromoter;
14+
15+
final readonly class ImperialMassFormatter implements Formatter
16+
{
17+
use UnitPromoter;
18+
19+
private const array UNIT_RATIOS = [
20+
'ton' => [35_840, 1],
21+
'st' => [224, 1],
22+
'lb' => [16, 1],
23+
'oz' => [1, 1],
24+
];
25+
26+
public function __construct(private string $unit)
27+
{
28+
if (!isset(self::UNIT_RATIOS[$unit])) {
29+
throw new InvalidFormatterException('Unsupported imperial mass unit');
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)