use crate::error::{Error, Result};
pub fn base64_encode(data: &[u8]) -> Result<String> {
botan::base64_encode(data).map_err(Error::botan)
}
pub fn base64_decode(data: &str) -> Result<Vec<u8>> {
botan::base64_decode(data).map_err(Error::botan)
}
pub fn rfc3339(epoch_secs: Option<i64>) -> String {
let (secs, nanos) = match epoch_secs {
Some(secs) => (secs, 0),
None => {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
(now.as_secs() as i64, now.subsec_nanos())
}
};
let days = secs.div_euclid(86_400);
let secs_of_day = secs.rem_euclid(86_400);
let (y, m, d) = civil_from_days(days);
format!(
"{y:04}-{m:02}-{d:02}T{:02}:{:02}:{:02}.{nanos:09}Z",
secs_of_day / 3600,
(secs_of_day % 3600) / 60,
secs_of_day % 60
)
}
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
(if m <= 2 { y + 1 } else { y }, m, d)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc3339_epoch_pin() {
assert_eq!(rfc3339(Some(0)), "1970-01-01T00:00:00.000000000Z");
assert_eq!(rfc3339(Some(1_784)), "1970-01-01T00:29:44.000000000Z");
}
#[test]
fn civil_from_days_known_dates() {
assert_eq!(civil_from_days(0), (1970, 1, 1));
assert_eq!(civil_from_days(19_723), (2024, 1, 1));
assert_eq!(civil_from_days(20_681), (2026, 8, 16));
}
}