use super::formatter::Item;
use crate::{parser::Token, ParsingErrors};
use crate::{Epoch, Errors, MonthName, TimeScale, Unit, Weekday};
use core::fmt;
use core::str::FromStr;
const MAX_TOKENS: usize = 16;
#[derive(Copy, Clone, Default, PartialEq)]
pub struct Format {
pub(crate) items: [Option<Item>; MAX_TOKENS],
pub(crate) num_items: usize,
}
impl Format {
pub(crate) fn need_gregorian(&self) -> bool {
for item in self.items.iter().take(self.num_items) {
match item.as_ref().unwrap().token {
Token::Year
| Token::YearShort
| Token::Month
| Token::MonthName
| Token::MonthNameShort
| Token::Day
| Token::Hour
| Token::Minute
| Token::Second
| Token::Subsecond
| Token::OffsetHours
| Token::OffsetMinutes => return true,
Token::Timescale
| Token::DayOfYearInteger
| Token::DayOfYear
| Token::Weekday
| Token::WeekdayShort
| Token::WeekdayDecimal => {
}
}
}
false
}
pub fn parse(&self, s_in: &str) -> Result<Epoch, Errors> {
let mut decomposed = [0_i32; MAX_TOKENS];
let mut ts = TimeScale::UTC;
let mut offset_sign = 1;
let mut day_of_year: Option<f64> = None;
let mut weekday: Option<Weekday> = None;
let mut prev_idx = 0;
let mut cur_item_idx = 0;
let mut cur_item = match self.items[cur_item_idx] {
Some(item) => item,
None => return Err(Errors::ParseError(ParsingErrors::UnknownFormat)),
};
let mut cur_token = cur_item.token;
let mut prev_item = cur_item;
let mut prev_token;
let s = s_in.trim();
for (idx, char) in s.chars().enumerate() {
if idx == s.len() - 1
|| ((cur_token.is_numeric() && !char.is_numeric())
|| (!cur_token.is_numeric() && (cur_item.sep_char_is(char))))
{
if idx == prev_idx
&& (prev_item.second_sep_char.is_none() || prev_item.second_sep_char_is(char))
{
prev_idx += 1;
continue;
}
if cur_token == Token::Timescale {
if idx != s.len() - 1 {
ts = TimeScale::from_str(s[idx..].trim())?;
}
break;
} else if char == 'Z' {
break;
}
prev_item = cur_item;
prev_token = cur_token;
let end_idx = if idx != s.len() - 1 || !char.is_numeric() {
if cur_item.sep_char_is_not(char)
&& (cur_item.second_sep_char.is_none()
|| (cur_item.second_sep_char_is_not(char)))
{
return Err(Errors::ParseError(ParsingErrors::UnexpectedCharacter {
found: char,
option1: cur_item.sep_char,
option2: cur_item.second_sep_char,
}));
}
if cur_item_idx == self.num_items {
break;
}
cur_item_idx += 1;
match self.items[cur_item_idx] {
Some(item) => {
cur_item = item;
cur_token = cur_item.token;
}
None => break,
}
idx
} else {
idx + 1
};
let sub_str = &s[prev_idx..end_idx];
match prev_token {
Token::YearShort => {
decomposed[0] = sub_str
.parse::<i32>()
.map_err(|_| Errors::ParseError(ParsingErrors::ValueError))?
+ 2000;
}
Token::DayOfYear => {
match lexical_core::parse(sub_str.as_bytes()) {
Ok(val) => day_of_year = Some(val),
Err(_) => return Err(Errors::ParseError(ParsingErrors::ValueError)),
}
}
Token::Weekday | Token::WeekdayShort => {
match Weekday::from_str(sub_str) {
Ok(day) => weekday = Some(day),
Err(err) => return Err(Errors::ParseError(err)),
}
}
Token::WeekdayDecimal => {
todo!()
}
Token::MonthName | Token::MonthNameShort => {
match MonthName::from_str(sub_str) {
Ok(month) => {
decomposed[1] = ((month as u8) + 1) as i32;
}
Err(_) => return Err(Errors::ParseError(ParsingErrors::ValueError)),
}
}
_ => {
match lexical_core::parse(sub_str.as_bytes()) {
Ok(val) => {
prev_token.value_ok(val)?;
match prev_token.gregorian_position() {
Some(pos) => {
if prev_token == Token::Subsecond {
if end_idx - prev_idx != 9 {
decomposed[pos] = val
* 10_i32.pow((9 - (end_idx - prev_idx)) as u32);
} else {
decomposed[pos] = val;
}
} else {
decomposed[pos] = val
}
}
None => match prev_token {
Token::DayOfYearInteger => day_of_year = Some(val as f64),
Token::Weekday => todo!(),
Token::WeekdayShort => todo!(),
Token::WeekdayDecimal => todo!(),
Token::MonthName => todo!(),
Token::MonthNameShort => todo!(),
_ => unreachable!(),
},
}
}
Err(_) => {
return Err(Errors::ParseError(ParsingErrors::ValueError));
}
}
}
}
prev_idx = idx + 1;
if cur_token == Token::OffsetHours {
if &s[idx..idx + 1] == "-" {
offset_sign = -1;
}
prev_idx += 1;
}
}
}
let tz = if offset_sign > 0 {
-(i64::from(decomposed[7]) * Unit::Hour + i64::from(decomposed[8]) * Unit::Minute)
} else {
i64::from(decomposed[7]) * Unit::Hour + i64::from(decomposed[8]) * Unit::Minute
};
let epoch = match day_of_year {
Some(days) => {
let elapsed = (decomposed[3] as i64) * Unit::Hour
+ (decomposed[4] as i64) * Unit::Minute
+ (decomposed[5] as i64) * Unit::Second
+ (decomposed[6] as i64) * Unit::Nanosecond;
Epoch::from_day_of_year(decomposed[0], days, ts) + elapsed
}
None => Epoch::maybe_from_gregorian(
decomposed[0],
decomposed[1].try_into().unwrap(),
decomposed[2].try_into().unwrap(),
decomposed[3].try_into().unwrap(),
decomposed[4].try_into().unwrap(),
decomposed[5].try_into().unwrap(),
decomposed[6].try_into().unwrap(),
ts,
)?,
};
if let Some(weekday) = weekday {
if weekday != epoch.weekday() {
return Err(Errors::ParseError(ParsingErrors::WeekdayMismatch {
found: weekday,
expected: epoch.weekday(),
}));
}
}
Ok(epoch + tz)
}
}
impl fmt::Debug for Format {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "EpochFormat:`")?;
for maybe_item in self.items.iter().take(self.num_items) {
let item = maybe_item.as_ref().unwrap();
write!(f, "{:?}", item.token)?;
if let Some(char) = item.sep_char {
write!(f, "{}", char)?;
}
if let Some(char) = item.second_sep_char {
write!(f, "{}", char)?;
}
if item.optional {
write!(f, "?")?;
}
}
write!(f, "`")?;
Ok(())
}
}
impl FromStr for Format {
type Err = ParsingErrors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut me = Format::default();
for token in s.split('%') {
match token.chars().next() {
Some(char) => match char {
'Y' => {
me.items[me.num_items] = Some(Item::new(
Token::Year,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'y' => {
me.items[me.num_items] = Some(Item::new(
Token::YearShort,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'm' => {
me.items[me.num_items] = Some(Item::new(
Token::Month,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'b' => {
me.items[me.num_items] = Some(Item::new(
Token::MonthNameShort,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'B' => {
me.items[me.num_items] = Some(Item::new(
Token::MonthName,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'd' => {
me.items[me.num_items] = Some(Item::new(
Token::Day,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'j' => {
me.items[me.num_items] = Some(Item::new(
Token::DayOfYearInteger,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'J' => {
me.items[me.num_items] = Some(Item::new(
Token::DayOfYear,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'A' => {
me.items[me.num_items] = Some(Item::new(
Token::Weekday,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'a' => {
me.items[me.num_items] = Some(Item::new(
Token::WeekdayShort,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'H' => {
me.items[me.num_items] = Some(Item::new(
Token::Hour,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'M' => {
me.items[me.num_items] = Some(Item::new(
Token::Minute,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'S' => {
me.items[me.num_items] = Some(Item::new(
Token::Second,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'f' => {
me.items[me.num_items] = Some(Item::new(
Token::Subsecond,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'T' => {
me.items[me.num_items] = Some(Item::new(
Token::Timescale,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'w' => {
me.items[me.num_items] = Some(Item::new(
Token::WeekdayDecimal,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
'z' => {
me.items[me.num_items] = Some(Item::new(
Token::OffsetHours,
token.chars().nth(1),
token.chars().nth(2),
));
me.num_items += 1;
}
_ => return Err(ParsingErrors::UnknownFormattingToken(char)),
},
None => continue, }
}
Ok(me)
}
}
#[test]
fn epoch_format_from_str() {
let fmt = Format::from_str("%Y-%m-%d").unwrap();
assert_eq!(fmt, crate::efmt::consts::ISO8601_DATE);
let fmt = Format::from_str("%Y-%m-%dT%H:%M:%S.%f %T").unwrap();
assert_eq!(fmt, crate::efmt::consts::ISO8601);
let fmt = Format::from_str("%Y-%m-%dT%H:%M:%S.%f? %T?").unwrap();
assert_eq!(fmt, crate::efmt::consts::ISO8601_FLEX);
let fmt = Format::from_str("%Y-%j").unwrap();
assert_eq!(fmt, crate::efmt::consts::ISO8601_ORDINAL);
let fmt = Format::from_str("%A, %d %B %Y %H:%M:%S").unwrap();
assert_eq!(fmt, crate::efmt::consts::RFC2822_LONG);
let fmt = Format::from_str("%a, %d %b %Y %H:%M:%S").unwrap();
assert_eq!(fmt, crate::efmt::consts::RFC2822);
}
#[cfg(feature = "std")]
#[test]
fn gh_248_regression() {
let e = Epoch::from_format_str("2023-117T12:55:26", "%Y-%jT%H:%M:%S").unwrap();
assert_eq!(format!("{e}"), "2023-04-27T12:55:26 UTC");
}