Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get rid of time 0.1.* dependency #1408

Merged
merged 14 commits into from
Mar 2, 2023
Merged
18 changes: 8 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/re_arrow_store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ arrow2 = { workspace = true, features = [
"compute_concatenate",
"compute_aggregate",
] }
chrono = "0.4"
document-features = "0.2"
indent = "0.1"
itertools = "0.10"
Expand Down
5 changes: 1 addition & 4 deletions crates/re_log_types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,6 @@ array-init = "2.1.0"
arrow2 = { workspace = true, features = ["io_ipc", "io_print"] }
arrow2_convert.workspace = true
bytemuck = "1.11"
chrono = { version = "0.4", features = [
"js-sys",
"wasmbind",
] } # TODO(emilk): replace `chrono` with `time` crate
document-features = "0.2"
fixed = { version = "1.17", default-features = false, features = ["serde"] }
half = { workspace = true, features = ["bytemuck"] }
Expand All @@ -76,6 +72,7 @@ nohash-hasher = "0.2"
num-derive = "0.3"
num-traits = "0.2"
thiserror.workspace = true
time = { workspace = true, default-features = false, features = ["formatting", "macros"] }
typenum = "1.15"
uuid = { version = "1.1", features = ["serde", "v4", "js"] }

Expand Down
129 changes: 75 additions & 54 deletions crates/re_log_types/src/time.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::ops::RangeInclusive;
use time::OffsetDateTime;

/// A date-time represented as nanoseconds since unix epoch
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -44,19 +45,11 @@ impl Time {
20 <= years_since_epoch && years_since_epoch <= 150
}

