use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
use crate::ported::utils::zwarnnam;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::ported::zsh_h::{OPT_ISSET, OPT_ARG};
pub fn getcurrentsecs() -> i64 { unsafe { libc::time(std::ptr::null_mut()) as i64 }
}
pub fn getcurrentrealtime() -> f64 { let mut now: crate::ported::zsh_system_h::timespec = unsafe { std::mem::zeroed() }; crate::ported::compat::zgettime(&mut now); (now.tv_sec as f64) + (now.tv_nsec as f64) * 1e-9 }
pub fn getcurrenttime() -> (i64, i64) { let mut now: crate::ported::zsh_system_h::timespec = unsafe { std::mem::zeroed() }; crate::ported::compat::zgettime(&mut now); (now.tv_sec as i64, now.tv_nsec as i64) }
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 dt = match NaiveDateTime::parse_from_str(input, format) {
Ok(d) => d,
Err(_) => { if quiet == 0 {
zwarnnam(nam, &format!("format not matched: {}", input));
}
return 1;
}
};
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 { crate::ported::params::setiparam(name, secs); } else { println!("{}", secs); }
0 }
pub fn output_strftime(nam: &str, argv: &[&str], ops: &crate::ported::zsh_h::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 !is_ident(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 {
crate::ported::params::setsparam(name,
&crate::ported::utils::metafy(&formatted)); } else {
print!("{}", formatted); if !OPT_ISSET(ops, b'n') { println!(); }
}
0 }
pub fn bin_strftime(nam: &str, argv: &[&str], ops: &crate::ported::zsh_h::options, func: i32) -> i32 {
let tz_saved = std::env::var("TZ").ok();
if let Some(ref tz) = tz_saved {
std::env::set_var("TZ", tz); }
let result = output_strftime(nam, argv, ops, func); if let Some(ref tz) = tz_saved {
std::env::set_var("TZ", tz);
}
result }
fn is_ident(s: &str) -> bool {
if s.is_empty() { return false; }
let mut chars = s.chars();
let first = chars.next().unwrap();
if first.is_ascii_digit() { return false; }
if !(first.is_alphanumeric() || first == '_') { return false; }
chars.all(|c| c.is_alphanumeric() || c == '_')
}
use crate::ported::zsh_h::module;
#[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 { 0
}
pub fn cleanup_(m: *const module) -> i32 { setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_epoch_seconds() {
let secs = getcurrentsecs();
assert!(secs > 1700000000);
}
#[test]
fn test_epoch_realtime() {
let rt = getcurrentrealtime();
assert!(rt > 1700000000.0);
let (secs, _) = getcurrenttime();
assert!((rt - secs as f64).abs() < 1.0);
}
#[test]
fn test_epoch_time() {
let (secs, nanos) = getcurrenttime();
assert!(secs > 1700000000);
assert!((0..1_000_000_000).contains(&nanos));
}
fn ops_for(flags: &[u8], scalar: Option<&str>) -> crate::ported::zsh_h::options {
use crate::ported::zsh_h::{options, MAX_OPS};
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 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 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 ops = ops_for(&[], None);
let r = output_strftime("strftime", &[], &ops, 0);
assert_eq!(r, 1);
}
}
use crate::ported::zsh_h::features as features_t;
use std::sync::{Mutex, OnceLock};
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
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,
}))
}
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> 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_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 4]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}