-
Notifications
You must be signed in to change notification settings - Fork 0
Rewrite time module in pure Rust #125
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
Conversation
d1b6701
to
e1acb6f
Compare
e1acb6f
to
3caed50
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! As always, I just left minor comments regarding documentation. I do agree we should tackle the panics on a future PR, just to improve error handling.
- 7 * (year as i64 + (month as i64 + 9) / 12) / 4 | ||
- 3 * ((year as i64 + (month as i64 - 9) / 7) / 100 + 1) / 4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May I ask, why don't use some of the constants you defined in the consts
module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only a few of the values are present in the constants module, mostly YEAR_MONTHS == 12
and WEEK_DAYS == 7
. I think the 12 in this formula likely does represent months, but I'm not entirely sure that the 7 actually represents weeks. And 367 is likely distantly related to the number of days in a year. The original source (http://www.leapsecond.com/tools/gpsdate.c) doesn't really shed more light on the meaning.
I've come across these sorts of formula before, they are always just an overly clever formula someone with a lot of time worked out and the constants have no real meaning and can be treated as magic numbers, so I'd propose we keep them as magic numbers here and just point people to the original source in the doc comment.
dt_ls: i8, | ||
dt_lsf: i8, | ||
) -> UtcParams { | ||
assert!(tot.is_valid() && t_lse.is_valid()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could tackle this on a later PR as you mentioned, but would prefer here to create an UtcError enum similar to the GPS time one, and return that instead of panicking.
swiftnav/src/time/utc.rs
Outdated
/// | ||
/// Note: This implementation does not aim to be able to represent arbitrary dates and times. | ||
/// It is only meant to represent dates and times over the period that GNSS systems have been | ||
/// around. Specifically it shouldn't be relied on for dates significantly before January 6th 1980, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't saw if we are actually panicking or returning an error if the user tries to use this struct for dates before 1980
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we panic, but I'm not sure if it makes sense to do that or double check it. This is meant to indicate that we don't really test that everything continues to work for dates before the start of GPS time. The UTC specific functionality likely would, but the conversion to/from GPS time likely will break. That's where we should really should put sanity checks and error types. I'll update this to make sure that we largely only test on dates back this far, and to use the chrono crate if you need correct handling of dates further back in time.
Co-authored-by: Alejandro Duarte <[email protected]>
Co-authored-by: Alejandro Duarte <[email protected]>
Co-authored-by: Alejandro Duarte <[email protected]>
} | ||
} | ||
/// Makes a new GPS time object without checking the validity of the given values. | ||
pub(crate) const fn new_unchecked(wn: i16, tow: f64) -> GpsTime { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make this as unsafe. It's a pretty common convention in rust to do this to ensure that the user is super careful (even though it has nothing to do with borrow checking).
An example:
https://doc.rust-lang.org/src/core/pin.rs.html#1349
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is something that people are moving towards, but honestly I am not a fan. For the record that pin new_unchecked
you linked to is correctly marked as unsafe
as it can lead to UB if the pin api contract is not upheld. I am kind of skeptical setting a negative week for i16, or NaN
for tow can lead to undefined behavior and memory safety by itself... rather it leads to logic errors, but i leave it to @jbangelo judgement there.
That said, there is precedent in the rust community for using unsafe
for things that you know can lead to logic errors: e.g. https://docs.rs/sguaba/latest/sguaba/#use-of-unsafe
up to you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned in the PR description I'd like to limit the API and behavior changes made in this PR, but I do want to go through this module in particular with a fine tooth comb and sort out potential numerical issues with the arithmetic and make a more idiomatic API. I'm open to marking this as unsafe
in a follow up PR if we want to adopt that idiom. I do agree with you @pcrumley that it's an abuse of the keyword, which also makes me skeptical but I've not done much research on the subject to see the arguments for it.
assert!(self.is_valid()); | ||
assert!(self >= GAL_TIME_START); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here for most of this, I would expect returning an error will be better. We can let the caller decide if they would like to panic or not rather than forcing them to do so here.
The only time i'd encourage panics is if there is truly an unrecoverable error that occurs.
swiftnav/src/time/mod.rs
Outdated
let diff = test_case.diff(&round_trip).abs(); | ||
assert!( | ||
diff < TOW_TOL, | ||
"gps2mjd2gps failure. original: {:?}, round trip: {:?}, diff: {}, TOW_TOL: {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use rust's face new inline feature for these asserts?
"gps2mjd2gps failure. original: {:?}, round trip: {:?}, diff: {}, TOW_TOL: {}", | |
assert!( | |
diff < TOW_TOL, | |
"gps2mjd2gps failure. original: {test_case:?}, round trip: {round_trip:?}, diff: {diff}, TOW_TOL: {TOW_TOL}" | |
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a feature new in 1.89, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1.58, except for old editions in the panic!
macro. https://blog.rust-lang.org/2022/01/13/Rust-1.58.0/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, just realized that this crate is on edition 2018. I'm going to leave this as is for the moment, and when we bump the edition (which we should) I think these should get caught by a clippy lint (especially if we enable pedantic)
The next chunk of changes from #122.
This re-implements the time related functionality in native Rust. This one is a bit larger, mostly from the fact that the conversions in the C implementation are very inter-dependent and so several new bits of functionality were pulled in to make the Rust implementation mirror the C implementation. There's a few bits of functionality thrown out because they caused some significant unsoundness, but I'm sure there is still several bits of unsoundness in this translation (e.g. several panics/asserts are still present). I'd prefer to keep this PR as close to a straight translation as possible, and re-consider the soundness concerns in a future PR.
What's been changed
What's been removed
GloTime
- GLONASS time is particularly complicated and rarely usedGpsTime::to_glo()
andGpsTime::to_glo_hardcoded()
- See aboveimpl Sub<GpsTime> for GpsTime
- It turns out astd::time::Duration
can't hold a negative value. This implementation would occasionally panic because of this. Thechrono
crate handles this better, maybe we can move towards their approach in a later PR?UtcParams::decode()
- Decoding low-level messages seem unlikely to be neededimpl Default for UtcParams
- Defaulting this data can cause unexpected errorsimpl Default for UtcTime
- Defaulting this type can cause unexpected errorsWhat's been added
time::consts
moduleGpsTime::from_date()
andGpsTime::from_date_hardcoded()
GpsTime::to_mjd()
andGpsTime::to_mjd_hardcoded()
GpsTime::to_date()
andGpsTime::to_date_hardcoded()
UtcTime::to_date()
MJD::to_gps()
andMJD::to_gps_hardcoded()
MJD::to_date()