2018-05-24 22:28:06 -04:00
|
|
|
#![allow(dead_code)]
|
2018-05-24 22:31:08 -04:00
|
|
|
#![allow(unused)]
|
2018-05-24 22:28:06 -04:00
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
|
|
|
|
2018-05-13 16:18:45 -04:00
|
|
|
extern crate chrono;
|
2018-05-26 20:14:30 -04:00
|
|
|
extern crate num_traits;
|
2018-05-25 00:00:15 -04:00
|
|
|
extern crate rust_decimal;
|
2018-05-13 16:18:45 -04:00
|
|
|
|
2018-05-23 21:53:33 -04:00
|
|
|
use chrono::DateTime;
|
2018-05-17 22:56:46 -04:00
|
|
|
use chrono::Datelike;
|
2018-05-24 22:28:06 -04:00
|
|
|
use chrono::FixedOffset;
|
2018-05-17 22:56:46 -04:00
|
|
|
use chrono::Local;
|
2018-05-26 22:40:32 -04:00
|
|
|
use chrono::NaiveDate;
|
2018-05-23 21:53:33 -04:00
|
|
|
use chrono::NaiveDateTime;
|
|
|
|
use chrono::NaiveTime;
|
2018-05-26 22:40:32 -04:00
|
|
|
use chrono::Timelike;
|
2018-05-23 21:53:33 -04:00
|
|
|
use chrono::Utc;
|
2018-05-26 20:14:30 -04:00
|
|
|
use num_traits::cast::ToPrimitive;
|
2018-05-25 00:00:15 -04:00
|
|
|
use rust_decimal::Decimal;
|
|
|
|
use rust_decimal::Error as DecimalError;
|
2018-05-17 22:56:46 -04:00
|
|
|
use std::collections::HashMap;
|
2018-05-25 00:00:15 -04:00
|
|
|
use std::num::ParseIntError;
|
|
|
|
use std::str::FromStr;
|
2018-05-15 00:50:14 -04:00
|
|
|
use std::vec::Vec;
|
2018-05-13 16:18:45 -04:00
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
lazy_static! {
|
|
|
|
static ref ZERO: Decimal = Decimal::new(0, 0);
|
|
|
|
static ref ONE: Decimal = Decimal::new(1, 0);
|
|
|
|
static ref TWENTY_FOUR: Decimal = Decimal::new(24, 0);
|
|
|
|
static ref SIXTY: Decimal = Decimal::new(60, 0);
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
|
2018-05-25 00:00:15 -04:00
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub enum ParseInternalError {
|
2018-05-18 23:50:53 -04:00
|
|
|
// Errors that indicate internal bugs
|
|
|
|
YMDEarlyResolve,
|
2018-05-28 00:36:54 -04:00
|
|
|
YMDValueUnset(Vec<YMDLabel>),
|
2018-05-25 00:00:15 -04:00
|
|
|
ParseIndexError,
|
|
|
|
InvalidDecimal,
|
|
|
|
InvalidInteger,
|
2018-05-18 23:50:53 -04:00
|
|
|
|
|
|
|
// Python-style errors
|
|
|
|
ValueError(String),
|
|
|
|
}
|
|
|
|
|
2018-05-25 00:00:15 -04:00
|
|
|
impl From<DecimalError> for ParseInternalError {
|
2018-05-26 20:14:30 -04:00
|
|
|
fn from(err: DecimalError) -> Self {
|
|
|
|
ParseInternalError::InvalidDecimal
|
|
|
|
}
|
2018-05-25 00:00:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<ParseIntError> for ParseInternalError {
|
2018-05-26 20:14:30 -04:00
|
|
|
fn from(err: ParseIntError) -> Self {
|
|
|
|
ParseInternalError::InvalidInteger
|
|
|
|
}
|
2018-05-25 00:00:15 -04:00
|
|
|
}
|
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
#[derive(Debug)]
|
2018-05-25 00:00:15 -04:00
|
|
|
pub enum ParseError {
|
|
|
|
InternalError(ParseInternalError),
|
|
|
|
InvalidMonth,
|
2018-05-26 20:14:30 -04:00
|
|
|
UnrecognizedToken(String),
|
|
|
|
InvalidParseResult(ParsingResult),
|
2018-05-26 22:40:32 -04:00
|
|
|
AmPmWithoutHour,
|
|
|
|
InvalidHour,
|
|
|
|
TimezoneUnsupported,
|
2018-05-25 00:00:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<ParseInternalError> for ParseError {
|
2018-05-26 20:14:30 -04:00
|
|
|
fn from(err: ParseInternalError) -> Self {
|
|
|
|
ParseError::InternalError(err)
|
|
|
|
}
|
2018-05-25 00:00:15 -04:00
|
|
|
}
|
|
|
|
|
2018-05-18 23:50:53 -04:00
|
|
|
type ParseResult<I> = Result<I, ParseError>;
|
|
|
|
type ParseIResult<I> = Result<I, ParseInternalError>;
|
|
|
|
|
2018-05-15 00:50:14 -04:00
|
|
|
pub struct Tokenizer {
|
2018-05-24 22:24:28 -04:00
|
|
|
token_stack: Vec<String>,
|
2018-05-15 00:50:14 -04:00
|
|
|
parse_string: String,
|
|
|
|
}
|
|
|
|
|
2018-05-28 13:59:53 -04:00
|
|
|
#[derive(Debug, PartialEq)]
|
2018-05-15 00:50:14 -04:00
|
|
|
enum ParseState {
|
|
|
|
Empty,
|
|
|
|
Alpha,
|
|
|
|
AlphaDecimal,
|
|
|
|
Numeric,
|
|
|
|
NumericDecimal,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Tokenizer {
|
|
|
|
fn new(parse_string: String) -> Self {
|
|
|
|
Tokenizer {
|
|
|
|
token_stack: Vec::new(),
|
|
|
|
parse_string: parse_string.chars().rev().collect(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Iterator for Tokenizer {
|
2018-05-24 22:24:28 -04:00
|
|
|
type Item = String;
|
2018-05-15 00:50:14 -04:00
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
2018-05-17 00:31:57 -04:00
|
|
|
if !self.token_stack.is_empty() {
|
|
|
|
return Some(self.token_stack.pop().unwrap());
|
|
|
|
};
|
|
|
|
if self.parse_string.is_empty() {
|
|
|
|
return None;
|
|
|
|
};
|
2018-05-13 16:18:45 -04:00
|
|
|
|
2018-05-15 00:50:14 -04:00
|
|
|
let mut char_stack: Vec<char> = Vec::new();
|
|
|
|
let mut seen_letters = false;
|
|
|
|
let mut state = ParseState::Empty;
|
|
|
|
|
|
|
|
while let Some(next) = self.parse_string.pop() {
|
|
|
|
match state {
|
2018-05-17 00:31:57 -04:00
|
|
|
ParseState::Empty => {
|
|
|
|
if next.is_numeric() {
|
|
|
|
state = ParseState::Numeric;
|
|
|
|
char_stack.push(next);
|
|
|
|
} else if next.is_alphabetic() {
|
|
|
|
state = ParseState::Alpha;
|
|
|
|
seen_letters = true;
|
|
|
|
char_stack.push(next);
|
|
|
|
} else if next.is_whitespace() {
|
|
|
|
char_stack.push(' ');
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
char_stack.push(next);
|
|
|
|
break;
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
ParseState::Alpha => {
|
|
|
|
if next.is_alphabetic() {
|
|
|
|
char_stack.push(next);
|
|
|
|
} else if next == '.' {
|
|
|
|
state = ParseState::AlphaDecimal;
|
|
|
|
char_stack.push(next);
|
|
|
|
} else {
|
|
|
|
// We don't recognize the character, so push it back
|
|
|
|
// to be handled later.
|
|
|
|
self.parse_string.push(next);
|
|
|
|
break;
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
ParseState::AlphaDecimal => {
|
|
|
|
if next == '.' || next.is_alphabetic() {
|
|
|
|
char_stack.push(next);
|
|
|
|
} else if next.is_numeric() && char_stack.last().unwrap().clone() == '.' {
|
|
|
|
char_stack.push(next);
|
|
|
|
state = ParseState::NumericDecimal;
|
|
|
|
} else {
|
|
|
|
self.parse_string.push(next);
|
|
|
|
break;
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
ParseState::Numeric => {
|
|
|
|
if next.is_numeric() {
|
|
|
|
char_stack.push(next);
|
|
|
|
} else if next == '.' || (next == ',' && char_stack.len() >= 2) {
|
|
|
|
char_stack.push(next);
|
|
|
|
state = ParseState::NumericDecimal;
|
|
|
|
} else {
|
|
|
|
// We don't recognize the character, so push it back
|
|
|
|
// to be handled later
|
|
|
|
self.parse_string.push(next);
|
|
|
|
break;
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
ParseState::NumericDecimal => {
|
|
|
|
if next == '.' || next.is_numeric() {
|
|
|
|
char_stack.push(next);
|
|
|
|
} else if next.is_alphabetic() && char_stack.last().unwrap().clone() == '.' {
|
|
|
|
char_stack.push(next);
|
|
|
|
state = ParseState::AlphaDecimal;
|
|
|
|
} else {
|
|
|
|
self.parse_string.push(next);
|
|
|
|
break;
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
|
|
|
|
// I like Python's version of this much better:
|
|
|
|
// needs_split = seen_letters or char_stack.count('.') > 1 or char_stack[-1] in '.,'
|
2018-05-17 00:31:57 -04:00
|
|
|
let dot_count = char_stack.iter().fold(0, |count, character| {
|
|
|
|
count + (if character == &'.' { 1 } else { 0 })
|
|
|
|
});
|
|
|
|
let needs_split = seen_letters || dot_count > 1 || char_stack.last().unwrap() == &'.'
|
|
|
|
|| char_stack.last().unwrap() == &',';
|
2018-05-24 22:24:28 -04:00
|
|
|
let final_string: String = char_stack.into_iter().collect();
|
2018-05-15 00:50:14 -04:00
|
|
|
|
|
|
|
let mut tokens = match state {
|
2018-05-24 22:24:28 -04:00
|
|
|
ParseState::Empty => vec![final_string],
|
|
|
|
ParseState::Alpha => vec![final_string],
|
|
|
|
ParseState::Numeric => vec![final_string],
|
2018-05-15 00:50:14 -04:00
|
|
|
ParseState::AlphaDecimal => {
|
|
|
|
if needs_split {
|
2018-05-17 00:31:57 -04:00
|
|
|
decimal_split(&final_string, false)
|
2018-05-15 00:50:14 -04:00
|
|
|
} else {
|
2018-05-24 22:24:28 -04:00
|
|
|
vec![final_string]
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ParseState::NumericDecimal => {
|
|
|
|
if needs_split {
|
|
|
|
decimal_split(&final_string, dot_count == 0)
|
|
|
|
} else {
|
2018-05-24 22:24:28 -04:00
|
|
|
vec![final_string]
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
}.into_iter()
|
|
|
|
.rev()
|
|
|
|
.collect();
|
2018-05-15 00:50:14 -04:00
|
|
|
|
|
|
|
self.token_stack.append(&mut tokens);
|
|
|
|
// UNWRAP: Previous match guaranteed that at least one token was added
|
2018-05-28 13:59:53 -04:00
|
|
|
let token = self.token_stack.pop().unwrap();
|
|
|
|
if state == ParseState::NumericDecimal && !token.contains(".") {
|
|
|
|
Some(token.replace(",", "."))
|
|
|
|
} else {
|
|
|
|
Some(token)
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-24 22:24:28 -04:00
|
|
|
fn decimal_split(characters: &str, cast_period: bool) -> Vec<String> {
|
|
|
|
let mut token_stack: Vec<String> = Vec::new();
|
2018-05-15 00:50:14 -04:00
|
|
|
let mut char_stack: Vec<char> = Vec::new();
|
|
|
|
let mut state = ParseState::Empty;
|
|
|
|
|
2018-05-17 00:31:57 -04:00
|
|
|
for c in characters.chars() {
|
|
|
|
match state {
|
|
|
|
ParseState::Empty => {
|
|
|
|
if c.is_alphabetic() {
|
|
|
|
char_stack.push(c);
|
|
|
|
state = ParseState::Alpha;
|
|
|
|
} else if c.is_numeric() {
|
|
|
|
char_stack.push(c);
|
|
|
|
state = ParseState::Numeric;
|
|
|
|
} else {
|
|
|
|
let character = if cast_period { '.' } else { c };
|
2018-05-24 22:24:28 -04:00
|
|
|
token_stack.push(character.to_string());
|
2018-05-17 00:31:57 -04:00
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
ParseState::Alpha => {
|
|
|
|
if c.is_alphabetic() {
|
|
|
|
char_stack.push(c);
|
|
|
|
} else {
|
2018-05-24 22:24:28 -04:00
|
|
|
token_stack.push(char_stack.iter().collect());
|
2018-05-17 00:31:57 -04:00
|
|
|
char_stack.clear();
|
|
|
|
let character = if cast_period { '.' } else { c };
|
2018-05-24 22:24:28 -04:00
|
|
|
token_stack.push(character.to_string());
|
2018-05-17 00:31:57 -04:00
|
|
|
state = ParseState::Empty;
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
ParseState::Numeric => {
|
|
|
|
if c.is_numeric() {
|
|
|
|
char_stack.push(c);
|
|
|
|
} else {
|
2018-05-24 22:24:28 -04:00
|
|
|
token_stack.push(char_stack.iter().collect());
|
2018-05-17 00:31:57 -04:00
|
|
|
char_stack.clear();
|
|
|
|
let character = if cast_period { '.' } else { c };
|
2018-05-24 22:24:28 -04:00
|
|
|
token_stack.push(character.to_string());
|
2018-05-17 00:31:57 -04:00
|
|
|
state = ParseState::Empty;
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
2018-05-17 00:31:57 -04:00
|
|
|
_ => panic!("Invalid parse state during decimal_split()"),
|
|
|
|
}
|
|
|
|
}
|
2018-05-15 00:50:14 -04:00
|
|
|
|
|
|
|
match state {
|
2018-05-24 22:24:28 -04:00
|
|
|
ParseState::Alpha => token_stack.push(char_stack.iter().collect()),
|
|
|
|
ParseState::Numeric => token_stack.push(char_stack.iter().collect()),
|
2018-05-15 00:50:14 -04:00
|
|
|
ParseState::Empty => (),
|
2018-05-17 00:31:57 -04:00
|
|
|
_ => panic!("Invalid parse state during decimal_split()"),
|
2018-05-15 00:50:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
token_stack
|
|
|
|
}
|
|
|
|
|
2018-05-24 22:24:28 -04:00
|
|
|
pub fn tokenize(parse_string: &str) -> Vec<String> {
|
2018-05-15 00:50:14 -04:00
|
|
|
let tokenizer = Tokenizer::new(parse_string.to_owned());
|
|
|
|
tokenizer.collect()
|
|
|
|
}
|
2018-05-17 22:56:46 -04:00
|
|
|
|
|
|
|
fn parse_info(vec: Vec<Vec<&str>>) -> HashMap<String, usize> {
|
|
|
|
let mut m = HashMap::new();
|
|
|
|
|
|
|
|
if vec.len() == 1 {
|
|
|
|
for (i, val) in vec.get(0).unwrap().into_iter().enumerate() {
|
|
|
|
m.insert(val.to_lowercase(), i);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (i, val_vec) in vec.into_iter().enumerate() {
|
|
|
|
for val in val_vec.into_iter() {
|
|
|
|
m.insert(val.to_lowercase(), i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m
|
|
|
|
}
|
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
#[derive(Debug, PartialEq)]
|
2018-05-29 21:07:22 -04:00
|
|
|
pub struct ParserInfo {
|
2018-05-17 22:56:46 -04:00
|
|
|
jump: HashMap<String, usize>,
|
|
|
|
weekday: HashMap<String, usize>,
|
|
|
|
months: HashMap<String, usize>,
|
|
|
|
hms: HashMap<String, usize>,
|
|
|
|
ampm: HashMap<String, usize>,
|
|
|
|
utczone: HashMap<String, usize>,
|
|
|
|
pertain: HashMap<String, usize>,
|
|
|
|
tzoffset: HashMap<String, usize>,
|
|
|
|
dayfirst: bool,
|
|
|
|
yearfirst: bool,
|
2018-05-25 00:00:15 -04:00
|
|
|
year: i32,
|
|
|
|
century: i32,
|
2018-05-17 22:56:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ParserInfo {
|
|
|
|
fn default() -> Self {
|
|
|
|
let year = Local::now().year();
|
|
|
|
let century = year / 100 * 100;
|
|
|
|
|
|
|
|
ParserInfo {
|
2018-05-18 23:50:53 -04:00
|
|
|
jump: parse_info(vec![
|
|
|
|
vec![
|
|
|
|
" ", ".", ",", ";", "-", "/", "'", "at", "on", "and", "ad", "m", "t", "of",
|
|
|
|
"st", "nd", "rd", "th",
|
|
|
|
],
|
|
|
|
]),
|
2018-05-17 22:56:46 -04:00
|
|
|
weekday: parse_info(vec![
|
|
|
|
vec!["Mon", "Monday"],
|
|
|
|
vec!["Tue", "Tues", "Tuesday"],
|
|
|
|
vec!["Wed", "Wednesday"],
|
|
|
|
vec!["Thu", "Thurs", "Thursday"],
|
|
|
|
vec!["Fri", "Friday"],
|
|
|
|
vec!["Sat", "Saturday"],
|
|
|
|
vec!["Sun", "Sunday"],
|
|
|
|
]),
|
|
|
|
months: parse_info(vec![
|
|
|
|
vec!["Jan", "January"],
|
|
|
|
vec!["Feb", "February"],
|
|
|
|
vec!["Mar", "March"],
|
|
|
|
vec!["Apr", "April"],
|
|
|
|
vec!["May"],
|
|
|
|
vec!["Jun", "June"],
|
|
|
|
vec!["Jul", "July"],
|
|
|
|
vec!["Aug", "August"],
|
|
|
|
vec!["Sep", "Sept", "September"],
|
|
|
|
vec!["Oct", "October"],
|
|
|
|
vec!["Nov", "November"],
|
|
|
|
vec!["Dec", "December"],
|
|
|
|
]),
|
|
|
|
hms: parse_info(vec![
|
|
|
|
vec!["h", "hour", "hours"],
|
|
|
|
vec!["m", "minute", "minutes"],
|
|
|
|
vec!["s", "second", "seconds"],
|
|
|
|
]),
|
2018-05-18 23:50:53 -04:00
|
|
|
ampm: parse_info(vec![vec!["am", "a"], vec!["pm", "p"]]),
|
|
|
|
utczone: parse_info(vec![vec!["UTC", "GMT", "Z"]]),
|
2018-05-17 22:56:46 -04:00
|
|
|
pertain: parse_info(vec![vec!["of"]]),
|
|
|
|
tzoffset: parse_info(vec![vec![]]),
|
|
|
|
dayfirst: false,
|
|
|
|
yearfirst: false,
|
2018-05-25 00:00:15 -04:00
|
|
|
year: year,
|
|
|
|
century: century,
|
2018-05-17 22:56:46 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ParserInfo {
|
|
|
|
fn get_jump(&self, name: &str) -> bool {
|
|
|
|
self.jump.contains_key(&name.to_lowercase())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_weekday(&self, name: &str) -> Option<usize> {
|
|
|
|
self.weekday.get(&name.to_lowercase()).cloned() // TODO: Why do I have to clone a primitive?
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_month(&self, name: &str) -> Option<usize> {
|
|
|
|
self.months.get(&name.to_lowercase()).map(|u| u + 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_hms(&self, name: &str) -> Option<usize> {
|
|
|
|
self.hms.get(&name.to_lowercase()).cloned()
|
|
|
|
}
|
|
|
|
|
2018-05-26 22:40:32 -04:00
|
|
|
fn get_ampm(&self, name: &str) -> Option<bool> {
|
|
|
|
if let Some(v) = self.ampm.get(&name.to_lowercase()) {
|
|
|
|
Some(v.to_owned() == 1)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2018-05-17 22:56:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_pertain(&self, name: &str) -> bool {
|
|
|
|
self.pertain.contains_key(&name.to_lowercase())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_utczone(&self, name: &str) -> bool {
|
|
|
|
self.utczone.contains_key(&name.to_lowercase())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_tzoffset(&self, name: &str) -> Option<usize> {
|
|
|
|
if self.utczone.contains_key(&name.to_lowercase()) {
|
|
|
|
Some(0)
|
|
|
|
} else {
|
|
|
|
self.tzoffset.get(&name.to_lowercase()).cloned()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-25 00:00:15 -04:00
|
|
|
fn convertyear(&self, year: i32, century_specified: bool) -> i32 {
|
2018-05-17 22:56:46 -04:00
|
|
|
let mut year = year;
|
|
|
|
|
|
|
|
if year < 100 && !century_specified {
|
|
|
|
year += self.century;
|
|
|
|
if year >= self.year + 50 {
|
|
|
|
year -= 100;
|
|
|
|
} else if year < self.year - 50 {
|
|
|
|
year += 100
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
year
|
|
|
|
}
|
2018-05-25 00:00:15 -04:00
|
|
|
|
|
|
|
// TODO: Should this be moved elsewhere?
|
|
|
|
fn validate(&self, res: &mut ParsingResult) -> bool {
|
2018-05-26 20:14:30 -04:00
|
|
|
if let Some(y) = res.year {
|
|
|
|
res.year = Some(self.convertyear(y, res.century_specified))
|
|
|
|
};
|
|
|
|
|
|
|
|
if res.tzoffset == Some(0) && res.tzname.is_none() || res.tzname == Some("Z".to_owned()) {
|
2018-05-25 00:00:15 -04:00
|
|
|
res.tzname = Some("UTC".to_owned());
|
2018-05-26 20:14:30 -04:00
|
|
|
res.tzoffset = Some(0);
|
|
|
|
} else if res.tzoffset != Some(0) && res.tzname.is_some()
|
|
|
|
&& self.get_utczone(res.tzname.as_ref().unwrap())
|
|
|
|
{
|
|
|
|
res.tzoffset = Some(0);
|
2018-05-25 00:00:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
true
|
|
|
|
}
|
2018-05-18 23:50:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn days_in_month(year: i32, month: i32) -> Result<i32, ParseError> {
|
|
|
|
let leap_year = match year % 4 {
|
|
|
|
0 => year % 400 == 0,
|
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
|
|
|
|
match month {
|
2018-05-26 20:14:30 -04:00
|
|
|
2 => if leap_year {
|
|
|
|
Ok(29)
|
|
|
|
} else {
|
|
|
|
Ok(28)
|
|
|
|
},
|
2018-05-18 23:50:53 -04:00
|
|
|
1 | 3 | 5 | 7 | 8 | 10 | 12 => Ok(31),
|
|
|
|
4 | 6 | 9 | 11 => Ok(30),
|
|
|
|
_ => Err(ParseError::InvalidMonth),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
2018-05-28 00:36:54 -04:00
|
|
|
pub enum YMDLabel {
|
2018-05-18 23:50:53 -04:00
|
|
|
Year,
|
|
|
|
Month,
|
|
|
|
Day,
|
|
|
|
}
|
|
|
|
|
2018-05-25 00:00:15 -04:00
|
|
|
#[derive(Debug, Default)]
|
2018-05-18 23:50:53 -04:00
|
|
|
struct YMD {
|
|
|
|
_ymd: Vec<i32>, // TODO: This seems like a super weird way to store things
|
|
|
|
century_specified: bool,
|
|
|
|
dstridx: Option<usize>,
|
|
|
|
mstridx: Option<usize>,
|
|
|
|
ystridx: Option<usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl YMD {
|
2018-05-26 20:14:30 -04:00
|
|
|
fn len(&self) -> usize {
|
|
|
|
self._ymd.len()
|
|
|
|
}
|
2018-05-25 00:00:15 -04:00
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
fn could_be_day(&self, val: i32) -> bool {
|
2018-05-18 23:50:53 -04:00
|
|
|
if self.dstridx.is_some() {
|
2018-05-26 20:14:30 -04:00
|
|
|
false
|
2018-05-18 23:50:53 -04:00
|
|
|
} else if self.mstridx.is_none() {
|
2018-05-26 20:14:30 -04:00
|
|
|
(1 <= val) && (val <= 31)
|
2018-05-18 23:50:53 -04:00
|
|
|
} else if self.ystridx.is_none() {
|
|
|
|
// UNWRAP: mstridx guaranteed to have a value
|
|
|
|
// TODO: Justify unwrap for self._ymd
|
|
|
|
let month = self._ymd[self.mstridx.unwrap()];
|
2018-05-26 20:14:30 -04:00
|
|
|
1 <= val && (val <= days_in_month(2000, month).unwrap())
|
2018-05-18 23:50:53 -04:00
|
|
|
} else {
|
|
|
|
let month = self._ymd[self.mstridx.unwrap()];
|
|
|
|
let year = self._ymd[self.ystridx.unwrap()];
|
2018-05-26 20:14:30 -04:00
|
|
|
1 <= val && (val <= days_in_month(year, month).unwrap())
|
2018-05-18 23:50:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn append(&mut self, val: i32, label: Option<YMDLabel>) -> ParseIResult<()> {
|
|
|
|
let mut label = label;
|
|
|
|
|
2018-05-28 00:36:54 -04:00
|
|
|
// TODO: Duck typing with val being an array type?
|
2018-05-18 23:50:53 -04:00
|
|
|
if val > 100 {
|
|
|
|
self.century_specified = true;
|
|
|
|
match label {
|
|
|
|
None => label = Some(YMDLabel::Year),
|
|
|
|
Some(YMDLabel::Year) => (),
|
|
|
|
_ => {
|
|
|
|
return Err(ParseInternalError::ValueError(format!(
|
|
|
|
"Invalid label: {:?}",
|
|
|
|
label
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-28 00:36:54 -04:00
|
|
|
self._ymd.push(val);
|
|
|
|
|
2018-05-18 23:50:53 -04:00
|
|
|
match label {
|
|
|
|
Some(YMDLabel::Month) => {
|
|
|
|
if self.mstridx.is_some() {
|
|
|
|
Err(ParseInternalError::ValueError(
|
|
|
|
"Month already set.".to_owned(),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
self.mstridx = Some(self._ymd.len() - 1);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(YMDLabel::Day) => {
|
|
|
|
if self.dstridx.is_some() {
|
|
|
|
Err(ParseInternalError::ValueError(
|
|
|
|
"Day already set.".to_owned(),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
self.dstridx = Some(self._ymd.len() - 1);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(YMDLabel::Year) => {
|
|
|
|
if self.ystridx.is_some() {
|
|
|
|
Err(ParseInternalError::ValueError(
|
|
|
|
"Year already set.".to_owned(),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
self.ystridx = Some(self._ymd.len() - 1);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => Err(ParseInternalError::ValueError("Missing label.".to_owned())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn resolve_from_stridxs(
|
|
|
|
&mut self,
|
|
|
|
strids: &mut HashMap<YMDLabel, usize>,
|
2018-05-28 12:28:49 -04:00
|
|
|
) -> ParseIResult<(Option<i32>, Option<i32>, Option<i32>)> {
|
|
|
|
if self._ymd.len() == 3 && strids.len() == 2 {
|
2018-05-18 23:50:53 -04:00
|
|
|
let missing_key = if !strids.contains_key(&YMDLabel::Year) {
|
|
|
|
YMDLabel::Year
|
|
|
|
} else if !strids.contains_key(&YMDLabel::Month) {
|
|
|
|
YMDLabel::Month
|
|
|
|
} else {
|
|
|
|
YMDLabel::Day
|
|
|
|
};
|
|
|
|
|
|
|
|
let strids_vals: Vec<usize> = strids.values().map(|u| u.clone()).collect();
|
|
|
|
let missing_val = if !strids_vals.contains(&0) {
|
|
|
|
0
|
|
|
|
} else if !strids_vals.contains(&1) {
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
2
|
|
|
|
};
|
|
|
|
|
|
|
|
strids.insert(missing_key, missing_val);
|
|
|
|
}
|
|
|
|
|
2018-05-28 12:28:49 -04:00
|
|
|
if self._ymd.len() != strids.len() {
|
2018-05-18 23:50:53 -04:00
|
|
|
return Err(ParseInternalError::YMDEarlyResolve);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Why do I have to clone &usize? Isn't it Copy?
|
|
|
|
Ok((
|
2018-05-28 13:21:34 -04:00
|
|
|
strids
|
|
|
|
.get(&YMDLabel::Year)
|
|
|
|
.map(|i| self._ymd.get(*i).unwrap().clone()),
|
|
|
|
strids
|
|
|
|
.get(&YMDLabel::Month)
|
|
|
|
.map(|i| self._ymd.get(*i).unwrap().clone()),
|
|
|
|
strids
|
|
|
|
.get(&YMDLabel::Day)
|
|
|
|
.map(|i| self._ymd.get(*i).unwrap().clone()),
|
2018-05-18 23:50:53 -04:00
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2018-05-28 13:21:34 -04:00
|
|
|
fn resolve_ymd(
|
|
|
|
&mut self,
|
|
|
|
yearfirst: bool,
|
|
|
|
dayfirst: bool,
|
|
|
|
) -> ParseIResult<(Option<i32>, Option<i32>, Option<i32>)> {
|
2018-05-18 23:50:53 -04:00
|
|
|
let len_ymd = self._ymd.len();
|
|
|
|
let mut year: Option<i32> = None;
|
|
|
|
let mut month: Option<i32> = None;
|
|
|
|
let mut day: Option<i32> = None;
|
|
|
|
let mut other: Option<i32> = None;
|
|
|
|
|
|
|
|
let mut strids: HashMap<YMDLabel, usize> = HashMap::new();
|
|
|
|
self.ystridx
|
|
|
|
.map(|u| strids.insert(YMDLabel::Year, u.clone()));
|
|
|
|
self.mstridx
|
|
|
|
.map(|u| strids.insert(YMDLabel::Month, u.clone()));
|
|
|
|
self.dstridx
|
|
|
|
.map(|u| strids.insert(YMDLabel::Day, u.clone()));
|
|
|
|
|
|
|
|
// TODO: More Rustiomatic way of doing this?
|
2018-05-28 13:21:34 -04:00
|
|
|
if self._ymd.len() == strids.len() && strids.len() > 0
|
|
|
|
|| (self._ymd.len() == 3 && strids.len() == 2)
|
|
|
|
{
|
2018-05-28 00:36:54 -04:00
|
|
|
return self.resolve_from_stridxs(&mut strids);
|
2018-05-23 21:53:33 -04:00
|
|
|
};
|
2018-05-18 23:50:53 -04:00
|
|
|
|
|
|
|
// TODO: More Rustiomatic? Too many blocks for my liking
|
|
|
|
// Also having the array unpacking syntax is nice
|
|
|
|
if len_ymd > 3 {
|
|
|
|
return Err(ParseInternalError::ValueError(
|
|
|
|
"More than three YMD values".to_owned(),
|
|
|
|
));
|
|
|
|
} else if len_ymd == 1 || (self.mstridx.is_some() && len_ymd == 2) {
|
|
|
|
if self.mstridx.is_some() {
|
|
|
|
month = Some(self._ymd[self.mstridx.unwrap()]);
|
2018-05-28 13:21:34 -04:00
|
|
|
other = if len_ymd == 1 {
|
|
|
|
Some(self._ymd[0])
|
|
|
|
} else {
|
|
|
|
Some(self._ymd[1 - self.mstridx.unwrap()])
|
|
|
|
};
|
2018-05-18 23:50:53 -04:00
|
|
|
} else {
|
|
|
|
other = Some(self._ymd[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if len_ymd > 1 || self.mstridx.is_some() {
|
|
|
|
if other.unwrap_or(0) > 31 {
|
|
|
|
year = other;
|
|
|
|
} else {
|
|
|
|
day = other;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if len_ymd == 2 {
|
|
|
|
if self._ymd[0] > 31 {
|
|
|
|
year = Some(self._ymd[0]);
|
|
|
|
month = Some(self._ymd[1]);
|
|
|
|
} else if self._ymd[1] > 31 {
|
|
|
|
month = Some(self._ymd[0]);
|
|
|
|
year = Some(self._ymd[1]);
|
|
|
|
} else if dayfirst && self._ymd[1] <= 12 {
|
|
|
|
day = Some(self._ymd[0]);
|
|
|
|
month = Some(self._ymd[1]);
|
|
|
|
} else {
|
|
|
|
month = Some(self._ymd[0]);
|
|
|
|
day = Some(self._ymd[1]);
|
|
|
|
}
|
|
|
|
} else if len_ymd == 3 {
|
2018-05-28 11:03:11 -04:00
|
|
|
if self.mstridx == Some(0) {
|
2018-05-18 23:50:53 -04:00
|
|
|
if self._ymd[1] > 31 {
|
|
|
|
month = Some(self._ymd[0]);
|
|
|
|
year = Some(self._ymd[1]);
|
|
|
|
day = Some(self._ymd[2]);
|
|
|
|
} else {
|
|
|
|
month = Some(self._ymd[0]);
|
|
|
|
day = Some(self._ymd[1]);
|
|
|
|
year = Some(self._ymd[2]);
|
|
|
|
}
|
2018-05-28 11:03:11 -04:00
|
|
|
} else if self.mstridx == Some(1) {
|
2018-05-18 23:50:53 -04:00
|
|
|
if self._ymd[0] > 31 || (yearfirst && self._ymd[2] <= 31) {
|
|
|
|
year = Some(self._ymd[0]);
|
|
|
|
month = Some(self._ymd[1]);
|
|
|
|
day = Some(self._ymd[2]);
|
|
|
|
} else {
|
|
|
|
day = Some(self._ymd[0]);
|
|
|
|
month = Some(self._ymd[1]);
|
|
|
|
year = Some(self._ymd[2]);
|
|
|
|
}
|
2018-05-28 11:03:11 -04:00
|
|
|
} else if self.mstridx == Some(2) {
|
2018-05-18 23:50:53 -04:00
|
|
|
// It was in the original docs, so: WTF!?
|
|
|
|
if self._ymd[1] > 31 {
|
|
|
|
day = Some(self._ymd[0]);
|
|
|
|
year = Some(self._ymd[1]);
|
|
|
|
month = Some(self._ymd[2]);
|
|
|
|
} else {
|
|
|
|
year = Some(self._ymd[0]);
|
|
|
|
day = Some(self._ymd[1]);
|
|
|
|
month = Some(self._ymd[2]);
|
|
|
|
}
|
|
|
|
} else {
|
2018-05-29 21:07:22 -04:00
|
|
|
if self._ymd[0] > 31 || self.ystridx == Some(0)
|
2018-05-23 21:53:33 -04:00
|
|
|
|| (yearfirst && self._ymd[1] <= 12 && self._ymd[2] <= 31)
|
|
|
|
{
|
2018-05-18 23:50:53 -04:00
|
|
|
if dayfirst && self._ymd[2] <= 12 {
|
|
|
|
year = Some(self._ymd[0]);
|
|
|
|
day = Some(self._ymd[1]);
|
|
|
|
month = Some(self._ymd[2]);
|
|
|
|
} else {
|
|
|
|
year = Some(self._ymd[0]);
|
|
|
|
month = Some(self._ymd[1]);
|
|
|
|
day = Some(self._ymd[2]);
|
|
|
|
}
|
2018-05-23 21:53:33 -04:00
|
|
|
} else if self._ymd[0] > 12 || (dayfirst && self._ymd[1] <= 12) {
|
2018-05-18 23:50:53 -04:00
|
|
|
day = Some(self._ymd[0]);
|
|
|
|
month = Some(self._ymd[1]);
|
|
|
|
year = Some(self._ymd[2]);
|
|
|
|
} else {
|
|
|
|
month = Some(self._ymd[0]);
|
|
|
|
day = Some(self._ymd[1]);
|
|
|
|
year = Some(self._ymd[2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-28 12:28:49 -04:00
|
|
|
Ok((year, month, day))
|
2018-05-18 23:50:53 -04:00
|
|
|
}
|
|
|
|
}
|
2018-05-23 21:53:33 -04:00
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
#[derive(Default, Debug)]
|
|
|
|
pub struct ParsingResult {
|
2018-05-25 00:00:15 -04:00
|
|
|
year: Option<i32>,
|
|
|
|
month: Option<i32>,
|
|
|
|
day: Option<i32>,
|
|
|
|
weekday: Option<bool>,
|
|
|
|
hour: Option<i32>,
|
|
|
|
minute: Option<i32>,
|
|
|
|
second: Option<i32>,
|
|
|
|
microsecond: Option<i32>,
|
|
|
|
tzname: Option<String>,
|
2018-05-26 20:14:30 -04:00
|
|
|
tzoffset: Option<i32>,
|
2018-05-26 22:40:32 -04:00
|
|
|
ampm: Option<bool>,
|
2018-05-25 00:00:15 -04:00
|
|
|
century_specified: bool,
|
2018-05-23 21:53:33 -04:00
|
|
|
any_unused_tokens: Vec<String>,
|
|
|
|
}
|
|
|
|
|
2018-05-25 00:00:15 -04:00
|
|
|
#[derive(Default)]
|
2018-05-23 21:53:33 -04:00
|
|
|
struct Parser {
|
|
|
|
info: ParserInfo,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Parser {
|
|
|
|
pub fn parse(
|
2018-05-25 00:00:15 -04:00
|
|
|
&mut self,
|
2018-05-27 22:26:30 -04:00
|
|
|
timestr: &str,
|
2018-05-28 12:28:49 -04:00
|
|
|
default: Option<&NaiveDateTime>,
|
2018-05-23 21:53:33 -04:00
|
|
|
ignoretz: bool,
|
|
|
|
tzinfos: Vec<String>,
|
2018-05-26 20:14:30 -04:00
|
|
|
) -> Result<(NaiveDateTime, Option<FixedOffset>, Option<Vec<String>>), ParseError> {
|
2018-05-28 12:28:49 -04:00
|
|
|
let default_date = default.unwrap_or(&Local::now().naive_local()).date();
|
2018-05-23 21:53:33 -04:00
|
|
|
|
|
|
|
let default_ts = NaiveDateTime::new(default_date, NaiveTime::from_hms(0, 0, 0));
|
|
|
|
|
|
|
|
// TODO: What should be done with the tokens?
|
2018-05-26 20:14:30 -04:00
|
|
|
let (res, tokens) = self.parse_with_tokens(timestr, None, None, false, false)?;
|
2018-05-23 21:53:33 -04:00
|
|
|
|
2018-05-28 12:28:49 -04:00
|
|
|
let naive = self.build_naive(&res, &default_ts);
|
2018-05-26 20:14:30 -04:00
|
|
|
|
|
|
|
if !ignoretz {
|
2018-05-28 12:28:49 -04:00
|
|
|
let offset = self.build_tzaware(&naive, &res, &default_ts);
|
2018-05-28 11:03:11 -04:00
|
|
|
Ok((naive, offset.unwrap(), tokens))
|
2018-05-26 20:14:30 -04:00
|
|
|
} else {
|
|
|
|
Ok((naive, None, tokens))
|
|
|
|
}
|
2018-05-23 21:53:33 -04:00
|
|
|
}
|
|
|
|
|
2018-05-23 23:01:00 -04:00
|
|
|
fn parse_with_tokens(
|
2018-05-25 00:00:15 -04:00
|
|
|
&mut self,
|
2018-05-27 22:26:30 -04:00
|
|
|
timestr: &str,
|
2018-05-25 00:00:15 -04:00
|
|
|
dayfirst: Option<bool>,
|
|
|
|
yearfirst: Option<bool>,
|
2018-05-23 23:01:00 -04:00
|
|
|
fuzzy: bool,
|
|
|
|
fuzzy_with_tokens: bool,
|
2018-05-26 20:14:30 -04:00
|
|
|
) -> Result<(ParsingResult, Option<Vec<String>>), ParseError> {
|
2018-05-25 00:00:15 -04:00
|
|
|
let fuzzy = if fuzzy_with_tokens { true } else { fuzzy };
|
|
|
|
// This is probably a stylistic abomination
|
2018-05-26 20:14:30 -04:00
|
|
|
let dayfirst = if let Some(dayfirst) = dayfirst {
|
|
|
|
dayfirst
|
|
|
|
} else {
|
|
|
|
self.info.dayfirst
|
|
|
|
};
|
|
|
|
let yearfirst = if let Some(yearfirst) = yearfirst {
|
|
|
|
yearfirst
|
|
|
|
} else {
|
|
|
|
self.info.yearfirst
|
|
|
|
};
|
2018-05-25 00:00:15 -04:00
|
|
|
|
|
|
|
let mut res = ParsingResult::default();
|
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
let mut l = tokenize(×tr);
|
|
|
|
let mut skipped_idxs: Vec<usize> = Vec::new();
|
|
|
|
|
|
|
|
let mut ymd = YMD::default();
|
2018-05-25 00:00:15 -04:00
|
|
|
|
|
|
|
let len_l = l.len();
|
|
|
|
let mut i = 0;
|
|
|
|
|
|
|
|
while i < len_l {
|
2018-05-26 20:14:30 -04:00
|
|
|
let value_repr = l[i].clone();
|
2018-05-25 00:00:15 -04:00
|
|
|
|
2018-05-26 22:40:32 -04:00
|
|
|
if let Ok(v) = Decimal::from_str(&value_repr) {
|
2018-05-26 20:14:30 -04:00
|
|
|
i = self.parse_numeric_token(&l, i, &self.info, &mut ymd, &mut res, fuzzy)?;
|
|
|
|
} else if let Some(value) = self.info.get_weekday(&l[i]) {
|
|
|
|
res.weekday = Some(value != 0);
|
|
|
|
} else if let Some(value) = self.info.get_month(&l[i]) {
|
|
|
|
ymd.append(value as i32, Some(YMDLabel::Month));
|
|
|
|
|
|
|
|
if i + 1 < len_l {
|
|
|
|
if l[i + 1] == "-" || l[i + 1] == "/" {
|
|
|
|
// Jan-01[-99]
|
|
|
|
let sep = &l[i + 1];
|
|
|
|
// TODO: This seems like a very unsafe unwrap
|
|
|
|
ymd.append(l[i + 2].parse::<i32>().unwrap(), None);
|
|
|
|
|
|
|
|
if i + 3 < len_l && &l[i + 3] == sep {
|
|
|
|
// Jan-01-99
|
|
|
|
ymd.append(l[i + 4].parse::<i32>().unwrap(), None);
|
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
i += 2;
|
|
|
|
} else if (i + 4 < len_l && l[i + 1] == l[i + 3] && l[i + 3] == " "
|
|
|
|
&& self.info.get_pertain(&l[i + 2]))
|
|
|
|
{
|
|
|
|
// Jan of 01
|
|
|
|
if let Some(value) = l[i + 4].parse::<i32>().ok() {
|
|
|
|
let year = self.info.convertyear(value, false);
|
|
|
|
ymd.append(year, Some(YMDLabel::Year));
|
|
|
|
}
|
|
|
|
|
|
|
|
i += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if let Some(value) = self.info.get_ampm(&l[i]) {
|
|
|
|
let is_ampm = self.ampm_valid(res.hour, res.ampm, fuzzy);
|
|
|
|
|
2018-05-26 22:40:32 -04:00
|
|
|
if is_ampm.is_ok() {
|
2018-05-26 20:14:30 -04:00
|
|
|
res.hour = Some(self.adjust_ampm(res.hour.unwrap(), value));
|
|
|
|
res.ampm = Some(value);
|
|
|
|
} else if fuzzy {
|
|
|
|
skipped_idxs.push(i);
|
|
|
|
}
|
|
|
|
} else if self.could_be_tzname(res.hour, res.tzname.clone(), res.tzoffset, &l[i]) {
|
|
|
|
res.tzname = Some(l[i].clone());
|
|
|
|
|
|
|
|
let tzname = res.tzname.clone().unwrap();
|
|
|
|
res.tzoffset = self.info.get_tzoffset(&tzname).map(|t| t as i32);
|
|
|
|
|
|
|
|
if i + 1 < len_l && (l[i + 1] == "+" || l[i + 1] == "-") {
|
|
|
|
// GMT+3
|
|
|
|
// According to dateutil docs - reverse the size, as GMT+3 means
|
|
|
|
// "my time +3 is GMT" not "GMT +3 is my time"
|
|
|
|
|
|
|
|
// TODO: Is there a better way of in-place modifying a vector?
|
|
|
|
let item = if l[i + 1] == "+" {
|
|
|
|
"-".to_owned()
|
|
|
|
} else {
|
|
|
|
"-".to_owned()
|
|
|
|
};
|
|
|
|
l.remove(i + 1);
|
|
|
|
l.insert(i + 1, item);
|
|
|
|
|
|
|
|
res.tzoffset = None;
|
|
|
|
|
|
|
|
if self.info.get_utczone(&tzname) {
|
|
|
|
res.tzname = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if res.hour.is_some() && (l[i] == "+" || l[i] == "-") {
|
|
|
|
let signal = if l[i] == "+" { 1 } else { -1 };
|
|
|
|
let len_li = l[i].len();
|
|
|
|
|
|
|
|
let mut hour_offset: Option<i32> = None;
|
|
|
|
let mut min_offset: Option<i32> = None;
|
|
|
|
|
|
|
|
// TODO: check that l[i + 1] is integer?
|
|
|
|
if len_li == 4 {
|
|
|
|
// -0300
|
|
|
|
hour_offset = Some(l[i + 1][..2].parse::<i32>().unwrap());
|
|
|
|
min_offset = Some(l[i + 1][2..4].parse::<i32>().unwrap());
|
|
|
|
} else if i + 2 < len_l && l[i + 2] == ":" {
|
|
|
|
// -03:00
|
|
|
|
hour_offset = Some(l[i + 1].parse::<i32>().unwrap());
|
|
|
|
min_offset = Some(l[i + 3].parse::<i32>().unwrap());
|
|
|
|
i += 2;
|
|
|
|
} else if len_li <= 2 {
|
|
|
|
// -[0]3
|
|
|
|
hour_offset = Some(l[i + 1][..2].parse::<i32>().unwrap());
|
|
|
|
min_offset = Some(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
res.tzoffset =
|
|
|
|
Some(signal * (hour_offset.unwrap() * 3600 + min_offset.unwrap() * 60));
|
|
|
|
|
|
|
|
let tzname = res.tzname.clone();
|
|
|
|
if i + 5 < len_l && self.info.get_jump(&l[i + 2]) && l[i + 3] == "("
|
|
|
|
&& l[i + 5] == ")" && 3 <= l[i + 4].len()
|
|
|
|
&& self.could_be_tzname(res.hour, tzname, None, &l[i + 4])
|
|
|
|
{
|
|
|
|
// (GMT)
|
|
|
|
res.tzname = Some(l[i + 4].clone());
|
|
|
|
i += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
i += 1;
|
|
|
|
} else if !self.info.get_jump(&l[i]) || fuzzy {
|
|
|
|
return Err(ParseError::UnrecognizedToken(l[i].clone()));
|
|
|
|
} else {
|
|
|
|
skipped_idxs.push(i);
|
2018-05-25 00:00:15 -04:00
|
|
|
}
|
2018-05-26 20:14:30 -04:00
|
|
|
|
|
|
|
i += 1;
|
2018-05-25 00:00:15 -04:00
|
|
|
}
|
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
let (year, month, day) = ymd.resolve_ymd(yearfirst, dayfirst)?;
|
|
|
|
|
|
|
|
res.century_specified = ymd.century_specified;
|
2018-05-28 12:28:49 -04:00
|
|
|
res.year = year;
|
|
|
|
res.month = month;
|
|
|
|
res.day = day;
|
2018-05-26 20:14:30 -04:00
|
|
|
|
|
|
|
if !self.info.validate(&mut res) {
|
|
|
|
Err(ParseError::InvalidParseResult(res))
|
|
|
|
} else if fuzzy_with_tokens {
|
|
|
|
let skipped_tokens = skipped_idxs.into_iter().map(|i| l[i].clone()).collect();
|
|
|
|
Ok((res, Some(skipped_tokens)))
|
|
|
|
} else {
|
|
|
|
Ok((res, None))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn could_be_tzname(
|
|
|
|
&self,
|
|
|
|
hour: Option<i32>,
|
|
|
|
tzname: Option<String>,
|
|
|
|
tzoffset: Option<i32>,
|
|
|
|
token: &str,
|
|
|
|
) -> bool {
|
2018-05-26 22:40:32 -04:00
|
|
|
let all_ascii_upper = token == token.to_ascii_uppercase();
|
|
|
|
return hour.is_some() && tzname.is_none() && tzoffset.is_none() && token.len() <= 5
|
|
|
|
&& all_ascii_upper;
|
2018-05-26 20:14:30 -04:00
|
|
|
}
|
|
|
|
|
2018-05-26 22:40:32 -04:00
|
|
|
fn ampm_valid(&self, hour: Option<i32>, ampm: Option<bool>, fuzzy: bool) -> ParseResult<bool> {
|
|
|
|
if fuzzy && ampm == Some(true) {
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if hour.is_none() {
|
|
|
|
if fuzzy {
|
|
|
|
Ok(false)
|
|
|
|
} else {
|
|
|
|
Err(ParseError::AmPmWithoutHour)
|
|
|
|
}
|
|
|
|
} else if !(0 <= hour.unwrap() && hour.unwrap() <= 12) {
|
|
|
|
if fuzzy {
|
|
|
|
Ok(false)
|
|
|
|
} else {
|
|
|
|
Err(ParseError::InvalidHour)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Ok(false)
|
|
|
|
}
|
2018-05-23 21:53:33 -04:00
|
|
|
}
|
|
|
|
|
2018-05-28 12:28:49 -04:00
|
|
|
fn build_naive(&self, res: &ParsingResult, default: &NaiveDateTime) -> NaiveDateTime {
|
2018-05-29 23:41:40 -04:00
|
|
|
// TODO: Handle weekday here
|
|
|
|
|
2018-05-26 22:40:32 -04:00
|
|
|
// TODO: Change month/day to u32
|
|
|
|
let d = NaiveDate::from_ymd(
|
|
|
|
res.year.unwrap_or(default.year()),
|
|
|
|
res.month.unwrap_or(default.month() as i32) as u32,
|
|
|
|
res.day.unwrap_or(default.day() as i32) as u32,
|
|
|
|
);
|
|
|
|
|
|
|
|
let t = NaiveTime::from_hms_micro(
|
|
|
|
res.hour.unwrap_or(default.hour() as i32) as u32,
|
|
|
|
res.minute.unwrap_or(default.minute() as i32) as u32,
|
|
|
|
res.second.unwrap_or(default.second() as i32) as u32,
|
|
|
|
res.microsecond
|
|
|
|
.unwrap_or(default.timestamp_subsec_micros() as i32) as u32,
|
|
|
|
);
|
|
|
|
|
|
|
|
NaiveDateTime::new(d, t)
|
2018-05-23 21:53:33 -04:00
|
|
|
}
|
|
|
|
|
2018-05-23 23:01:00 -04:00
|
|
|
fn build_tzaware(
|
|
|
|
&self,
|
2018-05-26 20:14:30 -04:00
|
|
|
dt: &NaiveDateTime,
|
2018-05-23 23:01:00 -04:00
|
|
|
res: &ParsingResult,
|
2018-05-28 12:28:49 -04:00
|
|
|
default: &NaiveDateTime,
|
2018-05-28 11:03:11 -04:00
|
|
|
) -> ParseResult<Option<FixedOffset>> {
|
2018-05-28 12:28:49 -04:00
|
|
|
// TODO: Actual timezone support
|
2018-05-29 23:41:40 -04:00
|
|
|
if res.tzname.is_none() && res.tzoffset.is_none() || res.tzname == Some(" ".to_owned())
|
|
|
|
|| res.tzname == Some(".".to_owned())
|
|
|
|
{
|
2018-05-28 11:03:11 -04:00
|
|
|
Ok(None)
|
2018-05-26 22:40:32 -04:00
|
|
|
} else {
|
2018-05-28 12:28:49 -04:00
|
|
|
println!("Tzname: {:?}, Tzoffset: {:?}", res.tzname, res.tzoffset);
|
2018-05-26 22:40:32 -04:00
|
|
|
Err(ParseError::TimezoneUnsupported)
|
|
|
|
}
|
2018-05-23 21:53:33 -04:00
|
|
|
}
|
2018-05-25 00:00:15 -04:00
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
fn parse_numeric_token(
|
|
|
|
&self,
|
|
|
|
tokens: &Vec<String>,
|
|
|
|
idx: usize,
|
|
|
|
info: &ParserInfo,
|
|
|
|
ymd: &mut YMD,
|
|
|
|
res: &mut ParsingResult,
|
|
|
|
fuzzy: bool,
|
|
|
|
) -> Result<usize, ParseInternalError> {
|
|
|
|
let mut idx = idx;
|
2018-05-25 00:00:15 -04:00
|
|
|
let value_repr = &tokens[idx];
|
2018-05-26 20:14:30 -04:00
|
|
|
let mut value = Decimal::from_str(&value_repr).unwrap();
|
2018-05-25 00:00:15 -04:00
|
|
|
|
|
|
|
let len_li = value_repr.len();
|
|
|
|
let len_l = tokens.len();
|
|
|
|
|
|
|
|
// TODO: I miss the `x in y` syntax
|
|
|
|
// TODO: Decompose this logic a bit
|
2018-05-26 20:14:30 -04:00
|
|
|
if ymd.len() == 3 && (len_li == 2 || len_li == 4) && res.hour.is_none()
|
|
|
|
&& (idx + 1 >= len_l
|
|
|
|
|| (tokens[idx + 1] != ":" && info.get_hms(&tokens[idx + 1]).is_none()))
|
|
|
|
{
|
2018-05-25 00:00:15 -04:00
|
|
|
// 1990101T32[59]
|
2018-05-26 20:14:30 -04:00
|
|
|
let s = &tokens[idx];
|
|
|
|
res.hour = s[0..2].parse::<i32>().ok();
|
2018-05-25 00:00:15 -04:00
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
if len_li == 4 {
|
|
|
|
res.minute = Some(s[2..4].parse::<i32>()?)
|
|
|
|
}
|
|
|
|
} else if len_li == 6 || (len_li > 6 && tokens[idx].find(".") == Some(6)) {
|
|
|
|
// YYMMDD or HHMMSS[.ss]
|
|
|
|
let s = &tokens[idx];
|
|
|
|
|
|
|
|
if ymd.len() == 0 && tokens[idx].find(".") == None {
|
|
|
|
ymd.append(s[0..2].parse::<i32>().unwrap(), None);
|
|
|
|
ymd.append(s[2..4].parse::<i32>().unwrap(), None);
|
|
|
|
ymd.append(s[4..6].parse::<i32>().unwrap(), None);
|
|
|
|
} else {
|
|
|
|
// 19990101T235959[.59]
|
|
|
|
res.hour = s[0..2].parse::<i32>().ok();
|
|
|
|
res.minute = s[2..4].parse::<i32>().ok();
|
|
|
|
|
|
|
|
let t = self.parsems(&s[4..])?;
|
|
|
|
res.second = Some(t.0);
|
|
|
|
res.microsecond = Some(t.1);
|
|
|
|
}
|
|
|
|
} else if vec![8, 12, 14].contains(&len_li) {
|
|
|
|
// YYMMDD
|
|
|
|
let s = &tokens[idx];
|
|
|
|
ymd.append(s[..4].parse::<i32>().unwrap(), Some(YMDLabel::Year));
|
|
|
|
ymd.append(s[4..6].parse::<i32>().unwrap(), None);
|
|
|
|
ymd.append(s[6..8].parse::<i32>().unwrap(), None);
|
|
|
|
|
|
|
|
if len_li > 8 {
|
|
|
|
res.hour = Some(s[8..10].parse::<i32>()?);
|
|
|
|
res.minute = Some(s[10..12].parse::<i32>()?);
|
|
|
|
|
|
|
|
if len_li > 12 {
|
|
|
|
res.second = Some(s[12..].parse::<i32>()?);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if let Some(hms_idx) = self.find_hms_index(idx, tokens, info, true) {
|
|
|
|
// HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
2018-05-29 23:41:40 -04:00
|
|
|
let (new_idx, hms) = self.parse_hms(idx, tokens, info, Some(hms_idx));
|
2018-05-26 20:14:30 -04:00
|
|
|
if hms.is_some() {
|
|
|
|
// TODO: This unwrap is unjustified.
|
|
|
|
self.assign_hms(res, value_repr, hms.unwrap());
|
|
|
|
}
|
2018-05-29 23:41:40 -04:00
|
|
|
idx = new_idx;
|
2018-05-26 20:14:30 -04:00
|
|
|
} else if idx + 2 < len_l && tokens[idx + 1] == ":" {
|
|
|
|
// HH:MM[:SS[.ss]]
|
|
|
|
// TODO: Better story around Decimal handling
|
|
|
|
res.hour = Some(value.floor().to_i64().unwrap() as i32);
|
|
|
|
// TODO: Rescope `value` here?
|
|
|
|
value = self.to_decimal(&tokens[idx + 2]);
|
|
|
|
let min_sec = self.parse_min_sec(value);
|
|
|
|
res.minute = Some(min_sec.0);
|
|
|
|
res.second = min_sec.1;
|
|
|
|
|
|
|
|
if idx + 4 < len_l && tokens[idx + 3] == ":" {
|
|
|
|
// TODO: (x, y) = (a, b) syntax?
|
|
|
|
let ms = self.parsems(&tokens[idx + 4]).unwrap();
|
|
|
|
res.second = Some(ms.0);
|
|
|
|
res.microsecond = Some(ms.1);
|
|
|
|
|
|
|
|
idx += 2;
|
|
|
|
}
|
|
|
|
idx += 2;
|
|
|
|
} else if idx + 1 < len_l
|
|
|
|
&& (tokens[idx + 1] == "-" || tokens[idx + 1] == "/" || tokens[idx + 1] == ".")
|
|
|
|
{
|
|
|
|
// TODO: There's got to be a better way of handling the condition above
|
|
|
|
let sep = &tokens[idx + 1];
|
|
|
|
ymd.append(value_repr.parse::<i32>().unwrap(), None);
|
|
|
|
|
|
|
|
if idx + 2 < len_l && !info.get_jump(&tokens[idx + 2]) {
|
|
|
|
if let Ok(val) = tokens[idx + 2].parse::<i32>() {
|
|
|
|
ymd.append(val, None);
|
|
|
|
} else if let Some(val) = info.get_month(&tokens[idx + 2]) {
|
|
|
|
ymd.append(val as i32, Some(YMDLabel::Month));
|
|
|
|
}
|
|
|
|
|
|
|
|
if idx + 3 < len_l && &tokens[idx + 3] == sep {
|
|
|
|
if let Some(value) = info.get_month(&tokens[idx + 4]) {
|
|
|
|
ymd.append(value as i32, Some(YMDLabel::Month));
|
|
|
|
} else {
|
|
|
|
ymd.append(tokens[idx + 4].parse::<i32>().unwrap(), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
idx += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
idx += 1
|
|
|
|
} else if idx + 1 >= len_l || info.get_jump(&tokens[idx + 1]) {
|
|
|
|
if idx + 2 < len_l && info.get_ampm(&tokens[idx + 2]).is_some() {
|
|
|
|
let hour = value.to_i64().unwrap() as i32;
|
|
|
|
let ampm = info.get_ampm(&tokens[idx + 2]).unwrap();
|
|
|
|
res.hour = Some(self.adjust_ampm(hour, ampm));
|
2018-05-28 11:03:11 -04:00
|
|
|
} else {
|
|
|
|
ymd.append(value.floor().to_i64().unwrap() as i32, None);
|
2018-05-26 20:14:30 -04:00
|
|
|
}
|
|
|
|
} else if info.get_ampm(&tokens[idx + 1]).is_some()
|
|
|
|
&& (*ZERO <= value && value < *TWENTY_FOUR)
|
|
|
|
{
|
|
|
|
// 12am
|
|
|
|
let hour = value.to_i64().unwrap() as i32;
|
|
|
|
res.hour = Some(self.adjust_ampm(hour, info.get_ampm(&tokens[idx + 1]).unwrap()));
|
|
|
|
idx += 1;
|
|
|
|
} else if ymd.could_be_day(value.to_i64().unwrap() as i32) {
|
|
|
|
ymd.append(value.to_i64().unwrap() as i32, None);
|
|
|
|
} else if !fuzzy {
|
|
|
|
return Err(ParseInternalError::ValueError("".to_owned()));
|
2018-05-25 00:00:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(idx)
|
|
|
|
}
|
2018-05-26 20:14:30 -04:00
|
|
|
|
2018-05-26 22:40:32 -04:00
|
|
|
fn adjust_ampm(&self, hour: i32, ampm: bool) -> i32 {
|
|
|
|
if hour < 12 && ampm {
|
2018-05-26 20:14:30 -04:00
|
|
|
hour + 12
|
2018-05-29 23:41:40 -04:00
|
|
|
} else if hour == 12 && !ampm {
|
2018-05-26 20:14:30 -04:00
|
|
|
0
|
|
|
|
} else {
|
|
|
|
hour
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parsems(&self, seconds_str: &str) -> Result<(i32, i32), ParseInternalError> {
|
|
|
|
if seconds_str.contains(".") {
|
|
|
|
let split: Vec<&str> = seconds_str.split(".").collect();
|
|
|
|
let (i, f): (&str, &str) = (split[0], split[1]);
|
|
|
|
|
|
|
|
let i_parse = i.parse::<i32>()?;
|
|
|
|
let f_parse = ljust(f, 6, '0').parse::<i32>()?;
|
|
|
|
Ok((i_parse, f_parse))
|
|
|
|
} else {
|
|
|
|
Ok((seconds_str.parse::<i32>()?, 0))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_hms_index(
|
|
|
|
&self,
|
|
|
|
idx: usize,
|
|
|
|
tokens: &Vec<String>,
|
|
|
|
info: &ParserInfo,
|
|
|
|
allow_jump: bool,
|
|
|
|
) -> Option<usize> {
|
|
|
|
let len_l = tokens.len();
|
|
|
|
let mut hms_idx = None;
|
|
|
|
|
|
|
|
if idx + 1 < len_l && info.get_hms(&tokens[idx + 1]).is_some() {
|
|
|
|
hms_idx = Some(idx + 1)
|
|
|
|
} else if allow_jump && idx + 2 < len_l && tokens[idx + 1] == " "
|
|
|
|
&& info.get_hms(&tokens[idx + 2]).is_some()
|
|
|
|
{
|
|
|
|
hms_idx = Some(idx + 2)
|
|
|
|
} else if idx > 0 && info.get_hms(&tokens[idx - 1]).is_some() {
|
|
|
|
hms_idx = Some(idx - 1)
|
2018-05-28 13:21:34 -04:00
|
|
|
} else if len_l > 0 && idx > 0 && idx == len_l - 1 && tokens[idx - 1] == " "
|
2018-05-26 20:14:30 -04:00
|
|
|
&& info.get_hms(&tokens[idx - 2]).is_some()
|
|
|
|
{
|
|
|
|
hms_idx = Some(idx - 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
hms_idx
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_hms(
|
|
|
|
&self,
|
|
|
|
idx: usize,
|
|
|
|
tokens: &Vec<String>,
|
|
|
|
info: &ParserInfo,
|
|
|
|
hms_index: Option<usize>,
|
|
|
|
) -> (usize, Option<usize>) {
|
|
|
|
if hms_index.is_none() {
|
|
|
|
(idx, None)
|
|
|
|
} else if hms_index.unwrap() > idx {
|
|
|
|
(
|
|
|
|
hms_index.unwrap(),
|
|
|
|
info.get_hms(&tokens[hms_index.unwrap()]),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
idx,
|
|
|
|
info.get_hms(&tokens[hms_index.unwrap()]).map(|u| u + 1),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn assign_hms(&self, res: &mut ParsingResult, value_repr: &str, hms: usize) {
|
|
|
|
let value = self.to_decimal(value_repr);
|
|
|
|
|
|
|
|
if hms == 0 {
|
|
|
|
res.hour = Some(value.to_i64().unwrap() as i32);
|
2018-05-29 23:41:40 -04:00
|
|
|
if !close_to_integer(&value) {
|
2018-05-26 20:14:30 -04:00
|
|
|
// TODO: High probability of issues with rounding here.
|
|
|
|
res.minute = Some((*SIXTY * (value % *ONE)).to_i64().unwrap() as i32);
|
|
|
|
}
|
|
|
|
} else if hms == 1 {
|
|
|
|
let (min, sec) = self.parse_min_sec(value);
|
2018-05-29 23:41:40 -04:00
|
|
|
res.minute = Some(min);
|
|
|
|
res.second = sec;
|
2018-05-26 20:14:30 -04:00
|
|
|
} else if hms == 2 {
|
|
|
|
let (sec, micro) = self.parsems(value_repr).unwrap();
|
2018-05-29 23:41:40 -04:00
|
|
|
res.second = Some(sec);
|
|
|
|
res.microsecond = Some(micro);
|
2018-05-26 20:14:30 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_decimal(&self, value: &str) -> Decimal {
|
|
|
|
// TODO: Actual decimals, and handling infinity
|
|
|
|
Decimal::from_str(value).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_min_sec(&self, value: Decimal) -> (i32, Option<i32>) {
|
|
|
|
let minute = value.floor().to_i64().unwrap() as i32;
|
|
|
|
let mut second = None;
|
|
|
|
|
|
|
|
let sec_remainder = value % *ONE;
|
|
|
|
if sec_remainder != *ZERO {
|
|
|
|
second = Some((*SIXTY * sec_remainder).floor().to_i64().unwrap() as i32);
|
|
|
|
}
|
|
|
|
|
|
|
|
(minute, second)
|
|
|
|
}
|
2018-05-23 21:53:33 -04:00
|
|
|
}
|
|
|
|
|
2018-05-26 20:14:30 -04:00
|
|
|
fn close_to_integer(value: &Decimal) -> bool {
|
|
|
|
value % *ONE == *ZERO
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ljust(s: &str, chars: usize, replace: char) -> String {
|
|
|
|
if s.len() >= chars {
|
|
|
|
s[..chars].to_owned()
|
|
|
|
} else {
|
|
|
|
format!("{}{}", s, replace.to_string().repeat(chars - s.len()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-29 21:07:22 -04:00
|
|
|
pub fn parse_with_info(
|
2018-05-27 22:26:30 -04:00
|
|
|
timestr: &str,
|
2018-05-26 20:14:30 -04:00
|
|
|
info: ParserInfo,
|
2018-05-28 12:28:49 -04:00
|
|
|
default: Option<&NaiveDateTime>,
|
2018-05-26 20:14:30 -04:00
|
|
|
) -> ParseResult<(NaiveDateTime, Option<FixedOffset>, Option<Vec<String>>)> {
|
2018-05-25 00:00:15 -04:00
|
|
|
// TODO: Is `::new()` more stylistic?
|
|
|
|
let mut parser = Parser { info: info };
|
2018-05-28 12:28:49 -04:00
|
|
|
parser.parse(timestr, default, false, vec![])
|
|
|
|
}
|
|
|
|
|
2018-05-29 21:07:22 -04:00
|
|
|
pub fn parse_with_default(
|
2018-05-28 13:21:34 -04:00
|
|
|
timestr: &str,
|
|
|
|
default: &NaiveDateTime,
|
|
|
|
) -> ParseResult<(NaiveDateTime, Option<FixedOffset>)> {
|
2018-05-28 12:28:49 -04:00
|
|
|
let parse_result = parse_with_info(timestr, ParserInfo::default(), Some(default))?;
|
|
|
|
Ok((parse_result.0, parse_result.1))
|
2018-05-23 21:53:33 -04:00
|
|
|
}
|
2018-05-23 23:01:00 -04:00
|
|
|
|
2018-05-29 21:07:22 -04:00
|
|
|
pub fn parse(timestr: &str) -> ParseResult<(NaiveDateTime, Option<FixedOffset>)> {
|
2018-05-28 12:28:49 -04:00
|
|
|
let parse_result = parse_with_info(timestr, ParserInfo::default(), None)?;
|
2018-05-26 20:14:30 -04:00
|
|
|
Ok((parse_result.0, parse_result.1))
|
2018-05-23 23:01:00 -04:00
|
|
|
}
|