Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Use system timezone database when available #7849

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

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions 4 .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -648,10 +648,6 @@ jobs:
;;
esac
outputs CARGO_TEST_OPTIONS
# ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml")
if [ "${CARGO_CMD}" = 'cross' ] && [ ! -e "Cross.toml" ] ; then
printf "[build.env]\npassthrough = [\"CI\", \"RUST_BACKTRACE\", \"CARGO_TERM_COLOR\"]\n" > Cross.toml
fi
# * executable for `strip`?
STRIP="strip"
case ${{ matrix.job.target }} in
Expand Down
11 changes: 11 additions & 0 deletions 11 Cargo.lock

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

3 changes: 2 additions & 1 deletion 3 Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# coreutils (uutils)
# * see the repository LICENSE, README, and CONTRIBUTING files for more information

# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap procfs uuhelp startswith constness expl
# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap procfs tzfile uuhelp startswith constness expl

[package]
name = "coreutils"
Expand Down Expand Up @@ -341,6 +341,7 @@ terminal_size = "0.4.0"
textwrap = { version = "0.16.1", features = ["terminal_size"] }
thiserror = "2.0.3"
time = { version = "0.3.36" }
tzfile = "0.1.3"
unicode-segmentation = "1.11.0"
unicode-width = "0.2.0"
utf-8 = "0.7.6"
Expand Down
7 changes: 7 additions & 0 deletions 7 Cross.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# spell-checker:ignore (misc) dpkg noninteractive tzdata
[build]
pre-build = [
"apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install tzdata",
]
[build.env]
passthrough = ["CI", "RUST_BACKTRACE", "CARGO_TERM_COLOR"]
17 changes: 17 additions & 0 deletions 17 fuzz/Cargo.lock

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

5 changes: 3 additions & 2 deletions 5 src/uucore/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# spell-checker:ignore (features) bigdecimal zerocopy extendedbigdecimal
# spell-checker:ignore (features) bigdecimal zerocopy extendedbigdecimal tzfile

[package]
name = "uucore"
Expand Down Expand Up @@ -59,6 +59,7 @@ regex = { workspace = true, optional = true }
bigdecimal = { workspace = true, optional = true }
num-traits = { workspace = true, optional = true }
selinux = { workspace = true, optional = true }
tzfile = { workspace = true, optional = true }

[target.'cfg(unix)'.dependencies]
walkdir = { workspace = true, optional = true }
Expand Down Expand Up @@ -134,6 +135,6 @@ utf8 = []
utmpx = ["time", "time/macros", "libc", "dns-lookup"]
version-cmp = []
wide = []
custom-tz-fmt = ["chrono", "chrono-tz", "iana-time-zone"]
custom-tz-fmt = ["chrono", "chrono-tz", "tzfile", "iana-time-zone"]
tty = []
uptime = ["chrono", "libc", "windows-sys", "utmpx", "utmp-classic", "thiserror"]
26 changes: 26 additions & 0 deletions 26 src/uucore/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (vars) tzfile zoneinfo

use std::env;

pub fn main() {
// If custom-tz-fmt feature is enabled, set an "embed_tz" config to decide whether
// to embed a full timezone database, or we can just use `tzfile` (which reads
// from /usr/share/zoneinfo).
println!("cargo::rustc-check-cfg=cfg(embed_tz)");
let custom_tz_fmt = env::var("CARGO_FEATURE_CUSTOM_TZ_FMT");
if custom_tz_fmt.is_ok() {
// TODO: It might be worth considering making this an option:
// - People concerned with executable size may be willing to forgo timezone database
// completely.
// - Some other people may want to use an embedded timezone database _anyway_, instead
// of the one provided by the system.
if cfg!(windows) || cfg!(target_os = "android") {
println!("cargo::rustc-cfg=embed_tz");
}
}
}
99 changes: 87 additions & 12 deletions 99 src/uucore/src/lib/features/custom_tz_fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,66 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use chrono::{TimeZone, Utc};
use chrono_tz::{OffsetName, Tz};
// spell-checker:ignore (misc) tzfile WARST zoneinfo

use chrono::Local;
use iana_time_zone::get_timezone;

#[cfg(embed_tz)]
use chrono_tz::{ParseError, Tz};
#[cfg(not(embed_tz))]
use tzfile::Tz;

#[cfg(embed_tz)]
fn str_to_timezone(str: &str) -> Result<Tz, ParseError> {
str.parse()
}

#[cfg(not(embed_tz))]
fn str_to_timezone(str: &str) -> Result<Tz, std::io::Error> {
Tz::named(str)
}

