mirror of
https://github.com/bspeice/dtparse
synced 2024-11-14 09:58:09 -05:00
Weekday support
This commit is contained in:
parent
2c648a6401
commit
d76e1b4b91
@ -14,7 +14,10 @@ tests = {
|
|||||||
# testPertain
|
# testPertain
|
||||||
'Sep 03', 'Sep of 03',
|
'Sep 03', 'Sep of 03',
|
||||||
# test_hmBY - Note: This appears to be Python 3 only, no idea why
|
# test_hmBY - Note: This appears to be Python 3 only, no idea why
|
||||||
'02:17NOV2017'
|
'02:17NOV2017',
|
||||||
|
# Weekdays
|
||||||
|
"Thu Sep 10:36:28", "Thu 10:36:28", "Wed", "Wednesday"
|
||||||
|
# TODO: Tests for when forwarding to the
|
||||||
],
|
],
|
||||||
'test_parse_simple': [
|
'test_parse_simple': [
|
||||||
"Thu Sep 25 10:36:28 2003", "Thu Sep 25 2003", "2003-09-25T10:49:41",
|
"Thu Sep 25 10:36:28 2003", "Thu Sep 25 2003", "2003-09-25T10:49:41",
|
||||||
@ -79,7 +82,6 @@ tests = {
|
|||||||
'Tue Apr 4 00:22:12 PDT 1995'
|
'Tue Apr 4 00:22:12 PDT 1995'
|
||||||
],
|
],
|
||||||
'test_parse_default_ignore': [
|
'test_parse_default_ignore': [
|
||||||
"Thu Sep 10:36:28", "Thu 10:36:28", "Wed", "Wednesday"
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
src/lib.rs
35
src/lib.rs
@ -10,6 +10,7 @@ extern crate rust_decimal;
|
|||||||
|
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
|
use chrono::Duration;
|
||||||
use chrono::FixedOffset;
|
use chrono::FixedOffset;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
@ -26,6 +27,11 @@ use std::num::ParseIntError;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
mod weekday;
|
||||||
|
|
||||||
|
use weekday::day_of_week;
|
||||||
|
use weekday::DayOfWeek;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ZERO: Decimal = Decimal::new(0, 0);
|
static ref ZERO: Decimal = Decimal::new(0, 0);
|
||||||
static ref ONE: Decimal = Decimal::new(1, 0);
|
static ref ONE: Decimal = Decimal::new(1, 0);
|
||||||
@ -62,6 +68,7 @@ impl From<ParseIntError> for ParseInternalError {
|
|||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
AmbiguousWeekday,
|
AmbiguousWeekday,
|
||||||
InternalError(ParseInternalError),
|
InternalError(ParseInternalError),
|
||||||
|
InvalidDay,
|
||||||
InvalidMonth,
|
InvalidMonth,
|
||||||
UnrecognizedToken(String),
|
UnrecognizedToken(String),
|
||||||
InvalidParseResult(ParsingResult),
|
InvalidParseResult(ParsingResult),
|
||||||
@ -760,7 +767,7 @@ pub struct ParsingResult {
|
|||||||
year: Option<i32>,
|
year: Option<i32>,
|
||||||
month: Option<i32>,
|
month: Option<i32>,
|
||||||
day: Option<i32>,
|
day: Option<i32>,
|
||||||
weekday: Option<bool>,
|
weekday: Option<usize>,
|
||||||
hour: Option<i32>,
|
hour: Option<i32>,
|
||||||
minute: Option<i32>,
|
minute: Option<i32>,
|
||||||
second: Option<i32>,
|
second: Option<i32>,
|
||||||
@ -848,7 +855,7 @@ impl Parser {
|
|||||||
if let Ok(v) = Decimal::from_str(&value_repr) {
|
if let Ok(v) = Decimal::from_str(&value_repr) {
|
||||||
i = self.parse_numeric_token(&l, i, &self.info, &mut ymd, &mut res, fuzzy)?;
|
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]) {
|
} else if let Some(value) = self.info.get_weekday(&l[i]) {
|
||||||
res.weekday = Some(value != 0);
|
res.weekday = Some(value);
|
||||||
} else if let Some(value) = self.info.get_month(&l[i]) {
|
} else if let Some(value) = self.info.get_month(&l[i]) {
|
||||||
ymd.append(value as i32, &l[i], Some(YMDLabel::Month));
|
ymd.append(value as i32, &l[i], Some(YMDLabel::Month));
|
||||||
|
|
||||||
@ -1013,21 +1020,33 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_naive(&self, res: &ParsingResult, default: &NaiveDateTime) -> ParseResult<NaiveDateTime> {
|
fn build_naive(&self, res: &ParsingResult, default: &NaiveDateTime) -> ParseResult<NaiveDateTime> {
|
||||||
// TODO: Handle weekday here - dateutils uses relativedelta to accomplish this
|
|
||||||
if res.weekday.is_some() && res.day.is_none() {
|
|
||||||
return Err(ParseError::AmbiguousWeekday);
|
|
||||||
}
|
|
||||||
|
|
||||||
let y = res.year.unwrap_or(default.year());
|
let y = res.year.unwrap_or(default.year());
|
||||||
let m = res.month.unwrap_or(default.month() as i32) as u32;
|
let m = res.month.unwrap_or(default.month() as i32) as u32;
|
||||||
|
|
||||||
|
let d_offset = if res.weekday.is_some() && res.day.is_none() {
|
||||||
|
// TODO: Unwrap not justified
|
||||||
|
let dow = day_of_week(y as u32, m, default.day()).unwrap();
|
||||||
|
println!("dow: {:?}", dow);
|
||||||
|
|
||||||
|
// UNWRAP: We've already check res.weekday() is some
|
||||||
|
let actual_weekday = (res.weekday.unwrap() + 1) % 7;
|
||||||
|
let other = DayOfWeek::from_numeral(actual_weekday as u32);
|
||||||
|
Duration::days(dow.difference(other) as i64)
|
||||||
|
} else {
|
||||||
|
Duration::days(0)
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Change month/day to u32
|
// TODO: Change month/day to u32
|
||||||
let d = NaiveDate::from_ymd(
|
let mut d = NaiveDate::from_ymd(
|
||||||
y,
|
y,
|
||||||
m,
|
m,
|
||||||
min(res.day.unwrap_or(default.day() as i32) as u32, days_in_month(y, m as i32).unwrap())
|
min(res.day.unwrap_or(default.day() as i32) as u32, days_in_month(y, m as i32).unwrap())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
println!("d: {:?}, d_offset: {:?}", d, d_offset);
|
||||||
|
|
||||||
|
let d = d + d_offset;
|
||||||
|
|
||||||
let t = NaiveTime::from_hms_micro(
|
let t = NaiveTime::from_hms_micro(
|
||||||
res.hour.unwrap_or(default.hour() as i32) as u32,
|
res.hour.unwrap_or(default.hour() as i32) as u32,
|
||||||
res.minute.unwrap_or(default.minute() as i32) as u32,
|
res.minute.unwrap_or(default.minute() as i32) as u32,
|
||||||
|
132
src/weekday.rs
Normal file
132
src/weekday.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
use std::cmp::max;
|
||||||
|
|
||||||
|
use ParseResult;
|
||||||
|
use ParseError;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum DayOfWeek {
|
||||||
|
Sunday,
|
||||||
|
Monday,
|
||||||
|
Tuesday,
|
||||||
|
Wednesday,
|
||||||
|
Thursday,
|
||||||
|
Friday,
|
||||||
|
Saturday
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DayOfWeek {
|
||||||
|
|
||||||
|
pub fn to_numeral(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
DayOfWeek::Sunday => 0,
|
||||||
|
DayOfWeek::Monday => 1,
|
||||||
|
DayOfWeek::Tuesday => 2,
|
||||||
|
DayOfWeek::Wednesday => 3,
|
||||||
|
DayOfWeek::Thursday => 4,
|
||||||
|
DayOfWeek::Friday => 5,
|
||||||
|
DayOfWeek::Saturday => 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_numeral(num: u32) -> DayOfWeek {
|
||||||
|
match num % 7 {
|
||||||
|
0 => DayOfWeek::Sunday,
|
||||||
|
1 => DayOfWeek::Monday,
|
||||||
|
2 => DayOfWeek::Tuesday,
|
||||||
|
3 => DayOfWeek::Wednesday,
|
||||||
|
4 => DayOfWeek::Thursday,
|
||||||
|
5 => DayOfWeek::Friday,
|
||||||
|
6 => DayOfWeek::Saturday,
|
||||||
|
_ => panic!("Unreachable.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given the current day of the week, how many days until the next day?
|
||||||
|
pub fn difference(&self, other: DayOfWeek) -> u32 {
|
||||||
|
// Have to use i32 because of wraparound issues
|
||||||
|
let s_num = self.to_numeral() as i32;
|
||||||
|
let o_num = other.to_numeral() as i32;
|
||||||
|
|
||||||
|
if o_num - s_num >= 0 {
|
||||||
|
(o_num - s_num) as u32
|
||||||
|
} else {
|
||||||
|
(7 + o_num - s_num) as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn day_of_week(year: u32, month: u32, day: u32) -> ParseResult<DayOfWeek> {
|
||||||
|
// From https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Schwerdtfeger's_method
|
||||||
|
let (c, g) = match month {
|
||||||
|
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 => {
|
||||||
|
let c = year / 100;
|
||||||
|
(c, year - 100 * c)
|
||||||
|
},
|
||||||
|
1 | 2 => {
|
||||||
|
let c = (year - 1) / 100;
|
||||||
|
(c, year - 1 - 100 * c)
|
||||||
|
},
|
||||||
|
_ => return Err(ParseError::InvalidMonth)
|
||||||
|
};
|
||||||
|
|
||||||
|
let e = match month {
|
||||||
|
1 | 5 => 0,
|
||||||
|
2 | 6 => 3,
|
||||||
|
3 | 11 => 2,
|
||||||
|
4 | 7 => 5,
|
||||||
|
8 => 1,
|
||||||
|
9 | 12 => 4,
|
||||||
|
10 => 6,
|
||||||
|
_ => panic!("Unreachable.")
|
||||||
|
};
|
||||||
|
|
||||||
|
// This implementation is Gregorian-only.
|
||||||
|
let f = match c % 4 {
|
||||||
|
0 => 0,
|
||||||
|
1 => 5,
|
||||||
|
2 => 3,
|
||||||
|
3 => 1,
|
||||||
|
_ => panic!("Unreachable.")
|
||||||
|
};
|
||||||
|
|
||||||
|
match (day + e + f + g + g / 4) % 7 {
|
||||||
|
0 => Ok(DayOfWeek::Sunday),
|
||||||
|
1 => Ok(DayOfWeek::Monday),
|
||||||
|
2 => Ok(DayOfWeek::Tuesday),
|
||||||
|
3 => Ok(DayOfWeek::Wednesday),
|
||||||
|
4 => Ok(DayOfWeek::Thursday),
|
||||||
|
5 => Ok(DayOfWeek::Friday),
|
||||||
|
6 => Ok(DayOfWeek::Saturday),
|
||||||
|
_ => panic!("Unreachable.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use weekday::day_of_week;
|
||||||
|
use weekday::DayOfWeek;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn day_of_week_examples() {
|
||||||
|
assert_eq!(day_of_week(2018, 6, 24).unwrap(), DayOfWeek::Sunday);
|
||||||
|
assert_eq!(day_of_week(2003, 9, 25).unwrap(), DayOfWeek::Thursday);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn weekday_difference() {
|
||||||
|
|
||||||
|
assert_eq!(DayOfWeek::Sunday.difference(DayOfWeek::Sunday), 0);
|
||||||
|
assert_eq!(DayOfWeek::Sunday.difference(DayOfWeek::Monday), 1);
|
||||||
|
assert_eq!(DayOfWeek::Sunday.difference(DayOfWeek::Tuesday), 2);
|
||||||
|
assert_eq!(DayOfWeek::Sunday.difference(DayOfWeek::Wednesday), 3);
|
||||||
|
assert_eq!(DayOfWeek::Sunday.difference(DayOfWeek::Thursday), 4);
|
||||||
|
assert_eq!(DayOfWeek::Sunday.difference(DayOfWeek::Friday), 5);
|
||||||
|
assert_eq!(DayOfWeek::Sunday.difference(DayOfWeek::Saturday), 6);
|
||||||
|
assert_eq!(DayOfWeek::Monday.difference(DayOfWeek::Sunday), 6);
|
||||||
|
assert_eq!(DayOfWeek::Tuesday.difference(DayOfWeek::Sunday), 5);
|
||||||
|
assert_eq!(DayOfWeek::Wednesday.difference(DayOfWeek::Sunday), 4);
|
||||||
|
assert_eq!(DayOfWeek::Thursday.difference(DayOfWeek::Sunday), 3);
|
||||||
|
assert_eq!(DayOfWeek::Friday.difference(DayOfWeek::Sunday), 2);
|
||||||
|
assert_eq!(DayOfWeek::Saturday.difference(DayOfWeek::Sunday), 1);
|
||||||
|
}
|
||||||
|
}
|
@ -639,6 +639,58 @@ fn test_parse_default42() {
|
|||||||
Some(default_rsdate), false, HashMap::new());
|
Some(default_rsdate), false, HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_default43() {
|
||||||
|
let info = ParserInfo::default();
|
||||||
|
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
|
||||||
|
let pdt = PyDateTime {
|
||||||
|
year: 2003, month: 9, day: 25,
|
||||||
|
hour: 10, minute: 36, second: 28,
|
||||||
|
micros: 0, tzo: None
|
||||||
|
};
|
||||||
|
parse_and_assert(pdt, info, "Thu Sep 10:36:28", None, None, false, false,
|
||||||
|
Some(default_rsdate), false, HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_default44() {
|
||||||
|
let info = ParserInfo::default();
|
||||||
|
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
|
||||||
|
let pdt = PyDateTime {
|
||||||
|
year: 2003, month: 9, day: 25,
|
||||||
|
hour: 10, minute: 36, second: 28,
|
||||||
|
micros: 0, tzo: None
|
||||||
|
};
|
||||||
|
parse_and_assert(pdt, info, "Thu 10:36:28", None, None, false, false,
|
||||||
|
Some(default_rsdate), false, HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_default45() {
|
||||||
|
let info = ParserInfo::default();
|
||||||
|
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
|
||||||
|
let pdt = PyDateTime {
|
||||||
|
year: 2003, month: 10, day: 1,
|
||||||
|
hour: 0, minute: 0, second: 0,
|
||||||
|
micros: 0, tzo: None
|
||||||
|
};
|
||||||
|
parse_and_assert(pdt, info, "Wed", None, None, false, false,
|
||||||
|
Some(default_rsdate), false, HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_default46() {
|
||||||
|
let info = ParserInfo::default();
|
||||||
|
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
|
||||||
|
let pdt = PyDateTime {
|
||||||
|
year: 2003, month: 10, day: 1,
|
||||||
|
hour: 0, minute: 0, second: 0,
|
||||||
|
micros: 0, tzo: None
|
||||||
|
};
|
||||||
|
parse_and_assert(pdt, info, "Wednesday", None, None, false, false,
|
||||||
|
Some(default_rsdate), false, HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_simple0() {
|
fn test_parse_simple0() {
|
||||||
let pdt = PyDateTime {
|
let pdt = PyDateTime {
|
||||||
@ -1645,59 +1697,3 @@ fn test_parse_ignoretz7() {
|
|||||||
parse_and_assert(pdt, info, "Tue Apr 4 00:22:12 PDT 1995", None, None, false, false,
|
parse_and_assert(pdt, info, "Tue Apr 4 00:22:12 PDT 1995", None, None, false, false,
|
||||||
None, true, HashMap::new());
|
None, true, HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn test_parse_default_ignore0() {
|
|
||||||
let info = ParserInfo::default();
|
|
||||||
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
|
|
||||||
let pdt = PyDateTime {
|
|
||||||
year: 2003, month: 9, day: 25,
|
|
||||||
hour: 10, minute: 36, second: 28,
|
|
||||||
micros: 0, tzo: None
|
|
||||||
};
|
|
||||||
parse_and_assert(pdt, info, "Thu Sep 10:36:28", None, None, false, false,
|
|
||||||
Some(default_rsdate), false, HashMap::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn test_parse_default_ignore1() {
|
|
||||||
let info = ParserInfo::default();
|
|
||||||
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
|
|
||||||
let pdt = PyDateTime {
|
|
||||||
year: 2003, month: 9, day: 25,
|
|
||||||
hour: 10, minute: 36, second: 28,
|
|
||||||
micros: 0, tzo: None
|
|
||||||
};
|
|
||||||
parse_and_assert(pdt, info, "Thu 10:36:28", None, None, false, false,
|
|
||||||
Some(default_rsdate), false, HashMap::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn test_parse_default_ignore2() {
|
|
||||||
let info = ParserInfo::default();
|
|
||||||
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
|
|
||||||
let pdt = PyDateTime {
|
|
||||||
year: 2003, month: 10, day: 1,
|
|
||||||
hour: 0, minute: 0, second: 0,
|
|
||||||
micros: 0, tzo: None
|
|
||||||
};
|
|
||||||
parse_and_assert(pdt, info, "Wed", None, None, false, false,
|
|
||||||
Some(default_rsdate), false, HashMap::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn test_parse_default_ignore3() {
|
|
||||||
let info = ParserInfo::default();
|
|
||||||
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
|
|
||||||
let pdt = PyDateTime {
|
|
||||||
year: 2003, month: 10, day: 1,
|
|
||||||
hour: 0, minute: 0, second: 0,
|
|
||||||
micros: 0, tzo: None
|
|
||||||
};
|
|
||||||
parse_and_assert(pdt, info, "Wednesday", None, None, false, false,
|
|
||||||
Some(default_rsdate), false, HashMap::new());
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user