pethost 0.1.0

Client for the API of Pethost, managed hosting for Docker Compose projects: deploy, logs, HTTP traffic, files
Documentation
// @generated by protoc-gen-panel-rust. DO NOT EDIT. The same for every API: no message decides it.

use std::fmt;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use buffa_types::google::protobuf::Timestamp as Wire;

/// A moment in time as the API holds it: seconds and nanoseconds since 1970-01-01T00:00:00Z.
///
/// It prints as RFC 3339 (`2026-10-06T09:30:00.25Z`), orders by time, and comes from the
/// system's clock with `SystemTime::now().into()`. A time crate takes the two fields:
/// `jiff::Timestamp::new(t.seconds, t.nanos as i32)`,
/// `chrono::DateTime::from_timestamp(t.seconds, t.nanos)`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Timestamp {
    /// Whole seconds since 1970-01-01T00:00:00Z; negative before it.
    pub seconds: i64,
    /// Nanoseconds after `seconds`: 0 to 999,999,999.
    pub nanos: u32,
}

impl Timestamp {
    /// The same moment on the system's clock. `None` = that clock cannot hold it, as Windows'
    /// cannot a time before 1601.
    pub fn to_system_time(self) -> Option<SystemTime> {
        let whole = Duration::from_secs(self.seconds.unsigned_abs());
        let start = if self.seconds >= 0 {
            UNIX_EPOCH.checked_add(whole)?
        } else {
            UNIX_EPOCH.checked_sub(whole)?
        };
        start.checked_add(Duration::from_nanos(u64::from(self.nanos)))
    }
}

impl From<SystemTime> for Timestamp {
    fn from(time: SystemTime) -> Self {
        match time.duration_since(UNIX_EPOCH) {
            Ok(after) => Self {
                seconds: after.as_secs() as i64,
                nanos: after.subsec_nanos(),
            },
            Err(before) => {
                // Seconds round down, so that nanoseconds always count forward.
                let before = before.duration();
                let partial = before.subsec_nanos();
                Self {
                    seconds: -(before.as_secs() as i64) - i64::from(partial > 0),
                    nanos: (1_000_000_000 - partial) % 1_000_000_000,
                }
            }
        }
    }
}

impl fmt::Display for Timestamp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let (year, month, day) = civil_date(self.seconds.div_euclid(86_400));
        let second_of_day = self.seconds.rem_euclid(86_400);
        let (hour, minute, second) = (second_of_day / 3600, second_of_day / 60 % 60, second_of_day % 60);
        write!(f, "{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}")?;

        if self.nanos > 0 {
            let fraction = format!("{:09}", self.nanos);
            write!(f, ".{}", fraction.trim_end_matches('0'))?;
        }
        f.write_str("Z")
    }
}

impl fmt::Debug for Timestamp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

/// The year, month and day of a day counted from 1970-01-01, in the Gregorian calendar
/// (Howard Hinnant's `civil_from_days`).
fn civil_date(days: i64) -> (i64, i64, i64) {
    let shifted = days + 719_468;
    let era = shifted.div_euclid(146_097);
    let day_of_era = shifted.rem_euclid(146_097);
    let year_of_era = (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
    let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
    let month_from_march = (5 * day_of_year + 2) / 153;
    let day = day_of_year - (153 * month_from_march + 2) / 5 + 1;
    let month = if month_from_march < 10 {
        month_from_march + 3
    } else {
        month_from_march - 9
    };
    let year = year_of_era + era * 400 + i64::from(month <= 2);
    (year, month, day)
}

pub(crate) fn from_wire(wire: Wire) -> Timestamp {
    Timestamp {
        seconds: wire.seconds,
        nanos: u32::try_from(wire.nanos).unwrap_or(0),
    }
}

pub(crate) fn to_wire(time: Timestamp) -> Wire {
    Wire {
        seconds: time.seconds,
        // Over 999,999,999 is no time: the API refuses it, whatever number arrives.
        nanos: i32::try_from(time.nanos).unwrap_or(i32::MAX),
        ..Default::default()
    }
}