use crate::duration::Duration;
const KILOBYTE: u64 = 1024;
pub trait ByteSize {
fn bytes(self) -> u64;
fn kilobytes(self) -> u64;
fn megabytes(self) -> u64;
fn gigabytes(self) -> u64;
fn terabytes(self) -> u64;
fn petabytes(self) -> u64;
fn exabytes(self) -> u64;
}
pub trait NumericDuration {
fn seconds(self) -> Duration;
fn minutes(self) -> Duration;
fn hours(self) -> Duration;
fn days(self) -> Duration;
fn weeks(self) -> Duration;
fn months(self) -> Duration;
fn years(self) -> Duration;
}
pub trait Ordinalize {
fn ordinal(&self) -> &'static str;
fn ordinalize(&self) -> String;
}
fn pow_1024(exponent: u32) -> u64 {
KILOBYTE.saturating_pow(exponent)
}
fn clamp_signed_to_u64<T>(value: T) -> u64
where
T: Into<i128>,
{
let value = value.into();
if value <= 0 {
0
} else if value >= u64::MAX as i128 {
u64::MAX
} else {
value as u64
}
}
fn clamp_float_to_u64(value: f64) -> u64 {
if !value.is_finite() || value <= 0.0 {
0
} else if value >= u64::MAX as f64 {
u64::MAX
} else {
value.round() as u64
}
}
fn ordinal_suffix(value: i128) -> &'static str {
let abs = value.abs();
let last_two = abs % 100;
if (11..=13).contains(&last_two) {
return "th";
}
match abs % 10 {
1 => "st",
2 => "nd",
3 => "rd",
_ => "th",
}
}
macro_rules! impl_integer_numeric_traits {
($($ty:ty),* $(,)?) => {
$(
impl ByteSize for $ty {
fn bytes(self) -> u64 {
clamp_signed_to_u64(self)
}
fn kilobytes(self) -> u64 {
clamp_signed_to_u64(self).saturating_mul(pow_1024(1))
}
fn megabytes(self) -> u64 {
clamp_signed_to_u64(self).saturating_mul(pow_1024(2))
}
fn gigabytes(self) -> u64 {
clamp_signed_to_u64(self).saturating_mul(pow_1024(3))
}
fn terabytes(self) -> u64 {
clamp_signed_to_u64(self).saturating_mul(pow_1024(4))
}
fn petabytes(self) -> u64 {
clamp_signed_to_u64(self).saturating_mul(pow_1024(5))
}
fn exabytes(self) -> u64 {
clamp_signed_to_u64(self).saturating_mul(pow_1024(6))
}
}
impl NumericDuration for $ty {
fn seconds(self) -> Duration {
Duration::seconds(self as i64)
}
fn minutes(self) -> Duration {
Duration::minutes(self as i64)
}
fn hours(self) -> Duration {
Duration::hours(self as i64)
}
fn days(self) -> Duration {
Duration::days(self as i64)
}
fn weeks(self) -> Duration {
Duration::weeks(self as i64)
}
fn months(self) -> Duration {
Duration::months(self as i64)
}
fn years(self) -> Duration {
Duration::years(self as i64)
}
}
impl Ordinalize for $ty {
fn ordinal(&self) -> &'static str {
ordinal_suffix(*self as i128)
}
fn ordinalize(&self) -> String {
format!("{}{}", self, self.ordinal())
}
}
)*
};
}
impl_integer_numeric_traits!(i32, i64, u32, u64);
impl ByteSize for f64 {
fn bytes(self) -> u64 {
clamp_float_to_u64(self)
}
fn kilobytes(self) -> u64 {
clamp_float_to_u64(self * pow_1024(1) as f64)
}
fn megabytes(self) -> u64 {
clamp_float_to_u64(self * pow_1024(2) as f64)
}
fn gigabytes(self) -> u64 {
clamp_float_to_u64(self * pow_1024(3) as f64)
}
fn terabytes(self) -> u64 {
clamp_float_to_u64(self * pow_1024(4) as f64)
}
fn petabytes(self) -> u64 {
clamp_float_to_u64(self * pow_1024(5) as f64)
}
fn exabytes(self) -> u64 {
clamp_float_to_u64(self * pow_1024(6) as f64)
}
}
impl NumericDuration for f64 {
fn seconds(self) -> Duration {
Duration::seconds(self.round() as i64)
}
fn minutes(self) -> Duration {
Duration::seconds((self * 60.0).round() as i64)
}
fn hours(self) -> Duration {
Duration::seconds((self * 3_600.0).round() as i64)
}
fn days(self) -> Duration {
Duration::seconds((self * 86_400.0).round() as i64)
}
fn weeks(self) -> Duration {
Duration::seconds((self * 604_800.0).round() as i64)
}
fn months(self) -> Duration {
Duration::seconds((self * 2_629_746.0).round() as i64)
}
fn years(self) -> Duration {
Duration::seconds((self * 31_556_952.0).round() as i64)
}
}
impl Ordinalize for f64 {
fn ordinal(&self) -> &'static str {
ordinal_suffix(self.trunc() as i128)
}
fn ordinalize(&self) -> String {
if self.fract() == 0.0 {
format!("{}{}", *self as i64, self.ordinal())
} else {
format!("{}{}", self, self.ordinal())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bytes_are_identity() {
assert_eq!(10_u64.bytes(), 10);
}
#[test]
fn kilobytes_scale_by_1024() {
assert_eq!(1024_u64.kilobytes(), 1_048_576);
}
#[test]
fn megabytes_scale_by_1024_squared() {
assert_eq!(5_u64.megabytes(), 5_242_880);
}
#[test]
fn gigabytes_scale_by_1024_cubed() {
assert_eq!(3_u64.gigabytes(), 3_221_225_472);
}
#[test]
fn terabytes_scale_by_1024_fourth() {
assert_eq!(1_u64.terabytes(), 1_099_511_627_776);
}
#[test]
fn petabytes_scale_by_1024_fifth() {
assert_eq!(1_u64.petabytes(), 1_125_899_906_842_624);
}
#[test]
fn exabytes_scale_by_1024_sixth() {
assert_eq!(1_u64.exabytes(), 1_152_921_504_606_846_976);
}
#[test]
fn floating_point_byte_sizes_are_rounded() {
assert_eq!(3.5_f64.megabytes(), 3_670_016);
assert_eq!(3.5_f64.gigabytes(), 3_758_096_384);
}
#[test]
fn negative_signed_byte_sizes_clamp_to_zero() {
assert_eq!((-1_i64).bytes(), 0);
assert_eq!((-1_i64).megabytes(), 0);
}
#[test]
fn duration_factories_match_duration_constructors() {
assert_eq!(2_i64.hours(), Duration::hours(2));
assert_eq!(5_u32.minutes(), Duration::minutes(5));
assert_eq!(1_i32.days(), Duration::days(1));
assert_eq!(3_u64.weeks(), Duration::weeks(3));
assert_eq!(4_i64.months(), Duration::months(4));
assert_eq!(2_i64.years(), Duration::years(2));
}
#[test]
fn floating_point_duration_factories_round_to_seconds() {
assert_eq!(1.5_f64.hours(), Duration::seconds(5_400));
assert_eq!(1.5_f64.days(), Duration::seconds(129_600));
}
#[test]
fn ordinal_suffixes_cover_common_cases() {
assert_eq!(1_i64.ordinal(), "st");
assert_eq!(2_i64.ordinal(), "nd");
assert_eq!(3_i64.ordinal(), "rd");
assert_eq!(4_i64.ordinal(), "th");
}
#[test]
fn ordinal_suffixes_handle_teens() {
assert_eq!(11_i64.ordinal(), "th");
assert_eq!(12_i64.ordinal(), "th");
assert_eq!(13_i64.ordinal(), "th");
}
#[test]
fn ordinal_suffixes_handle_large_numbers() {
assert_eq!(21_i64.ordinal(), "st");
assert_eq!(112_i64.ordinal(), "th");
assert_eq!(1_003_i64.ordinal(), "rd");
}
#[test]
fn ordinalize_formats_integers() {
assert_eq!(1_i64.ordinalize(), "1st");
assert_eq!(2_i64.ordinalize(), "2nd");
assert_eq!(112_i64.ordinalize(), "112th");
}
#[test]
fn ordinalize_formats_zero_and_negative_numbers() {
assert_eq!(0_i64.ordinalize(), "0th");
assert_eq!((-1_i64).ordinalize(), "-1st");
assert_eq!((-11_i64).ordinalize(), "-11th");
}
#[test]
fn ordinalize_formats_unsigned_values() {
assert_eq!(22_u64.ordinalize(), "22nd");
}
#[test]
fn floating_point_ordinalize_uses_truncated_value_for_suffix() {
assert_eq!(1.0_f64.ordinalize(), "1st");
assert_eq!(2.5_f64.ordinalize(), "2.5nd");
}
#[test]
fn byte_size_operations_compose_as_expected() {
assert_eq!(1_u64.kilobytes().pow(4), 1_u64.terabytes());
assert_eq!(1024_u64.kilobytes() + 2_u64.megabytes(), 3_u64.megabytes());
}
#[test]
fn byte_sizes_saturate_at_large_bounds() {
assert_eq!(0_i32.kilobytes(), 0);
assert_eq!(u64::MAX.kilobytes(), u64::MAX);
assert_eq!(i64::MAX.exabytes(), u64::MAX);
}
#[test]
fn floating_point_byte_sizes_clamp_non_finite_and_round_small_values() {
assert_eq!(f64::NAN.bytes(), 0);
assert_eq!(f64::INFINITY.bytes(), 0);
assert_eq!(f64::NEG_INFINITY.kilobytes(), 0);
assert_eq!(0.49_f64.bytes(), 0);
assert_eq!(0.5_f64.bytes(), 1);
assert_eq!(1e40_f64.bytes(), u64::MAX);
}
#[test]
fn integer_duration_helpers_support_zero_and_negative_values() {
assert_eq!(0_i32.seconds(), Duration::seconds(0));
assert_eq!((-2_i64).hours(), Duration::hours(-2));
assert_eq!((-3_i64).weeks(), Duration::weeks(-3));
assert_eq!((-1_i64).months(), Duration::months(-1));
assert_eq!((-4_i64).years(), Duration::years(-4));
}
#[test]
fn floating_point_duration_helpers_round_minutes_weeks_months_and_years() {
assert_eq!(1.5_f64.minutes(), Duration::seconds(90));
assert_eq!((-1.5_f64).seconds(), Duration::seconds(-2));
assert_eq!(1.25_f64.weeks(), Duration::seconds(756_000));
assert_eq!(1.5_f64.months(), Duration::seconds(3_944_619));
assert_eq!(1.25_f64.years(), Duration::seconds(39_446_190));
}
#[test]
fn ordinal_suffixes_handle_negative_and_large_teens() {
assert_eq!((-12_i64).ordinal(), "th");
assert_eq!((-23_i64).ordinal(), "rd");
assert_eq!(1_011_i64.ordinal(), "th");
assert_eq!(1_012_i64.ordinal(), "th");
assert_eq!(1_013_i64.ordinal(), "th");
}
#[test]
fn floating_point_ordinalize_handles_negative_whole_and_fractional_values() {
assert_eq!((-11.0_f64).ordinalize(), "-11th");
assert_eq!((-2.5_f64).ordinalize(), "-2.5nd");
}
#[test]
fn bytes_are_identity_for_signed_positive_integers() {
assert_eq!(2_i32.bytes(), 2);
assert_eq!(7_i64.bytes(), 7);
}
#[test]
fn bytes_are_identity_for_unsigned_integers() {
assert_eq!(3_u32.bytes(), 3);
assert_eq!(9_u64.bytes(), 9);
}
#[test]
fn kilobytes_scale_signed_values() {
assert_eq!(2_i64.kilobytes(), 2_048);
}
#[test]
fn megabytes_scale_unsigned_values() {
assert_eq!(2_u32.megabytes(), 2_097_152);
}
#[test]
fn gigabytes_scale_unsigned_values() {
assert_eq!(2_u64.gigabytes(), 2_147_483_648);
}
#[test]
fn terabytes_scale_signed_values() {
assert_eq!(2_i64.terabytes(), 2_199_023_255_552);
}
#[test]
fn petabytes_scale_unsigned_values() {
assert_eq!(2_u64.petabytes(), 2_251_799_813_685_248);
}
#[test]
fn exabytes_scale_unsigned_values() {
assert_eq!(2_u64.exabytes(), 2_305_843_009_213_693_952);
}
#[test]
fn negative_signed_sizes_clamp_all_units_to_zero() {
assert_eq!((-2_i32).kilobytes(), 0);
assert_eq!((-2_i32).gigabytes(), 0);
assert_eq!((-2_i32).terabytes(), 0);
}
#[test]
fn floating_point_kilobytes_round_to_nearest_byte() {
assert_eq!(0.1_f64.kilobytes(), 102);
assert_eq!(0.5_f64.kilobytes(), 512);
}
#[test]
fn floating_point_terabytes_and_petabytes_scale() {
assert_eq!(1.5_f64.terabytes(), 1_649_267_441_664);
assert_eq!(1.25_f64.petabytes(), 1_407_374_883_553_280);
}
#[test]
fn floating_point_size_helpers_clamp_negative_values() {
assert_eq!((-0.5_f64).bytes(), 0);
assert_eq!((-0.5_f64).petabytes(), 0);
}
#[test]
fn integer_duration_helpers_support_unsigned_values() {
assert_eq!(7_u64.seconds(), Duration::seconds(7));
assert_eq!(8_u32.minutes(), Duration::minutes(8));
assert_eq!(9_u64.days(), Duration::days(9));
}
#[test]
fn integer_duration_helpers_support_negative_days() {
assert_eq!((-2_i32).days(), Duration::days(-2));
}
#[test]
fn floating_point_duration_helpers_round_hours_and_days() {
assert_eq!(1.25_f64.hours(), Duration::seconds(4_500));
assert_eq!(0.5_f64.days(), Duration::seconds(43_200));
}
#[test]
fn floating_point_duration_helpers_handle_zero_and_negative_years() {
assert_eq!(0.0_f64.years(), Duration::seconds(0));
assert_eq!((-1.25_f64).years(), Duration::seconds(-39_446_190));
}
#[test]
fn ordinal_suffixes_handle_zero_and_hundreds() {
assert_eq!(0_i64.ordinal(), "th");
assert_eq!(100_i64.ordinal(), "th");
assert_eq!(101_i64.ordinal(), "st");
assert_eq!(102_i64.ordinal(), "nd");
assert_eq!(103_i64.ordinal(), "rd");
}
#[test]
fn ordinal_suffixes_handle_unsigned_teens_and_twenties() {
assert_eq!(11_u32.ordinal(), "th");
assert_eq!(12_u32.ordinal(), "th");
assert_eq!(13_u32.ordinal(), "th");
assert_eq!(21_u32.ordinal(), "st");
}
#[test]
fn ordinalize_formats_large_unsigned_and_positive_floats() {
assert_eq!(101_u64.ordinalize(), "101st");
assert_eq!(3.0_f64.ordinalize(), "3rd");
}
}