Skip to content

Commit 2845546

Browse files
committed
date: add support for the 'J' military timezone
1 parent b17129d commit 2845546

File tree

2 files changed

+42
-9
lines changed

2 files changed

+42
-9
lines changed

src/uu/date/src/date.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,35 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
205205
// Iterate over all dates - whether it's a single date or a file.
206206
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
207207
DateSource::Human(ref input) => {
208+
// GNU compatibility (Military timezone 'J'):
209+
// 'J' is reserved for local time in military timezones.
210+
// GNU date accepts it and treats it as midnight today (00:00:00).
211+
let is_military_j = input.eq_ignore_ascii_case("j");
212+
208213
// GNU compatibility (Pure numbers in date strings):
209214
// - Manual: https://www.gnu.org/software/coreutils/manual/html_node/Pure-numbers-in-date-strings.html
210-
// - Semantics: a pure decimal number denotes todays time-of-day (HH or HHMM).
215+
// - Semantics: a pure decimal number denotes today's time-of-day (HH or HHMM).
211216
// Examples: "0"/"00" => 00:00 today; "7"/"07" => 07:00 today; "0700" => 07:00 today.
212217
// For all other forms, fall back to the general parser.
213218
let is_pure_digits =
214219
!input.is_empty() && input.len() <= 4 && input.chars().all(|c| c.is_ascii_digit());
215220

216-
let date = if is_pure_digits {
221+
let date = if is_military_j {
222+
// Treat 'J' as midnight today (00:00:00) in local time
223+
let date_part =
224+
strtime::format("%F", &now).unwrap_or_else(|_| String::from("1970-01-01"));
225+
let offset = if settings.utc {
226+
String::from("+00:00")
227+
} else {
228+
strtime::format("%:z", &now).unwrap_or_default()
229+
};
230+
let composed = if offset.is_empty() {
231+
format!("{date_part} 00:00")
232+
} else {
233+
format!("{date_part} 00:00 {offset}")
234+
};
235+
parse_date(composed)
236+
} else if is_pure_digits {
217237
// Derive HH and MM from the input
218238
let (hh_opt, mm_opt) = if input.len() <= 2 {
219239
(input.parse::<u32>().ok(), Some(0u32))

tests/by-util/test_date.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -946,17 +946,30 @@ fn test_date_tz_abbreviation_unknown() {
946946
}
947947

948948
#[test]
949-
#[ignore = "we reject 'J', GNU treats as midnight"]
950-
fn test_date_fuzz_military_timezone_j() {
951-
// J is reserved for local time in military timezones
952-
// GNU date treats it as midnight, we reject it
949+
fn test_date_military_timezone_j_variations() {
950+
// Test multiple variations of 'J' input (case insensitive, with whitespace)
951+
// All should produce midnight (00:00:00)
952+
let test_cases = vec!["J", "j", " J ", " j ", "\tJ\t"];
953+
954+
for input in test_cases {
955+
new_ucmd!()
956+
.env("TZ", "UTC")
957+
.arg("-d")
958+
.arg(input)
959+
.arg("+%T")
960+
.succeeds()
961+
.stdout_is("00:00:00\n");
962+
}
963+
964+
// Test with -u flag to verify UTC behavior
953965
new_ucmd!()
954-
.env("TZ", "UTC+1")
966+
.arg("-u")
955967
.arg("-d")
956968
.arg("J")
957-
.arg("+%F %T %Z")
969+
.arg("+%T %Z")
958970
.succeeds()
959-
.stdout_contains("00:00:00");
971+
.stdout_contains("00:00:00")
972+
.stdout_contains("UTC");
960973
}
961974

962975
#[test]

0 commit comments

Comments
 (0)