diff --git a/Cargo.lock b/Cargo.lock index 58520470c..3acba3351 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,15 +73,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anes" version = "0.1.6" @@ -509,19 +500,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - [[package]] name = "ciborium" version = "0.2.2" @@ -1567,30 +1545,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icu_casemap" version = "2.2.0" @@ -1820,10 +1774,14 @@ checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ "defmt", "jiff-static", + "jiff-tzdb-platform", + "js-sys", "log", "portable-atomic", "portable-atomic-util", "serde_core", + "wasm-bindgen", + "windows-link", ] [[package]] @@ -1837,6 +1795,21 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jni" version = "0.22.4" @@ -3575,7 +3548,6 @@ dependencies = [ "base64", "blake2", "bzip2", - "chrono", "constant_time_eq", "crc32fast", "crossbeam-utils", @@ -3591,6 +3563,7 @@ dependencies = [ "indexmap", "insta", "itertools 0.15.0", + "jiff", "libc", "libsqlite3-sys", "libz-rs-sys", @@ -3669,7 +3642,6 @@ dependencies = [ "ascii", "bitflags 2.13.1", "bstr", - "chrono", "constant_time_eq", "crossbeam-utils", "exitcode", @@ -3682,6 +3654,7 @@ dependencies = [ "is-macro", "itertools 0.15.0", "itoa", + "jiff", "libc", "log", "malachite-bigint", @@ -4713,65 +4686,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 6bb22e36f..3b489a886 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -200,7 +200,6 @@ bitflags = "2.11.0" bitflagset = "0.0.3" bstr = { version = "1", default-features = false, features = ["unicode"] } bzip2 = "0.6" -chrono = { version = "0.4.44", default-features = false, features = ["clock", "std"] } console_error_panic_hook = "0.1" constant_time_eq = "0.5" cranelift = "0.132.0" @@ -231,6 +230,7 @@ insta = "1.47" itertools = { version = "0.15.0", default-features = false, features = ["use_alloc"] } itoa = "1" is-macro = "0.3.7" +jiff = "0.2" js-sys = "0.3" junction = "2.0.0" lexical-parse-float = "1.0.6" diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index d32e7ce8b..98a6677de 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -86,10 +86,10 @@ libz-rs-sys = { workspace = true } bzip2 = { workspace = true } # tkinter +jiff = { workspace = true } tk-sys = { workspace = true, optional = true } tcl-sys = { workspace = true, optional = true } widestring = { workspace = true, optional = true } -chrono.workspace = true # uuid [target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies] diff --git a/crates/stdlib/src/ssl/cert.rs b/crates/stdlib/src/ssl/cert.rs index e304781b6..f12f43072 100644 --- a/crates/stdlib/src/ssl/cert.rs +++ b/crates/stdlib/src/ssl/cert.rs @@ -10,7 +10,7 @@ //! - Loading certificates from files, directories, and bytes use alloc::sync::Arc; -use chrono::{DateTime, Utc}; +use jiff::{Timestamp, Zoned, tz::TimeZone}; use parking_lot::RwLock as ParkingRwLock; use rustls::{ DigitallySignedStruct, RootCertStore, SignatureScheme, @@ -201,10 +201,10 @@ fn format_ip_address(ip: &[u8]) -> String { /// Formats certificate validity dates in the format: /// "Mon DD HH:MM:SS YYYY GMT" fn format_asn1_time(time: &x509_parser::time::ASN1Time) -> String { - let timestamp = time.timestamp(); - DateTime::::from_timestamp(timestamp, 0) - .expect("ASN1Time must be valid timestamp") - .format("%b %e %H:%M:%S %Y GMT") + let timestamp = + Timestamp::from_second(time.timestamp()).expect("ASN1Time must be valid timestamp"); + Zoned::new(timestamp, TimeZone::UTC) + .strftime("%b %e %H:%M:%S %Y GMT") .to_string() } @@ -341,7 +341,7 @@ pub(super) fn cert_to_dict( let serial = format_serial_number(&cert.serial); dict.set_item("serialNumber", vm.ctx.new_str(serial).into(), vm)?; - // Validity dates - format with GMT using chrono + // Validity dates - format with GMT using jiff dict.set_item( "notBefore", vm.ctx @@ -414,7 +414,7 @@ pub(super) fn cert_der_to_dict_helper( // CPython ordering: issuer, notAfter, notBefore, serialNumber, subject, version dict.set_item("issuer", name_to_tuple(cert.issuer())?, vm)?; - // Validity - format with GMT using chrono + // Validity - format with GMT using jiff dict.set_item( "notAfter", vm.ctx diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 6601fb03d..cc7c8dec2 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -26,7 +26,7 @@ ast = ["ruff_python_ast", "ruff_text_size"] codegen = ["rustpython-codegen", "ast"] parser = ["ast"] serde = ["dep:serde"] -wasmbind = ["rustpython-common/wasm_js", "chrono/wasmbind", "wasm-bindgen"] +wasmbind = ["rustpython-common/wasm_js", "jiff/js", "wasm-bindgen"] [dependencies] rustpython-compiler = { workspace = true, optional = true } @@ -48,7 +48,6 @@ ascii = { workspace = true } bitflags = { workspace = true } bstr = { workspace = true } crossbeam-utils = { workspace = true } -chrono = { workspace = true } constant_time_eq = { workspace = true } flame = { workspace = true, optional = true } hex = { workspace = true } @@ -56,6 +55,7 @@ indexmap = { workspace = true } itertools = { workspace = true } itoa = { workspace = true } is-macro = { workspace = true } +jiff = { workspace = true } libc = { workspace = true } log = { workspace = true } malachite-bigint = { workspace = true } @@ -91,9 +91,9 @@ widestring = { workspace = true } wasm-bindgen = { workspace = true, optional = true } [build-dependencies] -chrono = { workspace = true } glob = { workspace = true } itertools = { workspace = true } +jiff = { workspace = true } [lints] workspace = true diff --git a/crates/vm/build.rs b/crates/vm/build.rs index 36e7a5d9d..2846adfe5 100644 --- a/crates/vm/build.rs +++ b/crates/vm/build.rs @@ -3,9 +3,8 @@ reason = "build scripts cannot use rustpython-host_env" )] -use chrono::{Local, prelude::DateTime}; -use core::time::Duration; use itertools::Itertools; +use jiff::{Timestamp, Zoned, tz::TimeZone}; use std::{ env, io::{self, prelude::*}, @@ -123,24 +122,27 @@ fn git_identifier() -> String { } } -fn get_git_timestamp_datetime() -> DateTime { - let timestamp = git_timestamp().parse::().unwrap_or_default(); - let datetime = UNIX_EPOCH + Duration::from_secs(timestamp); - datetime.into() +fn get_git_timestamp_raw() -> Option { + let git_timestamp = git_timestamp().parse::().ok()?; + Timestamp::from_second(git_timestamp).ok() +} + +fn get_git_timestamp_datetime() -> Zoned { + get_git_timestamp_raw().map_or_else(Zoned::now, |timestamp| { + Zoned::new(timestamp, TimeZone::system()) + }) } #[must_use] fn get_git_date() -> String { let datetime = get_git_timestamp_datetime(); - - datetime.format("%b %e %Y").to_string() + datetime.strftime("%b %e %Y").to_string() } #[must_use] fn get_git_time() -> String { let datetime = get_git_timestamp_datetime(); - - datetime.format("%H:%M:%S").to_string() + datetime.strftime("%H:%M:%S").to_string() } fn rustc_version() -> String { diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 226432654..54ccd46d7 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -25,12 +25,9 @@ mod decl { common::wtf8::Wtf8Buf, convert::{ToPyException, ToPyObject}, }; - #[cfg(not(any(unix, windows)))] - use chrono::{ - DateTime, Datelike, TimeZone, Timelike, - naive::{NaiveDate, NaiveDateTime, NaiveTime}, - }; use core::time::Duration; + #[cfg(not(any(unix, windows)))] + use jiff::{Timestamp, Zoned, civil::DateTime, tz::TimeZone}; #[cfg(target_os = "wasi")] use rustpython_host_env::time::ClockId; #[cfg(any(unix, windows))] @@ -289,10 +286,7 @@ mod decl { } #[cfg(not(any(unix, windows)))] - fn pyobj_to_date_time( - value: Either, - vm: &VirtualMachine, - ) -> PyResult> { + fn pyobj_to_timestamp(value: Either, vm: &VirtualMachine) -> PyResult { let secs = match value { Either::A(float) => { if !float.is_finite() { @@ -302,19 +296,19 @@ mod decl { } Either::B(int) => int, }; - DateTime::::from_timestamp(secs, 0) - .ok_or_else(|| vm.new_overflow_error("timestamp out of range for platform time_t")) + Timestamp::from_second(secs) + .map_err(|_| vm.new_overflow_error("timestamp out of range for platform time_t")) } #[cfg(not(any(unix, windows)))] impl OptionalArg>> { /// Construct a localtime from the optional seconds, or get the current local time. - fn naive_or_local(self, vm: &VirtualMachine) -> PyResult { + fn naive_or_local(self, vm: &VirtualMachine) -> PyResult { Ok(match self { - Self::Present(Some(secs)) => pyobj_to_date_time(secs, vm)? - .with_timezone(&chrono::Local) - .naive_local(), - Self::Present(None) | Self::Missing => chrono::offset::Local::now().naive_local(), + Self::Present(Some(secs)) => { + pyobj_to_timestamp(secs, vm)?.to_zoned(TimeZone::system()) + } + Self::Present(None) | Self::Missing => Zoned::now(), }) } } @@ -432,10 +426,10 @@ mod decl { #[cfg(not(any(unix, windows)))] impl OptionalArg { - fn naive_or_local(self, vm: &VirtualMachine) -> PyResult { + fn naive_or_local(self, vm: &VirtualMachine) -> PyResult { Ok(match self { Self::Present(t) => t.to_date_time(vm)?, - Self::Missing => chrono::offset::Local::now().naive_local(), + Self::Missing => Zoned::now().datetime(), }) } } @@ -456,9 +450,9 @@ mod decl { } _ => { let instant = match secs { - OptionalArg::Present(Some(secs)) => pyobj_to_date_time(secs, vm)?.naive_utc(), + OptionalArg::Present(Some(secs)) => pyobj_to_timestamp(secs, vm)?.to_zoned(TimeZone::UTC), OptionalArg::Present(None) | OptionalArg::Missing => { - chrono::offset::Utc::now().naive_utc() + Zoned::now().with_time_zone(TimeZone::UTC) } }; Ok(StructTimeData::new_utc(vm, instant)) @@ -481,7 +475,7 @@ mod decl { } _ => { let instant = secs.naive_or_local(vm)?; - Ok(StructTimeData::new_local(vm, instant, 0)) + StructTimeData::new_local(vm, instant.into(), 0) } } } @@ -502,11 +496,10 @@ mod decl { { let datetime = t.to_date_time(vm)?; // mktime interprets struct_time as local time - let local_dt = chrono::Local - .from_local_datetime(&datetime) - .single() - .ok_or_else(|| vm.new_overflow_error("mktime argument out of range"))?; - let seconds_since_epoch = local_dt.timestamp() as f64; + let local_dt = datetime + .to_zoned(TimeZone::system()) + .map_err(|_| vm.new_overflow_error("mktime argument out of range"))?; + let seconds_since_epoch = local_dt.timestamp().as_second() as f64; Ok(seconds_since_epoch) } } @@ -534,7 +527,7 @@ mod decl { #[cfg(not(any(unix, windows)))] { let instant = t.naive_or_local(vm)?; - let formatted_time = instant.format(CFMT).to_string(); + let formatted_time = instant.strftime(CFMT).to_string(); Ok(vm.ctx.new_str(formatted_time).into()) } } @@ -555,7 +548,7 @@ mod decl { #[cfg(not(any(unix, windows)))] { let instant = secs.naive_or_local(vm)?; - Ok(instant.format(CFMT).to_string()) + Ok(instant.strftime(CFMT).to_string()) } } @@ -645,7 +638,7 @@ mod decl { }; let mut formatted_time = String::new(); - write!(&mut formatted_time, "{}", instant.format(&fmt_lossy)) + write!(&mut formatted_time, "{}", instant.strftime(&*fmt_lossy)) .unwrap_or_else(|_| formatted_time = format.to_string()); Ok(vm.ctx.new_str(formatted_time).into()) } @@ -757,13 +750,7 @@ mod decl { impl StructTimeData { #[cfg(not(any(unix, windows)))] - fn new_inner( - vm: &VirtualMachine, - tm: NaiveDateTime, - isdst: i32, - gmtoff: i32, - zone: &str, - ) -> Self { + fn new_inner(vm: &VirtualMachine, tm: Zoned, isdst: i32) -> Self { Self { tm_year: vm.ctx.new_int(tm.year()).into(), tm_mon: vm.ctx.new_int(tm.month()).into(), @@ -771,46 +758,47 @@ mod decl { tm_hour: vm.ctx.new_int(tm.hour()).into(), tm_min: vm.ctx.new_int(tm.minute()).into(), tm_sec: vm.ctx.new_int(tm.second()).into(), - tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(), - tm_yday: vm.ctx.new_int(tm.ordinal()).into(), + tm_wday: vm.ctx.new_int(tm.weekday().to_sunday_zero_offset()).into(), + tm_yday: vm.ctx.new_int(tm.day_of_year()).into(), tm_isdst: vm.ctx.new_int(isdst).into(), - tm_zone: vm.ctx.new_str(zone).into(), - tm_gmtoff: vm.ctx.new_int(gmtoff).into(), + tm_zone: vm.ctx.new_str(tm.strftime("%Z").to_string()).into(), + tm_gmtoff: vm.ctx.new_int(tm.offset().seconds()).into(), } } /// Create struct_time for UTC (gmtime) #[cfg(not(any(unix, windows)))] - fn new_utc(vm: &VirtualMachine, tm: NaiveDateTime) -> Self { - Self::new_inner(vm, tm, 0, 0, "UTC") + fn new_utc(vm: &VirtualMachine, tm: Zoned) -> Self { + Self::new_inner(vm, tm, 0) } /// Create struct_time for local timezone (localtime) #[cfg(not(any(unix, windows)))] - fn new_local(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self { - let local_time = chrono::Local.from_local_datetime(&tm).unwrap(); - let offset_seconds = local_time.offset().local_minus_utc(); - let tz_abbr = local_time.format("%Z").to_string(); - Self::new_inner(vm, tm, isdst, offset_seconds, &tz_abbr) + fn new_local(vm: &VirtualMachine, tm: DateTime, isdst: i32) -> PyResult { + tm.to_zoned(TimeZone::system()) + .map(|tm| Self::new_inner(vm, tm, isdst)) + .map_err(|_| { + vm.new_overflow_error("timestamp is ambiguous for the system timezone") + }) } #[cfg(not(any(unix, windows)))] - fn to_date_time(&self, vm: &VirtualMachine) -> PyResult { - let invalid_overflow = || vm.new_overflow_error("mktime argument out of range"); - let invalid_value = || vm.new_value_error("invalid struct_time parameter"); - + fn to_date_time(&self, vm: &VirtualMachine) -> PyResult { macro_rules! field { ($field:ident) => { self.$field.clone().try_into_value(vm)? }; } - let dt = NaiveDateTime::new( - NaiveDate::from_ymd_opt(field!(tm_year), field!(tm_mon), field!(tm_mday)) - .ok_or_else(invalid_value)?, - NaiveTime::from_hms_opt(field!(tm_hour), field!(tm_min), field!(tm_sec)) - .ok_or_else(invalid_overflow)?, - ); - Ok(dt) + DateTime::new( + field!(tm_year), + field!(tm_mon), + field!(tm_mday), + field!(tm_hour), + field!(tm_min), + field!(tm_sec), + 0, + ) + .map_err(|_| vm.new_overflow_error("mktime argument out of range")) } }