containerization-framework 0.10.1

Rust bindings for Apple's Containerization framework: Linux containers in lightweight VMs, in-process and without a daemon.
use crate::platform;
use std::time::SystemTime;

/// `FileTimestamps`. [`Default`] is Swift's `init()`, which sets every date
/// to now.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FileTimestamps {
  pub access: SystemTime,
  pub modification: SystemTime,
  pub creation: SystemTime,
  pub now: SystemTime,
}

impl FileTimestamps {
  /// `FileTimestamps(access:modification:creation:)`. A missing date is now.
  pub fn new(access: Option<SystemTime>, modification: Option<SystemTime>, creation: Option<SystemTime>) -> Self {
    let now = SystemTime::now();

    Self {
      access: access.unwrap_or(now),
      modification: modification.unwrap_or(now),
      creation: creation.unwrap_or(now),
      now,
    }
  }

  /// `accessLo`.
  pub fn access_lo(&self) -> u32 {
    lo(self.access)
  }

  /// `accessHi`.
  pub fn access_hi(&self) -> u32 {
    hi(self.access)
  }

  /// `modificationLo`.
  pub fn modification_lo(&self) -> u32 {
    lo(self.modification)
  }

  /// `modificationHi`.
  pub fn modification_hi(&self) -> u32 {
    hi(self.modification)
  }

  /// `creationLo`.
  pub fn creation_lo(&self) -> u32 {
    lo(self.creation)
  }

  /// `creationHi`.
  pub fn creation_hi(&self) -> u32 {
    hi(self.creation)
  }

  /// `nowLo`.
  pub fn now_lo(&self) -> u32 {
    lo(self.now)
  }

  /// `nowHi`.
  pub fn now_hi(&self) -> u32 {
    hi(self.now)
  }
}

impl Default for FileTimestamps {
  fn default() -> Self {
    Self::new(None, None, None)
  }
}

impl FileTimestamps {
  /// Swift's internal `Date.fs()`, which the getters above read: the low 32
  /// bits of the seconds since 1970, two bits counting how often they
  /// wrapped, and 30 bits of nanoseconds.
  ///
  /// The date is read as a Swift `Date` holds it, as seconds since 2001 in a
  /// `Double`. Swift's `timeIntervalSince1970` adds the years before 2001
  /// back, which can change a fraction's low bits, so this does the same.
  pub(crate) fn fs(time: SystemTime) -> u64 {
    const SECONDS_FROM_1970_TO_2001: f64 = 978_307_200.0;
    // `Date.distantPast`, the first instant of year 1 in Foundation's calendar.
    const DISTANT_PAST: f64 = -63_114_076_800.0;

    let since_2001 = platform::seconds(time) - SECONDS_FROM_1970_TO_2001;
    if since_2001 == DISTANT_PAST {
      return 0;
    }

    let seconds = since_2001 + SECONDS_FROM_1970_TO_2001;
    if seconds < -(0x8000_0000_i64 as f64) {
      return 0x8000_0000;
    }
    if seconds > 0x3_7fff_ffff_i64 as f64 {
      return 0x3_7fff_ffff;
    }

    let whole = seconds.floor() as i64;
    let base = whole as i32;
    let epoch = (whole - base as i64) as u64;
    let nanoseconds = (((seconds - seconds.floor()) * 1_000_000_000.0) as u32).min(999_999_999);

    base as u32 as u64 | epoch | ((nanoseconds as u64) << 34)
  }
}

fn lo(time: SystemTime) -> u32 {
  FileTimestamps::fs(time) as u32
}

fn hi(time: SystemTime) -> u32 {
  (FileTimestamps::fs(time) >> 32) as u32
}

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

  #[test]
  fn takes_missing_dates_as_now() {
    let access = SystemTime::UNIX_EPOCH + Duration::from_secs(7);
    let timestamps = FileTimestamps::new(Some(access), None, None);

    assert_eq!(timestamps.access, access);
    assert_eq!(timestamps.modification, timestamps.now);
    assert_eq!(timestamps.creation, timestamps.now);
  }
}