mirror of
https://github.com/bspeice/dtparse
synced 2024-11-13 17:38:09 -05:00
Weekday support
This commit is contained in:
parent
2c648a6401
commit
d76e1b4b91
@ -14,7 +14,10 @@ tests = {
|
||||
# testPertain
|
||||
'Sep 03', 'Sep of 03',
|
||||
# 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': [
|
||||
"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'
|
||||
],
|
||||
'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::Datelike;
|
||||
use chrono::Duration;
|
||||
use chrono::FixedOffset;
|
||||
use chrono::Local;
|
||||
use chrono::NaiveDate;
|
||||
@ -26,6 +27,11 @@ use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
use std::vec::Vec;
|
||||
|
||||
mod weekday;
|
||||
|
||||
use weekday::day_of_week;
|
||||
use weekday::DayOfWeek;
|
||||
|
||||
lazy_static! {
|
||||
static ref ZERO: Decimal = Decimal::new(0, 0);
|
||||
static ref ONE: Decimal = Decimal::new(1, 0);
|
||||
@ -62,6 +68,7 @@ impl From<ParseIntError> for ParseInternalError {
|
||||
pub enum ParseError {
|
||||
AmbiguousWeekday,
|
||||
InternalError(ParseInternalError),
|
||||
InvalidDay,
|
||||
InvalidMonth,
|
||||
UnrecognizedToken(String),
|
||||
InvalidParseResult(ParsingResult),
|
||||
@ -760,7 +767,7 @@ pub struct ParsingResult {
|
||||
year: Option<i32>,
|
||||
month: Option<i32>,
|
||||
day: Option<i32>,
|
||||
weekday: Option<bool>,
|
||||
weekday: Option<usize>,
|
||||
hour: Option<i32>,
|
||||
minute: Option<i32>,
|
||||
second: Option<i32>,
|
||||
@ -848,7 +855,7 @@ impl Parser {
|
||||
if let Ok(v) = Decimal::from_str(&value_repr) {
|
||||
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);
|
||||
res.weekday = Some(value);
|
||||
} else if let Some(value) = self.info.get_month(&l[i]) {
|
||||
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> {
|
||||
// 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 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
|
||||
let d = NaiveDate::from_ymd(
|
||||
let mut d = NaiveDate::from_ymd(
|
||||
y,
|
||||
m,
|
||||
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(
|
||||
res.hour.unwrap_or(default.hour() 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());
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_parse_simple0() {
|
||||
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,
|
||||
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