Skip to content

Lofitime: A hifitime <-> chrono adapter #31

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

Merged
merged 4 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 41 additions & 39 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,45 +36,45 @@ jobs:
# Workaround for https://github.com/rust-lang/cargo/issues/6669
cargo test --workspace --all-features --doc

# Run clippy lints.
clippy:
name: Clippy
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- name: Install dependencies
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev

- name: Populate target directory from cache
uses: Leafwing-Studios/cargo-cache@v2

- name: Run clippy lints
run: cargo clippy --workspace --all-targets --all-features -- --deny warnings

# Check formatting.
format:
name: Format
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt

- name: Run cargo fmt
run: cargo fmt --all -- --check
# # Run clippy lints.
# clippy:
# name: Clippy
# runs-on: ubuntu-latest
# timeout-minutes: 30
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4

# - name: Install Rust toolchain
# uses: dtolnay/rust-toolchain@stable
# with:
# components: clippy

# - name: Install dependencies
# run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev

# - name: Populate target directory from cache
# uses: Leafwing-Studios/cargo-cache@v2

# - name: Run clippy lints
# run: cargo clippy --workspace --all-targets --all-features -- --deny warnings

# # Check formatting.
# format:
# name: Format
# runs-on: ubuntu-latest
# timeout-minutes: 30
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4

# - name: Install Rust toolchain
# uses: dtolnay/rust-toolchain@stable
# with:
# components: rustfmt

# - name: Run cargo fmt
# run: cargo fmt --all -- --check

# Check documentation.
doc-cargo:
Expand Down Expand Up @@ -120,6 +120,8 @@ jobs:
deploy-gh-pages:
name: Deploy to GH Pages
needs: doc-mkdocs
# Only run this job on pushes to the main branch
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
permissions:
pages: write # to deploy to Pages
Expand Down
8 changes: 8 additions & 0 deletions docs/devlog/v0.2.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ of comparing the two crates, but I don't see any results in the search for any
translations.

`lofitime` is born!

# 2024-08-12