/// Returns the absolute datetime, if this is a valid, unambiguous, absolute time.
pub fn to_chrono(&self) -> Option<chrono::DateTime<chrono::Utc>> {
/// Returns the absolute datetime if applicable.
pub fn to_datetime(&self) -> Option<OffsetDateTime> {
let ns_since_epoch = self.nanos_since_epoch();
if self.is_abolute_date() {
use chrono::TimeZone as _;
if let chrono::LocalResult::Single(datetime) = chrono::Utc.timestamp_opt(
ns_since_epoch / 1_000_000_000,
(ns_since_epoch % 1_000_000_000) as _,
) {
Some(datetime)
} else {
None
}
OffsetDateTime::from_unix_timestamp_nanos(ns_since_epoch as i128).ok()
} else {
None
}
Expand All @@ -74,35 +67,26 @@ impl Time {
pub fn format(&self) -> String {
let nanos_since_epoch = self.nanos_since_epoch();

if self.is_abolute_date() {
use chrono::TimeZone as _;
let datetime = chrono::Utc.timestamp_opt(
nanos_since_epoch / 1_000_000_000,
(nanos_since_epoch % 1_000_000_000) as _,
);
match datetime {
chrono::LocalResult::Single(datetime) => {
let is_whole_second = nanos_since_epoch % 1_000_000_000 == 0;
let is_whole_millisecond = nanos_since_epoch % 1_000_000 == 0;

let time_format = if is_whole_second {
"%H:%M:%SZ"
} else if is_whole_millisecond {
"%H:%M:%S%.3fZ"
} else {
"%H:%M:%S%.6fZ"
};

if datetime.date_naive() == chrono::offset::Utc::now().date_naive() {
datetime.format(time_format).to_string()
} else {
let date_format = format!("%Y-%m-%d {time_format}");
datetime.format(&date_format).to_string()
}
}
chrono::LocalResult::None => "Invalid timestamp".to_owned(),
chrono::LocalResult::Ambiguous(_, _) => "Ambiguous timestamp".to_owned(),
}
if let Some(datetime) = self.to_datetime() {
let is_whole_second = nanos_since_epoch % 1_000_000_000 == 0;
let is_whole_millisecond = nanos_since_epoch % 1_000_000 == 0;

let time_format = if is_whole_second {
"[hour]:[minute]:[second]Z"
} else if is_whole_millisecond {
"[hour]:[minute]:[second].[subsecond digits:3]Z"
} else {
"[hour]:[minute]:[second].[subsecond digits:6]Z"
};

let date_is_today = datetime.date() == OffsetDateTime::now_utc().date();
let date_format = format!("[year]-[month]-[day] {time_format}");
let parsed_format = if date_is_today {
time::format_description::parse(time_format).unwrap()
} else {
time::format_description::parse(&date_format).unwrap()
};
datetime.format(&parsed_format).unwrap()
} else {
// Relative time
let secs = nanos_since_epoch as f64 * 1e-9;
Expand All @@ -116,20 +100,6 @@ impl Time {
}
}

pub fn format_time(&self, format_str: &str) -> String {
use chrono::TimeZone as _;
let nanos_since_epoch = self.nanos_since_epoch();
let datetime = chrono::Utc.timestamp_opt(
nanos_since_epoch / 1_000_000_000,
(nanos_since_epoch % 1_000_000_000) as _,
);
match datetime {
chrono::LocalResult::Single(datetime) => datetime.format(format_str).to_string(),
chrono::LocalResult::None => "Invalid timestamp".to_owned(),
chrono::LocalResult::Ambiguous(_, _) => "Ambiguous timestamp".to_owned(),
}
}

#[inline]
pub fn lerp(range: RangeInclusive<Time>, t: f32) -> Time {
let (min, max) = (range.start().0, range.end().0);
Expand Down Expand Up @@ -186,6 +156,57 @@ impl TryFrom<std::time::SystemTime> for Time {
}
}

impl TryFrom<time::OffsetDateTime> for Time {
type Error = core::num::TryFromIntError;

fn try_from(datetime: time::OffsetDateTime) -> Result<Time, Self::Error> {
i64::try_from(datetime.unix_timestamp_nanos()).map(|ns| Time::from_ns_since_epoch(ns))
}
}

// ---------------

#[cfg(test)]
mod tests {
use super::*;
use time::macros::{datetime, time};

#[test]
fn test_formatting_short_times() {
assert_eq!(&Time::from_us_since_epoch(42_000_000).format(), "+42s");
assert_eq!(&Time::from_us_since_epoch(69_000).format(), "+0.069s");
assert_eq!(&Time::from_us_since_epoch(69_900).format(), "+0.070s");
}

#[test]
fn test_formatting_whole_second_for_datetime() {
let datetime = Time::try_from(datetime!(2022-02-28 22:35:42 UTC)).unwrap();
assert_eq!(&datetime.format(), "2022-02-28 22:35:42Z");
}

#[test]
fn test_formatting_whole_millisecond_for_datetime() {
let datetime = Time::try_from(datetime!(2022-02-28 22:35:42.069 UTC)).unwrap();
assert_eq!(&datetime.format(), "2022-02-28 22:35:42.069Z");
}

#[test]
fn test_formatting_many_digits_for_datetime() {
let datetime = Time::try_from(datetime!(2022-02-28 22:35:42.069_042_7 UTC)).unwrap();
assert_eq!(&datetime.format(), "2022-02-28 22:35:42.069042Z"); // format function is not rounding
}

/// Check that formatting today times doesn't display the date.
/// WARNING: this test could easily flake with current implementation
/// (checking day instead of hour-distance)
#[test]
fn test_formatting_today_omit_date() {
let today = OffsetDateTime::now_utc().replace_time(time!(22:35:42));
let datetime = Time::try_from(today).unwrap();
assert_eq!(&datetime.format(), "22:35:42Z");
}
}

// ----------------------------------------------------------------------------

/// A signed duration represented as nanoseconds since unix epoch
Expand Down
1 change: 0 additions & 1 deletion crates/re_query/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ arrow2 = { workspace = true, features = [
"compute_concatenate",
"compute_aggregate",
] }
chrono = "0.4"
document-features = "0.2"
indent = "0.1"
itertools = "0.10"
Expand Down
2 changes: 1 addition & 1 deletion crates/re_viewer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ re_analytics = { workspace = true, optional = true }
ahash = "0.8"
anyhow.workspace = true
bytemuck = { version = "1.11", features = ["extern_crate_alloc"] }
chrono = "0.4"
eframe = { workspace = true, default-features = false, features = [
"default_fonts",
"persistence",
Expand Down Expand Up @@ -101,6 +100,7 @@ rfd = "0.11"
serde = { version = "1", features = ["derive"] }
slotmap = { version = "1.0.6", features = ["serde"] }
smallvec = { version = "1.10", features = ["serde"] }
time = { workspace = true, default-features = false, features = ["formatting"] }
uuid = { version = "1.1", features = ["serde", "v4", "js"] }
vec1 = "1.8"
wgpu.workspace = true
Expand Down
23 changes: 9 additions & 14 deletions crates/re_viewer/src/misc/format_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,17 @@ pub fn format_time_compact(time: re_log_types::Time) -> String {
let relative_ns = ns % 1_000_000_000;
let is_whole_second = relative_ns == 0;
if is_whole_second {
if let Some(datetime) = time.to_chrono() {
return if time.is_exactly_midnight() {
// Show just the date:
datetime.format("%Y-%m-%dZ").to_string()
if let Some(datetime) = time.to_datetime() {
let is_whole_minute = ns % 60_000_000_000 == 0;
let time_format = if time.is_exactly_midnight() {
"[year]-[month]-[day]Z"
} else if is_whole_minute {
"[hour]:[minute]Z"
} else {
// Show just the time:
let is_whole_minute = ns % 60_000_000_000 == 0;
let is_whole_second = ns % 1_000_000_000 == 0;
if is_whole_minute {
datetime.time().format("%H:%MZ").to_string()
} else if is_whole_second {
datetime.time().format("%H:%M:%SZ").to_string()
} else {
datetime.time().format("%H:%M:%S%.3fZ").to_string()
}
"[hour]:[minute]:[second]Z"
};
let parsed_format = time::format_description::parse(time_format).unwrap();
return datetime.format(&parsed_format).unwrap();
}

re_log_types::Duration::from_nanos(ns).to_string()
Expand Down