168 lines
5.2 KiB
PHP
168 lines
5.2 KiB
PHP
<?php
|
|
|
|
/*
|
|
* byte conversion from and to human readable
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace CoreLibs\Convert;
|
|
|
|
class Byte
|
|
{
|
|
// define byteFormat
|
|
public const BYTE_FORMAT_NOSPACE = 1;
|
|
public const BYTE_FORMAT_ADJUST = 2;
|
|
public const BYTE_FORMAT_SI = 4;
|
|
|
|
/**
|
|
* This function replaces the old byteStringFormat
|
|
*
|
|
* Converts any number string to human readable byte format
|
|
* Maxium is Exobytes and above that the Exobytes suffix is used for all
|
|
* If more are needed only the correct short name for the suffix has to be
|
|
* added to the labels array
|
|
* On no number string it returns string as is
|
|
* Source Idea: SOURCE: https://programming.guide/worlds-most-copied-so-snippet.html
|
|
*
|
|
* The class itself hast the following defined
|
|
* BYTE_FORMAT_NOSPACE [1] turn off spaces between number and suffix
|
|
* BYTE_FORMAT_ADJUST [2] use sprintf to always print two decimals
|
|
* BYTE_FORMAT_SI [4] use si standard 1000 instead of bytes 1024
|
|
* To use the constant from outside use class::CONSTANT
|
|
*
|
|
* @param string|int|float $bytes bytes as string int or pure int
|
|
* @param int $flags bitwise flag with use space turned on
|
|
* BYTE_FORMAT_NOSPACE: no space between number and suffix
|
|
* BYTE_FORMAT_ADJUST: sprintf adjusted two 2 decimals
|
|
* BYTE_FORMAT_SI: use 1000 instead of 1024
|
|
* @return string converted byte number (float) with suffix
|
|
* @throws \Exception 1: no valid flag set
|
|
*/
|
|
public static function humanReadableByteFormat(string|int|float $bytes, int $flags = 0): string
|
|
{
|
|
// if not numeric, return as is
|
|
if (is_numeric($bytes)) {
|
|
// flags bit wise check
|
|
// remove space between number and suffix
|
|
if ($flags & self::BYTE_FORMAT_NOSPACE) {
|
|
$space = false;
|
|
} else {
|
|
$space = true;
|
|
}
|
|
// use sprintf instead of round
|
|
if ($flags & self::BYTE_FORMAT_ADJUST) {
|
|
$adjust = true;
|
|
} else {
|
|
$adjust = false;
|
|
}
|
|
// use SI 1000 mod and not 1024 mod
|
|
if ($flags & self::BYTE_FORMAT_SI) {
|
|
$si = true;
|
|
} else {
|
|
$si = false;
|
|
}
|
|
if ($flags > 7) {
|
|
throw new \Exception("Invalid flags parameter: $flags", 1);
|
|
}
|
|
|
|
// si or normal
|
|
$unit = $si ? 1000 : 1024;
|
|
// always positive
|
|
$abs_bytes = $bytes == PHP_INT_MIN ? PHP_INT_MAX : abs((float)$bytes);
|
|
// smaller than unit is always B
|
|
if ($abs_bytes < $unit) {
|
|
return $bytes . 'B';
|
|
}
|
|
// labels in order of size [Y, Z]
|
|
$labels = ['', 'K', 'M', 'G', 'T', 'P', 'E'];
|
|
// exp position calculation
|
|
$exp = floor(log($abs_bytes, $unit));
|
|
// avoid printing out anything larger than max labels
|
|
if ($exp >= count($labels)) {
|
|
$exp = count($labels) - 1;
|
|
}
|
|
// deviation calculation
|
|
$dev = pow($unit, $exp) * ($unit - 0.05);
|
|
// shift the exp +1 for on the border units
|
|
if (
|
|
$exp < 6 &&
|
|
$abs_bytes > ($dev - (((int)$dev & 0xfff) == 0xd00 ? 52 : 0))
|
|
) {
|
|
$exp++;
|
|
}
|
|
// label name, including leading space if flagged
|
|
$pre = ($space ? ' ' : '') . ($labels[$exp] ?? '>E') . ($si ? 'i' : '') . 'B';
|
|
$bytes_calc = $abs_bytes / pow($unit, $exp);
|
|
// if original is negative, reverse
|
|
if ($bytes < 0) {
|
|
$bytes_calc *= -1;
|
|
}
|
|
if ($adjust) {
|
|
return sprintf("%.2f%s", $bytes_calc, $pre);
|
|
} else {
|
|
return round($bytes_calc, 2) . $pre;
|
|
}
|
|
} else {
|
|
// if anything other return as string
|
|
return (string)$bytes;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* calculates the bytes based on a string with nnG, nnGB, nnM, etc
|
|
* NOTE: large exabyte numbers will overflow
|
|
* flag allowed:
|
|
* BYTE_FORMAT_SI [4] use si standard 1000 instead of bytes 1024
|
|
*
|
|
* @param string|int|float $number any string or number to convert
|
|
* @param int $flags bitwise flag with use space turned on
|
|
* BYTE_FORMAT_SI: use 1000 instead of 1024
|
|
* @return string|int|float converted value or original value
|
|
* @throws \Exception 1: no valid flag set
|
|
*/
|
|
public static function stringByteFormat(string|int|float $number, int $flags = 0): string|int|float
|
|
{
|
|
// use SI 1000 mod and not 1024 mod
|
|
if ($flags & self::BYTE_FORMAT_SI) {
|
|
$si = true;
|
|
} else {
|
|
$si = false;
|
|
}
|
|
if ($flags != 0 && $flags != 4) {
|
|
throw new \Exception("Invalid flags parameter: $flags", 1);
|
|
}
|
|
// matches in regex
|
|
$matches = [];
|
|
// all valid units
|
|
$valid_units_ = 'bkmgtpezy';
|
|
// detects up to exo bytes
|
|
preg_match(
|
|
"/(-)?([\d.,]*)\s?(eib|pib|tib|gib|mib|kib|eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/i",
|
|
strtolower((string)$number),
|
|
$matches
|
|
);
|
|
if (isset($matches[2]) && isset($matches[3])) {
|
|
// remove all non valid characters from the number
|
|
$number = preg_replace('/[^0-9\.]/', '', $matches[2]);
|
|
// final clean up and convert to float
|
|
$number = (float)trim((string)$number);
|
|
// convert any mb/gb/etc to single m/b
|
|
$unit = preg_replace('/[^bkmgtpezy]/i', '', $matches[3]);
|
|
if ($unit) {
|
|
$number = $number * pow($si ? 1000 : 1024, stripos($valid_units_, $unit[0]) ?: 0);
|
|
}
|
|
// convert to INT to avoid +E output
|
|
$number = (int)round($number);
|
|
// if negative input, keep nnegative
|
|
if (!empty($matches[1])) {
|
|
$number *= -1;
|
|
}
|
|
}
|
|
// if not matching return as is
|
|
return $number;
|
|
}
|
|
}
|
|
|
|
// __END__
|