diff --git a/src/lib.rs b/src/lib.rs index 67c48ba..f60fd0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,9 +8,6 @@ extern crate chrono; extern crate num_traits; extern crate rust_decimal; -#[cfg(test)] -extern crate pyo3; - use chrono::DateTime; use chrono::Datelike; use chrono::FixedOffset; @@ -28,9 +25,6 @@ use std::num::ParseIntError; use std::str::FromStr; use std::vec::Vec; -#[cfg(test)] -mod tests; - lazy_static! { static ref ZERO: Decimal = Decimal::new(0, 0); static ref ONE: Decimal = Decimal::new(1, 0); @@ -311,7 +305,7 @@ fn parse_info(vec: Vec>) -> HashMap { } #[derive(Debug, PartialEq)] -struct ParserInfo { +pub struct ParserInfo { jump: HashMap, weekday: HashMap, months: HashMap, @@ -705,7 +699,7 @@ impl YMD { month = Some(self._ymd[2]); } } else { - if self._ymd[0] > 31 || self.ystridx.unwrap() == 0 + if self._ymd[0] > 31 || self.ystridx == Some(0) || (yearfirst && self._ymd[1] <= 12 && self._ymd[2] <= 31) { if dayfirst && self._ymd[2] <= 12 { @@ -1270,7 +1264,7 @@ fn ljust(s: &str, chars: usize, replace: char) -> String { } } -fn parse_with_info( +pub fn parse_with_info( timestr: &str, info: ParserInfo, default: Option<&NaiveDateTime>, @@ -1280,7 +1274,7 @@ fn parse_with_info( parser.parse(timestr, default, false, vec![]) } -fn parse_with_default( +pub fn parse_with_default( timestr: &str, default: &NaiveDateTime, ) -> ParseResult<(NaiveDateTime, Option)> { @@ -1288,7 +1282,7 @@ fn parse_with_default( Ok((parse_result.0, parse_result.1)) } -fn parse(timestr: &str) -> ParseResult<(NaiveDateTime, Option)> { +pub fn parse(timestr: &str) -> ParseResult<(NaiveDateTime, Option)> { let parse_result = parse_with_info(timestr, ParserInfo::default(), None)?; Ok((parse_result.0, parse_result.1)) } diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index 883ebd7..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,188 +0,0 @@ -use chrono::Datelike; -use chrono::NaiveDate; -use chrono::NaiveDateTime; -use chrono::NaiveTime; -use pyo3::FromPyObject; -use pyo3::ObjectProtocol; -use pyo3::PyDict; -use pyo3::PyList; -use pyo3::PyObject; -use pyo3::PyObjectRef; -use pyo3::Python; -use std::collections::HashMap; - -use parse; -use parse_with_default; -use tokenize; - -static FORMAT: &str = "%Y-%m-%d %H:%M:%S.%f"; - -macro_rules! test_split { - ($py:ident, $timelex:ident, $s:expr) => { - let f = $timelex.call_method1($py, "split", $s).unwrap(); - let l: &PyList = f.extract($py).unwrap(); - let s: Vec = l.iter().map(|i| format!("{}", i)).collect(); - - assert_eq!(s, tokenize($s)); - }; -} - -#[test] -fn test_split() { - let gil = Python::acquire_gil(); - let py = gil.python(); - - let module = py.import("dateutil.parser").unwrap(); - let t: PyObject = module.get("_timelex").unwrap().extract().unwrap(); - - // TODO: Fix disagreement about whether or not to replace commas with periods - // test_split!(py, t, "24, 50, ABC"); - test_split!(py, t, "2018.5.15"); - test_split!(py, t, "May 5, 2018"); - test_split!(py, t, "Mar. 5, 2018"); - test_split!(py, t, "19990101T23"); - test_split!(py, t, "19990101T2359"); -} - -macro_rules! test_parse_naive { - // Handle tests where the times involved are unambiguous - ($py:ident, $parser:ident, $s:expr) => { - let dt: PyObject = $parser - .call_method1("parse", $s) - .unwrap() - .extract() - .unwrap(); - let dt_s: String = dt.call_method1($py, "strftime", FORMAT) - .unwrap() - .extract($py) - .unwrap(); - - let r_rs = parse($s); - if r_rs.is_err() { - println!("{:?}", r_rs); - assert!(false); - } - - let rs = r_rs.unwrap(); - assert_eq!(rs.1, None); - // Because chrono stores nanos, and python goes to micros, - // we have to trim a couple things off the end. - let rs_formatted = format!("{}", rs.0.format(FORMAT)); - assert_eq!(dt_s, rs_formatted[..rs_formatted.len()-3]); - }; - - // Handle tests with some ambiguity, and thus needing a `default` - ($py:ident, $parser:ident, $s:expr, $datetime:ident, $d:expr) => { - let rust_date = $d.date(); - let dt_tuple = (rust_date.year(), rust_date.month(), rust_date.day()); - let pydefault: &PyObjectRef = $datetime.call_method1("datetime", dt_tuple).unwrap(); - - let mut kwargs = HashMap::new(); - kwargs.insert("default", pydefault); - - let dt: PyObject = $parser - .call_method("parse", $s, kwargs) - .unwrap() - .extract() - .unwrap(); - let dt_s: String = dt.call_method1($py, "strftime", FORMAT) - .unwrap() - .extract($py) - .unwrap(); - - let r_rs = parse_with_default($s, $d); - if r_rs.is_err() { - println!("{:?}", r_rs); - assert!(false); - } - - let rs = r_rs.unwrap(); - assert_eq!(rs.1, None); - // Because chrono stores nanos, and python goes to micros, - // we have to trim a couple things off the end. - let rs_formatted = format!("{}", rs.0.format(FORMAT)); - assert_eq!(dt_s, rs_formatted[..rs_formatted.len()-3]); - }; -} - -#[test] -fn test_basic_parse() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let parser = py.import("dateutil.parser").unwrap(); - - test_parse_naive!(py, parser, "2018.5.15"); - test_parse_naive!(py, parser, "May 5, 2018"); - test_parse_naive!(py, parser, "Mar. 5, 2018"); - test_parse_naive!(py, parser, "19990101T23"); - test_parse_naive!(py, parser, "19990101T2359"); -} - -#[test] -fn test_dateutil_compat() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let parser = py.import("dateutil.parser").unwrap(); - let datetime = py.import("datetime").unwrap(); - - let default = NaiveDateTime::new( - NaiveDate::from_ymd(2003, 9, 25), - NaiveTime::from_hms(0, 0, 0), - ); - - // testDateCommandFormatStrip1 - test_parse_naive!(py, parser, "Thu Sep 25 10:36:28 2003", datetime, &default); - // testDateCommandFormatStrip2 - test_parse_naive!(py, parser, "Thu Sep 25 10:36:28", datetime, &default); - // testDateCommandFormatStrip3 - test_parse_naive!(py, parser, "Thu Sep 10:36:28", datetime, &default); - // testDateCommandFormatStrip4 - test_parse_naive!(py, parser, "Thu 10:36:28", datetime, &default); - // testDateCommandFormatStrip5 - test_parse_naive!(py, parser, "Sep 10:36:28", datetime, &default); - // testDateCommandFormatStrip6 - test_parse_naive!(py, parser, "10:36:28", datetime, &default); - // testDateCommandFormatStrip7 - test_parse_naive!(py, parser, "10:36", datetime, &default); - // testDateCommandFormatStrip8 - test_parse_naive!(py, parser, "Thu Sep 25 2003", datetime, &default); - // testDateCommandFormatStrip10 - test_parse_naive!(py, parser, "Sep 2003", datetime, &default); - // testDateCommandFormatStrip11 - test_parse_naive!(py, parser, "Sep", datetime, &default); - // testDateCommandFormatStrip12 - test_parse_naive!(py, parser, "2003", datetime, &default); - - // testISOFormatStrip2 - test_parse_naive!(py, parser, "2003-09-25T10:49:41", datetime, &default); - // testISOFormatStrip3 - test_parse_naive!(py, parser, "2003-09-25T10:49", datetime, &default); - // testISOFormatStrip4 - test_parse_naive!(py, parser, "2003-09-25T10", datetime, &default); - // testISOFormatStrip5 - test_parse_naive!(py, parser, "2003-09-25", datetime, &default); - - // testISOStrippedFormatStrip2 - test_parse_naive!(py, parser, "20030925T104941", datetime, &default); - // testISOStrippedFormatStrip3 - test_parse_naive!(py, parser, "20030925T1049", datetime, &default); - // testISOStrippedFormatStrip4 - test_parse_naive!(py, parser, "20030925T10", datetime, &default); - // testISOStrippedFormatStrip5 - test_parse_naive!(py, parser, "20030925", datetime, &default); - - // testPythonLoggerFormat - test_parse_naive!(py, parser, "2003-09-25 10:49:41,502", datetime, &default); - - // testNoSeparator1 - test_parse_naive!(py, parser, "199709020908", datetime, &default); - // testNoSeparator1 - test_parse_naive!(py, parser, "19970902090807", datetime, &default); - - // testDateWithDash1 - test_parse_naive!(py, parser, "2003-09-25", datetime, &default); - // testDateWithDash6 - test_parse_naive!(py, parser, "09-25-2003", datetime, &default); - // testDateWithDash7 - test_parse_naive!(py, parser, "25-09-2003", datetime, &default); -} diff --git a/tests/compat.rs b/tests/compat.rs new file mode 100644 index 0000000..8789f2b --- /dev/null +++ b/tests/compat.rs @@ -0,0 +1,268 @@ +extern crate chrono; +extern crate pyo3; + +use chrono::Datelike; +use chrono::NaiveDate; +use chrono::Timelike; +use pyo3::ObjectProtocol; +use pyo3::PyDict; +use pyo3::PyList; +use pyo3::PyObject; +use pyo3::Python; +use std::collections::HashMap; + +extern crate dtparse; + +use dtparse::parse_with_default; +use dtparse::tokenize; + +macro_rules! test_split { + ($py:ident, $timelex:ident, $s:expr) => { + let f = $timelex.call_method1($py, "split", $s).unwrap(); + let l: &PyList = f.extract($py).unwrap(); + let s: Vec = l.iter().map(|i| format!("{}", i)).collect(); + + assert_eq!(s, tokenize($s)); + }; +} + +#[test] +fn test_split() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let module = py.import("dateutil.parser").unwrap(); + let t: PyObject = module.get("_timelex").unwrap().extract().unwrap(); + + // TODO: Fix disagreement about whether or not to replace commas with periods + // test_split!(py, t, "24, 50, ABC"); + test_split!(py, t, "2018.5.15"); + test_split!(py, t, "May 5, 2018"); + test_split!(py, t, "Mar. 5, 2018"); + test_split!(py, t, "19990101T23"); + test_split!(py, t, "19990101T2359"); +} + +macro_rules! test_parse { + ($py:ident, $parser:ident, $datetime:ident, $s:expr) => { + println!("Attempting to parse: {}", $s); + + let default_pydate = $datetime + .call_method1("datetime", (2003, 9, 25)) + .expect("Unable to create default datetime"); + let default_tzinfos = PyDict::new($py); + default_tzinfos.set_item("BRST", -10800).unwrap(); + + let mut kwargs = HashMap::new(); + kwargs.insert("default", default_pydate); + kwargs.insert("tzinfos", default_tzinfos.into()); + + let py_parsed: PyObject = $parser + .call_method("parse", $s, kwargs) + .expect("Unable to call method `parse`") + .extract() + .expect("Unable to extract result of `parse` call"); + + let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0); + let rs_parsed = + parse_with_default($s, default_rsdate).expect("Unable to parse date in Rust"); + + if let Some(_offset) = rs_parsed.1 { + // TODO: Handle tests involving timezones + } else { + // Naive timestamps + let rs_dt = rs_parsed.0; + + // First make sure that Python doesn't have any timestamps set + let py_tzoffset = py_parsed + .getattr($py, "tzinfo") + .expect("Unable to get `tzinfo` value"); + assert_eq!(py_tzoffset, $py.None()); + + // TODO: Should years by i32? + let py_year: i32 = py_parsed + .getattr($py, "year") + .expect("Unable to get `year` value") + .extract($py) + .expect("Unable to convert `year` to i32"); + assert_eq!(py_year, rs_dt.year()); + + let py_month: u32 = py_parsed + .getattr($py, "month") + .expect("Unable to get `month` value") + .extract($py) + .expect("Unable to convert `month` to u32"); + assert_eq!(py_month, rs_dt.month()); + + let py_day: u32 = py_parsed + .getattr($py, "day") + .expect("Unable to get `day` value") + .extract($py) + .expect("Unable to convert `day` to u32"); + assert_eq!(py_day, rs_dt.day()); + + let py_hour: u32 = py_parsed + .getattr($py, "hour") + .expect("Unable to get `hour` value") + .extract($py) + .expect("Unable to convert `hour` to u32"); + assert_eq!(py_hour, rs_dt.hour()); + + let py_minute: u32 = py_parsed + .getattr($py, "minute") + .expect("Unable to get `minute` value") + .extract($py) + .expect("Unable to convert `minute` to u32"); + assert_eq!(py_minute, rs_dt.minute()); + + let py_second: u32 = py_parsed + .getattr($py, "second") + .expect("Unable to get `second` value") + .extract($py) + .expect("Unable to convert `second` to u32"); + assert_eq!(py_second, rs_dt.second()); + + let py_microsecond: u32 = py_parsed + .getattr($py, "microsecond") + .expect("Unable to get `microsecond` value") + .extract($py) + .expect("Unable to convert `microsecond` to u32"); + assert_eq!(py_microsecond, rs_dt.nanosecond() / 1000); + } + }; +} + +#[test] +fn test_dateutil_compat() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let parser = py.import("dateutil.parser").unwrap(); + let datetime = py.import("datetime").unwrap(); + + // TODO: Uncomment tests once timezone support is in + + // testDateCommandFormat + // test_parse!(py, parser, datetime, "Thu Sep 25 10:36:28 BRST 2003"); + // testDateCommandFormatReversed + // test_parse!(py, parser, datetime, "2003 10:36:28 BRST 25 Sep Thu"); + + // testDateCommandFormatStrip1 + test_parse!(py, parser, datetime, "Thu Sep 25 10:36:28 2003"); + // testDateCommandFormatStrip2 + test_parse!(py, parser, datetime, "Thu Sep 25 10:36:28"); + // testDateCommandFormatStrip3 + test_parse!(py, parser, datetime, "Thu Sep 10:36:28"); + // testDateCommandFormatStrip4 + test_parse!(py, parser, datetime, "Thu 10:36:28"); + // testDateCommandFormatStrip5 + test_parse!(py, parser, datetime, "Sep 10:36:28"); + // testDateCommandFormatStrip6 + test_parse!(py, parser, datetime, "10:36:28"); + // testDateCommandFormatStrip7 + test_parse!(py, parser, datetime, "10:36"); + // testDateCommandFormatStrip8 + test_parse!(py, parser, datetime, "Thu Sep 25 2003"); + // testDateCommandFormatStrip10 + test_parse!(py, parser, datetime, "Sep 2003"); + // testDateCommandFormatStrip11 + test_parse!(py, parser, datetime, "Sep"); + // testDateCommandFormatStrip12 + test_parse!(py, parser, datetime, "2003"); + // testDateRCommandFormat + // test_parse!(py, parser, datetime, "Thu, 25 Sep 2003 10:49:41 -0300"); + // testISOFormat + // test_parse!(py, parser, datetime, "2003-09-25T10:49:41.5-03:00"); + // testISOFormatStrip1 + // test_parse!(py, parser, datetime, "2003-09-25T10:49:41-03:00"); + // testISOFormatStrip2 + test_parse!(py, parser, datetime, "2003-09-25T10:49:41"); + // testISOFormatStrip3 + test_parse!(py, parser, datetime, "2003-09-25T10:49"); + // testISOFormatStrip4 + test_parse!(py, parser, datetime, "2003-09-25T10"); + // testISOFormatStrip5 + test_parse!(py, parser, datetime, "2003-09-25"); + // testISOStrippedFormat + // test_parse!(py, parser, datetime, "20030925T104941.5-0300"); + // testISOStrippedFormatStrip1 + // test_parse!(py, parser, datetime, "20030925T104941-0300"); + // testISOStrippedFormatStrip2 + test_parse!(py, parser, datetime, "20030925T104941"); + // testISOStrippedFormatStrip3 + test_parse!(py, parser, datetime, "20030925T1049"); + // testISOStrippedFormatStrip4 + test_parse!(py, parser, datetime, "20030925T10"); + // testISOStrippedFormatStrip5 + test_parse!(py, parser, datetime, "20030925"); + // testPythonLoggerFormat + test_parse!(py, parser, datetime, "2003-09-25 10:49:41,502"); + // testNoSeparator1 + test_parse!(py, parser, datetime, "199709020908"); + // testNoSeparator1 + test_parse!(py, parser, datetime, "19970902090807"); + // testDateWithDash1 + test_parse!(py, parser, datetime, "2003-09-25"); + // testDateWithDash6 + test_parse!(py, parser, datetime, "09-25-2003"); + // testDateWithDash7 + test_parse!(py, parser, datetime, "25-09-2003"); + // testDateWithDash8 - Needs `dayfirst` support + // test_parse!(py, parser, datetime, "10-09-2003"); + // testDateWithDash9 + test_parse!(py, parser, datetime, "10-09-2003"); + // testDateWithDash10 + test_parse!(py, parser, datetime, "10-09-03"); + // testDateWithDash11 - Needs `yearfirst` support + // test_parse!(py, parser, datetime, "10-09-03") + // testDateWithDot1 + test_parse!(py, parser, datetime, "2003.09.25"); + // testDateWithDot6 + test_parse!(py, parser, datetime, "09.25.2003"); + // testDateWithDot7 + test_parse!(py, parser, datetime, "25.09.2003"); + // testDateWithDot8 - Needs `dayfirst` support + // test_parse!(py, parser, datetime, "10.09.2003"); + // testDateWithDot9 + test_parse!(py, parser, datetime, "10.09.2003"); + // testDateWithDot10 + test_parse!(py, parser, datetime, "10.09.03"); + // testDateWithDot11 - Needs `yearfirst` support + // test_parse!(py, parser, datetime, "10.09.03"); + // testDateWithSlash1 + test_parse!(py, parser, datetime, "2003/09/25"); + // testDateWithSlash6 + test_parse!(py, parser, datetime, "09/25/2003"); + // testDateWithSlash7 + test_parse!(py, parser, datetime, "25/09/2003"); + // testDateWithSlash8 - Needs `dayfirst` support + // test_parse!(py, parser, datetime, "10/09/2003"); + // testDateWithSlash9 + test_parse!(py, parser, datetime, "10/09/2003"); + // testDateWithSlash10 + test_parse!(py, parser, datetime, "10/09/03"); + // testDateWithSlash11 - Needs `yearfirst` support + // test_parse!(py, parser, datetime, "10/09/03"); + // testDateWithSpace1 + test_parse!(py, parser, datetime, "2003 09 25"); + // testDateWithSpace6 + test_parse!(py, parser, datetime, "09 25 2003"); + // testDateWithSpace7 + test_parse!(py, parser, datetime, "25 09 2003"); + // testDateWithSpace8 - Needs `dayfirst` support + // test_parse!(py, parser, datetime, "10 09 2003"); + // testDateWithSpace9 + test_parse!(py, parser, datetime, "10 09 2003"); + // testDateWithSpace10 + test_parse!(py, parser, datetime, "10 09 03"); + // testDateWithSpace11 - Needs `yearfirst` support + // test_parse!(py, parser, datetime, "10 09 03"); + // testDateWithSpace12 + test_parse!(py, parser, datetime, "25 09 03"); + // testStrangelyOrderedDate1 + test_parse!(py, parser, datetime, "03 25 Sep"); + // testStrangelyOrderedDate3 + test_parse!(py, parser, datetime, "25 03 Sep"); + // TODO: Fix UnrecognizedToken error + // testHourWithLetters + // test_parse!(py, parser, datetime, "10h36m28.5s"); +}