Compare commits

...

17 Commits

Author SHA1 Message Date
Bradlee Speice 6a5ec31d8e Release version 1.1.0 2020-06-11 15:42:03 -04:00
bors[bot] 23f50fb62b
Merge #29
29: Properly handle no date content being found r=bspeice a=bspeice

Fixes #22 

Co-authored-by: Bradlee Speice <bradlee@speice.io>
2020-06-11 19:33:20 +00:00
Bradlee Speice f1ca602e9f Properly handle no date content being found 2020-06-11 15:00:37 -04:00
bors[bot] 899cd88280
Merge #27
27: Add fixes for dateutil/dateutil#822 r=bspeice a=bspeice

Fixes #16 

Co-authored-by: Bradlee Speice <bradlee@speice.io>
2020-06-11 18:18:24 +00:00
Bradlee Speice a08bb2d9d7 Add fixes for dateutil/dateutil#822 2020-06-11 13:59:07 -04:00
bors[bot] af6c3238c4
Merge #28
28: Disable clippy component for 1.28 r=bspeice a=bspeice

And fix some other issues from a `.travis.yml` file I definitely didn't just copy-paste from a separate project...

Co-authored-by: Bradlee Speice <bradlee@speice.io>
2020-06-11 17:40:24 +00:00
Bradlee Speice b098f54f8b Convert clippy lints 2020-06-11 13:33:09 -04:00
Bradlee Speice 61022c323e Cargo fmt 2020-06-11 13:11:52 -04:00
Bradlee Speice 4079b3ce2f Fix ENV naming 2020-06-11 13:06:26 -04:00
Bradlee Speice 3e03b188b4 Disable clippy component for 1.28 2020-06-11 13:05:21 -04:00
bspeice 7147677926
Merge pull request #25 from bspeice/simplify_testing
Simplify testing procedure
2020-06-11 12:26:12 -04:00
Bradlee Speice 22b6a321e6 Remove Appveyor badge from README 2020-06-11 12:25:55 -04:00
Bradlee Speice 9edc2a3102 Simplify testing procedure 2020-06-11 12:23:36 -04:00
bspeice 245f746c8c
Merge pull request #24 from bspeice/panic_fuzzing
Fix #21
2020-06-11 12:23:10 -04:00
bspeice 5782a573bc
Merge pull request #23 from gma2th/master
Implement Error trait for ParseError
2020-06-11 12:13:12 -04:00
Matthieu Guilbert e895fbd9f3 Implement Error trait for ParseError 2020-06-11 23:29:37 +08:00
Bradlee Speice 2a2f1e7fbd Fix #21 2020-05-29 14:23:54 -04:00
9 changed files with 152 additions and 264 deletions

View File

