Reputation: 20125
I'd like Intl.NumberFormat()
to automatically convert between units from smaller to bigger ones based on common rules. I.e. a given number should be converted to between centimeters, meters, and kilometers in the output depending on how big the number is.
Code examples:
const bytes = 1000000;
const transferSpeed = new Intl.NumberFormat('en-US',
{style: 'unit', unit: 'byte-per-second', unitDisplay: 'narrow'}).format(bytes);
console.log(transferSpeed);
const days = 365;
const timespan = new Intl.NumberFormat('en-US',
{style: 'unit', unit: 'day', unitDisplay: 'long'}).format(days);
console.log(timespan);
The output of these two calls is:
1,000,000B/s
365 days
In that case I'd expect this, though:
1MB/s
1 year
And one might want to define the threshold for when to convert to the next bigger unit. So it could be that the conversion should happen once the exact value is reached but also earlier, let's say at 90% of the next bigger unit. Given the examples above, the output would then be this:
0.9MB/s
0.9 years
Are there configuration options for the API to do that?
Upvotes: 13
Views: 8484
Reputation: 61
Building on DmitryScaletta’s answer, I’ve implemented a method that dynamically adjusts both the unit and value based on the provided input. This approach is flexible and easily extendable to support additional units and, it supports concatenated units using '-per-' to handle compound units:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/supportedValuesOf#supported_unit_identifiers
const UNIT_SETS = [
{
units: [
"nanosecond",
"microsecond",
"millisecond",
"second",
"minute",
"hour",
"day",
"week",
"month",
"year",
],
factors: [1000, 1000, 1000, 60, 60, 24, 7, 4.345, 12], // Approximating months (4.345 weeks) and years (12 months)
},
{
units: ["bit", "kilobit", "megabit", "gigabit", "terabit"],
factors: [1000, 1000, 1000, 1000],
},
{
units: ["byte", "kilobyte", "megabyte", "gigabyte", "terabyte", "petabyte"],
factors: [1024, 1024, 1024, 1024, 1024],
},
// ...
];
function formatUnit(
value,
options, // Intl.NumberFormatOptions
locale = "en-US",
) {
const [numeratorUnit, denominatorUnit] = options.unit.split("-per-");
const unitSet = UNIT_SETS.find(({ units }) => units.includes(numeratorUnit));
if (unitSet) {
const { units, factors } = unitSet;
let index = units.indexOf(numeratorUnit);
let adjustedValue = value;
// Scale up if the value is too large
while (index < units.length - 1 && adjustedValue >= factors[index]) {
adjustedValue /= factors[index];
index++;
}
// Scale down if the value is too small
while (index > 0 && adjustedValue < 1) {
index--;
adjustedValue *= factors[index];
}
value = adjustedValue;
options.unit = denominatorUnit
? `${units[index]}-per-${denominatorUnit}`
: units[index];
}
const formatter = new Intl.NumberFormat(locale, {
style: "unit",
...options,
});
return formatter.format(value);
}
console.log(formatUnit(3600, { unit: "second" })); // "1 hour"
console.log(formatUnit(86400, { unit: "second" })); // "1 day"
console.log(formatUnit(0.000001, { unit: "second" })); // "1 microsecond"
console.log(formatUnit(5000000000, { unit: "bit" })); // "5 gigabits"
console.log(formatUnit(1048576, { unit: "byte" })); // "1 megabyte"
console.log(formatUnit(1, { unit: "gigabyte-per-second" })); // "1 gigabyte per second"
console.log(formatUnit(8192, { unit: "bit" })); // "8 kilobits"
console.log(formatUnit(0.5, { unit: "megabyte" })); // "512 kilobytes"
console.log(formatUnit(1000, { unit: "kilobit" })); // "1 megabit"
console.log(formatUnit(120, { unit: "minute" })); // "2 hours"
console.log(formatUnit(0.001, { unit: "kilobyte" })); // "1 byte"
console.log(formatUnit(7000000000, { unit: "bit" })); // "7 gigabits"
console.log(formatUnit(2, { unit: "terabyte" })); // "2 terabytes"
console.log(formatUnit(1000000000000, { unit: "bit" })); // "1 terabit"
Upvotes: 1
Reputation: 89
By default Intl.NumberFormat
displays gigabytes as 1BB (billion bytes) and petabytes as 1000TB.
Here is my workaround.
const UNITS = ["byte", "kilobyte", "megabyte", "gigabyte", "terabyte", 'petabyte'];
const getValueAndUnit = (n) => {
const i = n == 0 ? 0 : Math.floor(Math.log(n) / Math.log(1024));
const value = n / Math.pow(1024, i);
return { value, unit: UNITS[i] };
};
const bytePerSecondFormatter = (n) => {
const { unit, value } = getValueAndUnit(n);
return new Intl.NumberFormat("en", {
notation: "compact",
style: "unit",
unit: `${unit}-per-second`,
unitDisplay: "narrow",
}).format(value);
};
console.log(bytePerSecondFormatter(10));
console.log(bytePerSecondFormatter(200000));
console.log(bytePerSecondFormatter(50000000));
console.log(bytePerSecondFormatter(30000000000));
console.log(bytePerSecondFormatter(70000000000000));
console.log(bytePerSecondFormatter(9000000000000000));
Upvotes: 4
Reputation: 352
Not exactly a full answer to the question, but I thought I dropped this here in case it can help anybody.
Something close to this is actually possible with Intl.NumberFormat, but it has some limitations. If you wanted to format byte values, for instance, you could:
compact
notation.unit
as your style, and provide byte
as the unit.narrow
as unitDisplay
.With these options, the formatter will correctly convert from one unit to the other and, thanks to the unitDisplay
value, it will display the unit of measurement as you would expect.
This is of course only usable with the few supported units for which this makes sense, and it limits you to have the unit right next to value, even though that's usually what you'd want anyway. Browser support may also be an issue if you need to target older platforms.
Here's a sample.
const byteValueNumberFormatter = Intl.NumberFormat("en", {
notation: "compact",
style: "unit",
unit: "byte",
unitDisplay: "narrow",
});
console.log(byteValueNumberFormatter.format(10));
console.log(byteValueNumberFormatter.format(200000));
console.log(byteValueNumberFormatter.format(50000000));
Upvotes: 24
Reputation: 1945
Unfortunately, there is no such feature. You can see all possible options and methods in the MDN documentation. Also, the list of supported units for ECMAScript contains e.g. byte
, kilobyte
and megabyte
as separate units.
I propose you implement this yourself or find a module that suits your needs. In the meantime, I searched the ECMAScript proposals but didn't find anything like it, so I filed an idea in the discussion board. Maybe it will catch on: https://es.discourse.group/t/automatic-unit-conversion-for-intl-numberformat/763
Upvotes: 3