use core::{
cmp::min,
fmt::{self, Debug, Display},
str::FromStr,
};
use crate::{Calendar, CalendarTime, TimeResult};
const NANOS_IN_SECS: i128 = 1_000_000_000;
pub trait TimeZone: Calendar<Time = OffsetTime<Self::Sigil>> + Eq + PartialEq {
type Sigil: Sigil;
}
pub trait Sigil: Display + Eq + FromStr + PartialEq {
fn read(&self, t: &OffsetTime<Self>) -> crate::Result<TimeResult>;
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct OffsetTime<Sig> {
sigil: Sig,
pseudo_nanos: i128,
extra_nanos: u64,
}
impl<Sig> OffsetTime<Sig> {
pub const fn from_pseudo_nanos_since_posix_epoch(
sigil: Sig,
pseudo_nanos: i128,
extra_nanos: u64,
) -> Self {
Self {
sigil,
pseudo_nanos,
extra_nanos,
}
}
pub fn as_pseudo_nanos_since_posix_epoch(&self) -> i128 {
self.pseudo_nanos
}
pub fn extra_nanos(&self) -> u64 {
self.extra_nanos
}
pub fn sigil(&self) -> &Sig {
&self.sigil
}
}
impl<Sig: Sigil> CalendarTime for OffsetTime<Sig> {
fn read(&self) -> crate::Result<crate::TimeResult> {
self.sigil.read(self)
}
}
impl<Sig: Display> Display for OffsetTime<Sig> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let secs = self.pseudo_nanos / NANOS_IN_SECS;
let nanos = (self.pseudo_nanos % NANOS_IN_SECS).abs();
if self.pseudo_nanos < 0 && secs == 0 {
f.write_str("-")?; }
write!(f, "{secs}.{nanos:09}{}", self.sigil)
}
}
impl<Sig: Display> Debug for OffsetTime<Sig> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let secs = self.pseudo_nanos / NANOS_IN_SECS;
let nanos = (self.pseudo_nanos % NANOS_IN_SECS).abs();
if self.pseudo_nanos < 0 && secs == 0 {
f.write_str("-")?; }
write!(
f,
"{secs}.{nanos:09}(+{}ns){}",
self.extra_nanos, self.sigil
)
}
}
#[derive(Clone, Debug)]
pub enum ParseError<SigErr> {
ParsingInt(core::num::ParseIntError),
Overflow,
ParsingSigil(SigErr),
}
impl<Sig: FromStr> FromStr for OffsetTime<Sig> {
type Err = ParseError<Sig::Err>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let non_ascii_digit = |c: char| !c.is_ascii_digit();
let first_non_digit = s.find(non_ascii_digit).unwrap_or(s.len());
let (seconds, rest) = s.split_at(first_non_digit);
let seconds = i128::from_str(seconds).map_err(ParseError::ParsingInt)?;
let (nanos, rest) = match rest.strip_prefix('.') {
None => (0, rest),
Some(rest) => {
let first_non_digit = min(9, rest.find(non_ascii_digit).unwrap_or(s.len()));
let (nanos, rest) = s.split_at(first_non_digit);
let nanos = i128::from_str(nanos)
.map_err(ParseError::ParsingInt)?
.checked_mul(10_i128.pow((9 - nanos.len()) as u32))
.unwrap(); (nanos, rest)
}
};
let sigil = Sig::from_str(rest).map_err(ParseError::ParsingSigil)?;
Ok(Self {
sigil,
pseudo_nanos: seconds
.checked_mul(NANOS_IN_SECS)
.ok_or(ParseError::Overflow)?
.checked_add(nanos)
.ok_or(ParseError::Overflow)?,
extra_nanos: 0,
})
}
}
#[cfg(test)]
mod tests {
use crate::{leap_seconds::BuiltinIersSigil, OffsetTime};
#[test]
fn display_small_negative_values_properly() {
let t = OffsetTime::from_pseudo_nanos_since_posix_epoch(BuiltinIersSigil, -1, 10);
let iers_sigil = BuiltinIersSigil;
assert_eq!(format!("{t}"), format!("-0.000000001{iers_sigil}"));
assert_eq!(format!("{t:?}"), format!("-0.000000001(+10ns){iers_sigil}"));
}
}