Skip to content

Commit 8c6519b

Browse files
committed
test(date): use parse_date for test helper timestamp
1 parent 502f3b1 commit 8c6519b

File tree

3 files changed

+131
-35
lines changed

3 files changed

+131
-35
lines changed

.vscode/cspell.dictionaries/jargon.wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ shortcodes
136136
siginfo
137137
sigusr
138138
strcasecmp
139+
strtime
139140
subcommand
140141
subexpression
141142
submodule

src/uu/comm/src/comm.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ struct OrderChecker {
5454
has_error: bool,
5555
}
5656

57-
enum Input {
58-
Stdin(StdinLock<'static>),
57+
enum Input<'a> {
58+
Stdin(StdinLock<'a>),
5959
FileIn(BufReader<File>),
6060
}
6161

62-
impl Input {
62+
impl Input<'_> {
6363
fn stdin() -> Self {
6464
Self::Stdin(stdin().lock())
6565
}
@@ -69,13 +69,13 @@ impl Input {
6969
}
7070
}
7171

72-
struct LineReader {
72+
struct LineReader<'a> {
7373
line_ending: LineEnding,
74-
input: Input,
74+
input: Input<'a>,
7575
}
7676

77-
impl LineReader {
78-
fn new(input: Input, line_ending: LineEnding) -> Self {
77+
impl<'a> LineReader<'a> {
78+
fn new(input: Input<'a>, line_ending: LineEnding) -> Self {
7979
Self { line_ending, input }
8080
}
8181

@@ -178,7 +178,12 @@ pub fn are_files_identical(path1: &Path, path2: &Path) -> io::Result<bool> {
178178
}
179179
}
180180

181-
fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) -> UResult<()> {
181+
fn comm(
182+
a: &mut LineReader<'_>,
183+
b: &mut LineReader<'_>,
184+
delim: &str,
185+
opts: &ArgMatches,
186+
) -> UResult<()> {
182187
let width_col_1 = usize::from(!opts.get_flag(options::COLUMN_1));
183188
let width_col_2 = usize::from(!opts.get_flag(options::COLUMN_2));
184189

@@ -291,7 +296,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches)
291296
}
292297
}
293298

294-
fn open_file(name: &OsString, line_ending: LineEnding) -> io::Result<LineReader> {
299+
fn open_file(name: &OsString, line_ending: LineEnding) -> io::Result<LineReader<'static>> {
295300
if name == "-" {
296301
Ok(LineReader::new(Input::stdin(), line_ending))
297302
} else {

src/uu/date/src/locale.rs

Lines changed: 116 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,65 @@ cfg_langinfo! {
4444
DEFAULT_FORMAT_CACHE.get_or_init(|| {
4545
// Try to get locale format string
4646
if let Some(format) = get_locale_format_string() {
47-
let format_with_tz = ensure_timezone_in_format(&format);
48-
return Box::leak(format_with_tz.into_boxed_str());
47+
// Validate that the format is complete (contains date and time components)
48+
// On some platforms (e.g., macOS ARM64), nl_langinfo may return minimal formats
49+
if is_format_complete(&format) {
50+
let format_with_tz = ensure_timezone_in_format(&format);
51+
return Box::leak(format_with_tz.into_boxed_str());
52+
}
4953
}
5054

5155
// Fallback: use 24-hour format as safe default
5256
"%a %b %e %X %Z %Y"
5357
})
5458
}
5559

60+
/// Checks if a format string contains both date and time components.
61+
/// Returns false for minimal formats that would produce incomplete output.
62+
fn is_format_complete(format: &str) -> bool {
63+
// Check for date components (at least one of: day, month, or year)
64+
let has_date = format.contains("%d") || format.contains("%e")
65+
|| format.contains("%b") || format.contains("%B")
66+
|| format.contains("%m")
67+
|| format.contains("%Y") || format.contains("%y")
68+
|| format.contains("%F") || format.contains("%x") || format.contains("%D");
69+
70+
// Check for time components (at least one of: hour, minute, or time format)
71+
let has_time = format.contains("%H") || format.contains("%I")
72+
|| format.contains("%M")
73+
|| format.contains("%T") || format.contains("%X")
74+
|| format.contains("%R") || format.contains("%r")
75+
|| format.contains("%l") || format.contains("%k")
76+
|| format.contains("%p") || format.contains("%P");
77+
78+
has_date && has_time
79+
}
80+
5681
/// Retrieves the date/time format string from the system locale
5782
fn get_locale_format_string() -> Option<String> {
5883
unsafe {
5984
// Set locale from environment variables
6085
libc::setlocale(libc::LC_TIME, c"".as_ptr());
6186

62-
// Get the date/time format string
87+
// Try _DATE_FMT (GNU extension) first as it typically includes %Z
88+
// and is what 'locale date_fmt' returns.
89+
// On glibc, _DATE_FMT is 0x2006C (some systems use 0x2003B)
90+
#[cfg(target_os = "linux")]
91+
const _DATE_FMT: libc::nl_item = 0x2006C;
92+
93+
#[cfg(target_os = "linux")]
94+
{
95+
let date_fmt_ptr = libc::nl_langinfo(_DATE_FMT);
96+
if !date_fmt_ptr.is_null() {
97+
if let Ok(format) = CStr::from_ptr(date_fmt_ptr).to_str() {
98+
if !format.is_empty() {
99+
return Some(format.to_string());
100+
}
101+
}
102+
}
103+
}
104+
105+
// Fallback to POSIX D_T_FMT
63106
let d_t_fmt_ptr = libc::nl_langinfo(libc::D_T_FMT);
64107
if d_t_fmt_ptr.is_null() {
65108
return None;
@@ -112,6 +155,23 @@ mod tests {
112155
cfg_langinfo! {
113156
use super::*;
114157

158+
/// Helper function to expand a format string with a known test date
159+
///
160+
/// Uses a fixed test date: Monday, January 15, 2024, 14:30:45 UTC
161+
/// This allows us to validate format strings by checking their expanded output
162+
/// rather than looking for literal format codes.
163+
fn expand_format_with_test_date(format: &str) -> String {
164+
use jiff::fmt::strtime;
165+
166+
// Create test timestamp: Monday, January 15, 2024, 14:30:45 UTC
167+
// Use parse_date to get a proper Zoned timestamp (same as production code)
168+
let test_date = crate::parse_date("2024-01-15 14:30:45 UTC")
169+
.expect("Test date parse should never fail");
170+
171+
// Expand the format string with the test date
172+
strtime::format(format, &test_date).unwrap_or_default()
173+
}
174+
115175
#[test]
116176
fn test_locale_detection() {
117177
// Just verify the function doesn't panic
@@ -121,10 +181,31 @@ mod tests {
121181
#[test]
122182
fn test_default_format_contains_valid_codes() {
123183
let format = get_locale_default_format();
124-
assert!(format.contains("%a")); // abbreviated weekday
125-
assert!(format.contains("%b")); // abbreviated month
126-
assert!(format.contains("%Y") || format.contains("%y")); // year (4-digit or 2-digit)
127-
assert!(format.contains("%Z")); // timezone
184+
185+
let expanded = expand_format_with_test_date(format);
186+
187+
// Verify expanded output contains expected components
188+
// Test date: Monday, January 15, 2024, 14:30:45
189+
assert!(
190+
expanded.contains("Mon") || expanded.contains("Monday"),
191+
"Expanded format should contain weekday name, got: {expanded}"
192+
);
193+
194+
assert!(
195+
expanded.contains("Jan") || expanded.contains("January"),
196+
"Expanded format should contain month name, got: {expanded}"
197+
);
198+
199+
assert!(
200+
expanded.contains("2024") || expanded.contains("24"),
201+
"Expanded format should contain year, got: {expanded}"
202+
);
203+
204+
// Keep literal %Z check - this is enforced by ensure_timezone_in_format()
205+
assert!(
206+
format.contains("%Z"),
207+
"Format string must contain %Z timezone (enforced by ensure_timezone_in_format)"
208+
);
128209
}
129210

130211
#[test]
@@ -135,25 +216,34 @@ mod tests {
135216
// The format should not be empty
136217
assert!(!format.is_empty(), "Locale format should not be empty");
137218

138-
// Should contain date/time components
139-
let has_date_component = format.contains("%a")
140-
|| format.contains("%A")
141-
|| format.contains("%b")
142-
|| format.contains("%B")
143-
|| format.contains("%d")
144-
|| format.contains("%e");
145-
assert!(has_date_component, "Format should contain date components");
146-
147-
// Should contain time component (hour)
148-
let has_time_component = format.contains("%H")
149-
|| format.contains("%I")
150-
|| format.contains("%k")
151-
|| format.contains("%l")
152-
|| format.contains("%r")
153-
|| format.contains("%R")
154-
|| format.contains("%T")
155-
|| format.contains("%X");
156-
assert!(has_time_component, "Format should contain time components");
219+
let expanded = expand_format_with_test_date(format);
220+
221+
// Verify expanded output contains date components
222+
// Test date: Monday, January 15, 2024
223+
let has_date_component = expanded.contains("15") // day
224+
|| expanded.contains("Jan") // month name
225+
|| expanded.contains("January") // full month
226+
|| expanded.contains("Mon") // weekday
227+
|| expanded.contains("Monday"); // full weekday
228+
229+
assert!(
230+
has_date_component,
231+
"Expanded format should contain date components, got: {expanded}"
232+
);
233+
234+
// Verify expanded output contains time components
235+
// Test time: 14:30:45
236+
let has_time_component = expanded.contains("14") // 24-hour
237+
|| expanded.contains("02") // 12-hour
238+
|| expanded.contains("30") // minutes
239+
|| expanded.contains(':') // time separator
240+
|| expanded.contains("PM") // AM/PM indicator
241+
|| expanded.contains("pm");
242+
243+
assert!(
244+
has_time_component,
245+
"Expanded format should contain time components, got: {expanded}"
246+
);
157247
}
158248

159249
#[test]

0 commit comments

Comments
 (0)