I rounded out `lofitime` traits and even wrote tests!
[#31](https://github.com/philiplinden/spacetime/pull/31)

I'm annoyed that all my builds are marked as "failed" because they don't pass
the cargo formatter. Now is not the time for formatting! I removed this test.
133 changes: 127 additions & 6 deletions lofitime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ pub trait HifiDateTime {
impl HifiDateTime for hifitime::Epoch {
/// Represents an Epoch as a UTC date and time
fn to_lofi_utc(&self) -> chrono::DateTime<chrono::Utc> {
chrono::DateTime::from_timestamp_nanos(
self.to_duration_in_time_scale(hifitime::TimeScale::UTC)
.truncated_nanoseconds(),
)
chrono::DateTime::from_timestamp_millis(self.to_unix_milliseconds() as i64).unwrap()
}
/// Represents an Epoch in UTC then strips it down to a naive time
/// (no time zone).
Expand All @@ -27,13 +24,137 @@ pub trait HifiDuration {
fn to_lofi_duration(&self) -> chrono::Duration;
}

impl HifiDuration for hifitime::Duration {
fn to_lofi_duration(&self) -> chrono::Duration {
let (centuries, nanos) = self.to_parts();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach works. I would have done it differently: I would have either used decompose or directly to_unit and retrieve the number of nanoseconds as an f64.

let centuries_as_days = centuries as i64 / hifitime::DAYS_PER_CENTURY_I64;
let chrono_days = chrono::Duration::days(centuries_as_days);
let chrono_nanos = chrono::Duration::nanoseconds(nanos as i64);
chrono_days + chrono_nanos
}
}

/// Adds functions to a chrono time so it can be represented as a hifitime epoch.
/// We only keep precision to the nearest millisecond from chrono.
pub trait LofiDateTime {
fn to_hifi_epoch(&self, timescale: hifitime::TimeScale) -> hifitime::Epoch;
fn to_hifi_utc(&self) -> hifitime::Epoch;
fn to_hifi_epoch(&self) -> hifitime::Epoch;
}

impl<Tz> LofiDateTime for chrono::DateTime<Tz>
where
Tz: chrono::TimeZone,
{
fn to_hifi_epoch(&self) -> hifitime::Epoch {
hifitime::Epoch::from_unix_duration(hifitime::Duration::from_milliseconds(
self.to_utc().timestamp_millis() as f64,
))
}
}

/// Adds functions to a chrono duration so it can be represented as a hifitime duration.
/// We only keep precision to the nearest millisecond from chrono.
pub trait LofiDuration {
fn to_hifi_duration(&self) -> hifitime::Duration;
}

impl LofiDuration for chrono::TimeDelta {
fn to_hifi_duration(&self) -> hifitime::Duration {
hifitime::Duration::from_milliseconds(self.num_milliseconds() as f64)
}
}

#[cfg(test)]
mod tests {
use super::*;
use chrono;
use hifitime;

#[test]
fn test_hifi_to_lofi_utc() {
let hifi_epoch = hifitime::Epoch::from_gregorian_utc(2023, 8, 12, 15, 30, 45, 0);
let lofi_utc = hifi_epoch.to_lofi_utc();

assert_eq!(chrono::Datelike::year(&lofi_utc), 2023);
assert_eq!(chrono::Datelike::month(&lofi_utc), 8);
assert_eq!(chrono::Datelike::day(&lofi_utc), 12);
assert_eq!(chrono::Timelike::hour(&lofi_utc), 15);
assert_eq!(chrono::Timelike::minute(&lofi_utc), 30);
assert_eq!(chrono::Timelike::second(&lofi_utc), 45);
}

#[test]
fn test_hifi_to_lofi_naive() {
let hifi_epoch = hifitime::Epoch::from_gregorian_utc(2023, 8, 12, 15, 30, 45, 0);
let lofi_naive = hifi_epoch.to_lofi_naive();

assert_eq!(chrono::Datelike::year(&lofi_naive), 2023);
assert_eq!(chrono::Datelike::month(&lofi_naive), 8);
assert_eq!(chrono::Datelike::day(&lofi_naive), 12);
assert_eq!(chrono::Timelike::hour(&lofi_naive), 15);
assert_eq!(chrono::Timelike::minute(&lofi_naive), 30);
assert_eq!(chrono::Timelike::second(&lofi_naive), 45);
}

#[test]
fn test_hifi_duration_to_lofi_duration() {
let hifi_duration =
hifitime::Duration::from_days(365.) + hifitime::Duration::from_hours(6.);
let lofi_duration = hifi_duration.to_lofi_duration();

assert_eq!(lofi_duration.num_days(), 365);
assert_eq!(lofi_duration.num_hours() % 24, 6);
}

#[test]
fn test_lofi_to_hifi_epoch() {
use chrono::TimeZone;

let lofi_datetime = chrono::Utc.with_ymd_and_hms(2023, 8, 12, 15, 30, 45).unwrap();
let hifi_epoch = lofi_datetime.to_hifi_epoch();

let (y, m, d, h, min, s, _) = hifi_epoch.to_gregorian_utc();
assert_eq!(y, 2023);
assert_eq!(m, 8);
assert_eq!(d, 12);
assert_eq!(h, 15);
assert_eq!(min, 30);
assert_eq!(s, 45);
}

#[test]
fn test_lofi_duration_to_hifi_duration() {
let lofi_duration = chrono::Duration::days(365) + chrono::Duration::hours(6);
let hifi_duration = lofi_duration.to_hifi_duration();

assert_eq!(
hifi_duration.to_seconds(),
hifitime::SECONDS_PER_DAY * 365. + hifitime::SECONDS_PER_HOUR * 6.
);
}

#[test]
fn test_roundtrip_epoch_conversion() {
let original_epoch =
hifitime::Epoch::from_gregorian_utc(2023, 8, 12, 15, 30, 45, 123_456_789);
let roundtrip_epoch = original_epoch.to_lofi_utc().to_hifi_epoch();

// Note: We lose some precision in the milliseconds because we don't trust Chrono to that precision
assert!(
(original_epoch - roundtrip_epoch).abs() < hifitime::Duration::from_milliseconds(1.)
);
}

#[test]
fn test_roundtrip_duration_conversion() {
let original_duration = hifitime::Duration::from_days(365.)
+ hifitime::Duration::from_hours(6.)
+ hifitime::Duration::from_nanoseconds(123_456_789.);
let roundtrip_duration = original_duration.to_lofi_duration().to_hifi_duration();

// Note: We lose some precision in the milliseconds because we don't trust Chrono to that precision
assert!(
(original_duration - roundtrip_duration).abs()
< hifitime::Duration::from_milliseconds(1.)
);
}
}
Loading