Skip to content

Commit

Permalink
switch to jiff from chrono (#6205)
Browse files Browse the repository at this point in the history
This PR migrates uv's use of `chrono` to `jiff`.

I did most of this work a while back as one of my tests to ensure Jiff
could actually be used in a real world project. I decided to revive
this because I noticed that `reqwest-retry` dropped its Chrono
dependency,
which is I believe the only other thing requiring Chrono in uv.
(Although, we use a fork of `reqwest-middleware` at present, and that
hasn't been updated to latest upstream yet. I wasn't quite sure of the
process we have for that.)

In course of doing this, I actually made two changes to uv:

First is that the lock file now writes an RFC 3339 timestamp for
`exclude-newer`. Previously, we were using Chrono's `Display`
implementation for this which is a non-standard but "human readable"
format. I think the right thing to do here is an RFC 3339 timestamp.

Second is that, in addition to an RFC 3339 timestamp, `--exclude-newer`
used to accept a "UTC date." But this PR changes it to a "local date."
That is, a date in the user's system configured time zone. I think
this makes more sense than a UTC date, but one alternative is to drop
support for a date and just rely on an RFC 3339 timestamp. The main
motivation here is that automatically assuming UTC is often somewhat
confusing, since just writing an unqualified date like `2024-08-19` is
often assumed to be interpreted relative to the writer's "local" time.
  • Loading branch information
BurntSushi authored Aug 19, 2024
1 parent c80a831 commit 4617eb3
Show file tree
Hide file tree
Showing 38 changed files with 285 additions and 213 deletions.
39 changes: 32 additions & 7 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ base64 = { version = "0.22.0" }
boxcar = { version = "0.2.5" }
cachedir = { version = "0.3.1" }
cargo-util = { version = "0.2.8" }
chrono = { version = "0.4.31" }
clap = { version = "4.5.9" }
clap_complete_command = { version = "0.6.0" }
configparser = { version = "3.0.4" }
Expand Down Expand Up @@ -98,6 +97,7 @@ indexmap = { version = "2.2.5" }
indicatif = { version = "0.17.7" }
indoc = { version = "2.0.4" }
itertools = { version = "0.13.0" }
jiff = { version = "0.1.6", features = ["serde"] }
junction = { version = "1.0.0" }
mailparse = { version = "0.15.0" }
md-5 = { version = "0.10.6" }
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,8 @@ cases, but may lose fidelity for complex package and platform combinations._
uv supports an `--exclude-newer` option to limit resolution to distributions published before a
specific date, allowing reproduction of installations regardless of new package releases. The date
may be specified as an [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) timestamp (e.g.,
`2006-12-02T02:07:43Z`) or UTC date in the same format (e.g., `2006-12-02`).
`2006-12-02T02:07:43Z`) or a local date in the same format (e.g., `2006-12-02`) in your system's
configured time zone.

