Fmt.php
PHP
Path: src/I18n/Fmt.php
<?php
namespace mini\I18n;
use IntlDateFormatter;
/**
* Stateless formatting utility that queries the current request locale
*
* All methods query the current locale via \Locale::getDefault() and delegate to PHP's intl classes.
* This class holds no state - it's purely a convenience wrapper that reads request state on each call.
*
* Date/time methods accept DateTimeInterface or SQL datetime strings (assumed UTC).
* All output is converted to the application timezone (date_default_timezone_get()).
*/
class Fmt
{
/**
* Normalize date input to DateTimeImmutable in local timezone.
* Strings are assumed to be UTC (e.g., from database).
*/
private static function ensureDateTime(\DateTimeInterface|string $datetime): \DateTimeImmutable
{
if (is_string($datetime)) {
$datetime = new \DateTimeImmutable($datetime, new \DateTimeZone('UTC'));
} elseif ($datetime instanceof \DateTime) {
$datetime = \DateTimeImmutable::createFromMutable($datetime);
}
return $datetime->setTimezone(new \DateTimeZone(date_default_timezone_get()));
}
/**
* Format a number with specified decimal places
*/
public static function number(float|int $number, int $decimals = 0): string
{
$formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::DECIMAL);
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $decimals);
return $formatter->format($number) ?: (string)$number;
}
/**
* Format currency with explicit currency code
*
* Currency must be explicitly provided - no defaults to prevent pricing errors
*/
public static function currency(float $amount, string $currencyCode): string
{
$formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::CURRENCY);
return $formatter->formatCurrency($amount, $currencyCode) ?: $amount . ' ' . $currencyCode;
}
/**
* Format percentage (0.75 -> "75%")
*/
public static function percent(float $ratio, int $decimals = 0): string
{
$formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::PERCENT);
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $decimals);
return $formatter->format($ratio) ?: (($ratio * 100) . '%');
}
/**
* Format file size in human-readable format
*/
public static function fileSize(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$factor = floor((strlen((string)$bytes) - 1) / 3);
$factor = min($factor, count($units) - 1);
$value = $bytes / pow(1024, $factor);
$decimals = $factor > 0 ? 1 : 0;
return self::number($value, $decimals) . ' ' . $units[$factor];
}
/**
* Format date in short format (e.g., "12/31/2023", "31.12.2023")
*/
public static function dateShort(\DateTimeInterface|string $date): string
{
$date = self::ensureDateTime($date);
$result = \IntlDateFormatter::formatObject($date, [\IntlDateFormatter::SHORT, \IntlDateFormatter::NONE], \Locale::getDefault());
return $result ?: $date->format('Y-m-d');
}
/**
* Format date in long format (e.g., "December 31, 2023", "31. desember 2023")
*/
public static function dateLong(\DateTimeInterface|string $date): string
{
$date = self::ensureDateTime($date);
$result = \IntlDateFormatter::formatObject($date, [\IntlDateFormatter::LONG, \IntlDateFormatter::NONE], \Locale::getDefault());
return $result ?: $date->format('F j, Y');
}
/**
* Format time in short format (e.g., "2:30 PM", "14:30")
*/
public static function timeShort(\DateTimeInterface|string $time): string
{
$time = self::ensureDateTime($time);
$result = \IntlDateFormatter::formatObject($time, [\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT], \Locale::getDefault());
return $result ?: $time->format('H:i');
}
/**
* Format datetime in short format
*/
public static function dateTimeShort(\DateTimeInterface|string $dateTime): string
{
$dateTime = self::ensureDateTime($dateTime);
$result = \IntlDateFormatter::formatObject($dateTime, [\IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT], \Locale::getDefault());
return $result ?: $dateTime->format('Y-m-d H:i');
}
/**
* Format datetime in long format
*/
public static function dateTimeLong(\DateTimeInterface|string $dateTime): string
{
$dateTime = self::ensureDateTime($dateTime);
$result = \IntlDateFormatter::formatObject($dateTime, [\IntlDateFormatter::LONG, \IntlDateFormatter::SHORT], \Locale::getDefault());
return $result ?: $dateTime->format('F j, Y H:i');
}
}