robinpath 0.2.0

RobinPath - A lightweight, fast scripting language interpreter for automation and data processing
Documentation
use crate::executor::Environment;
use crate::value::Value;

/// Convert seconds since epoch to ISO 8601 date string (no chrono dependency)
fn epoch_secs_to_iso(secs: u64) -> String {
    // Simple conversion: days since epoch -> year/month/day
    let total_days = (secs / 86400) as i64;
    let time_of_day = secs % 86400;
    let hours = time_of_day / 3600;
    let minutes = (time_of_day % 3600) / 60;
    let seconds = time_of_day % 60;

    // Calculate year, month, day from days since epoch (1970-01-01)
    let mut days = total_days;
    let mut year = 1970i64;

    loop {
        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
        if days < days_in_year {
            break;
        }
        days -= days_in_year;
        year += 1;
    }

    let days_in_months: [i64; 12] = if is_leap_year(year) {
        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };

    let mut month = 0;
    for (i, &dim) in days_in_months.iter().enumerate() {
        if days < dim {
            month = i + 1;
            break;
        }
        days -= dim;
    }
    if month == 0 {
        month = 12;
    }
    let day = days + 1;

    format!(
        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.000Z",
        year, month, day, hours, minutes, seconds
    )
}

fn is_leap_year(year: i64) -> bool {
    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}

pub fn register(env: &mut Environment) {
    env.register_builtin("time.now", |_args, _| {
        let secs = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        Ok(Value::String(epoch_secs_to_iso(secs)))
    });

    env.register_builtin("time.timestamp", |_args, _| {
        let ts = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs() as f64;
        Ok(Value::Number(ts))
    });

    env.register_builtin("time.format", |args, _| {
        // Format a date string -- if given a date string, return it as-is for now
        if let Some(Value::String(s)) = args.first() {
            Ok(Value::String(s.clone()))
        } else {
            let secs = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs();
            Ok(Value::String(epoch_secs_to_iso(secs)))
        }
    });

    env.register_builtin("time.addDays", |args, _| {
        // Simple: return the date string with days added
        if let Some(Value::String(s)) = args.first() {
            let days = args.get(1).and_then(|v| v.as_number()).unwrap_or(0.0) as i64;
            // Parse ISO date, add days, return new ISO
            if let Some(secs) = parse_iso_to_epoch(s) {
                let new_secs = (secs as i64 + days * 86400) as u64;
                Ok(Value::String(epoch_secs_to_iso(new_secs)))
            } else {
                Ok(Value::String(s.clone()))
            }
        } else {
            Ok(Value::Null)
        }
    });

    env.register_builtin("time.diffDays", |args, _| {
        let date1 = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let date2 = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
        match (parse_iso_to_epoch(date1), parse_iso_to_epoch(date2)) {
            (Some(d1), Some(d2)) => {
                let diff = (d2 as i64 - d1 as i64).abs() / 86400;
                Ok(Value::Number(diff as f64))
            }
            _ => Ok(Value::Number(0.0)),
        }
    });
}

fn parse_iso_to_epoch(s: &str) -> Option<u64> {
    // Simple ISO 8601 parser: "2024-01-15T00:00:00.000Z"
    let date_part = s.split('T').next()?;
    let parts: Vec<&str> = date_part.split('-').collect();
    if parts.len() < 3 {
        return None;
    }
    let year: i64 = parts[0].parse().ok()?;
    let month: usize = parts[1].parse().ok()?;
    let day: i64 = parts[2].parse().ok()?;

    let days_in_months: [i64; 12] = if is_leap_year(year) {
        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };

    let mut total_days: i64 = 0;
    for y in 1970..year {
        total_days += if is_leap_year(y) { 366 } else { 365 };
    }
    for m in 0..(month - 1) {
        total_days += days_in_months[m];
    }
    total_days += day - 1;

    Some((total_days * 86400) as u64)
}