1use crate::{
2 DtErr, DtErrKind, SEC_PER_DAY, SEC_PER_MONTH, SEC_PER_WEEK, SEC_PER_YEAR, TSpan, an_err,
3};
4
5struct ParsedComponent {
6 unit: u8,
7 signed_int: i64,
8 frac_digits: usize,
9 frac_num: i64,
10}
11
12impl TSpan {
13 pub fn from_iso(s: &str) -> Result<TSpan, DtErr> {
14 let len = s.len();
15 if len == 0 {
16 return Err(an_err!(DtErrKind::Incomplete, "empty"));
17 }
18
19 let b = s.as_bytes();
20 let mut i = 0usize;
21
22 let mut sign: i64 = 1;
24 if i < len && matches!(b[i], b'+' | b'-') {
25 if b[i] == b'-' {
26 sign = -1;
27 }
28 i += 1;
29 }
30
31 if i >= len || !matches!(b[i], b'P' | b'p') {
33 return Err(an_err!(DtErrKind::MustStartWith, "P"));
34 }
35 i += 1;
36
37 let t_pos = b[i..]
39 .iter()
40 .position(|&c| matches!(c, b'T' | b't'))
41 .map(|p| i + p);
42
43 let (date_part, time_part) = match t_pos {
44 Some(pos) => {
45 if pos == len - 1 {
46 return Err(an_err!(DtErrKind::InvalidSyntax, "T with no time"));
47 }
48 if b[pos + 1..].iter().any(|&c| matches!(c, b'T' | b't')) {
49 return Err(an_err!(DtErrKind::InvalidSyntax, "multiple T"));
50 }
51 (&b[i..pos], &b[pos + 1..])
52 }
53 None => (&b[i..], &[] as &[u8]),
54 };
55
56 let mut has_fraction = false;
57 let mut total_nanos: i128 = 0;
58
59 Self::parse_duration_part(date_part, &mut total_nanos, true, sign, &mut has_fraction)?;
61 Self::parse_duration_part(time_part, &mut total_nanos, false, sign, &mut has_fraction)?;
62
63 let total_attos = total_nanos * 1_000_000_000i128;
65 Ok(TSpan::from_attos(total_attos))
66 }
67
68 fn parse_next_component(
71 chars: &[u8],
72 i: &mut usize,
73 sign: i64,
74 has_fraction: &mut bool,
75 ) -> Result<Option<ParsedComponent>, DtErr> {
76 if *i >= chars.len() {
77 return Ok(None);
78 }
79
80 if *has_fraction {
81 return Err(an_err!(DtErrKind::InvalidSyntax, "components after frac"));
82 }
83
84 let start = *i;
86 while *i < chars.len() && chars[*i].is_ascii_digit() {
87 *i += 1;
88 }
89 if start == *i {
90 return Err(an_err!(DtErrKind::ExpectedValue, "number"));
91 }
92
93 let int_str = core::str::from_utf8(&chars[start..*i])
94 .map_err(|_| an_err!(DtErrKind::InvalidNumber, "invalid utf8 in int"))?;
95 let int: i64 = int_str.parse().map_err(|e: core::num::ParseIntError| {
96 an_err!(DtErrKind::InvalidNumber, "{}: {}", int_str, e)
97 })?;
98
99 let mut frac_num: i64 = 0;
101 let mut frac_digits: usize = 0;
102 if *i < chars.len() && matches!(chars[*i], b'.' | b',') {
103 *i += 1;
104 let frac_start = *i;
105 while *i < chars.len() && chars[*i].is_ascii_digit() {
106 *i += 1;
107 }
108 frac_digits = *i - frac_start;
109 if frac_digits == 0 {
110 return Err(an_err!(DtErrKind::ExpectedValue, "empty frac after ."));
111 }
112 if frac_digits > 9 {
113 return Err(an_err!(DtErrKind::OutOfRange, "frac >9"));
114 }
115
116 let frac_str = core::str::from_utf8(&chars[frac_start..*i])
117 .map_err(|_| an_err!(DtErrKind::InvalidNumber, "invalid utf8 in frac"))?;
118 frac_num = frac_str.parse().map_err(|e: core::num::ParseIntError| {
119 an_err!(DtErrKind::InvalidNumber, "{}: {}", frac_str, e)
120 })?;
121 }
122
123 if *i >= chars.len() {
125 return Err(an_err!(
126 DtErrKind::InvalidSyntax,
127 "missing unit after number"
128 ));
129 }
130 let unit = chars[*i];
131 *i += 1;
132
133 if frac_digits > 0 {
135 if !matches!(unit, b'S' | b's') {
136 return Err(an_err!(
137 DtErrKind::InvalidSyntax,
138 "frac only supported for seconds"
139 ));
140 }
141 *has_fraction = true;
142 }
143
144 let signed_int = (int as i128 * sign as i128) as i64;
145
146 Ok(Some(ParsedComponent {
147 unit,
148 signed_int,
149 frac_digits,
150 frac_num,
151 }))
152 }
153
154 fn parse_duration_part(
160 chars: &[u8],
161 total_nanos: &mut i128,
162 is_date: bool,
163 sign: i64,
164 has_fraction: &mut bool,
165 ) -> Result<(), DtErr> {
166 let mut i = 0;
167 while let Some(comp) = Self::parse_next_component(chars, &mut i, sign, has_fraction)? {
168 let contrib_nanos = match (is_date, comp.unit) {
169 (true, b'Y' | b'y') => {
170 let total_secs = (comp.signed_int as i128)
171 .checked_mul(SEC_PER_YEAR)
172 .ok_or_else(|| an_err!(DtErrKind::OutOfRange, "year"))?;
173 total_secs * 1_000_000_000i128
174 }
175 (true, b'M' | b'm') => {
176 let total_secs = (comp.signed_int as i128)
177 .checked_mul(SEC_PER_MONTH)
178 .ok_or_else(|| an_err!(DtErrKind::OutOfRange, "month"))?;
179 total_secs * 1_000_000_000i128
180 }
181 (true, b'W' | b'w') => {
182 let total_secs = (comp.signed_int as i128)
183 .checked_mul(SEC_PER_WEEK as i128)
184 .ok_or_else(|| an_err!(DtErrKind::OutOfRange, "week"))?;
185 total_secs * 1_000_000_000i128
186 }
187 (true, b'D' | b'd') => {
188 let total_secs = (comp.signed_int as i128)
189 .checked_mul(SEC_PER_DAY)
190 .ok_or_else(|| an_err!(DtErrKind::OutOfRange, "day"))?;
191 total_secs * 1_000_000_000i128
192 }
193 (false, b'H' | b'h') => (comp.signed_int as i128) * 3_600_000_000_000i128,
194 (false, b'M' | b'm') => (comp.signed_int as i128) * 60_000_000_000i128,
195 (false, b'S' | b's') => {
196 let mut sec_nanos = (comp.signed_int as i128) * 1_000_000_000i128;
197 if comp.frac_digits > 0 {
198 let frac_ns = (comp.frac_num as i128 * sign as i128 * 1_000_000_000i128)
199 / 10i128.pow(comp.frac_digits as u32);
200 sec_nanos += frac_ns;
201 }
202 sec_nanos
203 }
204 _ => {
205 return Err(an_err!(DtErrKind::InvalidItem, "{}", comp.unit as char));
206 }
207 };
208
209 *total_nanos = total_nanos.saturating_add(contrib_nanos);
210 }
211 Ok(())
212 }
213
214 pub fn looks_like_iso(s: &str) -> bool {
218 let len = s.len();
219 if matches!(len, 0 | 1) {
220 return false;
221 }
222 let b = s.as_bytes();
223 let mut i = 0usize;
224 if matches!(b[0], b'+' | b'-') {
226 i += 1;
227 }
228 if !matches!(b[i], b'P' | b'p') {
230 return false;
231 }
232 i += 1;
233 let mut has_digit = false;
234 let mut has_designator = false;
235 while i < len {
236 match b[i] {
237 b'0'..=b'9' => has_digit = true,
238 b'.' | b',' => {} b'Y' | b'y' | b'M' | b'm' | b'W' | b'w' | b'D' | b'd' | b'T' | b't' | b'H'
240 | b'h' | b'S' | b's' => {
241 has_designator = true;
242 }
243 _ => return false, }
245
246 i += 1;
247 }
248 has_digit && has_designator
250 }
251}