Files
umami/src/lib/format.ts
T
Rup Sarmah 0715aef477 fix(format): divide by 1e9 in formatLongNumber billions branch
The billions branch of formatLongNumber divided by 1000000 while
appending the 'b' suffix, so values >= 1e9 rendered about 1000x too
large (1000000000 became '1000.0b' instead of '1.0b'). Divide by
1000000000 to match the 'b' unit, consistent with the sibling
formatLongCurrency. Add regression tests for the billions branch.
2026-07-21 17:52:40 +05:30

141 lines
3.5 KiB
TypeScript

import { toUnicode } from 'punycode';
import { DEFAULT_CURRENCY } from './constants';
export function parseTime(val: number) {
const days = ~~(val / 86400);
const hours = ~~(val / 3600) - days * 24;
const minutes = ~~(val / 60) - days * 1440 - hours * 60;
const seconds = ~~val - days * 86400 - hours * 3600 - minutes * 60;
const ms = (val - ~~val) * 1000;
return {
days,
hours,
minutes,
seconds,
ms,
};
}
export function formatTime(val: number) {
const { hours, minutes, seconds } = parseTime(val);
const h = hours > 0 ? `${hours}:` : '';
const m = hours > 0 ? minutes.toString().padStart(2, '0') : minutes;
const s = seconds.toString().padStart(2, '0');
return `${h}${m}:${s}`;
}
export function formatShortTime(val: number, formats = ['m', 's'], space = '') {
const { days, hours, minutes, seconds, ms } = parseTime(val);
let t = '';
if (days > 0 && formats.indexOf('d') !== -1) t += `${days}d${space}`;
if (hours > 0 && formats.indexOf('h') !== -1) t += `${hours}h${space}`;
if (minutes > 0 && formats.indexOf('m') !== -1) t += `${minutes}m${space}`;
if (seconds > 0 && formats.indexOf('s') !== -1) t += `${seconds}s${space}`;
if (ms > 0 && formats.indexOf('ms') !== -1) t += `${ms}ms`;
if (!t) {
return `0${formats[formats.length - 1]}`;
}
return t;
}
export function formatNumber(n: string | number) {
return Number(n).toFixed(0);
}
export function formatLongNumber(value: number) {
const n = Number(value);
if (n >= 1000000000) {
return `${(n / 1000000000).toFixed(1)}b`;
}
if (n >= 1000000) {
return `${(n / 1000000).toFixed(1)}m`;
}
if (n >= 100000) {
return `${(n / 1000).toFixed(0)}k`;
}
if (n >= 10000) {
return `${(n / 1000).toFixed(1)}k`;
}
if (n >= 1000) {
return `${(n / 1000).toFixed(2)}k`;
}
return formatNumber(n);
}
export function stringToColor(str: string) {
if (!str) {
return '#ffffff';
}
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
color += `00${value.toString(16)}`.slice(-2);
}
return color;
}
export function formatCurrency(value: number, currency: string, locale = 'en-US') {
let formattedValue: Intl.NumberFormat;
try {
formattedValue = new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
});
} catch {
// Fallback to default currency format if an error occurs
formattedValue = new Intl.NumberFormat(locale, {
style: 'currency',
currency: DEFAULT_CURRENCY,
});
}
return formattedValue.format(value);
}
export function formatLongCurrency(value: number, currency: string, locale = 'en-US') {
const n = Number(value);
if (n >= 1000000000) {
return `${formatCurrency(n / 1000000000, currency, locale)}b`;
}
if (n >= 1000000) {
return `${formatCurrency(n / 1000000, currency, locale)}m`;
}
if (n >= 1000) {
return `${formatCurrency(n / 1000, currency, locale)}k`;
}
return formatCurrency(n, currency, locale);
}
export function decodePunycodeDomain(domain?: string | null) {
if (!domain) {
return domain;
}
try {
return toUnicode(domain);
} catch {
return domain;
}
}
export function truncateString<T extends string | null | undefined>(
value: T,
maxLength: number,
): T extends string ? string : T {
return (value ? value.substring(0, maxLength) : value) as T extends string ? string : T;
}