containerization-framework 0.9.0

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)
  }
}

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

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

/// 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)
}

#[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);
  }
}