1
0
mirror of https://github.com/bspeice/dtparse synced 2024-12-22 12:28:08 -05:00

Weekday support

This commit is contained in:
Bradlee Speice 2018-06-24 23:53:33 -04:00
parent 2c648a6401
commit d76e1b4b91
4 changed files with 215 additions and 66 deletions

View File

@ -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"
], ],
} }

View File

@ -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
View 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);
}
}

View File

@ -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());
}