/// Get the alphabetic abbreviation of the current timezone.
///
/// For example, "UTC" or "CET" or "PDT"
fn timezone_abbreviation() -> String {
let tz = match std::env::var("TZ") {
// TODO Support other time zones...
Ok(s) if s == "UTC0" || s.is_empty() => Tz::Etc__UTC,
///
/// We need this function even for local dates as chrono(_tz) does not provide a
/// way to convert Local to a fully specified timezone with abbreviation
/// (<https://github.com/chronotope/chrono-tz/issues/13>).
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should link to chronotope/chrono#960 here, instead.

///
/// `timezone` is an optional parameter, if None, TZ environment variable is used.
//
// TODO(#7659): This should take into account the date to be printed.
// - Timezone abbreviation depends on daylight savings.
// - We should do no special conversion for UTC dates.
// - If our custom logic fails, but chrono obtained a non-UTC local timezone
// from the system, we should not just return UTC.
fn timezone_abbreviation(timezone: Option<&str>) -> String {
let utc = str_to_timezone("Etc/UTC").unwrap();
// We need this logic as `iana_time_zone::get_timezone` does not look
// at TZ variable: https://github.com/strawlab/iana-time-zone/issues/118.
let tz = match timezone.or(std::env::var("TZ").as_deref().ok()) {
// TODO: This is not fully exhaustive, we should understand how to handle
// invalid TZ values and more complex POSIX-specified values:
// https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
Some(s) if s == "UTC0" || s.is_empty() => utc,
Some(s) => str_to_timezone(s).unwrap_or(utc),
_ => match get_timezone() {
Ok(tz_str) => tz_str.parse().unwrap(),
Err(_) => Tz::Etc__UTC,
Ok(tz_str) => str_to_timezone(&tz_str).unwrap_or(utc),
Err(_) => utc,
},
};

let offset = tz.offset_from_utc_date(&Utc::now().date_naive());
offset.abbreviation().unwrap_or("UTC").to_string()
#[cfg(not(embed_tz))]
let tz = &tz;

// TODO: It looks a bit absurd to use `%Z` here and manually expand it
// in `custom_time_format`, instead of directly modifying the date to be
// formatted. We should create another function that returns
// `localtime.with_timezone(&tz)` (a local time with fully specified
// timezone abbreviation).
Local::now().with_timezone(&tz).format("%Z").to_string()
}

/// Adapt the given string to be accepted by the chrono library crate.
Expand All @@ -37,7 +78,7 @@ pub fn custom_time_format(fmt: &str) -> String {
// TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970
// GNU `date` uses `%N` for nano seconds, however the `chrono` crate uses `%f`.
fmt.replace("%N", "%f")
.replace("%Z", timezone_abbreviation().as_ref())
.replace("%Z", timezone_abbreviation(None).as_ref())
}

#[cfg(test)]
Expand All @@ -53,6 +94,40 @@ mod tests {
custom_time_format("%Y-%m-%d %H-%M-%S.%N"),
"%Y-%m-%d %H-%M-%S.%f"
);
assert_eq!(custom_time_format("%Z"), timezone_abbreviation());
assert_eq!(custom_time_format("%Z"), timezone_abbreviation(None));
}

#[test]
fn test_timezone_abbreviation() {
// Test if a timezone abbreviation is one of the values in ok_abbr.
// TODO(#7659): We should modify this test to 2 fixed dates, one that falls in
// daylight savings, and the other not. But right now the abbreviation depends
// on the current time.
fn test_zone(zone: &str, ok_abbr: &[&str]) {
let abbr = timezone_abbreviation(Some(zone));
assert!(
ok_abbr.contains(&abbr.as_str()),
"Timezone {zone} abbreviation {abbr} is not contained within [{}].",
ok_abbr.join(", ")
)
}

// Test a few random timezones.
test_zone("US/Pacific", &["PST", "PDT"]);
test_zone("Europe/Zurich", &["CEST", "CET"]);
test_zone("Africa/Cairo", &["EET", "EEST"]); // spell-checker:disable-line
test_zone("Asia/Taipei", &["CST"]);
test_zone("Australia/Sydney", &["AEDT", "AEST"]); // spell-checker:disable-line
// Looks like Pacific/Tahiti is provided in /usr/share/zoneinfo, but not in chrono-tz (yet).
//test_zone("Pacific/Tahiti", &["-10"]); // No abbreviation?
test_zone("Antarctica/South_Pole", &["NZDT", "NZST"]); // spell-checker:disable-line

// TODO: This is not fully exhaustive, we should understand how to handle
// invalid TZ values and more complex POSIX-specified values:
// https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
// Examples:
//test_zone("WART4WARST,J1/0,J365/25", &["WART", "WARST"])
//test_zone(":Europe/Zurich", &["CEST", "CET"]);
//test_zone("invalid", &["invalid"]);
}
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.