pub mod parser;
pub(crate) use parser::*;
use crate::error::{DtErr, DtErrKind};
use crate::{Dt, TimeParts, an_err};
use core::result::Result;
use core::str;
#[cfg(feature = "alloc")]
use crate::Scale;
#[derive(Debug, Clone, Copy)]
pub struct StrPTimeFmt {
fmt: [u8; Self::MAX_FORMAT_LEN],
len: usize,
}
impl StrPTimeFmt {
pub const MAX_FORMAT_LEN: usize = 256;
pub fn new(fmt: &str) -> Result<Self, DtErr> {
if fmt.len() > Self::MAX_FORMAT_LEN {
return Err(an_err!(
DtErrKind::UnexpectedEnd,
"format string too long (max {} bytes)",
Self::MAX_FORMAT_LEN
));
}
let fmt = fmt.as_bytes();
if !fmt.is_ascii() {
return Err(an_err!(
DtErrKind::UnexpectedEnd,
"format string must be ASCII"
));
}
Self::validate_format(fmt)?;
let mut buffer = [0u8; Self::MAX_FORMAT_LEN];
buffer[..fmt.len()].copy_from_slice(fmt);
Ok(Self {
fmt: buffer,
len: fmt.len(),
})
}
pub fn to_dt(
&self,
s: &str,
inp_can_end_before_fmt: bool,
fmt_can_end_before_inp: bool,
allow_partial_date: bool,
) -> Result<Dt, DtErr> {
TimeParts::from_str(
self.as_str()?,
s,
inp_can_end_before_fmt,
fmt_can_end_before_inp,
allow_partial_date,
)
.and_then(|p| p.to_dt())
}
#[cfg(feature = "alloc")]
pub fn to_str(
&self,
s: &str,
output_fmt: &str,
inp_can_end_before_fmt: bool,
fmt_can_end_before_inp: bool,
allow_partial_date: bool,
) -> Result<alloc::string::String, DtErr> {
self.to_dt(
s,
inp_can_end_before_fmt,
fmt_can_end_before_inp,
allow_partial_date,
)?
.to_str(Scale::TAI, output_fmt)
}
fn validate_format(mut fmt: &[u8]) -> Result<(), DtErr> {
while !fmt.is_empty() {
if fmt[0] != b'%' {
fmt = &fmt[1..];
continue;
}
if fmt.len() == 1 {
return Err(an_err!(DtErrKind::UnexpectedEnd, "after %"));
}
fmt = &fmt[1..];
let (_, _, _, new_fmt) = Parser::parse_format_extensions(fmt, 0);
fmt = new_fmt;
if fmt.is_empty() {
return Err(an_err!(DtErrKind::UnexpectedEnd, "expected directive"));
}
let directive = fmt[0];
match directive {
b'%' | b'A' | b'a' | b'B' | b'b' | b'h' | b'C' | b'd' | b'e' |
b'f' | b'N' | b'G' | b'g' | b'H' | b'k' | b'I' | b'l' | b'j' |
b'M' | b'm' | b'n' | b't' | b'P' | b'p' | b'Q' | b'S' | b's' |
b'U' | b'u' | b'V' | b'W' | b'w' | b'Y' | b'y' | b'z' |
b'F' | b'D' | b'T' | b'R' |
b'*' => {
fmt = &fmt[1..];
}
b'.' => {
fmt = &fmt[1..];
while !fmt.is_empty() && fmt[0].is_ascii_digit() {
fmt = &fmt[1..];
}
let next = fmt.first().copied().unwrap_or(0);
if !matches!(next, b'f' | b'N') {
return Err(an_err!(DtErrKind::BadFractional, "{}", char::from(next)));
}
fmt = &fmt[1..];
}
b'c' | b'r' | b'X' | b'x' | b'Z' => {
return Err(an_err!(
DtErrKind::UnsupportedDirective,
"{}",
char::from(directive)
));
}
_ => {
return Err(an_err!(DtErrKind::UnknownDirective));
}
}
}
Ok(())
}
#[inline]
fn as_bytes(&self) -> &[u8] {
&self.fmt[..self.len]
}
#[inline]
fn as_str(&self) -> Result<&str, DtErr> {
match core::str::from_utf8(self.as_bytes()) {
Ok(f) => Ok(f),
Err(e) => Err(an_err!(DtErrKind::InvalidBytes, "{}", e)),
}
}
}