Skip to content
Open
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
58 changes: 45 additions & 13 deletions src/uu/date/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@

use uucore::parser::shortcut_value_parser::ShortcutValueParser;

/// OHOS helper: pass through the system time zone ID returned by

Check warning on line 35 in src/uu/date/src/date.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'OHOS' (file:'src/uu/date/src/date.rs', line:35)
/// TimeService (OH_TimeService_GetTimeZone, e.g. "Asia/Shanghai") and
/// resolve it against the embedded IANA tzdata (jiff-tzdb) so that

Check warning on line 37 in src/uu/date/src/date.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'tzdb' (file:'src/uu/date/src/date.rs', line:37)

Check warning on line 37 in src/uu/date/src/date.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'tzdata' (file:'src/uu/date/src/date.rs', line:37)
/// historial DST rules and transitions are preserved. jiff's
/// `try_system()` is useless on OHOS because both `/etc/localtime` and
/// the zoneinfo dirs are absent.
Expand Down Expand Up @@ -497,25 +497,13 @@

let date = if is_empty_or_whitespace || input == "-" || is_military_j {
// Treat empty string, single hyphen, or 'J' as midnight today in local time
let date_part =
strtime::format("%F", &now).unwrap_or_else(|_| String::from("1970-01-01"));
let offset = if settings.utc {
String::from("+00:00")
} else {
strtime::format("%:z", &now).unwrap_or_default()
};
let composed = if offset.is_empty() {
format!("{date_part} 00:00")
} else {
format!("{date_part} 00:00 {offset}")
};
if settings.debug {
let _ = writeln!(
stderr(),
"date: warning: using midnight as starting time: 00:00:00"
);
}
parse(&composed, false)
parse(&midnight_today(&now, settings.utc), false)
} else if let Some((total_hours, day_delta)) = military_tz_with_offset {
// Military timezone with optional hour offset
// Convert to UTC time: midnight + military_tz_offset + additional_hours
Expand Down Expand Up @@ -576,6 +564,7 @@
DateSource::Stdin => parse_dates_from_reader(
std::io::stdin(),
&now,
settings.utc,
DebugOptions::new(settings.debug, true),
allow_extended,
),
Expand All @@ -590,6 +579,7 @@
parse_dates_from_reader(
file,
&now,
settings.utc,
DebugOptions::new(settings.debug, true),
allow_extended,
)
Expand Down Expand Up @@ -1144,25 +1134,67 @@
Some(zoned.with_time_zone(now.time_zone().clone()))
}

/// Whether a date string only asks for midnight today: it is empty, whitespace
/// (possibly a parenthesized comment), or a lone `-`.
fn is_midnight_today_input(input: &str) -> bool {
let input = strip_parenthesized_comments(input);
let input = input.trim();
input.is_empty() || input == "-"
}

/// The date string for midnight today, in the local time zone or in UTC.
///
/// GNU parses an empty date string (or a lone `-`) as midnight today; this is
/// the equivalent input for our parser. The start of the day is resolved in
/// the time zone itself, because on a DST transition day the UTC offset at
/// midnight is not the offset right now.
fn midnight_today(now: &Zoned, utc: bool) -> String {
let today = if utc {
now.with_time_zone(TimeZone::UTC)
} else {
now.clone()
};
let midnight = today.start_of_day().unwrap_or(today);
strtime::format("%F %H:%M %:z", &midnight).unwrap_or_else(|_| String::from("1970-01-01 00:00"))
}

/// Helper function to parse dates from a line-based reader (stdin or file)
///
/// Takes any `Read` source, reads it line by line, and parses each line as a date.
/// Returns a boxed iterator over the parse results.
fn parse_dates_from_reader<R: Read + 'static>(
reader: R,
now: &Zoned,
utc: bool,
dbg_opts: DebugOptions,
allow_extended: bool,
) -> Box<
dyn Iterator<Item = Result<ParsedDateTime, (String, parse_datetime::ParseDateTimeError)>> + '_,
> {
let midnight = midnight_today(now, utc);
let lines = BufReader::new(reader).split(b'\n');
Box::new(lines.map_while(Result::ok).map(move |mut bytes| {
// Strip a trailing '\r' (CRLF input; GNU's lexer ignores it too)
if bytes.last() == Some(&b'\r') {
bytes.pop();
}
match String::from_utf8(bytes) {
// GNU compatibility: an empty (or whitespace-only) line and a lone
// hyphen are midnight today, just like `-d ''`, not the current time.
Ok(s) if is_midnight_today_input(&s) => {
if dbg_opts.debug {
let _ = writeln!(
stderr(),
"date: warning: using midnight as starting time: 00:00:00"
);
Comment on lines +1186 to +1189

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
let _ = writeln!(
stderr(),
"date: warning: using midnight as starting time: 00:00:00"
);
show_error!("date: warning: using midnight as starting time: 00:00:00");

}
parse_date(
&midnight,
now,
DebugOptions::new(dbg_opts.debug, false),
allow_extended,
)
}
Ok(s) => parse_date(s, now, dbg_opts, allow_extended),
// Report lines with invalid UTF-8 (with non-printable bytes
// octal-escaped like GNU) instead of silently stopping the input
Expand Down
28 changes: 28 additions & 0 deletions tests/by-util/test_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,34 @@ fn test_date_stdin_invalid_utf8_line() {
.stderr_contains("date: invalid date 'Hello\\377x'");
}

#[test]
fn test_date_file_blank_line_is_midnight() {
// GNU treats an empty (or whitespace-only) line and a lone `-` the same
// way as `-d ''`: midnight today, not the current time.
new_ucmd!()
.env("TZ", "UTC0")
.args(&["-f", "-", "+%T"])
.pipe_in("\n \n-\n2023-03-27 08:30:00\n\r\n")
.succeeds()
.stdout_is("00:00:00\n00:00:00\n00:00:00\n08:30:00\n00:00:00\n");
}

#[test]
fn test_date_file_blank_line_is_midnight_in_local_time() {
new_ucmd!()
.env("TZ", "Asia/Tokyo")
.args(&["-f", "-", "+%T %Z"])
.pipe_in("\n")
.succeeds()
.stdout_is("00:00:00 JST\n");
new_ucmd!()
.env("TZ", "Asia/Tokyo")
.args(&["-u", "-f", "-", "+%T %Z"])
.pipe_in("\n")
.succeeds()
.stdout_is("00:00:00 UTC\n");
}

#[test]
fn test_date_for_file_mtime() {
let (at, mut ucmd) = at_and_ucmd!();
Expand Down
Loading