use crate::ported::compat::zgettime;
use crate::ported::params::{getsparam, isident, setiparam, setsparam};
use crate::ported::utils::{metafy, zwarnnam};
use crate::ported::zsh_h::{features, module, options, MAX_OPS, OPT_ARG, OPT_ISSET};
use crate::ported::zsh_system_h::timespec;
use chrono::format::{Parsed, StrftimeItems};
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone};
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub fn reverse_strftime(
nam: &str,
argv: &[&str], scalar: Option<&str>,
quiet: i32,
) -> i32 {
if argv.len() < 2 {
zwarnnam(nam, "timestring expected");
return 1;
}
let format = argv[0];
let input = argv[1];
let mut parsed = Parsed::new();
if chrono::format::parse(&mut parsed, input, StrftimeItems::new(format)).is_err() {
if quiet == 0 {
zwarnnam(nam, &format!("format not matched: {}", input));
}
return 1;
}
let year = parsed.year.or_else(|| parsed.year_div_100.zip(parsed.year_mod_100).map(|(d, m)| d * 100 + m)).unwrap_or(1970);
let month = parsed.month.unwrap_or(1);
let day = parsed.day.unwrap_or(1);
let hour = parsed.hour_div_12.zip(parsed.hour_mod_12).map(|(d, m)| (d * 12 + m) as u32).unwrap_or(0);
let minute = parsed.minute.unwrap_or(0);
let second = parsed.second.unwrap_or(0);
let date = match NaiveDate::from_ymd_opt(year, month, day) {
Some(d) => d,
None => {
if quiet == 0 {
zwarnnam(nam, &format!("format not matched: {}", input));
}
return 1;
}
};
let time = NaiveTime::from_hms_opt(hour, minute, second).unwrap_or_else(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap());
let dt = NaiveDateTime::new(date, time);
let secs = match Local.from_local_datetime(&dt) {
chrono::LocalResult::Single(d) => d.timestamp(),
chrono::LocalResult::Ambiguous(d, _) => d.timestamp(),
chrono::LocalResult::None => {
if quiet == 0 {
zwarnnam(nam, "unable to convert to time");
}
return 1;
}
};
if let Some(name) = scalar {
setiparam(name, secs); } else {
println!("{}", secs); }
0 }
pub fn output_strftime(
nam: &str,
argv: &[&str], ops: &options,
_func: i32,
) -> i32 {
let scalar: Option<&str> = if OPT_ISSET(ops, b's') {
Some(OPT_ARG(ops, b's').unwrap_or(""))
} else {
None
};
if let Some(name) = scalar {
if !isident(name) {
zwarnnam(nam, &format!("not an identifier: {}", name)); return 1; }
}
if OPT_ISSET(ops, b'r') {
let quiet = if OPT_ISSET(ops, b'q') { 1 } else { 0 };
return reverse_strftime(nam, argv, scalar, quiet); }
if argv.is_empty() {
zwarnnam(nam, "format expected");
return 1;
}
let (secs, nsec) = if argv.len() < 2 {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO);
(now.as_secs() as i64, now.subsec_nanos() as i64) } else {
let secs = match argv[1].parse::<i64>() {
Ok(v) => v,
Err(_) => {
zwarnnam(nam, &format!("{}: invalid decimal number", argv[1]));
return 1; }
};
let nsec = if argv.len() > 2 {
match argv[2].parse::<i64>() {
Ok(v) if (0..=999_999_999).contains(&v) => v, Ok(_) => {
zwarnnam(nam, &format!("{}: invalid nanosecond value", argv[2]));
return 1; }
Err(_) => {
zwarnnam(nam, &format!("{}: invalid decimal number", argv[2]));
return 1;
}
}
} else {
0
};
(secs, nsec)
};
let format = argv[0];
let dt: DateTime<Local> = match Local.timestamp_opt(secs, nsec as u32) {
chrono::LocalResult::Single(d) => d,
chrono::LocalResult::Ambiguous(d, _) => d,
chrono::LocalResult::None => {
zwarnnam(nam, &format!("bad/unsupported format: '{}'", format));
return 1; }
};
let mut work = String::with_capacity(format.len() * 2);
let bytes = format.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' && i + 1 < bytes.len() {
match bytes[i + 1] {
b'N' => {
work.push_str(&format!("{:09}", nsec));
i += 2;
continue;
}
b'.' if i + 2 < bytes.len() && bytes[i + 2] == b'N' => {
work.push_str(&format!(".{:09}", nsec));
i += 3;
continue;
}
d if d.is_ascii_digit() && i + 2 < bytes.len() && bytes[i + 2] == b'N' => {
let digits = (d - b'0') as usize;
let scaled = if digits >= 9 {
nsec
} else {
nsec / 10i64.pow((9 - digits) as u32)
};
work.push_str(&format!("{:0width$}", scaled, width = digits));
i += 3;
continue;
}
b'%' => {
work.push_str("%%");
i += 2;
continue;
}
_ => {}
}
}
work.push(bytes[i] as char);
i += 1;
}
let formatted = dt.format(&work).to_string();
if let Some(name) = scalar {
setsparam(name, &metafy(&formatted));
} else {
print!("{}", formatted); if !OPT_ISSET(ops, b'n') {
println!(); }
}
0 }
pub fn bin_strftime(
nam: &str,
argv: &[String], ops: &options,
func: i32,
) -> i32 {
let tz_saved = getsparam("TZ"); if let Some(ref tz) = tz_saved {
std::env::set_var("TZ", tz); }
let argv_views: Vec<&str> = argv.iter().map(String::as_str).collect();
let result = output_strftime(nam, &argv_views, ops, func); if let Some(ref tz) = tz_saved {
std::env::set_var("TZ", tz);
}
result }
pub fn getcurrentsecs() -> i64 {
unsafe { libc::time(std::ptr::null_mut()) as i64 }
}
pub fn getcurrentrealtime() -> f64 {
let mut now: timespec = unsafe { std::mem::zeroed() }; zgettime(&mut now); (now.tv_sec as f64) + (now.tv_nsec as f64) * 1e-9 }
pub fn getcurrenttime() -> Vec<String> {
let mut arr: Vec<String> = Vec::with_capacity(2); let mut now: timespec = unsafe { std::mem::zeroed() }; zgettime(&mut now);
let buf = format!("{}", now.tv_sec as i64);
arr.push(buf); let buf = format!("{}", now.tv_nsec as i64);
arr.push(buf); arr }
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 {
0
}
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
*features = featuresarray(m, module_features());
0 }
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
handlefeatures(m, module_features(), enables) }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
use crate::ported::params::{paramtab, createparam};
use crate::ported::zsh_h::{
PM_INTEGER, PM_FFLOAT, PM_ARRAY, PM_READONLY, PM_HIDE, PM_HIDEVAL, PM_SPECIAL,
};
let entries: &[(&str, u32)] = &[
("EPOCHSECONDS",
PM_INTEGER | PM_READONLY | PM_HIDE | PM_HIDEVAL | PM_SPECIAL),
("EPOCHREALTIME",
PM_FFLOAT | PM_READONLY | PM_HIDE | PM_HIDEVAL | PM_SPECIAL),
("epochtime",
PM_ARRAY | PM_READONLY | PM_HIDE | PM_HIDEVAL | PM_SPECIAL),
];
for (name, flags) in entries {
let exists = paramtab()
.read()
.ok()
.map(|t| t.contains_key(*name))
.unwrap_or(false);
if !exists {
let _ = createparam(name, *flags as i32);
}
}
0
}
pub fn cleanup_(m: *const module) -> i32 {
setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
0
}
static MODULE_FEATURES: OnceLock<Mutex<features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features>) -> Vec<String> {
vec![
"b:strftime".to_string(),
"p:EPOCHSECONDS".to_string(),
"p:EPOCHREALTIME".to_string(),
"p:epochtime".to_string(),
]
}
fn handlefeatures(_m: *const module, _f: &Mutex<features>, enables: &mut Option<Vec<i32>>) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 4]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(features {
bn_list: None,
bn_size: 1,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 3,
n_abstract: 0,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_epoch_seconds() {
let _g = crate::test_util::global_state_lock();
let secs = getcurrentsecs();
assert!(secs > 1700000000);
}
#[test]
fn test_epoch_realtime() {
let _g = crate::test_util::global_state_lock();
let rt = getcurrentrealtime();
assert!(rt > 1700000000.0);
let arr = getcurrenttime();
let secs: i64 = arr[0].parse().unwrap();
assert!((rt - secs as f64).abs() < 1.0);
}
#[test]
fn test_epoch_time() {
let _g = crate::test_util::global_state_lock();
let arr = getcurrenttime();
let secs: i64 = arr[0].parse().unwrap();
let nanos: i64 = arr[1].parse().unwrap();
assert!(secs > 1700000000);
assert!((0..1_000_000_000).contains(&nanos));
}
fn ops_for(flags: &[u8], scalar: Option<&str>) -> options {
let mut ops = options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
for f in flags {
ops.ind[*f as usize] = 1;
}
if let Some(s) = scalar {
ops.ind[b's' as usize] = 4;
ops.args.push(s.to_string());
ops.argscount = 1;
ops.argsalloc = 1;
}
ops
}
fn pt_get(name: &str) -> Option<String> {
crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(name).and_then(|p| p.u_str.clone()))
}
#[test]
fn test_output_strftime_nanoseconds() {
let _g = crate::test_util::global_state_lock();
let ops = ops_for(&[b'n'], Some("OUT"));
let r = output_strftime("strftime", &["%9N", "1700000000", "123456789"], &ops, 0);
assert_eq!(r, 0);
assert_eq!(pt_get("OUT").as_deref(), Some("123456789"));
let r = output_strftime("strftime", &["%3N", "1700000000", "123456789"], &ops, 0);
assert_eq!(r, 0);
assert_eq!(pt_get("OUT").as_deref(), Some("123"));
}
#[test]
fn test_output_strftime_to_scalar() {
let _g = crate::test_util::global_state_lock();
let ops = ops_for(&[b'n'], Some("OUT2"));
let r = output_strftime("strftime", &["%s", "1700000000"], &ops, 0);
assert_eq!(r, 0);
assert_eq!(pt_get("OUT2").as_deref(), Some("1700000000"));
}
#[test]
fn test_output_strftime_format_required() {
let _g = crate::test_util::global_state_lock();
let ops = ops_for(&[], None);
let r = output_strftime("strftime", &[], &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn getcurrentsecs_matches_libc_time() {
let _g = crate::test_util::global_state_lock();
let libc_now = unsafe { libc::time(std::ptr::null_mut()) } as i64;
let our_now = getcurrentsecs();
assert!(
(our_now - libc_now).abs() <= 1,
"getcurrentsecs {} drifted from libc::time {}",
our_now,
libc_now
);
}
#[test]
fn getcurrentrealtime_carries_subsecond_precision() {
let _g = crate::test_util::global_state_lock();
let mut saw_fractional = false;
for _ in 0..10 {
let rt = getcurrentrealtime();
if rt.fract().abs() > 1e-9 {
saw_fractional = true;
break;
}
}
assert!(
saw_fractional,
"getcurrentrealtime over 10 samples never produced a fractional part"
);
}
#[test]
fn getcurrenttime_nanos_under_one_billion() {
let _g = crate::test_util::global_state_lock();
for _ in 0..5 {
let arr = getcurrenttime();
let nanos: i64 = arr[1].parse().unwrap();
assert!(
nanos < 1_000_000_000,
"nanos {} >= 1e9 — unit confusion in c:220 port",
nanos
);
assert!(nanos >= 0, "nanos {} negative", nanos);
}
}
#[test]
fn getcurrentrealtime_advances_forward() {
let _g = crate::test_util::global_state_lock();
let a = getcurrentrealtime();
std::thread::sleep(Duration::from_millis(10));
let b = getcurrentrealtime();
assert!(b >= a, "realtime went backward: {} -> {}", a, b);
assert!(
b - a < 5.0,
"realtime jumped {} seconds in 10ms sleep",
b - a
);
}
#[test]
fn getcurrentsecs_advances_or_stays_equal() {
let _g = crate::test_util::global_state_lock();
let a = getcurrentsecs();
std::thread::sleep(Duration::from_millis(20));
let b = getcurrentsecs();
assert!(b >= a, "seconds went backward: {} -> {}", a, b);
}
#[test]
fn output_strftime_percent_s_round_trips() {
let _g = crate::test_util::global_state_lock();
let ops = ops_for(&[b'n'], Some("EPOCH_ROUND_TRIP"));
let r = output_strftime("strftime", &["%s", "1234567890"], &ops, 0);
assert_eq!(r, 0);
assert_eq!(pt_get("EPOCH_ROUND_TRIP").as_deref(), Some("1234567890"));
}
#[test]
fn output_strftime_invalid_epoch_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = ops_for(&[b'n'], Some("BAD"));
let r = output_strftime("strftime", &["%s", "not-a-number"], &ops, 0);
assert_ne!(r, 0, "garbage epoch must be rejected");
}
#[test]
fn module_lifecycle_shims_all_return_zero() {
let _g = crate::test_util::global_state_lock();
let m: *const module = std::ptr::null();
assert_eq!(setup_(m), 0);
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
#[test]
fn enables_populates_some_vec() {
let _g = crate::test_util::global_state_lock();
let m: *const module = std::ptr::null();
let mut enables: Option<Vec<i32>> = None;
assert_eq!(enables_(m, &mut enables), 0);
assert!(enables.is_some(), "enables must be Some after enables_");
}
#[test]
fn getcurrentsecs_returns_post_2020_epoch() {
let _g = crate::test_util::global_state_lock();
let s = getcurrentsecs();
assert!(s > 1_577_836_800, "epoch must be after 2020-01-01; got {s}");
assert!(
s < 4_102_444_800,
"epoch must be before 2100-01-01; got {s}"
);
}
#[test]
fn getcurrentsecs_is_monotonic_non_decreasing() {
let _g = crate::test_util::global_state_lock();
let a = getcurrentsecs();
let b = getcurrentsecs();
assert!(b >= a, "time went backwards: {a} → {b}");
}
#[test]
fn getcurrentrealtime_returns_positive_float() {
let _g = crate::test_util::global_state_lock();
let t = getcurrentrealtime();
assert!(t > 1_577_836_800.0, "realtime must be after 2020; got {t}");
}
#[test]
fn getcurrentrealtime_matches_getcurrentsecs_within_a_second() {
let _g = crate::test_util::global_state_lock();
let secs = getcurrentsecs() as f64;
let real = getcurrentrealtime();
let diff = (real - secs).abs();
assert!(diff < 2.0, "realtime/secs diverge by {diff} seconds");
}
#[test]
fn getcurrenttime_returns_two_element_array() {
let _g = crate::test_util::global_state_lock();
let arr = getcurrenttime();
assert_eq!(arr.len(), 2, "must be exactly 2 elements [secs, nanos]");
}
#[test]
fn getcurrenttime_first_element_is_parseable_epoch_seconds() {
let _g = crate::test_util::global_state_lock();
let arr = getcurrenttime();
let secs: i64 = arr[0].parse().expect("element 0 must be valid i64");
assert!(secs > 1_577_836_800, "secs must be post-2020");
}
#[test]
fn getcurrenttime_second_element_is_valid_nanoseconds() {
let _g = crate::test_util::global_state_lock();
let arr = getcurrenttime();
let nanos: i64 = arr[1].parse().expect("element 1 must be valid i64");
assert!(nanos >= 0, "nanos must be non-negative");
assert!(nanos < 1_000_000_000, "nanos must be < 1e9; got {nanos}");
}
#[test]
fn getcurrenttime_monotonic_seconds() {
let _g = crate::test_util::global_state_lock();
let a: i64 = getcurrenttime()[0].parse().unwrap();
let b: i64 = getcurrenttime()[0].parse().unwrap();
assert!(b >= a, "time went backwards: {a} → {b}");
}
#[test]
fn datetime_corpus_getcurrentsecs_positive() {
let _g = crate::test_util::global_state_lock();
let s = getcurrentsecs();
assert!(s > 1_577_836_800, "post-2020 epoch, got {s}");
}
#[test]
fn datetime_corpus_getcurrentsecs_monotonic() {
let _g = crate::test_util::global_state_lock();
let a = getcurrentsecs();
let b = getcurrentsecs();
assert!(b >= a, "time went backwards: {a} → {b}");
}
#[test]
fn datetime_corpus_getcurrentrealtime_positive() {
let _g = crate::test_util::global_state_lock();
let r = getcurrentrealtime();
assert!(r > 1_577_836_800.0, "post-2020 epoch, got {r}");
}
#[test]
fn datetime_corpus_getcurrentrealtime_returns_f64() {
let _g = crate::test_util::global_state_lock();
let r = getcurrentrealtime();
assert!(r.is_finite(), "must be finite, got {r}");
}
#[test]
fn datetime_corpus_getcurrenttime_two_elements() {
let _g = crate::test_util::global_state_lock();
let arr = getcurrenttime();
assert_eq!(arr.len(), 2, "exactly 2 elements (secs, nanos)");
}
#[test]
fn datetime_corpus_getcurrenttime_both_parse_as_i64() {
let _g = crate::test_util::global_state_lock();
let arr = getcurrenttime();
let _: i64 = arr[0].parse().expect("secs parses");
let _: i64 = arr[1].parse().expect("nanos parses");
}
#[test]
fn datetime_corpus_getcurrentsecs_matches_getcurrenttime_secs() {
let _g = crate::test_util::global_state_lock();
let a = getcurrentsecs();
let b: i64 = getcurrenttime()[0].parse().unwrap();
assert!(
(a - b).abs() <= 2,
"getcurrentsecs={a} should match getcurrenttime[0]={b} within ~2s"
);
}
#[test]
fn getcurrentsecs_returns_positive_epoch() {
let _g = crate::test_util::global_state_lock();
let s = getcurrentsecs();
assert!(s > 0, "epoch must be positive, got {}", s);
assert!(s > 946_684_800, "must be after Y2000");
}
#[test]
fn getcurrentsecs_is_monotonic_non_decreasing_pin() {
let _g = crate::test_util::global_state_lock();
let a = getcurrentsecs();
let b = getcurrentsecs();
let c = getcurrentsecs();
assert!(b >= a, "{} -> {} went backwards", a, b);
assert!(c >= b, "{} -> {} went backwards", b, c);
}
#[test]
fn getcurrentrealtime_returns_positive() {
let _g = crate::test_util::global_state_lock();
let t = getcurrentrealtime();
assert!(t > 0.0);
}
#[test]
fn getcurrentrealtime_is_finite() {
let _g = crate::test_util::global_state_lock();
let t = getcurrentrealtime();
assert!(!t.is_nan());
assert!(t.is_finite());
}
#[test]
fn getcurrentrealtime_agrees_with_secs() {
let _g = crate::test_util::global_state_lock();
let real = getcurrentrealtime();
let secs = getcurrentsecs() as f64;
assert!((real - secs).abs() < 5.0);
}
#[test]
fn getcurrenttime_returns_two_elements_pin() {
let _g = crate::test_util::global_state_lock();
let arr = getcurrenttime();
assert_eq!(arr.len(), 2);
}
#[test]
fn getcurrenttime_nsec_in_valid_range() {
let _g = crate::test_util::global_state_lock();
let arr = getcurrenttime();
let nsec: i64 = arr[1].parse().expect("nsec parses");
assert!(
nsec >= 0 && nsec < 1_000_000_000,
"nsec out of range: {}",
nsec
);
}
#[test]
fn bin_strftime_no_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_strftime("strftime", &[], &ops, 0);
assert_ne!(r, 0, "no args → usage error");
}
#[test]
fn datetime_setup_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(setup_(std::ptr::null()), 0);
}
#[test]
fn datetime_boot_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(boot_(std::ptr::null()), 0);
}
#[test]
fn getcurrentsecs_returns_i64_type() {
let _: i64 = getcurrentsecs();
}
#[test]
fn getcurrentsecs_monotonic_across_many_calls() {
let mut prev = getcurrentsecs();
for _ in 0..100 {
let cur = getcurrentsecs();
assert!(cur >= prev, "secs went backwards: {} < {}", cur, prev);
prev = cur;
}
}
#[test]
fn getcurrentsecs_after_epoch() {
assert!(getcurrentsecs() > 0, "must be after 1970-01-01");
}
#[test]
fn getcurrentrealtime_returns_f64_type() {
let _: f64 = getcurrentrealtime();
}
#[test]
fn getcurrentrealtime_non_negative() {
assert!(getcurrentrealtime() >= 0.0);
}
#[test]
fn getcurrenttime_returns_vec_string() {
let _: Vec<String> = getcurrenttime();
}
#[test]
fn getcurrenttime_first_is_numeric() {
let v = getcurrenttime();
assert!(!v.is_empty(), "must have ≥ 1 element");
let parsed: Result<i64, _> = v[0].parse();
assert!(parsed.is_ok(), "first element {:?} must parse as i64", v[0]);
}
#[test]
fn bin_strftime_return_in_exit_code_range() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
for args in [
vec![],
vec!["%Y".to_string()],
vec!["%Y".to_string(), "1000".to_string()],
] {
let r = bin_strftime("strftime", &args, &ops, 0);
assert!(
(0..256).contains(&r),
"exit code {} must fit in u8 for {:?}",
r,
args
);
}
}
#[test]
fn datetime_full_lifecycle_returns_zero_for_all() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
assert_eq!(setup_(null), 0);
let mut feats = Vec::new();
let _ = features_(null, &mut feats);
let mut enables: Option<Vec<i32>> = None;
let _ = enables_(null, &mut enables);
assert_eq!(boot_(null), 0);
assert_eq!(cleanup_(null), 0);
assert_eq!(finish_(null), 0);
}
#[test]
fn datetime_finish_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(finish_(std::ptr::null()), 0);
}
}
#[test]
fn getcurrentsecs_two_calls_close_in_time() {
let _g = crate::test_util::global_state_lock();
let a = getcurrentsecs();
let b = getcurrentsecs();
assert!(
(b - a).abs() <= 2,
"two getcurrentsecs calls must be within 2 seconds"
);
}
#[test]
fn getcurrentrealtime_returns_finite_f64() {
let _g = crate::test_util::global_state_lock();
let v = getcurrentrealtime();
assert!(v.is_finite(), "must be finite");
assert!(v > 0.0, "after epoch");
}
#[test]
fn getcurrentrealtime_floor_matches_getcurrentsecs() {
let _g = crate::test_util::global_state_lock();
let secs = getcurrentsecs() as f64;
let real = getcurrentrealtime();
assert!(
(real - secs).abs() < 2.0,
"realtime {} and secs {} must agree within 2 sec",
real,
secs
);
}
#[test]
fn getcurrenttime_second_element_is_nsec() {
let _g = crate::test_util::global_state_lock();
let v = getcurrenttime();
if v.len() >= 2 {
let nsec: Result<i64, _> = v[1].parse();
assert!(nsec.is_ok(), "second element {:?} must parse as i64", v[1]);
if let Ok(n) = nsec {
assert!(n >= 0, "nsec must be ≥ 0");
}
}
}
#[test]
fn getcurrenttime_length_is_two() {
let _g = crate::test_util::global_state_lock();
let v = getcurrenttime();
assert_eq!(v.len(), 2, "secs + nsec = 2 elements");
}
#[test]
fn getcurrenttime_always_two_elements() {
let _g = crate::test_util::global_state_lock();
for _ in 0..5 {
assert_eq!(getcurrenttime().len(), 2);
}
}
#[test]
fn reverse_strftime_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = reverse_strftime("strftime", &[], None, 0);
}
#[test]
fn reverse_strftime_empty_argv_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let r = reverse_strftime("strftime", &[], None, 0);
assert_ne!(r, 0, "empty argv → usage error");
}
#[test]
fn getcurrentsecs_always_positive() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert!(getcurrentsecs() > 0);
}
}
#[test]
fn getcurrentsecs_returns_i64_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _: i64 = getcurrentsecs();
}
#[test]
fn getcurrentrealtime_returns_f64_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _: f64 = getcurrentrealtime();
}
#[test]
fn getcurrenttime_returns_vec_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Vec<String> = getcurrenttime();
}
#[test]
fn getcurrentsecs_monotonically_non_decreasing() {
let _g = crate::test_util::global_state_lock();
let mut prev = getcurrentsecs();
for _ in 0..50 {
let now = getcurrentsecs();
assert!(now >= prev, "time went backwards: {} → {}", prev, now);
prev = now;
}
}
#[test]
fn getcurrentrealtime_monotonically_non_decreasing() {
let _g = crate::test_util::global_state_lock();
let mut prev = getcurrentrealtime();
for _ in 0..50 {
let now = getcurrentrealtime();
assert!(now >= prev, "realtime went backwards: {} → {}", prev, now);
prev = now;
}
}
#[test]
fn getcurrentsecs_in_plausible_epoch_range() {
let _g = crate::test_util::global_state_lock();
let now = getcurrentsecs();
assert!(
now >= 1_577_836_800,
"current time {} must be after 2020-01-01 epoch",
now
);
assert!(
now <= 4_102_444_800,
"current time {} must be before 2100-01-01 epoch",
now
);
}
#[test]
fn getcurrenttime_first_element_is_secs() {
let _g = crate::test_util::global_state_lock();
let v = getcurrenttime();
assert!(v.len() >= 1, "must have at least one element");
let secs: Result<i64, _> = v[0].parse();
assert!(
secs.is_ok(),
"first element {:?} must parse as i64 secs",
v[0]
);
}
#[test]
fn getcurrenttime_nsec_within_one_second() {
let _g = crate::test_util::global_state_lock();
let v = getcurrenttime();
if v.len() >= 2 {
if let Ok(n) = v[1].parse::<i64>() {
assert!(n <= 999_999_999, "nsec {} must be ≤ 999_999_999", n);
}
}
}
#[test]
fn bin_strftime_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let _: i32 = bin_strftime("strftime", &[], &ops, 0);
}
#[test]
fn bin_strftime_no_args_returns_nonzero_pin_alt() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_strftime("strftime", &[], &ops, 0);
assert_ne!(r, 0, "no args → usage error");
}
#[test]
fn datetime_each_lifecycle_hook_returns_zero_individually() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
let mut v: Vec<String> = Vec::new();
let mut e: Option<Vec<i32>> = None;
assert_eq!(setup_(null), 0, "c:329 setup_");
assert_eq!(features_(null, &mut v), 0, "c:342 features_");
assert_eq!(enables_(null, &mut e), 0, "c:350 enables_");
assert_eq!(boot_(null), 0, "c:357 boot_");
assert_eq!(cleanup_(null), 0, "c:367 cleanup_");
assert_eq!(finish_(null), 0, "c:374 finish_");
}
}