1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
use crate::request_rate::duration_unit::DurationUnit;
use std::time::Duration;
/// Converts a numerator and denominator into an English expression of a rate.
///
/// ## Arguments:
///
/// * `numerator` ‧ The numerator of the rate. i.e. _Files_ per second,
/// _requests_ per minute, etc.
///
/// * `duration` ‧ The denominator of the rate. i.e. Bytes per _second_,
/// pages per _hour_, etc.
///
/// * `numerator_singular` ‧ The singular form of the numerator's name as a
/// string to be displayed to the end-user. For example, `"request"` or
/// `"file"`.
///
/// * `numerator_plural` ‧ The plural form of the numerator's name as a string
/// to be displayed to the end-user. For example, `"bytes"` or `"queries"`.
///
/// ## Description:
///
/// This function converts a numerator and Duration into an English expression
/// of a rate. For example, "1 file per month," "5.83 pages a minute," etc. The
/// unit of time (i.e. milliseconds or seconds) is automatically selected by
/// this function.
pub fn rate_to_string(
numerator: &u64,
duration: &Duration,
numerator_singular: &str,
numerator_plural: &str,
) -> String {
// Multiplication is much faster than division:
const MILLISECONDS_IN_A_SECOND: f64 = 1.0 / 0.001;
const SECONDS_IN_A_SECOND: f64 = 1.0;
const MINUTES_IN_A_SECOND: f64 = 1.0 / 60.0;
const HOURS_IN_A_SECOND: f64 = 1.0 / 3_600.0;
const DAYS_IN_A_SECOND: f64 = 1.0 / 86_400.0;
const WEEKS_IN_A_SECOND: f64 = 1.0 / 604_800.0;
const MONTHS_IN_A_SECOND: f64 = 1.0 / 2_629_746.0;
const YEARS_IN_A_SECOND: f64 = 1.0 / 31_556_952.0;
// This match takes the duration passed by the caller and adjusts the
// time/duration unit for better readability when presenting to an end user.
// This match structure finds the smallest time unit that results in a value
// over 1.0. It returns a tuple with - the adjusted duration quantity in
// index 0, and the duration unit of time in index 1:
let duration_in_secs = duration.as_secs_f64();
let numerator = *numerator as f64;
let adjusted_units = match duration_in_secs {
s if numerator / (s * MILLISECONDS_IN_A_SECOND) > 1.0 => (
numerator / (s * MILLISECONDS_IN_A_SECOND),
DurationUnit::Milliseconds,
),
s if numerator / (s * SECONDS_IN_A_SECOND) > 1.0 => {
(numerator / (s * SECONDS_IN_A_SECOND), DurationUnit::Seconds)
}
s if numerator / (s * MINUTES_IN_A_SECOND) > 1.0 => {
(numerator / (s * MINUTES_IN_A_SECOND), DurationUnit::Minutes)
}
s if numerator / (s * HOURS_IN_A_SECOND) > 1.0 => {
(numerator / (s * HOURS_IN_A_SECOND), DurationUnit::Hours)
}
s if numerator / (s * DAYS_IN_A_SECOND) > 1.0 => {
(numerator / (s * DAYS_IN_A_SECOND), DurationUnit::Days)
}
s if numerator / (s * WEEKS_IN_A_SECOND) > 1.0 => {
(numerator / (s * WEEKS_IN_A_SECOND), DurationUnit::Weeks)
}
s if numerator / (s * MONTHS_IN_A_SECOND) > 1.0 => {
(numerator / (s * MONTHS_IN_A_SECOND), DurationUnit::Months)
}
_ => (duration_in_secs * YEARS_IN_A_SECOND, DurationUnit::Years),
}; // match
// The fractional portion of a large value (i.e. 40075.14159) is less
// significant compared to the same fractional portion of a tiny value
// (i.e. 3.14159). This match suppresses the fractional portion for large
// values and shows more of the fractional portion for small values:
let mut quantity_string = match adjusted_units.0 {
q if q < 0.001 => format!("{q:.6}"),
q if q < 0.01 => format!("{q:.5}"),
q if q < 0.1 => format!("{q:.4}"),
q if q < 1.0 => format!("{q:.3}"),
q if q < 10.0 => format!("{q:.2}"),
q if q < 100.0 => format!("{q:.1}"),
_ => format!("{:.0}", adjusted_units.0),
}; // match
// If the value has a fractional part, remove any insignificant digits:
if quantity_string.contains('.') {
quantity_string = quantity_string.trim_end_matches('0').to_string();
quantity_string = quantity_string.trim_end_matches('.').to_string();
}
// The rate type. For example it could be "_bytes_ per second," "_file_ per
// minute." It will return singular if the quantity is exactly one. If the
// quantity is not 1, or a fractional 1, it returns plural.
let rate_type_string = if quantity_string == "1" {
numerator_singular
} else {
numerator_plural
}; // if
// Returns the unit of time enum into a string that can be presented to the
// user. It also returns the time unit's noun in singular if the value is
// "1", and in plural if it is not.
let units_string = match adjusted_units.1 {
DurationUnit::Days => String::from("day"),
DurationUnit::Hours => String::from("hour"),
DurationUnit::Milliseconds => String::from("millisecond"),
DurationUnit::Minutes => String::from("minute"),
DurationUnit::Months => String::from("month"),
DurationUnit::Seconds => String::from("second"),
DurationUnit::Weeks => String::from("week"),
DurationUnit::Years => String::from("year"),
// This can never be reached but it keeps the Rust compiler happy:
_ => adjusted_units.1.to_string(),
}; // match
// Formats the final string and returns it to the caller:
quantity_string + " " + rate_type_string + " per " + &units_string
} // fn