diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -342,6 +342,7 @@ 'function' => array( 'Futures' => 'future/functions.php', + '_phabricator_date_format' => 'utils/viewutils.php', '_qsprintf_check_scalar_type' => 'xsprintf/qsprintf.php', '_qsprintf_check_type' => 'xsprintf/qsprintf.php', 'array_fuse' => 'utils/utils.php', @@ -374,6 +375,11 @@ 'msort' => 'utils/utils.php', 'newv' => 'utils/utils.php', 'nonempty' => 'utils/utils.php', + 'phabricator_format_bytes' => 'utils/viewutils.php', + 'phabricator_format_relative_time' => 'utils/viewutils.php', + 'phabricator_format_relative_time_detailed' => 'utils/viewutils.php', + 'phabricator_format_units_generic' => 'utils/viewutils.php', + 'phabricator_parse_bytes' => 'utils/viewutils.php', 'phlog' => 'error/phlog.php', 'pht' => 'internationalization/pht.php', 'phutil_censor_credentials' => 'utils/utils.php', diff --git a/src/utils/viewutils.php b/src/utils/viewutils.php new file mode 100644 --- /dev/null +++ b/src/utils/viewutils.php @@ -0,0 +1,171 @@ +<?php + +function _phabricator_date_format($epoch) { + $now = time(); + $shift = 30 * 24 * 60 * 60; + if ($epoch < $now + $shift && $epoch > $now - $shift) { + $format = pht('D, M j'); + } else { + $format = pht('M j Y'); + } + return $format; +} + +function phabricator_format_relative_time($duration) { + return phabricator_format_units_generic( + $duration, + array(60, 60, 24, 7), + array('s', 'm', 'h', 'd', 'w'), + $precision = 0); +} + +/** + * Format a relative time (duration) into weeks, days, hours, minutes, + * seconds, but unlike phabricator_format_relative_time, does so for more than + * just the largest unit. + * + * @param int Duration in seconds. + * @param int Levels to render - will render the three highest levels, ie: + * 5 h, 37 m, 1 s + * @return string Human-readable description. + */ +function phabricator_format_relative_time_detailed($duration, $levels = 2) { + if ($duration == 0) { + return 'now'; + } + $levels = max(1, min($levels, 5)); + $remainder = 0; + + $is_negative = false; + if ($duration < 0) { + $is_negative = true; + $duration = abs($duration); + } + + $this_level = 1; + $detailed_relative_time = phabricator_format_units_generic( + $duration, + array(60, 60, 24, 7), + array('s', 'm', 'h', 'd', 'w'), + $precision = 0, + $remainder); + $duration = $remainder; + + while ($remainder > 0 && $this_level < $levels) { + $detailed_relative_time .= ', '.phabricator_format_units_generic( + $duration, + array(60, 60, 24, 7), + array('s', 'm', 'h', 'd', 'w'), + $precision = 0, + $remainder); + $duration = $remainder; + $this_level++; + }; + + if ($is_negative) { + $detailed_relative_time .= ' ago'; + } + + return $detailed_relative_time; +} + +/** + * Format a byte count for human consumption, e.g. "10MB" instead of + * "10000000". + * + * @param int Number of bytes. + * @return string Human-readable description. + */ +function phabricator_format_bytes($bytes) { + return phabricator_format_units_generic( + $bytes, + // NOTE: Using the SI version of these units rather than the 1024 version. + array(1000, 1000, 1000, 1000, 1000), + array('B', 'KB', 'MB', 'GB', 'TB', 'PB'), + $precision = 0); +} + + +/** + * Parse a human-readable byte description (like "6MB") into an integer. + * + * @param string Human-readable description. + * @return int Number of represented bytes. + */ +function phabricator_parse_bytes($input) { + $bytes = trim($input); + if (!strlen($bytes)) { + return null; + } + + // NOTE: Assumes US-centric numeral notation. + $bytes = preg_replace('/[ ,]/', '', $bytes); + + $matches = null; + if (!preg_match('/^(?:\d+(?:[.]\d+)?)([kmgtp]?)b?$/i', $bytes, $matches)) { + throw new Exception("Unable to parse byte size '{$input}'!"); + } + + $scale = array( + 'k' => 1000, + 'm' => 1000 * 1000, + 'g' => 1000 * 1000 * 1000, + 't' => 1000 * 1000 * 1000 * 1000, + 'p' => 1000 * 1000 * 1000 * 1000 * 1000, + ); + + $bytes = (float)$bytes; + if ($matches[1]) { + $bytes *= $scale[strtolower($matches[1])]; + } + + return (int)$bytes; +} + + +function phabricator_format_units_generic( + $n, + array $scales, + array $labels, + $precision = 0, + &$remainder = null) { + + $is_negative = false; + if ($n < 0) { + $is_negative = true; + $n = abs($n); + } + + $remainder = 0; + $accum = 1; + + $scale = array_shift($scales); + $label = array_shift($labels); + while ($n >= $scale && count($labels)) { + $remainder += ($n % $scale) * $accum; + $n /= $scale; + $accum *= $scale; + $label = array_shift($labels); + if (!count($scales)) { + break; + } + $scale = array_shift($scales); + } + + if ($is_negative) { + $n = -$n; + $remainder = -$remainder; + } + + if ($precision) { + $num_string = number_format($n, $precision); + } else { + $num_string = (int)floor($n); + } + + if ($label) { + $num_string .= ' '.$label; + } + + return $num_string; +}