export function formatBytes(bytes?: number): string { if (!bytes) return '—'; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } export function parseCpuToMillicores(cpu: string): number { if (!cpu) return 0; if (cpu.endsWith('m')) return parseFloat(cpu); return parseFloat(cpu) * 1000; } export function parseMemoryToMi(mem: string): number { if (!mem) return 0; if (mem.endsWith('Gi')) return parseFloat(mem) * 1024; if (mem.endsWith('Mi')) return parseFloat(mem); if (mem.endsWith('Ki')) return parseFloat(mem) / 1024; return parseFloat(mem); } /** Human-readable time left (days, hours, minutes). */ export function formatRemainingDurationMs(remainingMs: number): string { const ms = Math.max(0, remainingMs); const days = Math.floor(ms / 86400000); const hours = Math.floor((ms % 86400000) / 3600000); const minutes = Math.floor((ms % 3600000) / 60000); if (days > 0) return `${days}d ${hours}h ${minutes}m`; if (hours > 0) return `${hours}h ${minutes}m`; if (minutes > 0) return `${minutes}m`; return 'less than 1m'; } /** Persian (با ارقام فارسی) human-readable time left, e.g. «۲۹ روز و ۱۹ ساعت و ۵۱ دقیقه». */ function formatRemainingDurationFa(remainingMs: number): string { const ms = Math.max(0, remainingMs); const days = Math.floor(ms / 86400000); const hours = Math.floor((ms % 86400000) / 3600000); const minutes = Math.floor((ms % 3600000) / 60000); const fa = (n: number) => n.toLocaleString('fa-IR', { useGrouping: false }); const parts: string[] = []; if (days > 0) parts.push(`${fa(days)} روز`); if (hours > 0) parts.push(`${fa(hours)} ساعت`); if (minutes > 0) parts.push(`${fa(minutes)} دقیقه`); if (parts.length === 0) return 'کمتر از ۱ دقیقه'; return parts.join(' و '); } /** Locale-aware time left from an expiry timestamp. */ export function formatRemainingForLocale( expiresAt: string | Date, locale: string, ): string { const date = typeof expiresAt === 'string' ? new Date(expiresAt) : expiresAt; if (Number.isNaN(date.getTime())) return '—'; const remainingMs = date.getTime() - Date.now(); return locale === 'fa' ? formatRemainingDurationFa(remainingMs) : formatRemainingDurationMs(remainingMs); } /** * Expiry timestamp in the user's locale and timezone. For Persian it renders the * Jalali date with weekday and time, e.g. «یکشنبه، ۲۹ تیر ۱۴۰۵، ساعت ۱۹:۵۸». */ export function formatExpiresAtLocal( expiresAt: string | Date, locale?: string, ): string { const date = typeof expiresAt === 'string' ? new Date(expiresAt) : expiresAt; if (Number.isNaN(date.getTime())) return '—'; if (locale === 'fa') { // Assemble parts explicitly so the order is «روز هفته، روز ماه سال» regardless // of the runtime's ICU pattern data. const parts = new Intl.DateTimeFormat('fa-IR-u-ca-persian', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', }).formatToParts(date); const get = (type: Intl.DateTimeFormatPartTypes) => parts.find((p) => p.type === type)?.value ?? ''; const weekday = get('weekday'); const dayNum = get('day'); const month = get('month'); const year = get('year'); const time = new Intl.DateTimeFormat('fa-IR-u-ca-persian', { hour: '2-digit', minute: '2-digit', hour12: false, }).format(date); return `${weekday}، ${dayNum} ${month} ${year}، ساعت ${time}`; } return date.toLocaleString(locale || undefined, { dateStyle: 'medium', timeStyle: 'short', }); }