Repository: uutils/coreutils (language: rust)

Issue:
`date` behaves differently with no args
`date` behaves differently than coreutils when no argument is passed,  if the goal of the project is to behave a 100% like the gnu coreutils, this should be fixed. here are two examples , it should be pretty easy to reproduce unless it's a weird problem with both my systems (manjaro and endeavour os)

---

Exemple 1 :

![image](https://user-images.githubusercontent.com/91835054/181703296-c9459e5b-d1a0-4554-961a-6a2e864a72f4.png)
`$./date`
`Fri Jul 29 09:09:10 2022 `
`$date`
`$Fri 29 Jul 2022 09:09:12 CEST`

---

Exemple 2 :

![image](https://user-images.githubusercontent.com/91835054/181703364-3a12750f-f1f1-44a2-8195-2c0582f27533.png)
`$./date`
`Fri Jul 29 16:40:39 2022 `
`$date`
`$Fri Jul 29 04:40:40 PM CDT 2022`

---

I looked at the code and i think it's caused by the chrono dependency not correctly detecting the current locale when formatting with %c. not sure about that though.


Discussion / hints:
Could you please replace the screen shot by some text?
Screenshots for code are terrible foe accessibility and search

Thanks 
done.

I think this is related to this issue
https://github.com/chronotope/chrono/issues/708
I’ll work on this one. Would it be okay for this project to use chrono’s `unstable-locales` feature?
I'm pretty sure this can be fixed by just changing `Format::Default => "%c"` to `Format::Default => "%a %b %d %I:%M:%S  %Z %p %Y"` in line 351. However, as of now, `%Z` does not behave as expected. I already opened an issue https://github.com/chronotope/chrono/issues/749 
I don't think it does, this is from after changing to `"%a %b %d %I:%M:%S %Z %p %Y"`:
```
$ LC_ALL=fr_FR.UTF-8 cargo run date
Sat Jul 30 20:38:57 2022
$ LC_ALL=fr_FR.UTF-8 date
sam. 30 juil. 2022 20:39:25 EDT
```
chrono's `format_localized` isn't the same as GNU either though:
```bash
$ LC_ALL=en_US.UTF-8 cargo run date
Sat 30 Jul 2022 08:48:56 PM -04:00

$ LC_ALL=en_US.UTF-8 date
Sat Jul 30 08:49:01 PM EDT 2022
```
It's mostly the same, but [pure-rust-locales](https://crates.io/crates/pure-rust-locales), which chrono uses for `unstable-locales`, differs from GNU `date` for some locales.
can we directly call libc ? I have a fix if unsafe code is allowed but obviously it's not ideal
@rayan-mazouz `libc` is fine, we call it all throughout the codebase. But if there is a wrapper function in `nix` or some cross-platform solution, that would be preferable.
@tertsdiepraam great thanks, my fix works for linux, I'll try to make it work on mac, windows and redox, then do a merge request
I wonder if we can start using icu4x for this. Specifically the [`icu_datetime`](https://docs.rs/icu/latest/icu/datetime/index.html) component. Though, it might not be flexible enough yet.
@rayan-mazouz is your fix for linux available on some branch? I'd like to investigate this issue.
sorry, it is not.
There seems to be a workaround to get locale.
https://github.com/chronotope/chrono/issues/960#issuecomment-1537353803

I have raised a PR with the above fix: https://github.com/uutils/coreutils/pull/5164

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index c3f84e90be4..b564af6c407 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -302,6 +302,28 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
+[[package]]
+name = "chrono-tz"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e"
+dependencies = [
+ "chrono",
+ "chrono-tz-build",
+ "phf",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f"
+dependencies = [
+ "parse-zoneinfo",
+ "phf",
+ "phf_codegen",
+]
+
 [[package]]
 name = "clang-sys"
 version = "1.8.1"
@@ -1582,6 +1604,15 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
+[[package]]
+name = "parse-zoneinfo"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
+dependencies = [
+ "regex",
+]
+
 [[package]]
 name = "parse_datetime"
 version = "0.6.0"
@@ -2590,7 +2621,9 @@ name = "uu_date"
 version = "0.0.29"
 dependencies = [
  "chrono",
+ "chrono-tz",
  "clap",
+ "iana-time-zone",
  "libc",
  "parse_datetime",
  "uucore",
diff --git a/Cargo.toml b/Cargo.toml
index 8fdb15d5d6f..2fb991d5705 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -280,6 +280,8 @@ chrono = { version = "0.4.38", default-features = false, features = [
 ] }
 clap = { version = "4.5", features = ["wrap_help", "cargo"] }
 clap_complete = "4.4"
+chrono-tz = "0.8.3"
+iana-time-zone = "0.1.57"
 clap_mangen = "0.2"
 compare = "0.1.0"
 coz = { version = "0.1.3" }
diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml
index 75766507655..87e8d383a75 100644
--- a/src/uu/date/Cargo.toml
+++ b/src/uu/date/Cargo.toml
@@ -22,6 +22,8 @@ chrono = { workspace = true }
 clap = { workspace = true }
 uucore = { workspace = true }
 parse_datetime = { workspace = true }
+chrono-tz = { workspace = true }
+iana-time-zone = { workspace = true }
 
 [target.'cfg(unix)'.dependencies]
 libc = { workspace = true }
diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs
index 766e79bd497..d91f8f82c71 100644
--- a/src/uu/date/src/date.rs
+++ b/src/uu/date/src/date.rs
@@ -6,10 +6,12 @@
 // spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
 
 use chrono::format::{Item, StrftimeItems};
-use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, Utc};
+use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, TimeZone, Utc};
 #[cfg(windows)]
 use chrono::{Datelike, Timelike};
+use chrono_tz::{OffsetName, Tz};
 use clap::{crate_version, Arg, ArgAction, Command};
+use iana_time_zone::get_timezone;
 #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
 use libc::{clock_settime, timespec, CLOCK_REALTIME};
 use std::fs::File;
@@ -272,8 +274,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         for date in dates {
             match date {
                 Ok(date) => {
+                    // TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970
+                    let tz = match std::env::var("TZ") {
+                        // TODO Support other time zones...
+                        Ok(s) if s == "UTC0" => Tz::Etc__UTC,
+                        _ => match get_timezone() {
+                            Ok(tz_str) => tz_str.parse().unwrap(),
+                            Err(_) => Tz::Etc__UTC,
+                        },
+                    };
+                    let offset = tz.offset_from_utc_date(&Utc::now().date_naive());
+                    let tz_abbreviation = offset.abbreviation();
                     // GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f`
-                    let format_string = &format_string.replace("%N", "%f");
+                    let format_string = &format_string
+                        .replace("%N", "%f")
+                        .replace("%Z", tz_abbreviation);
                     // Refuse to pass this string to chrono as it is crashing in this crate
                     if format_string.contains("%#z") {
                         return Err(USimpleError::new(
@@ -403,7 +418,7 @@ fn make_format_string(settings: &Settings) -> &str {
             Rfc3339Format::Ns => "%F %T.%f%:z",
         },
         Format::Custom(ref fmt) => fmt,
-        Format::Default => "%c",
+        Format::Default => "%a %b %e %X %Z %Y",
     }
 }
 


Test patch:
diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs
index 553414af853..d0a9c09a201 100644
--- a/tests/by-util/test_date.rs
+++ b/tests/by-util/test_date.rs
@@ -144,11 +144,12 @@ fn test_date_utc() {
 #[test]
 fn test_date_utc_issue_6495() {
     new_ucmd!()
+        .env("TZ", "UTC0")
         .arg("-u")
         .arg("-d")
         .arg("@0")
         .succeeds()
-        .stdout_is("Thu Jan  1 00:00:00 1970\n");
+        .stdout_is("Thu Jan  1 00:00:00 UTC 1970\n");
 }
 
 #[test]
@@ -423,16 +424,18 @@ fn test_invalid_date_string() {
 #[test]
 fn test_date_one_digit_date() {
     new_ucmd!()
+        .env("TZ", "UTC0")
         .arg("-d")
         .arg("2000-1-1")
         .succeeds()
-        .stdout_contains("Sat Jan  1 00:00:00 2000");
+        .stdout_only("Sat Jan  1 00:00:00 UTC 2000\n");
 
     new_ucmd!()
+        .env("TZ", "UTC0")
         .arg("-d")
         .arg("2000-1-4")
         .succeeds()
-        .stdout_contains("Tue Jan  4 00:00:00 2000");
+        .stdout_only("Tue Jan  4 00:00:00 UTC 2000\n");
 }
 
 #[test]
@@ -464,6 +467,7 @@ fn test_date_parse_from_format() {
 #[test]
 fn test_date_from_stdin() {
     new_ucmd!()
+        .env("TZ", "UTC0")
         .arg("-f")
         .arg("-")
         .pipe_in(
@@ -473,8 +477,8 @@ fn test_date_from_stdin() {
         )
         .succeeds()
         .stdout_is(
-            "Mon Mar 27 08:30:00 2023\n\
-             Sat Apr  1 12:00:00 2023\n\
-             Sat Apr 15 18:30:00 2023\n",
+            "Mon Mar 27 08:30:00 UTC 2023\n\
+             Sat Apr  1 12:00:00 UTC 2023\n\
+             Sat Apr 15 18:30:00 UTC 2023\n",
         );
 }