date: switch from chrono to jiff

Also adds cargo dependency.
This commit is contained in:
Nicolas Boichat 2025-04-28 09:50:51 +08:00
parent 718b1a4ac7
commit 301e33cfe3
5 changed files with 180 additions and 50 deletions

51
Cargo.lock generated
View file

@ -1347,6 +1347,47 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.59.0",
]
[[package]]
name = "jiff-static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jiff-tzdb"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
[[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 = "js-sys"
version = "0.3.77"
@ -1849,6 +1890,15 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -2818,6 +2868,7 @@ version = "0.1.0"
dependencies = [
"chrono",
"clap",
"jiff",
"libc",
"parse_datetime",
"uucore",

View file

@ -304,6 +304,11 @@ hostname = "0.4"
iana-time-zone = "0.1.57"
indicatif = "0.17.8"
itertools = "0.14.0"
jiff = { version = "0.2.10", default-features = false, features = [
"std",
"alloc",
"tz-system",
] }
libc = "0.2.172"
linux-raw-sys = "0.9"
lscolors = { version = "0.20.0", default-features = false, features = [

57
fuzz/Cargo.lock generated
View file

@ -644,6 +644,47 @@ dependencies = [
"either",
]
[[package]]
name = "jiff"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys",
]
[[package]]
name = "jiff-static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jiff-tzdb"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
[[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 = "jobserver"
version = "0.1.33"
@ -895,6 +936,21 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@ -1316,6 +1372,7 @@ version = "0.1.0"
dependencies = [
"chrono",
"clap",
"jiff",
"libc",
"parse_datetime",
"uucore",

View file

@ -1,4 +1,4 @@
# spell-checker:ignore datetime
# spell-checker:ignore datetime tzdb zoneinfo
[package]
name = "uu_date"
description = "date ~ (uutils) display or set the current time"
@ -19,8 +19,13 @@ workspace = true
path = "src/date.rs"
[dependencies]
chrono = { workspace = true }
clap = { workspace = true }
chrono = { workspace = true } # TODO: Eventually we'll want to remove this
jiff = { workspace = true, features = [
"tzdb-bundle-platform",
"tzdb-zoneinfo",
"tzdb-concatenated",
] }
uucore = { workspace = true, features = ["custom-tz-fmt", "parser"] }
parse_datetime = { workspace = true }

View file

@ -3,19 +3,17 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
use chrono::format::{Item, StrftimeItems};
use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, Utc};
#[cfg(windows)]
use chrono::{Datelike, Timelike};
use clap::{Arg, ArgAction, Command};
use jiff::fmt::strtime;
use jiff::tz::TimeZone;
use jiff::{SignedDuration, Timestamp, Zoned};
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
use libc::{CLOCK_REALTIME, clock_settime, timespec};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use uucore::custom_tz_fmt::custom_time_format;
use uucore::display::Quotable;
use uucore::error::FromIo;
use uucore::error::{UResult, USimpleError};
@ -75,7 +73,7 @@ struct Settings {
utc: bool,
format: Format,
date_source: DateSource,
set_to: Option<DateTime<FixedOffset>>,
set_to: Option<Zoned>,
}
/// Various ways of displaying the date
@ -93,7 +91,7 @@ enum DateSource {
Custom(String),
File(PathBuf),
Stdin,
Human(TimeDelta),
Human(SignedDuration),
}
enum Iso8601Format {
@ -167,9 +165,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
};
let date_source = if let Some(date) = matches.get_one::<String>(OPT_DATE) {
let ref_time = Local::now();
if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date.as_str()) {
let duration = new_time.signed_duration_since(ref_time);
if let Ok(duration) = parse_offset(date.as_str()) {
DateSource::Human(duration)
} else {
DateSource::Custom(date.into())
@ -203,39 +199,37 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if let Some(date) = settings.set_to {
// All set time functions expect UTC datetimes.
let date: DateTime<Utc> = if settings.utc {
date.with_timezone(&Utc)
let date = if settings.utc {
date.with_time_zone(TimeZone::UTC)
} else {
date.into()
date
};
return set_system_datetime(date);
} else {
// Get the current time, either in the local time zone or UTC.
let now: DateTime<FixedOffset> = if settings.utc {
let now = Utc::now();
now.with_timezone(&now.offset().fix())
let now = if settings.utc {
Timestamp::now().to_zoned(TimeZone::UTC)
} else {
let now = Local::now();
now.with_timezone(now.offset())
Zoned::now()
};
// Iterate over all dates - whether it's a single date or a file.
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
DateSource::Custom(ref input) => {
let date = parse_date(input.clone());
let date = parse_date(input);
let iter = std::iter::once(date);
Box::new(iter)
}
DateSource::Human(relative_time) => {
// Double check the result is overflow or not of the current_time + relative_time
// it may cause a panic of chrono::datetime::DateTime add
match now.checked_add_signed(relative_time) {
Some(date) => {
match now.checked_add(relative_time) {
Ok(date) => {
let iter = std::iter::once(Ok(date));
Box::new(iter)
}
None => {
Err(_) => {
return Err(USimpleError::new(
1,
format!("invalid date {relative_time}"),
@ -272,23 +266,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// Format all the dates
for date in dates {
match date {
Ok(date) => {
let format_string = custom_time_format(format_string);
// Hack to work around panic in chrono,
// TODO - remove when a fix for https://github.com/chronotope/chrono/issues/623 is released
let format_items = StrftimeItems::new(format_string.as_str());
if format_items.clone().any(|i| i == Item::Error) {
// TODO: Switch to lenient formatting.
Ok(date) => match strtime::format(format_string, &date) {
Ok(s) => println!("{s}"),
Err(e) => {
return Err(USimpleError::new(
1,
format!("invalid format {}", format_string.replace("%f", "%N")),
format!("invalid format {} ({e})", format_string),
));
}
let formatted = date
.format_with_items(format_items)
.to_string()
.replace("%f", "%N");
println!("{formatted}");
}
},
Err((input, _err)) => show!(USimpleError::new(
1,
format!("invalid date {}", input.quote())
@ -388,13 +375,13 @@ fn make_format_string(settings: &Settings) -> &str {
Iso8601Format::Hours => "%FT%H%:z",
Iso8601Format::Minutes => "%FT%H:%M%:z",
Iso8601Format::Seconds => "%FT%T%:z",
Iso8601Format::Ns => "%FT%T,%f%:z",
Iso8601Format::Ns => "%FT%T,%N%:z",
},
Format::Rfc5322 => "%a, %d %h %Y %T %z",
Format::Rfc3339(ref fmt) => match *fmt {
Rfc3339Format::Date => "%F",
Rfc3339Format::Seconds => "%F %T%:z",
Rfc3339Format::Ns => "%F %T.%f%:z",
Rfc3339Format::Ns => "%F %T.%N%:z",
},
Format::Custom(ref fmt) => fmt,
Format::Default => "%a %b %e %X %Z %Y",
@ -403,19 +390,43 @@ fn make_format_string(settings: &Settings) -> &str {
/// Parse a `String` into a `DateTime`.
/// If it fails, return a tuple of the `String` along with its `ParseError`.
// TODO: Convert `parse_datetime` to jiff and remove wrapper from chrono to jiff structures.
fn parse_date<S: AsRef<str> + Clone>(
s: S,
) -> Result<DateTime<FixedOffset>, (String, parse_datetime::ParseDateTimeError)> {
parse_datetime::parse_datetime(s.as_ref()).map_err(|e| (s.as_ref().into(), e))
) -> Result<Zoned, (String, parse_datetime::ParseDateTimeError)> {
match parse_datetime::parse_datetime(s.as_ref()) {
Ok(date) => {
let timestamp =
Timestamp::new(date.timestamp(), date.timestamp_subsec_nanos() as i32).unwrap();
Ok(Zoned::new(timestamp, TimeZone::UTC))
}
Err(e) => Err((s.as_ref().into(), e)),
}
}
// TODO: Convert `parse_datetime` to jiff and remove wrapper from chrono to jiff structures.
// Also, consider whether parse_datetime::parse_datetime_at_date can be renamed to something
// like parse_datetime::parse_offset, instead of doing some addition/subtraction.
fn parse_offset(date: &str) -> Result<SignedDuration, ()> {
let ref_time = chrono::Local::now();
if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date) {
let duration = new_time.signed_duration_since(ref_time);
Ok(SignedDuration::new(
duration.num_seconds(),
duration.subsec_nanos(),
))
} else {
Err(())
}
}
#[cfg(not(any(unix, windows)))]
fn set_system_datetime(_date: DateTime<Utc>) -> UResult<()> {
fn set_system_datetime(_date: Zoned) -> UResult<()> {
unimplemented!("setting date not implemented (unsupported target)");
}
#[cfg(target_os = "macos")]
fn set_system_datetime(_date: DateTime<Utc>) -> UResult<()> {
fn set_system_datetime(_date: Zoned) -> UResult<()> {
Err(USimpleError::new(
1,
"setting the date is not supported by macOS".to_string(),
@ -423,7 +434,7 @@ fn set_system_datetime(_date: DateTime<Utc>) -> UResult<()> {
}
#[cfg(target_os = "redox")]
fn set_system_datetime(_date: DateTime<Utc>) -> UResult<()> {
fn set_system_datetime(_date: Zoned) -> UResult<()> {
Err(USimpleError::new(
1,
"setting the date is not supported by Redox".to_string(),
@ -436,10 +447,11 @@ fn set_system_datetime(_date: DateTime<Utc>) -> UResult<()> {
/// `<https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html>`
/// `<https://linux.die.net/man/3/clock_settime>`
/// `<https://www.gnu.org/software/libc/manual/html_node/Time-Types.html>`
fn set_system_datetime(date: DateTime<Utc>) -> UResult<()> {
fn set_system_datetime(date: Zoned) -> UResult<()> {
let ts = date.timestamp();
let timespec = timespec {
tv_sec: date.timestamp() as _,
tv_nsec: date.timestamp_subsec_nanos() as _,
tv_sec: ts.as_second() as _,
tv_nsec: ts.subsec_nanosecond() as _,
};
let result = unsafe { clock_settime(CLOCK_REALTIME, &timespec) };
@ -456,7 +468,7 @@ fn set_system_datetime(date: DateTime<Utc>) -> UResult<()> {
/// See here for more:
/// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime
/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
fn set_system_datetime(date: DateTime<Utc>) -> UResult<()> {
fn set_system_datetime(date: Zoned) -> UResult<()> {
let system_time = SYSTEMTIME {
wYear: date.year() as u16,
wMonth: date.month() as u16,
@ -467,7 +479,7 @@ fn set_system_datetime(date: DateTime<Utc>) -> UResult<()> {
wMinute: date.minute() as u16,
wSecond: date.second() as u16,
// TODO: be careful of leap seconds - valid range is [0, 999] - how to handle?
wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as u16,
wMilliseconds: ((date.subsec_nanosecond() / 1_000_000) % 1000) as u16,
};
let result = unsafe { SetSystemTime(&system_time) };