@ -1,109 +1,40 @@
# Based on the "trust" template v0.1.2
# https://github.com/japaric/trust/tree/v0.1.2
dist: trusty
language: rust
services: docker
sudo: required
addons:
chrome: stable
env:
global:
- CRATE_NAME=dtparse
matrix:
jobs:
include:
# Android
- env: TARGET=aarch64-linux-android DISABLE_TESTS=1
- env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
- env: TARGET=armv7-linux-androideabi DISABLE_TESTS=1
- env: TARGET=i686-linux-android DISABLE_TESTS=1
- env: TARGET=x86_64-linux-android DISABLE_TESTS=1
- rust: stable
os: linux
- rust: 1.28.0
os: linux
env: DISABLE_TOOLS=true
- rust: stable
os: osx
- rust: stable-msvc
os: windows
- rust: stable
os: windows
# iOS
- env: TARGET=aarch64-apple-ios DISABLE_TESTS=1
os: osx
- env: TARGET=armv7-apple-ios DISABLE_TESTS=1
os: osx
- env: TARGET=armv7s-apple-ios DISABLE_TESTS=1
os: osx
- env: TARGET=i386-apple-ios DISABLE_TESTS=1
os: osx
- env: TARGET=x86_64-apple-ios DISABLE_TESTS=1
os: osx
cache:
- cargo
# Linux
- env: TARGET=aarch64-unknown-linux-gnu
- env: TARGET=arm-unknown-linux-gnueabi
- env: TARGET=armv7-unknown-linux-gnueabihf
- env: TARGET=i686-unknown-linux-gnu
- env: TARGET=i686-unknown-linux-musl
- env: TARGET=mips-unknown-linux-gnu
- env: TARGET=mips64-unknown-linux-gnuabi64
- env: TARGET=mips64el-unknown-linux-gnuabi64
- env: TARGET=mipsel-unknown-linux-gnu
- env: TARGET=powerpc-unknown-linux-gnu
- env: TARGET=powerpc64-unknown-linux-gnu
- env: TARGET=powerpc64le-unknown-linux-gnu
- env: TARGET=s390x-unknown-linux-gnu DISABLE_TESTS=1
- env: TARGET=x86_64-unknown-linux-gnu
- env: TARGET=x86_64-unknown-linux-musl
# OSX
- env: TARGET=i686-apple-darwin
os: osx
- env: TARGET=x86_64-apple-darwin
os: osx
# *BSD
- env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1
- env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
- env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1
# Windows
- env: TARGET=x86_64-pc-windows-gnu
# Nightly and Beta
- env: TARGET=x86_64-unknown-linux-gnu
rust: nightly
- env: TARGET=x86_64-apple-darwin
os: osx
rust: nightly
- env: TARGET=x86_64-unknown-linux-gnu
rust: beta
- env: TARGET=x86_64-apple-darwin
os: osx
rust: beta
# Historical Rust versions
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.28.0
before_install:
- set -e
- rustup self update
install:
- sh ci/install.sh
- source ~/.cargo/env || true
before_script:
- rustup show
# CMake doesn't like the `sh.exe` provided by Git being in PATH
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then rm "C:/Program Files/Git/usr/bin/sh.exe"; fi
- if [[ "$DISABLE_TOOLS" == "" ]]; then rustup component add clippy; rustup component add rustfmt; fi
script:
- bash ci/script.sh
- if [[ "$DISABLE_TOOLS" == "" ]]; then cargo clippy --all && cargo fmt --all -- --check; fi
after_script: set +e
cache: cargo
before_cache:
# Travis can't cache files that are not readable by "others"
- chmod -R a+r $HOME/.cargo
# For default build, split up compilation and tests so we can track build times
- cargo test --no-run
- cargo test
- cargo test --release --no-run
- cargo test --release
branches:
only:
# release tags
- /^v\d+\.\d+\.\d+.*$/
- master
notifications:
email:
on_success: never
- staging
- trying

View File

@ -1,6 +1,6 @@
[package]
name = "dtparse"
version = "1.0.3"
version = "1.1.0"
authors = ["Bradlee Speice <bradlee@speice.io>"]
description = "A dateutil-compatible timestamp parser for Rust"
repository = "https://github.com/bspeice/dtparse.git"
@ -10,7 +10,6 @@ license = "Apache-2.0"
[badges]
travis-ci = { repository = "bspeice/dtparse" }
appveyor = { repository = "bspeice/dtparse" }
maintenance = { status = "passively-maintained" }
[lib]

View File

