use core::str::FromStr;
use crate::{
options::{RoundingIncrement, RoundingOptions, ToStringRoundingOptions, Unit},
parsers::Precision,
partial::PartialDuration,
provider::NeverProvider,
};
use super::Duration;
#[test]
fn partial_duration_empty() {
let err = Duration::from_partial_duration(PartialDuration::default());
assert!(err.is_err())
}
#[test]
fn partial_duration_values() {
let mut partial = PartialDuration::default();
let _ = partial.years.insert(20);
let result = Duration::from_partial_duration(partial).unwrap();
assert_eq!(result.years(), 20);
}
#[test]
fn default_duration_string() {
let duration = Duration::default();
let options = ToStringRoundingOptions {
precision: Precision::Auto,
smallest_unit: None,
rounding_mode: None,
};
let result = duration.as_temporal_string(options).unwrap();
assert_eq!(&result, "PT0S");
let options = ToStringRoundingOptions {
precision: Precision::Digit(0),
smallest_unit: None,
rounding_mode: None,
};
let result = duration.as_temporal_string(options).unwrap();
assert_eq!(&result, "PT0S");
let options = ToStringRoundingOptions {
precision: Precision::Digit(1),
smallest_unit: None,
rounding_mode: None,
};
let result = duration.as_temporal_string(options).unwrap();
assert_eq!(&result, "PT0.0S");
let options = ToStringRoundingOptions {
precision: Precision::Digit(3),
smallest_unit: None,
rounding_mode: None,
};
let result = duration.as_temporal_string(options).unwrap();
assert_eq!(&result, "PT0.000S");
}
#[test]
fn duration_to_string_auto_precision() {
let duration = Duration::new(1, 2, 3, 4, 5, 6, 7, 0, 0, 0).unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "P1Y2M3W4DT5H6M7S");
let duration = Duration::new(1, 2, 3, 4, 5, 6, 7, 987, 650, 0).unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "P1Y2M3W4DT5H6M7.98765S");
let duration = Duration::new(0, 0, 0, 2, 0, 0, 0, 0, 0, 1).unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "P2DT0.000000001S");
}
#[test]
fn empty_date_duration() {
let duration = Duration::from_partial_duration(PartialDuration {
hours: Some(1.into()),
..Default::default()
})
.unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "PT1H");
}
#[test]
fn negative_fields_to_string() {
let duration = Duration::from_partial_duration(PartialDuration {
years: Some(-1),
months: Some(-1),
weeks: Some(-1),
days: Some(-1),
hours: Some(-1),
minutes: Some(-1),
seconds: Some(-1),
milliseconds: Some(-1),
microseconds: Some(-1),
nanoseconds: Some(-1),
})
.unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "-P1Y1M1W1DT1H1M1.001001001S");
let duration = Duration::from_partial_duration(PartialDuration {
milliseconds: Some(-250),
..Default::default()
})
.unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "-PT0.25S");
let duration = Duration::from_partial_duration(PartialDuration {
milliseconds: Some(-3500),
..Default::default()
})
.unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "-PT3.5S");
let duration = Duration::from_partial_duration(PartialDuration {
milliseconds: Some(-3500),
..Default::default()
})
.unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "-PT3.5S");
let duration = Duration::from_partial_duration(PartialDuration {
weeks: Some(-1),
days: Some(-1),
..Default::default()
})
.unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "-P1W1D");
}
#[test]
fn preserve_precision_loss() {
let duration = Duration::from_partial_duration(PartialDuration {
milliseconds: Some(MAX_SAFE_INTEGER),
microseconds: Some(MAX_SAFE_INTEGER as i128),
..Default::default()
})
.unwrap();
let result = duration
.as_temporal_string(ToStringRoundingOptions::default())
.unwrap();
assert_eq!(&result, "PT9016206453995.731991S");
}
#[test]
fn duration_from_str() {
let duration = Duration::from_str("PT0.999999999H").unwrap();
assert_eq!(duration.minutes(), 59);
assert_eq!(duration.seconds(), 59);
assert_eq!(duration.milliseconds(), 999);
assert_eq!(duration.microseconds(), 996);
assert_eq!(duration.nanoseconds(), 400);
let duration = Duration::from_str("PT0.000000011H").unwrap();
assert_eq!(duration.minutes(), 0);
assert_eq!(duration.seconds(), 0);
assert_eq!(duration.milliseconds(), 0);
assert_eq!(duration.microseconds(), 39);
assert_eq!(duration.nanoseconds(), 600);
let duration = Duration::from_str("PT0.999999999M").unwrap();
assert_eq!(duration.seconds(), 59);
assert_eq!(duration.milliseconds(), 999);
assert_eq!(duration.microseconds(), 999);
assert_eq!(duration.nanoseconds(), 940);
}
#[test]
fn duration_max_safe() {
assert!(Duration::new(0, 0, 0, 0, 0, 0, 0, 0, 9_007_199_254_740_991_926_258, 0).is_err());
let mut options = RoundingOptions {
increment: Some(RoundingIncrement::ONE),
largest_unit: Some(Unit::Nanosecond),
..Default::default()
};
let d = Duration::new(
0,
0,
0,
0,
0,
0,
MAX_SAFE_INTEGER,
0,
0,
463_129_087,
)
.unwrap();
let _ = d
.round_with_provider(options, None, &NeverProvider::default())
.expect("Must successfully round");
let d = Duration::new(
0,
0,
0,
0,
0,
0,
MAX_SAFE_INTEGER,
0,
0,
463_129_088,
)
.unwrap();
assert!(d
.round_with_provider(options, None, &NeverProvider::default())
.is_err());
options.largest_unit = Some(Unit::Microsecond);
let _ = d
.round_with_provider(options, None, &NeverProvider::default())
.expect("Must successfully round");
let d = Duration::new(
0,
0,
0,
0,
0,
0,
MAX_SAFE_INTEGER,
0,
475_712,
0,
)
.unwrap();
assert!(d
.round_with_provider(options, None, &NeverProvider::default())
.is_err());
options.largest_unit = Some(Unit::Millisecond);
let _ = d
.round_with_provider(options, None, &NeverProvider::default())
.expect("Must successfully round");
}
#[test]
fn duration_max() {
let cases = [
(
Duration::new(0, 0, 0, 104249991374, 7, 36, 31, 999, 999, 999).unwrap(),
"max days",
9007199254740991.999999999,
),
(
Duration::new(0, 0, 0, 0, 2501999792983, 36, 31, 999, 999, 999).unwrap(),
"max hours",
9007199254740991.999999999,
),
(
Duration::new(0, 0, 0, 0, 0, 150119987579016, 31, 999, 999, 999).unwrap(),
"max minutes",
9007199254740991.999999999,
),
(
Duration::new(0, 0, 0, 0, 0, 0, 9007199254740991, 999, 999, 999).unwrap(),
"max seconds",
9007199254740991.999999999,
),
(
Duration::new(0, 0, 0, -104249991374, -7, -36, -31, -999, -999, -999).unwrap(),
"min days",
-9007199254740991.999999999,
),
(
Duration::new(0, 0, 0, 0, -2501999792983, -36, -31, -999, -999, -999).unwrap(),
"min hours",
-9007199254740991.999999999,
),
(
Duration::new(0, 0, 0, 0, 0, -150119987579016, -31, -999, -999, -999).unwrap(),
"min minutes",
-9007199254740991.999999999,
),
(
Duration::new(0, 0, 0, 0, 0, 0, -9007199254740991, -999, -999, -999).unwrap(),
"min seconds",
-9007199254740991.999999999,
),
];
for (duration, description, result) in cases {
assert_eq!(
duration
.total_with_provider(Unit::Second, None, &NeverProvider::default())
.unwrap()
.0,
result,
"{description}"
);
}
}
#[test]
fn duration_round_negative() {
let duration = Duration::new(0, 0, 0, 0, -60, 0, 0, 0, 0, 0).unwrap();
let result = duration
.round_with_provider(
RoundingOptions {
smallest_unit: Some(Unit::Day),
..Default::default()
},
None,
&NeverProvider::default(),
)
.unwrap();
assert_eq!(result.days(), -3);
}
#[test]
#[cfg(feature = "compiled_data")]
fn test_duration_compare() {
use crate::builtins::FS_TZ_PROVIDER;
use crate::options::{OffsetDisambiguation, RelativeTo};
use crate::ZonedDateTime;
use alloc::string::ToString;
if cfg!(not(windows)) {
let one = Duration::from_partial_duration(PartialDuration {
hours: Some(79),
minutes: Some(10),
..Default::default()
})
.unwrap();
let two = Duration::from_partial_duration(PartialDuration {
days: Some(3),
hours: Some(7),
seconds: Some(630),
..Default::default()
})
.unwrap();
let three = Duration::from_partial_duration(PartialDuration {
days: Some(3),
hours: Some(6),
minutes: Some(50),
..Default::default()
})
.unwrap();
let mut arr = [&one, &two, &three];
arr.sort_by(|a, b| Duration::compare_with_provider(a, b, None, &*FS_TZ_PROVIDER).unwrap());
assert_eq!(
arr.map(ToString::to_string),
[&three, &one, &two].map(ToString::to_string)
);
let zdt = ZonedDateTime::from_utf8_with_provider(
b"2020-11-01T00:00-07:00[America/Los_Angeles]",
Default::default(),
OffsetDisambiguation::Reject,
&*FS_TZ_PROVIDER,
)
.unwrap();
arr.sort_by(|a, b| {
Duration::compare_with_provider(
a,
b,
Some(RelativeTo::ZonedDateTime(zdt.clone())),
&*FS_TZ_PROVIDER,
)
.unwrap()
});
assert_eq!(
arr.map(ToString::to_string),
[&one, &three, &two].map(ToString::to_string)
)
}
}
const MAX_SAFE_INTEGER: i64 = 9_007_199_254_740_991;
#[test]
fn duration_round_out_of_range_norm_conversion() {
let duration = Duration::new(0, 0, 0, 0, 0, 0, MAX_SAFE_INTEGER, 0, 0, 999_999_999).unwrap();
let err = duration.round_with_provider(
RoundingOptions {
largest_unit: Some(Unit::Nanosecond),
increment: Some(RoundingIncrement::ONE),
..Default::default()
},
None,
&NeverProvider::default(),
);
assert!(err.is_err())
}
#[test]
#[cfg_attr(not(feature = "float64_representable_durations"), should_panic)]
fn duration_float64_representable() {
let duration = Duration::new(0, 0, 0, 0, 0, 0, 0, 0, MAX_SAFE_INTEGER as i128, 0).unwrap();
let duration2 = Duration::new(0, 0, 0, 0, 0, 0, 0, 0, MAX_SAFE_INTEGER as i128 - 1, 0).unwrap();
let added = duration.add(&duration2).unwrap();
assert_eq!(added.microseconds, 18014398509481980);
assert_eq!(
added.as_temporal_string(Default::default()).unwrap(),
"PT18014398509.48198S"
);
let one_ms = Duration::new(0, 0, 0, 0, 0, 0, 0, 0, 1, 0).unwrap();
let added_plus_one = added.add(&one_ms).unwrap();
assert_eq!(
added, added_plus_one,
"Should not internally use a more accurate representation when adding"
);
}
#[test]
#[cfg(feature = "compiled_data")]
fn total_full_numeric_precision() {
let d = Duration::new(0, 0, 0, 0, 816, 0, 0, 0, 0, 2_049_187_497_660).unwrap();
assert_eq!(d.total(Unit::Hour, None).unwrap(), 816.56921874935);
let d = Duration::new(0, 0, 0, 0, 0, 0, 0, MAX_SAFE_INTEGER + 1, 1999, 0).unwrap();
assert_eq!(d.total(Unit::Millisecond, None).unwrap(), 9007199254740994.);
}
#[test]
#[cfg(feature = "compiled_data")]
fn test_nudge_relative_date_total() {
use crate::Calendar;
use crate::PlainDate;
let d = Duration::new(1, 0, 0, 0, 1, 0, 0, 0, 0, 0).unwrap();
let relative = PlainDate::new(2020, 2, 29, Calendar::ISO).unwrap();
assert_eq!(
d.total(Unit::Year, Some(relative.into())).unwrap(),
1.0001141552511414
);
let d = Duration::new(0, 1, 0, 0, 10, 0, 0, 0, 0, 0).unwrap();
let relative = PlainDate::new(2020, 1, 31, Calendar::ISO).unwrap();
assert_eq!(
d.total(Unit::Month, Some(relative.into())).unwrap(),
1.0134408602150538
);
}
#[test]
#[cfg(feature = "compiled_data")]
fn rounding_out_of_range() {
use crate::options::{DifferenceSettings, RoundingMode};
use crate::{TimeZone, ZonedDateTime};
let earlier = ZonedDateTime::try_new_iso(0, TimeZone::utc()).unwrap();
let later = ZonedDateTime::try_new_iso(5, TimeZone::utc()).unwrap();
let options = DifferenceSettings {
smallest_unit: Some(Unit::Day),
increment: Some(RoundingIncrement::try_new(100_000_001).unwrap()),
..Default::default()
};
let error = later.since(&earlier, options);
assert!(
error.is_err(),
"Ending bound 100_000_001 is out of range and should fail."
);
let error = earlier.since(&later, options);
assert!(
error.is_err(),
"Ending bound -100_000_001 is out of range and should fail."
);
let options = DifferenceSettings {
smallest_unit: Some(Unit::Day),
increment: Some(RoundingIncrement::try_new(100_000_000).unwrap()),
rounding_mode: Some(RoundingMode::Expand),
..Default::default()
};
let duration = later.since(&earlier, options).unwrap();
assert_eq!(duration.days(), 100_000_000);
let duration = earlier.since(&later, options).unwrap();
assert_eq!(duration.days(), -100_000_000);
}
#[test]
#[cfg(feature = "compiled_data")]
fn total_precision() {
use crate::PlainDate;
let d = Duration::new(0, 0, 5, 5, 0, 0, 0, 0, 0, 0).unwrap();
let relative_to = PlainDate::try_new_iso(1972, 1, 31).unwrap();
let result = d.total(Unit::Month, Some(relative_to.into())).unwrap();
assert_eq!(
result.0, 1.3548387096774193,
"Loss of precision on Duration::total"
);
}
#[test]
#[cfg(feature = "compiled_data")]
fn rounding_window() {
use crate::PlainDate;
fn duration(years: i64, months: i64, weeks: i64, days: i64, hours: i64) -> Duration {
Duration::new(years, months, weeks, days, hours, 0, 0, 0, 0, 0).unwrap()
}
let d = duration(1, 0, 0, 0, 1);
let relative_to = PlainDate::try_new_iso(2020, 2, 29).unwrap();
let options = RoundingOptions {
smallest_unit: Some(Unit::Year),
..Default::default()
};
let result = d.round(options, Some(relative_to.into())).unwrap();
assert_eq!(result.years(), 1, "years must round down to 1");
let d = duration(0, 1, 0, 0, 10);
let relative_to = PlainDate::try_new_iso(2020, 1, 31).unwrap();
let options = RoundingOptions {
smallest_unit: Some(Unit::Month),
rounding_mode: Some(crate::options::RoundingMode::Expand),
..Default::default()
};
let result = d.round(options, Some(relative_to.into())).unwrap();
assert_eq!(result.months(), 2, "months rounding should expand to 2");
let d = duration(2345, 0, 0, 0, 12);
let relative_to = PlainDate::try_new_iso(2020, 2, 29).unwrap();
let options = RoundingOptions {
smallest_unit: Some(Unit::Year),
rounding_mode: Some(crate::options::RoundingMode::Expand),
..Default::default()
};
let result = d.round(options, Some(relative_to.into())).unwrap();
assert_eq!(result.years(), 2346, "years rounding should expand to 2346");
let d = duration(1, 0, 0, 0, 0);
let relative_to = PlainDate::try_new_iso(2020, 2, 29).unwrap();
let options = RoundingOptions {
smallest_unit: Some(Unit::Month),
..Default::default()
};
let result = d.round(options, Some(relative_to.into())).unwrap();
assert_eq!(result.years(), 1, "months rounding should no-op");
}
#[test]
#[cfg(feature = "compiled_data")]
fn zero_duration() {
use crate::{TimeZone, ZonedDateTime};
let zero = Duration::new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).unwrap();
let relative_to = ZonedDateTime::try_new_iso(0, TimeZone::utc()).unwrap();
let options = RoundingOptions {
smallest_unit: Some(Unit::Hour),
largest_unit: Some(Unit::Day),
..Default::default()
};
let result = zero.round(options, Some(relative_to.into())).unwrap();
assert_eq!(result, Duration::default(), "Duration's must be zero");
}
#[test]
fn out_of_bounds_duration_no_crash() {
let large = 9223372036854775807 * 9223372036854775807;
let duration = Duration::new(0, 0, 0, 0, 0, 0, 0, 0, large, large);
assert!(duration.is_err());
}