mirror of
https://github.com/nushell/nushell.git
synced 2024-11-26 10:23:52 +01:00
expand durations to include month, year, decade (#6123)
* expand durations to include month, year, decade * remove commented out fn * oops, found more debug comments * tweaked tests for the new way, borrowed heavily from chrono-humanize-rs * clippy * grammar
This commit is contained in:
parent
f5856b0914
commit
d856ac92f4
@ -28,6 +28,10 @@ impl Command for SubCommand {
|
|||||||
"Convert value to duration"
|
"Convert value to duration"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
"into duration does not take leap years into account and every month is calculated with 30 days"
|
||||||
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "time", "period"]
|
vec!["convert", "time", "period"]
|
||||||
}
|
}
|
||||||
@ -149,6 +153,9 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
|
|||||||
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
|
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
|
||||||
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||||
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||||
|
Unit::Month => return Ok(x * 30 * 24 * 60 * 60 * 1000 * 1000 * 1000), //30 days to a month
|
||||||
|
Unit::Year => return Ok(x * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
||||||
|
Unit::Decade => return Ok(x * 10 * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -442,6 +442,14 @@ fn convert_to_value(
|
|||||||
val: size * 1000 * 1000 * 1000 * 1000 * 1000,
|
val: size * 1000 * 1000 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
|
Unit::Exabyte => Ok(Value::Filesize {
|
||||||
|
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
Unit::Zettabyte => Ok(Value::Filesize {
|
||||||
|
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
|
||||||
Unit::Kibibyte => Ok(Value::Filesize {
|
Unit::Kibibyte => Ok(Value::Filesize {
|
||||||
val: size * 1024,
|
val: size * 1024,
|
||||||
@ -463,6 +471,14 @@ fn convert_to_value(
|
|||||||
val: size * 1024 * 1024 * 1024 * 1024 * 1024,
|
val: size * 1024 * 1024 * 1024 * 1024 * 1024,
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
|
Unit::Exbibyte => Ok(Value::Filesize {
|
||||||
|
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
Unit::Zebibyte => Ok(Value::Filesize {
|
||||||
|
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
|
||||||
Unit::Nanosecond => Ok(Value::Duration { val: size, span }),
|
Unit::Nanosecond => Ok(Value::Duration { val: size, span }),
|
||||||
Unit::Microsecond => Ok(Value::Duration {
|
Unit::Microsecond => Ok(Value::Duration {
|
||||||
@ -489,8 +505,8 @@ fn convert_to_value(
|
|||||||
Some(val) => Ok(Value::Duration { val, span }),
|
Some(val) => Ok(Value::Duration { val, span }),
|
||||||
None => Err(ShellError::OutsideSpannedLabeledError(
|
None => Err(ShellError::OutsideSpannedLabeledError(
|
||||||
original_text.to_string(),
|
original_text.to_string(),
|
||||||
"duration too large".into(),
|
"day duration too large".into(),
|
||||||
"duration too large".into(),
|
"day duration too large".into(),
|
||||||
expr.span,
|
expr.span,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
@ -499,11 +515,40 @@ fn convert_to_value(
|
|||||||
Some(val) => Ok(Value::Duration { val, span }),
|
Some(val) => Ok(Value::Duration { val, span }),
|
||||||
None => Err(ShellError::OutsideSpannedLabeledError(
|
None => Err(ShellError::OutsideSpannedLabeledError(
|
||||||
original_text.to_string(),
|
original_text.to_string(),
|
||||||
"duration too large".into(),
|
"week duration too large".into(),
|
||||||
"duration too large".into(),
|
"week duration too large".into(),
|
||||||
expr.span,
|
expr.span,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
Unit::Month => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 30) {
|
||||||
|
Some(val) => Ok(Value::Duration { val, span }),
|
||||||
|
None => Err(ShellError::OutsideSpannedLabeledError(
|
||||||
|
original_text.to_string(),
|
||||||
|
"month duration too large".into(),
|
||||||
|
"month duration too large".into(),
|
||||||
|
expr.span,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Unit::Year => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365) {
|
||||||
|
Some(val) => Ok(Value::Duration { val, span }),
|
||||||
|
None => Err(ShellError::OutsideSpannedLabeledError(
|
||||||
|
original_text.to_string(),
|
||||||
|
"year duration too large".into(),
|
||||||
|
"year duration too large".into(),
|
||||||
|
expr.span,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Unit::Decade => {
|
||||||
|
match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365 * 10) {
|
||||||
|
Some(val) => Ok(Value::Duration { val, span }),
|
||||||
|
None => Err(ShellError::OutsideSpannedLabeledError(
|
||||||
|
original_text.to_string(),
|
||||||
|
"decade duration too large".into(),
|
||||||
|
"decade duration too large".into(),
|
||||||
|
expr.span,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Var(..) => Err(ShellError::OutsideSpannedLabeledError(
|
Expr::Var(..) => Err(ShellError::OutsideSpannedLabeledError(
|
||||||
|
@ -291,7 +291,7 @@ fn duration_math() {
|
|||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_eq!(actual.out, "8day");
|
assert_eq!(actual.out, "1wk 1day");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -315,7 +315,7 @@ fn duration_math_with_nanoseconds() {
|
|||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_eq!(actual.out, "7day 10ns");
|
assert_eq!(actual.out, "1wk 10ns");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -327,7 +327,22 @@ fn duration_decimal_math_with_nanoseconds() {
|
|||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_eq!(actual.out, "10day 10ns");
|
assert_eq!(actual.out, "1wk 3day 10ns");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn duration_decimal_math_with_all_units() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
5dec + 3yr + 2month + 1wk + 3day + 8hr + 10min + 16sec + 121ms + 11us + 12ns
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
"53yr 2month 1wk 3day 8hr 10min 16sec 121ms 11µs 12ns"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1452,6 +1452,14 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
|||||||
val: size * 1000 * 1000 * 1000 * 1000 * 1000,
|
val: size * 1000 * 1000 * 1000 * 1000 * 1000,
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
Unit::Exabyte => Value::Filesize {
|
||||||
|
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Unit::Zettabyte => Value::Filesize {
|
||||||
|
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
|
||||||
Unit::Kibibyte => Value::Filesize {
|
Unit::Kibibyte => Value::Filesize {
|
||||||
val: size * 1024,
|
val: size * 1024,
|
||||||
@ -1473,6 +1481,14 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
|||||||
val: size * 1024 * 1024 * 1024 * 1024 * 1024,
|
val: size * 1024 * 1024 * 1024 * 1024 * 1024,
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
|
Unit::Exbibyte => Value::Filesize {
|
||||||
|
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Unit::Zebibyte => Value::Filesize {
|
||||||
|
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
|
||||||
Unit::Nanosecond => Value::Duration { val: size, span },
|
Unit::Nanosecond => Value::Duration { val: size, span },
|
||||||
Unit::Microsecond => Value::Duration {
|
Unit::Microsecond => Value::Duration {
|
||||||
@ -1499,8 +1515,8 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
|||||||
Some(val) => Value::Duration { val, span },
|
Some(val) => Value::Duration { val, span },
|
||||||
None => Value::Error {
|
None => Value::Error {
|
||||||
error: ShellError::GenericError(
|
error: ShellError::GenericError(
|
||||||
"duration too large".into(),
|
"day duration too large".into(),
|
||||||
"duration too large".into(),
|
"day duration too large".into(),
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@ -1511,8 +1527,44 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
|||||||
Some(val) => Value::Duration { val, span },
|
Some(val) => Value::Duration { val, span },
|
||||||
None => Value::Error {
|
None => Value::Error {
|
||||||
error: ShellError::GenericError(
|
error: ShellError::GenericError(
|
||||||
"duration too large".into(),
|
"week duration too large".into(),
|
||||||
"duration too large".into(),
|
"week duration too large".into(),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Unit::Month => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 30) {
|
||||||
|
Some(val) => Value::Duration { val, span },
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"month duration too large".into(),
|
||||||
|
"month duration too large".into(),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Unit::Year => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365) {
|
||||||
|
Some(val) => Value::Duration { val, span },
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"year duration too large".into(),
|
||||||
|
"year duration too large".into(),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Unit::Decade => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365 * 10) {
|
||||||
|
Some(val) => Value::Duration { val, span },
|
||||||
|
None => Value::Error {
|
||||||
|
error: ShellError::GenericError(
|
||||||
|
"decade duration too large".into(),
|
||||||
|
"decade duration too large".into(),
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
|
@ -2170,6 +2170,9 @@ pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> {
|
|||||||
(Unit::Hour, "HR", Some((Unit::Minute, 60))),
|
(Unit::Hour, "HR", Some((Unit::Minute, 60))),
|
||||||
(Unit::Day, "DAY", Some((Unit::Minute, 1440))),
|
(Unit::Day, "DAY", Some((Unit::Minute, 1440))),
|
||||||
(Unit::Week, "WK", Some((Unit::Day, 7))),
|
(Unit::Week, "WK", Some((Unit::Day, 7))),
|
||||||
|
(Unit::Month, "MONTH", Some((Unit::Day, 30))), //30 day month
|
||||||
|
(Unit::Year, "YR", Some((Unit::Day, 365))), //365 day year
|
||||||
|
(Unit::Decade, "DEC", Some((Unit::Year, 10))), //365 day years
|
||||||
];
|
];
|
||||||
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
|
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
|
||||||
let mut lhs = token;
|
let mut lhs = token;
|
||||||
@ -2264,11 +2267,15 @@ pub fn parse_filesize(
|
|||||||
(Unit::Gigabyte, "GB", Some((Unit::Megabyte, 1000))),
|
(Unit::Gigabyte, "GB", Some((Unit::Megabyte, 1000))),
|
||||||
(Unit::Terabyte, "TB", Some((Unit::Gigabyte, 1000))),
|
(Unit::Terabyte, "TB", Some((Unit::Gigabyte, 1000))),
|
||||||
(Unit::Petabyte, "PB", Some((Unit::Terabyte, 1000))),
|
(Unit::Petabyte, "PB", Some((Unit::Terabyte, 1000))),
|
||||||
|
(Unit::Exabyte, "EB", Some((Unit::Petabyte, 1000))),
|
||||||
|
(Unit::Zettabyte, "ZB", Some((Unit::Exabyte, 1000))),
|
||||||
(Unit::Kibibyte, "KIB", Some((Unit::Byte, 1024))),
|
(Unit::Kibibyte, "KIB", Some((Unit::Byte, 1024))),
|
||||||
(Unit::Mebibyte, "MIB", Some((Unit::Kibibyte, 1024))),
|
(Unit::Mebibyte, "MIB", Some((Unit::Kibibyte, 1024))),
|
||||||
(Unit::Gibibyte, "GIB", Some((Unit::Mebibyte, 1024))),
|
(Unit::Gibibyte, "GIB", Some((Unit::Mebibyte, 1024))),
|
||||||
(Unit::Tebibyte, "TIB", Some((Unit::Gibibyte, 1024))),
|
(Unit::Tebibyte, "TIB", Some((Unit::Gibibyte, 1024))),
|
||||||
(Unit::Pebibyte, "PIB", Some((Unit::Tebibyte, 1024))),
|
(Unit::Pebibyte, "PIB", Some((Unit::Tebibyte, 1024))),
|
||||||
|
(Unit::Exbibyte, "EIB", Some((Unit::Pebibyte, 1024))),
|
||||||
|
(Unit::Zebibyte, "ZIB", Some((Unit::Exbibyte, 1024))),
|
||||||
(Unit::Byte, "B", None),
|
(Unit::Byte, "B", None),
|
||||||
];
|
];
|
||||||
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
|
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
|
||||||
|
@ -5,32 +5,32 @@ mod range;
|
|||||||
mod stream;
|
mod stream;
|
||||||
mod unit;
|
mod unit;
|
||||||
|
|
||||||
|
use crate::ast::Operator;
|
||||||
|
use crate::ast::{CellPath, PathMember};
|
||||||
|
use crate::ShellError;
|
||||||
|
use crate::{did_you_mean, BlockId, Config, Span, Spanned, Type, VarId};
|
||||||
use byte_unit::ByteUnit;
|
use byte_unit::ByteUnit;
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, Duration, FixedOffset};
|
||||||
use chrono_humanize::HumanTime;
|
use chrono_humanize::HumanTime;
|
||||||
|
pub use custom_value::CustomValue;
|
||||||
pub use from_value::FromValue;
|
pub use from_value::FromValue;
|
||||||
use indexmap::map::IndexMap;
|
use indexmap::map::IndexMap;
|
||||||
use num_format::{Locale, ToFormattedString};
|
use num_format::{Locale, ToFormattedString};
|
||||||
pub use range::*;
|
pub use range::*;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
collections::HashMap,
|
||||||
|
fmt::{Display, Formatter, Result as FmtResult},
|
||||||
|
iter,
|
||||||
|
path::PathBuf,
|
||||||
|
{cmp::Ordering, fmt::Debug},
|
||||||
|
};
|
||||||
pub use stream::*;
|
pub use stream::*;
|
||||||
use sys_locale::get_locale;
|
use sys_locale::get_locale;
|
||||||
pub use unit::*;
|
pub use unit::*;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::{cmp::Ordering, fmt::Debug};
|
|
||||||
|
|
||||||
use crate::ast::{CellPath, PathMember};
|
|
||||||
use crate::{did_you_mean, BlockId, Config, Span, Spanned, Type, VarId};
|
|
||||||
|
|
||||||
use crate::ast::Operator;
|
|
||||||
pub use custom_value::CustomValue;
|
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
use crate::ShellError;
|
|
||||||
|
|
||||||
/// Core structured values that pass through the pipeline in Nushell.
|
/// Core structured values that pass through the pipeline in Nushell.
|
||||||
// NOTE: Please do not reorder these enum cases without thinking through the
|
// NOTE: Please do not reorder these enum cases without thinking through the
|
||||||
// impact on the PartialOrd implementation and the global sort order
|
// impact on the PartialOrd implementation and the global sort order
|
||||||
@ -2525,54 +2525,210 @@ impl From<Spanned<IndexMap<String, Value>>> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a duration in nanoseconds into a string
|
/// Is the given year a leap year?
|
||||||
|
#[allow(clippy::nonminimal_bool)]
|
||||||
|
pub fn is_leap_year(year: i32) -> bool {
|
||||||
|
(year % 4 == 0) && (year % 100 != 0 || (year % 100 == 0 && year % 400 == 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum TimePeriod {
|
||||||
|
Nanos(i64),
|
||||||
|
Micros(i64),
|
||||||
|
Millis(i64),
|
||||||
|
Seconds(i64),
|
||||||
|
Minutes(i64),
|
||||||
|
Hours(i64),
|
||||||
|
Days(i64),
|
||||||
|
Weeks(i64),
|
||||||
|
Months(i64),
|
||||||
|
Years(i64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimePeriod {
|
||||||
|
fn to_text(self) -> Cow<'static, str> {
|
||||||
|
match self {
|
||||||
|
Self::Nanos(n) => format!("{}ns", n).into(),
|
||||||
|
Self::Micros(n) => format!("{}µs", n).into(),
|
||||||
|
Self::Millis(n) => format!("{}ms", n).into(),
|
||||||
|
Self::Seconds(n) => format!("{}sec", n).into(),
|
||||||
|
Self::Minutes(n) => format!("{}min", n).into(),
|
||||||
|
Self::Hours(n) => format!("{}hr", n).into(),
|
||||||
|
Self::Days(n) => format!("{}day", n).into(),
|
||||||
|
Self::Weeks(n) => format!("{}wk", n).into(),
|
||||||
|
Self::Months(n) => format!("{}month", n).into(),
|
||||||
|
Self::Years(n) => format!("{}yr", n).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TimePeriod {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
write!(f, "{}", self.to_text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format_duration(duration: i64) -> String {
|
pub fn format_duration(duration: i64) -> String {
|
||||||
|
// Attribution: most of this is taken from chrono-humanize-rs. Thanks!
|
||||||
|
// https://gitlab.com/imp/chrono-humanize-rs/-/blob/master/src/humantime.rs
|
||||||
|
const DAYS_IN_YEAR: i64 = 365;
|
||||||
|
const DAYS_IN_MONTH: i64 = 30;
|
||||||
|
|
||||||
let (sign, duration) = if duration >= 0 {
|
let (sign, duration) = if duration >= 0 {
|
||||||
(1, duration)
|
(1, duration)
|
||||||
} else {
|
} else {
|
||||||
(-1, -duration)
|
(-1, -duration)
|
||||||
};
|
};
|
||||||
let (micros, nanos): (i64, i64) = (duration / 1000, duration % 1000);
|
|
||||||
let (millis, micros): (i64, i64) = (micros / 1000, micros % 1000);
|
|
||||||
let (secs, millis): (i64, i64) = (millis / 1000, millis % 1000);
|
|
||||||
let (mins, secs): (i64, i64) = (secs / 60, secs % 60);
|
|
||||||
let (hours, mins): (i64, i64) = (mins / 60, mins % 60);
|
|
||||||
let (days, hours): (i64, i64) = (hours / 24, hours % 24);
|
|
||||||
|
|
||||||
let mut output_prep = vec![];
|
let dur = Duration::nanoseconds(duration);
|
||||||
|
|
||||||
if days != 0 {
|
/// Split this a duration into number of whole years and the remainder
|
||||||
output_prep.push(format!("{}day", days));
|
fn split_years(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let years = duration.num_days() / DAYS_IN_YEAR;
|
||||||
|
let remainder = duration - Duration::days(years * DAYS_IN_YEAR);
|
||||||
|
normalize_split(years, remainder)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hours != 0 {
|
/// Split this a duration into number of whole months and the remainder
|
||||||
output_prep.push(format!("{}hr", hours));
|
fn split_months(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let months = duration.num_days() / DAYS_IN_MONTH;
|
||||||
|
let remainder = duration - Duration::days(months * DAYS_IN_MONTH);
|
||||||
|
normalize_split(months, remainder)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mins != 0 {
|
/// Split this a duration into number of whole weeks and the remainder
|
||||||
output_prep.push(format!("{}min", mins));
|
fn split_weeks(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
}
|
let weeks = duration.num_weeks();
|
||||||
// output 0sec for zero duration
|
let remainder = duration - Duration::weeks(weeks);
|
||||||
if duration == 0 || secs != 0 {
|
normalize_split(weeks, remainder)
|
||||||
output_prep.push(format!("{}sec", secs));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if millis != 0 {
|
/// Split this a duration into number of whole days and the remainder
|
||||||
output_prep.push(format!("{}ms", millis));
|
fn split_days(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let days = duration.num_days();
|
||||||
|
let remainder = duration - Duration::days(days);
|
||||||
|
normalize_split(days, remainder)
|
||||||
}
|
}
|
||||||
|
|
||||||
if micros != 0 {
|
/// Split this a duration into number of whole hours and the remainder
|
||||||
output_prep.push(format!("{}us", micros));
|
fn split_hours(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let hours = duration.num_hours();
|
||||||
|
let remainder = duration - Duration::hours(hours);
|
||||||
|
normalize_split(hours, remainder)
|
||||||
}
|
}
|
||||||
|
|
||||||
if nanos != 0 {
|
/// Split this a duration into number of whole minutes and the remainder
|
||||||
output_prep.push(format!("{}ns", nanos));
|
fn split_minutes(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let minutes = duration.num_minutes();
|
||||||
|
let remainder = duration - Duration::minutes(minutes);
|
||||||
|
normalize_split(minutes, remainder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Split this a duration into number of whole seconds and the remainder
|
||||||
|
fn split_seconds(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let seconds = duration.num_seconds();
|
||||||
|
let remainder = duration - Duration::seconds(seconds);
|
||||||
|
normalize_split(seconds, remainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split this a duration into number of whole milliseconds and the remainder
|
||||||
|
fn split_milliseconds(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let millis = duration.num_milliseconds();
|
||||||
|
let remainder = duration - Duration::milliseconds(millis);
|
||||||
|
normalize_split(millis, remainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split this a duration into number of whole seconds and the remainder
|
||||||
|
fn split_microseconds(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let micros = duration.num_microseconds().unwrap_or_default();
|
||||||
|
let remainder = duration - Duration::microseconds(micros);
|
||||||
|
normalize_split(micros, remainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split this a duration into number of whole seconds and the remainder
|
||||||
|
fn split_nanoseconds(duration: Duration) -> (Option<i64>, Duration) {
|
||||||
|
let nanos = duration.num_nanoseconds().unwrap_or_default();
|
||||||
|
let remainder = duration - Duration::nanoseconds(nanos);
|
||||||
|
normalize_split(nanos, remainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_split(
|
||||||
|
wholes: impl Into<Option<i64>>,
|
||||||
|
remainder: Duration,
|
||||||
|
) -> (Option<i64>, Duration) {
|
||||||
|
let wholes = wholes.into().map(i64::abs).filter(|x| *x > 0);
|
||||||
|
(wholes, remainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut periods = vec![];
|
||||||
|
let (years, remainder) = split_years(dur);
|
||||||
|
if let Some(years) = years {
|
||||||
|
periods.push(TimePeriod::Years(years));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (months, remainder) = split_months(remainder);
|
||||||
|
if let Some(months) = months {
|
||||||
|
periods.push(TimePeriod::Months(months));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (weeks, remainder) = split_weeks(remainder);
|
||||||
|
if let Some(weeks) = weeks {
|
||||||
|
periods.push(TimePeriod::Weeks(weeks));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (days, remainder) = split_days(remainder);
|
||||||
|
if let Some(days) = days {
|
||||||
|
periods.push(TimePeriod::Days(days));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (hours, remainder) = split_hours(remainder);
|
||||||
|
if let Some(hours) = hours {
|
||||||
|
periods.push(TimePeriod::Hours(hours));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (minutes, remainder) = split_minutes(remainder);
|
||||||
|
if let Some(minutes) = minutes {
|
||||||
|
periods.push(TimePeriod::Minutes(minutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (seconds, remainder) = split_seconds(remainder);
|
||||||
|
if let Some(seconds) = seconds {
|
||||||
|
periods.push(TimePeriod::Seconds(seconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (millis, remainder) = split_milliseconds(remainder);
|
||||||
|
if let Some(millis) = millis {
|
||||||
|
periods.push(TimePeriod::Millis(millis));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (micros, remainder) = split_microseconds(remainder);
|
||||||
|
if let Some(micros) = micros {
|
||||||
|
periods.push(TimePeriod::Micros(micros));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (nanos, _remainder) = split_nanoseconds(remainder);
|
||||||
|
if let Some(nanos) = nanos {
|
||||||
|
periods.push(TimePeriod::Nanos(nanos));
|
||||||
|
}
|
||||||
|
|
||||||
|
if periods.is_empty() {
|
||||||
|
periods.push(TimePeriod::Seconds(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// let last = periods.pop().map(|last| last.to_text().to_string());
|
||||||
|
let text = periods
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| p.to_text().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
// if let Some(last) = last {
|
||||||
|
// text.push(format!("and {}", last));
|
||||||
|
// }
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
if sign == -1 { "-" } else { "" },
|
if sign == -1 { "-" } else { "" },
|
||||||
output_prep.join(" ")
|
text.join(" ").trim()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ pub enum Unit {
|
|||||||
Gigabyte,
|
Gigabyte,
|
||||||
Terabyte,
|
Terabyte,
|
||||||
Petabyte,
|
Petabyte,
|
||||||
|
Exabyte,
|
||||||
|
Zettabyte,
|
||||||
|
|
||||||
// Filesize units: ISO/IEC 80000
|
// Filesize units: ISO/IEC 80000
|
||||||
Kibibyte,
|
Kibibyte,
|
||||||
@ -16,6 +18,8 @@ pub enum Unit {
|
|||||||
Gibibyte,
|
Gibibyte,
|
||||||
Tebibyte,
|
Tebibyte,
|
||||||
Pebibyte,
|
Pebibyte,
|
||||||
|
Exbibyte,
|
||||||
|
Zebibyte,
|
||||||
|
|
||||||
// Duration units
|
// Duration units
|
||||||
Nanosecond,
|
Nanosecond,
|
||||||
@ -26,4 +30,7 @@ pub enum Unit {
|
|||||||
Hour,
|
Hour,
|
||||||
Day,
|
Day,
|
||||||
Week,
|
Week,
|
||||||
|
Month,
|
||||||
|
Year,
|
||||||
|
Decade,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user