@ -1,7 +1,6 @@
# dtparse
[![travisci](https://travis-ci.org/bspeice/dtparse.svg?branch=master)](https://travis-ci.org/bspeice/dtparse)
[![appveyor](https://ci.appveyor.com/api/projects/status/r4de76tg9utfjva1/branch/master?svg=true)](https://ci.appveyor.com/project/bspeice/dtparse/branch/master)
[![crates.io](https://img.shields.io/crates/v/dtparse.svg)](https://crates.io/crates/dtparse)
[![docs.rs](https://docs.rs/dtparse/badge.svg)](https://docs.rs/dtparse/)

View File

@ -1,121 +0,0 @@
# Appveyor configuration template for Rust using rustup for Rust installation
# https://github.com/starkat99/appveyor-rust
## Operating System (VM environment) ##
# Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets.
os: Visual Studio 2017
## Build Matrix ##
# This configuration will setup a build for each channel & target combination (12 windows
# combinations in all).
#
# There are 3 channels: stable, beta, and nightly.
#
# Alternatively, the full version may be specified for the channel to build using that specific
# version (e.g. channel: 1.5.0)
#
# The values for target are the set of windows Rust build targets. Each value is of the form
#
# ARCH-pc-windows-TOOLCHAIN
#
# Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker
# toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for
# a description of the toolchain differences.
# See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of
# toolchains and host triples.
#
# Comment out channel/target combos you do not wish to build in CI.
#
# You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands
# and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly
# channels to enable unstable features when building for nightly. Or you could add additional
# matrix entries to test different combinations of features.
environment:
matrix:
### MSVC Toolchains ###
# Stable 64-bit MSVC
- channel: stable
target: x86_64-pc-windows-msvc
# Stable 32-bit MSVC
- channel: stable
target: i686-pc-windows-msvc
# Beta 64-bit MSVC
- channel: beta
target: x86_64-pc-windows-msvc
# Beta 32-bit MSVC
- channel: beta
target: i686-pc-windows-msvc
# Nightly 64-bit MSVC
- channel: nightly
target: x86_64-pc-windows-msvc
#cargoflags: --features "unstable"
# Nightly 32-bit MSVC
- channel: nightly
target: i686-pc-windows-msvc
#cargoflags: --features "unstable"
### GNU Toolchains ###
# Stable 64-bit GNU
- channel: stable
target: x86_64-pc-windows-gnu
# Stable 32-bit GNU
- channel: stable
target: i686-pc-windows-gnu
# Beta 64-bit GNU
- channel: beta
target: x86_64-pc-windows-gnu
# Beta 32-bit GNU
- channel: beta
target: i686-pc-windows-gnu
# Nightly 64-bit GNU
- channel: nightly
target: x86_64-pc-windows-gnu
#cargoflags: --features "unstable"
# Nightly 32-bit GNU
- channel: nightly
target: i686-pc-windows-gnu
#cargoflags: --features "unstable"
### Allowed failures ###
# See Appveyor documentation for specific details. In short, place any channel or targets you wish
# to allow build failures on (usually nightly at least is a wise choice). This will prevent a build
# or test failure in the matching channels/targets from failing the entire build.
matrix:
allow_failures:
- channel: nightly
# If you only care about stable channel build failures, uncomment the following line:
#- channel: beta
## Install Script ##
# This is the most important part of the Appveyor configuration. This installs the version of Rust
# specified by the 'channel' and 'target' environment variables from the build matrix. This uses
# rustup to install Rust.
#
# For simple configurations, instead of using the build matrix, you can simply set the
# default-toolchain and default-host manually here.
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %channel% --default-host %target%
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- rustc -vV
- cargo -vV
## Build Script ##
# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
# the "directory does not contain a project or solution file" error.
build: false
# Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs
#directly or perform other testing commands. Rust will automatically be placed in the PATH
# environment variable.
test_script:
- cargo test --verbose %cargoflags%

4
bors.toml Normal file
View File

@ -0,0 +1,4 @@
status = [
"continuous-integration/travis-ci/push",
]
delete_merged_branches = true

View File

@ -81,7 +81,7 @@ tests = {
'Thu Sep 25 10:36:28 BRST 2003', '1996.07.10 AD at 15:08:56 PDT',
'Tuesday, April 12, 1952 AD 3:30:42pm PST',
'November 5, 1994, 8:15:30 am EST', '1994-11-05T08:15:30-05:00',
'1994-11-05T08:15:30Z', '1976-07-04T00:01:02Z',
'1994-11-05T08:15:30Z', '1976-07-04T00:01:02Z', '1986-07-05T08:15:30z',
'Tue Apr 4 00:22:12 PDT 1995'
],
'test_fuzzy_tzinfo': [

View File

@ -1,4 +1,5 @@
#![deny(missing_docs)]
#![cfg_attr(test, allow(unknown_lints))]
#![cfg_attr(test, deny(warnings))]
//! # dtparse
@ -90,6 +91,8 @@ use rust_decimal::Decimal;
use rust_decimal::Error as DecimalError;
use std::cmp::min;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::num::ParseIntError;
use std::str::FromStr;
use std::vec::Vec;
@ -144,8 +147,18 @@ pub enum ParseError {
/// Parser unable to make sense of year/month/day parameters in the time string;
/// please report to maintainer as the timestring likely exposes a bug in implementation
YearMonthDayError(&'static str),
/// Parser unable to find any date/time-related content in the supplied string
NoDate,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for ParseError {}
type ParseResult<I> = Result<I, ParseError>;
pub(crate) fn tokenize(parse_string: &str) -> Vec<String> {
@ -155,16 +168,15 @@ pub(crate) fn tokenize(parse_string: &str) -> Vec<String> {
/// Utility function for `ParserInfo` that helps in constructing
/// the attributes that make up the `ParserInfo` container
#[cfg_attr(feature = "cargo-clippy", allow(get_unwrap))] // Recommended suggestion of &vec[0] doesn't compile
pub 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() {
for (i, val) in vec.get(0).unwrap().iter().enumerate() {
m.insert(val.to_lowercase(), i);
}
} else {
for (i, val_vec) in vec.into_iter().enumerate() {
for (i, val_vec) in vec.iter().enumerate() {
for val in val_vec {
m.insert(val.to_lowercase(), i);
}
@ -337,7 +349,9 @@ impl ParserInfo {
res.year = Some(self.convertyear(y, res.century_specified))
};
if res.tzoffset == Some(0) && res.tzname.is_none() || res.tzname == Some("Z".to_owned()) {
if (res.tzoffset == Some(0) && res.tzname.is_none())
|| (res.tzname == Some("Z".to_owned()) || res.tzname == Some("z".to_owned()))
{
res.tzname = Some("UTC".to_owned());
res.tzoffset = Some(0);
} else if res.tzoffset != Some(0)
@ -506,7 +520,7 @@ impl YMD {
))
}
#[cfg_attr(feature = "cargo-clippy", allow(needless_return))]
#[allow(clippy::needless_return)]
fn resolve_ymd(
&mut self,
yearfirst: bool,
@ -612,6 +626,31 @@ struct ParsingResult {
any_unused_tokens: Vec<String>,
}
macro_rules! option_len {
($o:expr) => {{
if $o.is_some() {
1
} else {
0
}
}};
}
impl ParsingResult {
fn len(&self) -> usize {
option_len!(self.year)
+ option_len!(self.month)
+ option_len!(self.day)
+ option_len!(self.weekday)
+ option_len!(self.hour)
+ option_len!(self.minute)
+ option_len!(self.second)
+ option_len!(self.microsecond)
+ option_len!(self.tzname)
+ option_len!(self.ampm)
}
}
/// Parser is responsible for doing the actual work of understanding a time string.
/// The root level `parse` function is responsible for constructing a default `Parser`
/// and triggering its behavior.
@ -660,7 +699,7 @@ impl Parser {
/// timezone name support (i.e. "EST", "BRST") is not available by default
/// at the moment, they must be added through `tzinfos` at the moment in
/// order to be resolved.
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] // Need to release a 2.0 for changing public API
#[allow(clippy::too_many_arguments)]
pub fn parse(
&self,
timestr: &str,
@ -679,6 +718,10 @@ impl Parser {
let (res, tokens) =
self.parse_with_tokens(timestr, dayfirst, yearfirst, fuzzy, fuzzy_with_tokens)?;
if res.len() == 0 {
return Err(ParseError::NoDate);
}
let naive = self.build_naive(&res, &default_ts)?;
if !ignoretz {
@ -689,7 +732,7 @@ impl Parser {
}
}
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] // Imitating Python API is priority
#[allow(clippy::cognitive_complexity)] // Imitating Python API is priority
fn parse_with_tokens(
&self,
timestr: &str,
@ -736,11 +779,11 @@ impl Parser {
// Jan-01[-99]
let sep = &l[i + 1];
// TODO: This seems like a very unsafe unwrap
ymd.append(l[i + 2].parse::<i32>().unwrap(), &l[i + 2], None)?;
ymd.append(l[i + 2].parse::<i32>()?, &l[i + 2], None)?;
if i + 3 < len_l && &l[i + 3] == sep {
// Jan-01-99
ymd.append(l[i + 4].parse::<i32>().unwrap(), &l[i + 4], None)?;
ymd.append(l[i + 4].parse::<i32>()?, &l[i + 4], None)?;
i += 2;
}
@ -803,17 +846,17 @@ impl Parser {
// 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());
hour_offset = Some(l[i + 1][..2].parse::<i32>()?);
min_offset = Some(l[i + 1][2..4].parse::<i32>()?);
} 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());
hour_offset = Some(l[i + 1].parse::<i32>()?);
min_offset = Some(l[i + 3].parse::<i32>()?);
i += 2;
} else if len_li <= 2 {
// -[0]3
let range_len = min(l[i + 1].len(), 2);
hour_offset = Some(l[i + 1][..range_len].parse::<i32>().unwrap());
hour_offset = Some(l[i + 1][..range_len].parse::<i32>()?);
min_offset = Some(0);
}
@ -875,9 +918,10 @@ impl Parser {
&& tzname.is_none()
&& tzoffset.is_none()
&& token.len() <= 5
&& all_ascii_upper
&& (all_ascii_upper || self.info.utczone.contains_key(token))
}
#[allow(clippy::unnecessary_unwrap)]
fn ampm_valid(&self, hour: Option<i32>, ampm: Option<bool>, fuzzy: bool) -> ParseResult<bool> {
let mut val_is_ampm = !(fuzzy && ampm.is_some());
@ -981,6 +1025,7 @@ impl Parser {
}
}
#[allow(clippy::unnecessary_unwrap)]
fn parse_numeric_token(
&self,
tokens: &[String],
@ -1017,9 +1062,9 @@ impl Parser {
let s = &tokens[idx];
if ymd.len() == 0 && tokens[idx].find('.') == None {
ymd.append(s[0..2].parse::<i32>().unwrap(), &s[0..2], None)?;
ymd.append(s[2..4].parse::<i32>().unwrap(), &s[2..4], None)?;
ymd.append(s[4..6].parse::<i32>().unwrap(), &s[4..6], None)?;
ymd.append(s[0..2].parse::<i32>()?, &s[0..2], None)?;
ymd.append(s[2..4].parse::<i32>()?, &s[2..4], None)?;
ymd.append(s[4..6].parse::<i32>()?, &s[4..6], None)?;
} else {
// 19990101T235959[.59]
res.hour = s[0..2].parse::<i32>().ok();
@ -1032,13 +1077,9 @@ impl Parser {
} else if vec![8, 12, 14].contains(&len_li) {
// YYMMDD
let s = &tokens[idx];
ymd.append(
s[..4].parse::<i32>().unwrap(),
&s[..4],
Some(YMDLabel::Year),
)?;
ymd.append(s[4..6].parse::<i32>().unwrap(), &s[4..6], None)?;
ymd.append(s[6..8].parse::<i32>().unwrap(), &s[6..8], None)?;
ymd.append(s[..4].parse::<i32>()?, &s[..4], Some(YMDLabel::Year))?;
ymd.append(s[4..6].parse::<i32>()?, &s[4..6], None)?;
ymd.append(s[6..8].parse::<i32>()?, &s[6..8], None)?;
if len_li > 8 {
res.hour = Some(s[8..10].parse::<i32>()?);
@ -1080,7 +1121,7 @@ impl Parser {
{
// 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(), &value_repr, None)?;
ymd.append(value_repr.parse::<i32>()?, &value_repr, None)?;
if idx + 2 < len_l && !info.jump_index(&tokens[idx + 2]) {
if let Ok(val) = tokens[idx + 2].parse::<i32>() {
@ -1202,6 +1243,7 @@ impl Parser {
hms_idx
}
#[allow(clippy::unnecessary_unwrap)]
fn parse_hms(
&self,
idx: usize,
@ -1261,7 +1303,6 @@ impl Parser {
(minute, second)
}
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] // Need Vec type because of mutability in the function that calls us
fn recombine_skipped(&self, skipped_idxs: Vec<usize>, tokens: Vec<String>) -> Vec<String> {
let mut skipped_tokens: Vec<String> = vec![];

View File

@ -17,26 +17,34 @@ fn test_fuzz() {
parse("2..\x00\x000d\x00+\x010d\x01\x00\x00\x00+"),
Err(ParseError::UnrecognizedFormat)
);
// OverflowError: Python int too large to convert to C long
// assert_eq!(parse("8888884444444888444444444881"), Err(ParseError::AmPmWithoutHour));
let default = NaiveDate::from_ymd(2016, 6, 29).and_hms(0, 0, 0);
let p = Parser::default();
let res = p
.parse(
"\x0D\x31",
None,
None,
false,
false,
Some(&default),
false,
&HashMap::new(),
)
.unwrap();
assert_eq!(res.0, default);
let res = p.parse(
"\x0D\x31",
None,
None,
false,
false,
Some(&default),
false,
&HashMap::new(),
);
assert_eq!(res, Err(ParseError::NoDate));
assert_eq!(
parse("\x2D\x2D\x32\x31\x38\x6D"),
Err(ParseError::ImpossibleTimestamp("Invalid minute"))
);
}
#[test]
fn large_int() {
let parse_result = parse("1412409095009.jpg");
assert!(parse_result.is_err());
}
#[test]
fn empty_string() {
assert_eq!(parse(""), Err(ParseError::NoDate))
}

View File

@ -3348,6 +3348,33 @@ fn test_parse_ignoretz6() {
#[test]
fn test_parse_ignoretz7() {
let info = ParserInfo::default();
let pdt = PyDateTime {
year: 1986,
month: 7,
day: 5,
hour: 8,
minute: 15,
second: 30,
micros: 0,
tzo: None,
};
parse_and_assert(
pdt,
info,
"1986-07-05T08:15:30z",
None,
None,
false,
false,
None,
true,
&HashMap::new(),
);
}
#[test]
fn test_parse_ignoretz8() {
let info = ParserInfo::default();
let pdt = PyDateTime {
year: 1995,