wikioxide 0.1.0

MediaWiki wikitext parser producing typed domain models (templates, links, categories, warnings) with template expansion — no HTML, no network
Documentation
//! Deterministic time injection.
//!
//! MediaWiki's `{{CURRENTYEAR}}`-family variables and `{{#time:}}` depend on "now".
//! The parser never reads the wall clock directly — it asks a [`Clock`], so tests and
//! golden fixtures inject a [`FixedClock`] and stay reproducible forever.

/// A point in time: seconds since the Unix epoch, UTC.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(i64);

impl Timestamp {
    /// Wraps a number of seconds since 1970-01-01T00:00:00Z.
    #[must_use]
    pub const fn from_unix(seconds: i64) -> Self {
        Self(seconds)
    }

    /// Seconds since the Unix epoch.
    #[must_use]
    pub const fn unix(self) -> i64 {
        self.0
    }
}

/// Source of "now" for time-dependent magic words.
pub trait Clock: Send + Sync {
    /// The current time.
    fn now(&self) -> Timestamp;
}

/// Wall-clock time (the default for interactive use).
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;

impl Clock for SystemClock {
    fn now(&self) -> Timestamp {
        let seconds = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|elapsed| i64::try_from(elapsed.as_secs()).unwrap_or(i64::MAX))
            .unwrap_or(0);
        Timestamp::from_unix(seconds)
    }
}

/// A frozen clock for reproducible parses (tests, golden fixtures).
#[derive(Debug, Clone, Copy)]
pub struct FixedClock {
    at: Timestamp,
}

impl FixedClock {
    /// A clock that always reports `at`.
    #[must_use]
    pub const fn new(at: Timestamp) -> Self {
        Self { at }
    }
}

impl Clock for FixedClock {
    fn now(&self) -> Timestamp {
        self.at
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fixed_clock_reports_the_given_instant() {
        let clock = FixedClock::new(Timestamp::from_unix(1_752_400_000));
        assert_eq!(clock.now().unix(), 1_752_400_000);
        assert_eq!(clock.now(), clock.now());
    }

    #[test]
    fn system_clock_is_after_2020() {
        assert!(SystemClock.now().unix() > 1_600_000_000);
    }

    #[test]
    fn timestamps_order_naturally() {
        assert!(Timestamp::from_unix(1) < Timestamp::from_unix(2));
        let debug = format!(
            "{:?} {:?}",
            Timestamp::from_unix(0),
            FixedClock::new(Timestamp::from_unix(0))
        );
        assert!(debug.contains("Timestamp"));
    }
}