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