1use core::{
2 cmp::min,
3 fmt::{self, Debug, Display},
4 str::FromStr,
5};
6
7use crate::{Calendar, CalendarTime, TimeResult};
8
9const NANOS_IN_SECS: i128 = 1_000_000_000;
10
11pub trait TimeZone: Calendar<Time = OffsetTime<Self::Sigil>> + Eq + PartialEq {
15 type Sigil: Sigil;
19}
20
21pub trait Sigil: Display + Eq + FromStr + PartialEq {
23 fn read(&self, t: &OffsetTime<Self>) -> crate::Result<TimeResult>;
25}
26
27#[derive(Copy, Clone, Eq, PartialEq)]
34pub struct OffsetTime<Sig> {
35 sigil: Sig,
36 pseudo_nanos: i128,
37 extra_nanos: u64,
38}
39
40impl<Sig> OffsetTime<Sig> {
41 pub const fn from_pseudo_nanos_since_posix_epoch(
58 sigil: Sig,
59 pseudo_nanos: i128,
60 extra_nanos: u64,
61 ) -> Self {
62 Self {
63 sigil,
64 pseudo_nanos,
65 extra_nanos,
66 }
67 }
68
69 pub fn as_pseudo_nanos_since_posix_epoch(&self) -> i128 {
71 self.pseudo_nanos
72 }
73
74 pub fn extra_nanos(&self) -> u64 {
78 self.extra_nanos
79 }
80
81 pub fn sigil(&self) -> &Sig {
83 &self.sigil
84 }
85}
86
87impl<Sig: Sigil> CalendarTime for OffsetTime<Sig> {
88 fn read(&self) -> crate::Result<crate::TimeResult> {
89 self.sigil.read(self)
90 }
91}
92
93impl<Sig: Display> Display for OffsetTime<Sig> {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 let secs = self.pseudo_nanos / NANOS_IN_SECS;
96 let nanos = (self.pseudo_nanos % NANOS_IN_SECS).abs();
97 if self.pseudo_nanos < 0 && secs == 0 {
98 f.write_str("-")?; }
100 write!(f, "{secs}.{nanos:09}{}", self.sigil)
101 }
102}
103
104impl<Sig: Display> Debug for OffsetTime<Sig> {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 let secs = self.pseudo_nanos / NANOS_IN_SECS;
107 let nanos = (self.pseudo_nanos % NANOS_IN_SECS).abs();
108 if self.pseudo_nanos < 0 && secs == 0 {
109 f.write_str("-")?; }
111 write!(
112 f,
113 "{secs}.{nanos:09}(+{}ns){}",
114 self.extra_nanos, self.sigil
115 )
116 }
117}
118
119#[derive(Clone, Debug)]
121pub enum ParseError<SigErr> {
122 ParsingInt(core::num::ParseIntError),
124
125 Overflow,
127
128 ParsingSigil(SigErr),
130}
131
132impl<Sig: FromStr> FromStr for OffsetTime<Sig> {
135 type Err = ParseError<Sig::Err>;
136
137 fn from_str(s: &str) -> Result<Self, Self::Err> {
138 let non_ascii_digit = |c: char| !c.is_ascii_digit();
139 let first_non_digit = s.find(non_ascii_digit).unwrap_or(s.len());
140 let (seconds, rest) = s.split_at(first_non_digit);
141 let seconds = i128::from_str(seconds).map_err(ParseError::ParsingInt)?;
142 let (nanos, rest) = match rest.strip_prefix('.') {
143 None => (0, rest),
144 Some(rest) => {
145 let first_non_digit = min(9, rest.find(non_ascii_digit).unwrap_or(s.len()));
146 let (nanos, rest) = s.split_at(first_non_digit);
147 let nanos = i128::from_str(nanos)
148 .map_err(ParseError::ParsingInt)?
149 .checked_mul(10_i128.pow((9 - nanos.len()) as u32))
150 .unwrap(); (nanos, rest)
152 }
153 };
154 let sigil = Sig::from_str(rest).map_err(ParseError::ParsingSigil)?;
155 Ok(Self {
156 sigil,
157 pseudo_nanos: seconds
158 .checked_mul(NANOS_IN_SECS)
159 .ok_or(ParseError::Overflow)?
160 .checked_add(nanos)
161 .ok_or(ParseError::Overflow)?,
162 extra_nanos: 0,
165 })
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use crate::{leap_seconds::BuiltinIersSigil, OffsetTime};
172
173 #[test]
174 fn display_small_negative_values_properly() {
175 let t = OffsetTime::from_pseudo_nanos_since_posix_epoch(BuiltinIersSigil, -1, 10);
176 let iers_sigil = BuiltinIersSigil;
177 assert_eq!(format!("{t}"), format!("-0.000000001{iers_sigil}"));
178 assert_eq!(format!("{t:?}"), format!("-0.000000001(+10ns){iers_sigil}"));
179 }
180}