Note the package index must support the `upload-time` field as specified in
[`PEP 700`](https://peps.python.org/pep-0700/). If the field is not present for a given
Expand Down
2 changes: 1 addition & 1 deletion crates/bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ uv-resolver = { workspace = true }
uv-types = { workspace = true }

anyhow = { workspace = true }
chrono = { workspace = true }
codspeed-criterion-compat = { version = "2.6.0", default-features = false, optional = true }
criterion = { version = "0.5.1", default-features = false, features = ["async_tokio"] }
jiff = { workspace = true }
tokio = { workspace = true }

[features]
Expand Down
8 changes: 3 additions & 5 deletions crates/bench/benches/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ mod resolver {
use std::sync::LazyLock;

use anyhow::Result;
use chrono::NaiveDate;

use distribution_types::IndexLocations;
use install_wheel_rs::linker::LinkMode;
Expand Down Expand Up @@ -144,11 +143,10 @@ mod resolver {
let concurrency = Concurrency::default();
let config_settings = ConfigSettings::default();
let exclude_newer = Some(
NaiveDate::from_ymd_opt(2024, 8, 8)
jiff::civil::date(2024, 8, 8)
.to_zoned(jiff::tz::TimeZone::UTC)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap()
.and_utc()
.timestamp()
.into(),
);
let flat_index = FlatIndex::default();
Expand Down
1 change: 1 addition & 0 deletions crates/distribution-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ uv-normalize = { workspace = true }
anyhow = { workspace = true }
fs-err = { workspace = true }
itertools = { workspace = true }
jiff = { workspace = true }
rkyv = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
Expand Down
5 changes: 3 additions & 2 deletions crates/distribution-types/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::{self, Display, Formatter};
use std::path::PathBuf;
use std::str::FromStr;

use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use url::Url;

Expand Down Expand Up @@ -31,7 +32,7 @@ pub struct File {
pub hashes: Vec<HashDigest>,
pub requires_python: Option<VersionSpecifiers>,
pub size: Option<u64>,
// N.B. We don't use a chrono DateTime<Utc> here because it's a little
// N.B. We don't use a Jiff timestamp here because it's a little
// annoying to do so with rkyv. Since we only use this field for doing
// comparisons in testing, we just store it as a UTC timestamp in
// milliseconds.
Expand All @@ -57,7 +58,7 @@ impl File {
.transpose()
.map_err(|err| FileConversionError::RequiresPython(err.line().clone(), err))?,
size: file.size,
upload_time_utc_ms: file.upload_time.map(|dt| dt.timestamp_millis()),
upload_time_utc_ms: file.upload_time.map(Timestamp::as_millisecond),
url: match Url::parse(&file.url) {
Ok(url) => FileLocation::AbsoluteUrl(url.into()),
Err(_) => FileLocation::RelativeUrl(base.to_string(), file.url),
Expand Down
2 changes: 1 addition & 1 deletion crates/pypi-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ uv-fs = { workspace = true, features = ["serde"] }
uv-git = { workspace = true }
uv-normalize = { workspace = true }

chrono = { workspace = true, features = ["serde"] }
indexmap = { workspace = true, features = ["serde"] }
itertools = { workspace = true }
jiff = { workspace = true, features = ["serde"] }
mailparse = { workspace = true }
regex = { workspace = true }
rkyv = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions crates/pypi-types/src/simple_json.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::str::FromStr;

use chrono::{DateTime, Utc};
use jiff::Timestamp;
use serde::{Deserialize, Deserializer, Serialize};

use pep440_rs::{VersionSpecifiers, VersionSpecifiersParseError};
Expand Down Expand Up @@ -51,7 +51,7 @@ pub struct File {
#[serde(default, deserialize_with = "deserialize_version_specifiers_lenient")]
pub requires_python: Option<Result<VersionSpecifiers, VersionSpecifiersParseError>>,
pub size: Option<u64>,
pub upload_time: Option<DateTime<Utc>>,
pub upload_time: Option<Timestamp>,
pub url: String,
pub yanked: Option<Yanked>,
}
Expand Down
16 changes: 8 additions & 8 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1993,8 +1993,8 @@ pub struct VenvArgs {

/// Limit candidate packages to those that were uploaded prior to the given date.
///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same
/// format (e.g., `2006-12-02`).
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = "UV_EXCLUDE_NEWER")]
pub exclude_newer: Option<ExcludeNewer>,

Expand Down Expand Up @@ -3288,8 +3288,8 @@ pub struct InstallerArgs {

/// Limit candidate packages to those that were uploaded prior to the given date.
///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same
/// format (e.g., `2006-12-02`).
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = "UV_EXCLUDE_NEWER", help_heading = "Resolver options")]
pub exclude_newer: Option<ExcludeNewer>,

Expand Down Expand Up @@ -3462,8 +3462,8 @@ pub struct ResolverArgs {

/// Limit candidate packages to those that were uploaded prior to the given date.
///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same
/// format (e.g., `2006-12-02`).
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = "UV_EXCLUDE_NEWER", help_heading = "Resolver options")]
pub exclude_newer: Option<ExcludeNewer>,

Expand Down Expand Up @@ -3634,8 +3634,8 @@ pub struct ResolverInstallerArgs {

/// Limit candidate packages to those that were uploaded prior to the given date.
///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same
/// format (e.g., `2006-12-02`).
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = "UV_EXCLUDE_NEWER", help_heading = "Resolver options")]
pub exclude_newer: Option<ExcludeNewer>,

Expand Down
2 changes: 1 addition & 1 deletion crates/uv-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ anyhow = { workspace = true }
async-trait = { workspace = true }
async_http_range_reader = { workspace = true }
async_zip = { workspace = true, features = ["tokio"] }
chrono = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
html-escape = { workspace = true }
http = { workspace = true }
itertools = { workspace = true }
jiff = { workspace = true }
reqwest = { workspace = true }
reqwest-middleware = { workspace = true }
reqwest-retry = { workspace = true }
Expand Down
22 changes: 15 additions & 7 deletions crates/uv-client/src/httpcache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1363,25 +1363,33 @@ fn unix_timestamp(time: SystemTime) -> u64 {
}

fn rfc2822_to_unix_timestamp(s: &str) -> Option<u64> {
rfc2822_to_datetime(s).and_then(|dt| u64::try_from(dt.timestamp()).ok())
rfc2822_to_datetime(s).and_then(|timestamp| u64::try_from(timestamp.as_second()).ok())
}

fn rfc2822_to_datetime(s: &str) -> Option<chrono::DateTime<chrono::Utc>> {
chrono::DateTime::parse_from_rfc2822(s)
fn rfc2822_to_datetime(s: &str) -> Option<jiff::Timestamp> {
jiff::fmt::rfc2822::DateTimeParser::new()
.parse_timestamp(s)
.ok()
.map(|dt| dt.to_utc())
}

fn unix_timestamp_to_header(seconds: u64) -> Option<HeaderValue> {
unix_timestamp_to_rfc2822(seconds).and_then(|string| HeaderValue::from_str(&string).ok())
}

fn unix_timestamp_to_rfc2822(seconds: u64) -> Option<String> {
unix_timestamp_to_datetime(seconds).map(|dt| dt.to_rfc2822())
use jiff::fmt::rfc2822::DateTimePrinter;

unix_timestamp_to_datetime(seconds).and_then(|timestamp| {
let mut buf = String::new();
DateTimePrinter::new()
.print_timestamp(&timestamp, &mut buf)
.ok()?;
Some(buf)
})
}

fn unix_timestamp_to_datetime(seconds: u64) -> Option<chrono::DateTime<chrono::Utc>> {
chrono::DateTime::from_timestamp(i64::try_from(seconds).ok()?, 0)
fn unix_timestamp_to_datetime(seconds: u64) -> Option<jiff::Timestamp> {
jiff::Timestamp::from_second(i64::try_from(seconds).ok()?).ok()
}

fn parse_seconds(value: &[u8]) -> Option<u64> {
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ uv-warnings = { workspace = true }
uv-workspace = { workspace = true }

anyhow = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive"], optional = true }
dashmap = { workspace = true }
derivative = { workspace = true }
either = { workspace = true }
futures = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
jiff = { workspace = true, features = ["serde"] }
owo-colors = { workspace = true }
petgraph = { workspace = true }
pubgrub = { workspace = true }
Expand Down
Loading

0 comments on commit 4617eb3

Please sign in to comment.