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

Parse time zone info in Rust on Unix systems #677

Merged
merged 16 commits into from Jun 9, 2022
Merged

Parse time zone info in Rust on Unix systems #677

merged 16 commits into from Jun 9, 2022

Conversation

djc
Copy link
Contributor

@djc djc commented Apr 12, 2022

Fixes #499 by vendoring a heavily modified copy of tz-rs.

Why we don't use tz-rs directly:

  • tz-rs only supports 1.46+, whereas we get to support 1.32+
  • Supply chain: the tz-rs maintainer is relatively unknown, and their GitHub profile is fairly opaque
  • By constraining the capabilities to what chrono needs, the tz_info module is simpler than tz-rs

Alternative solutions: like time, we could check for concurrently running threads before running localtime_r(), leaving tzinfo handling to libc. Using well-tested Rust code to parse the timezone info files seems preferable to me over platform-specific hacks to find out if other threads are running.

@x-hgg-x thanks for your work on tz-rs!

@djc djc force-pushed the tz-info branch 5 times, most recently from 43a4ff9 to c0e806e Compare April 12, 2022 13:49
@djc djc marked this pull request as ready for review April 12, 2022 13:50
@Dushistov
Copy link

Any reason to use once_cell instead of std::sync::Once?

@djc djc force-pushed the tz-info branch 3 times, most recently from 78b2835 to 69169ec Compare April 12, 2022 15:33
@djc djc requested a review from Milo123459 April 12, 2022 15:36
@x-hgg-x
Copy link

x-hgg-x commented Apr 12, 2022

For information, since the last release of tz-rs, I have added additional checks in the AlternateTime constructor, which are necessary for this portion of code to work:

  • Limit the UTC offset of the LocalTimeType fields between -25h and 26h, which is the maximum allowed range when parsing a POSIX TZ string.
  • Check for DST transition rules consistency: this ensures that the DST start and end time are always in the same order for each year. Otherwise, there will be an additional implementation-defined transition at the year boundary when the order is not the same between two consecutive years. This additional transition occurs at Jan 1 00:00:00 UTC for glibc and at Jan 1 00:00:01 UTC for musl libc, which is not what the TZ string would suggest.

I also added some documentation mentioning what a value of Julian0WithLeap(365) means for a non-leap year.

@djc
Copy link
Contributor Author

djc commented Apr 12, 2022

Any reason to use once_cell instead of std::sync::Once?

I wasn't aware of that! I found the interface substantially more painful but for this purpose it makes sense, so this PR now no longer pulls in once_cell. Thanks for the pointer!

For information, since the last release of tz-rs, I have added additional checks in the AlternateTime constructor, which are necessary for this portion of code to work:

Thanks, I will definitely be watching your follow-up commits after this is merged.

@esheppa
Copy link
Collaborator

esheppa commented Apr 13, 2022

Hi @djc - I've had an attempt to fix my above comments here and also added some more tests: https://github.com/esheppa/chrono/tree/tz-info-extra

(Please note that I've not yet fixed at least two areas with the *_from_local functions: AlternativeTime::find_local_time_type_from_local (local to and from UTC adjustments where necessary) and TimeZoneRef::find_local_time_type_from_local (leap second handling))

@djc
Copy link
Contributor Author

djc commented Apr 13, 2022

@esheppa I'm not seeing your "above comments" -- did you forget to submit them? If you have a chance, could you submit any fixes as a PR that targets this PR's branch?


pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> DateTime<Local> {
let offset = match local {
true => offset(d.timestamp()),
Copy link
Collaborator

Choose a reason for hiding this comment

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

the d.timestamp() in this case is in local timezone, not in UTC, so it needs to be adjusted back (or forward) to UTC before it can be passed to the offset function. Alternatively, a custom version of find_local_time_type could be implemented which takes a local timestamp instead of a UTC timestamp.

Copy link

Choose a reason for hiding this comment

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

I just published the version 0.6.8 of tz-rs where I have added a method DateTime::find() which covers this use case.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice @x-hgg-x - I tried this out on my old branch and it passes all the tests locally (however I'd need to properly handle leap seconds)

Copy link

Choose a reason for hiding this comment

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

I see that you used unwrap(), which will panic if the datetime is after the last transition and there is no extra rule.

false => FixedOffset::east(0),
};

DateTime::from_utc(*d - offset, offset)
Copy link
Collaborator

Choose a reason for hiding this comment

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

where d is a UTC timestamp we still need to provide the local offset, for example:

DateTime::from_utc(*d - offset, offset(d.timestamp()))

@esheppa
Copy link
Collaborator

esheppa commented Apr 13, 2022

I forgot to submit before, I've now submitted the comments and created PR #678

@djc
Copy link
Contributor Author

djc commented May 5, 2022

@esheppa CI passed on #682, any clue why it failed after merging?

@djc djc mentioned this pull request May 5, 2022
@esheppa
Copy link
Collaborator

esheppa commented May 6, 2022

@djc - from what I can see #682 never ran on CI due to targeting this branch rather than main - I've temporarily switched #683 to compare with main so that CI can run there, then if that succeeds, I can change it back to target this branch

@esheppa
Copy link
Collaborator

esheppa commented May 13, 2022

@djc - I've triggered the CI to test this at #689 however due to a rebase it can't be merged into this branch. I've created #690 which compresses all the localtime changes into one commit and targets this branch. I've temporarily edited the test.yml on #690 so that the CI runs.

@djc djc merged commit 50eb7e3 into main Jun 9, 2022
@djc djc deleted the tz-info branch June 9, 2022 08:42
@djc
Copy link
Contributor Author

djc commented Jun 9, 2022

See #674 for next release planning.

lopopolo added a commit to artichoke/artichoke that referenced this pull request Aug 5, 2022
See the release announcement:

- https://github.com/chronotope/chrono/releases/tag/v0.4.20

It looks like the fix for RUSTSEC-2020-0159 vendors much of the relevant
code from `tz-rs` (which Artichoke already uses):

- chronotope/chrono#677

Previous `cargo deny` error (I think this started triggering today now
that there is a fixed version out):

```console
$ cargo deny check
error[A001]: Potential segfault in `localtime_r` invocations
   ┌─ /Users/lopopolo/dev/artichoke/artichoke/Cargo.lock:15:1
   │
15 │ chrono 0.4.19 registry+https://github.com/rust-lang/crates.io-index
   │ ------------------------------------------------------------------- security vulnerability detected
   │
   = ID: RUSTSEC-2020-0159
   = Advisory: https://rustsec.org/advisories/RUSTSEC-2020-0159
   = ### Impact

     Unix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user's knowledge, notably in a third-party library.

     ### Workarounds

     No workarounds are known.

     ### References

     - [time-rs/time#293](time-rs/time#293)
   = Announcement: chronotope/chrono#499
   = Solution: Upgrade to >=0.4.20
   = chrono v0.4.19
     ├── chrono-tz v0.6.1
     │   └── spinoso-time v0.5.0
     │       └── artichoke-backend v0.13.0
     │           └── artichoke v0.1.0-pre.0
     └── spinoso-time v0.5.0 (*)

advisories FAILED, bans ok, licenses ok, sources ok
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The call to localtime_r may be unsound
4 participants