1
0
mirror of https://github.com/bspeice/dtparse synced 2025-07-03 06:45:01 -04:00

22 Commits

Author SHA1 Message Date
4d7c5dd995 Now with 100% more WASM!
Version bump and README update
2018-08-14 22:49:24 -04:00
afb7747cdf Fix target name 2018-08-14 22:38:05 -04:00
22e0300275 Test adding WASM support 2018-08-14 21:53:35 -04:00
0ef35527d9 Release 1.0.1 2018-08-11 13:13:12 -04:00
b5fa1d89ef Fix new tests not being date-neutral 2018-08-11 10:38:46 -04:00
246b389ac9 Clippy caught a bug
And now I have test cases for it!
2018-08-10 23:24:02 -04:00
4d48885f4b Add old rust versions to test agains 2018-08-10 22:15:27 -04:00
48705339e6 Add one final CONTRIBUTOR 2018-08-03 23:43:24 -04:00
01ebec84bb Release version 1.0! 2018-08-03 23:42:09 -04:00
28b7bec91d Add a custom parsing example in Russian 2018-08-03 23:40:54 -04:00
b966c02d04 Add ALL the badges 2018-08-03 23:15:13 -04:00
4133343e93 Don't attempt running a lib crate 2018-08-03 22:10:50 -04:00
0d3b646749 Add notes on codegen, and regenerate 2018-08-03 21:56:32 -04:00
50fe2c01d4 README and Cargo.toml updates 2018-08-03 21:52:36 -04:00
893cf6d40c Get some new CI set up 2018-08-03 21:48:09 -04:00
8f8ba7887a Write ALL the documentation! 2018-08-01 23:27:49 -04:00
256f937742 Add some initial crate documentation
Element documentation next!
2018-07-30 23:33:46 -04:00
91a3a4a481 Add travis status to the README 2018-07-24 23:36:11 -04:00
c884bc5842 Merge pull request #15 from bspeice/internal_refactor
Internal refactor
2018-07-24 23:32:44 -04:00
44e37b364c Much simpler API surface
And getting rid of the dumb "internal" error type
2018-07-24 23:28:38 -04:00
c6feaebe71 tokenize shouldn't be public 2018-07-24 22:47:22 -04:00
0d18eb524b Remove fuzzing, add CONTRIBUTING/CONTRIBUTORS 2018-07-24 22:41:56 -04:00
21 changed files with 1056 additions and 391 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@
Cargo.lock
.vscode
*.pyc
.idea/
*.swp

View File

@ -1,5 +1,126 @@
# Based on the "trust" template v0.1.2
# https://github.com/japaric/trust/tree/v0.1.2
dist: trusty
language: rust
rust:
- stable
- beta
- nightly
services: docker
sudo: required
addons:
chrome: stable
env:
global:
- CRATE_NAME=dtparse
matrix:
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
# 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
# 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.21.0
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.22.0
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.23.0
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.24.0
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.25.0
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.26.0
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.27.0
# WASM support
- env: TARGET=asmjs-unknown-emscripten USE_CARGO_WEB=true
rust: nightly
before_install:
- set -e
- rustup self update
install:
- sh ci/install.sh
- source ~/.cargo/env || true
script:
- bash ci/script.sh
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
branches:
only:
# release tags
- /^v\d+\.\d+\.\d+.*$/
- master
notifications:
email:
on_success: never

26
CHANGELOG.md Normal file
View File

@ -0,0 +1,26 @@
Version 1.0.2 (2018-08-14)
==========================
Misc
----
- Add tests for WASM
Version 1.0.1 (2018-08-11)
==========================
Bugfixes
--------
- Fixed an issue with "GMT+3" not being handled correctly
Misc
----
- Upgrade `lazy_static` and `rust_decimal` dependencies
Version 1.0.0 (2018-08-03)
==========================
Initial release. Passes all relevant unit tests from Python's
`dateutil` project.

44
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,44 @@
# Contributing
The `dtparse` crate is better for the contributions made by members of the open source community,
and seeks to make it easy to contribute back to the community it comes from. The goals are
fairly straight-forward, but here are the ways that would be most beneficial:
## Bug Reports
The testing suite for `dtparse` is built using tests derived from the [`dateutil`](https://github.com/dateutil/dateutil)
package in Python. Some Rust-specific behavior may show up though, for example in how
Rust handles nanoseconds where Python's standard library will only go to microseconds.
If you believe that behavior is improper, you are encouraged to file an issue; there are no dumb
issues or suggestions, and the world is a better place for having your input.
## Testing/Fuzzing
`dtparse`'s history as a port of Python software has led to some behavior being shown in Rust
that would not otherwise be an issue in Python. Testing for these issues to prevent panics
is greatly appreciated, and some great work has already happened surrounding fuzzing.
New test cases built either by fuzzers or humans are welcome.
## Feature Requests
Handling weird date formats and quirks is the name of the game. Any ideas on how to improve that
or utilities useful in handling the mapping of human time to computers is appreciated.
Writing code to implement the feature is never mandatory (though always appreciated); if there's
something you believe `dtparse` should do that it doesn't currently support, let's make that happen.
# Development Setup
The setup requirements for `dtparse` should be fairly straightforward - the project can be built
and deployed using only the `cargo` tool in Rust.
Much of the test coee is generated from Python code, and then the generated versions are stored
in version control. Thi is to ensure that all users can run the tests even without
installing Python or the other necessary packages.
To regenerate the tests, please use Python 3.6 with the `dateutil` package installed, and run:
- `python build_pycompat.py`
- `python build_pycompat_tokenizer.py`

7
CONTRIBUTORS.md Normal file
View File

@ -0,0 +1,7 @@
This project benefits from the Rust and open source communities, but most specifically from these people:
# Contributors:
- [@messense](https://github.com/messense)
- [@mjmeehan](https://github.com/mjmeehan)
- [@neosilky](https://github.com/neosilky)

View File

@ -1,6 +1,6 @@
[package]
name = "dtparse"
version = "0.9.1"
version = "1.0.2"
authors = ["Bradlee Speice <bradlee@speice.io>"]
description = "A dateutil-compatible timestamp parser for Rust"
repository = "https://github.com/bspeice/dtparse.git"
@ -10,12 +10,14 @@ license = "Apache-2.0"
[badges]
travis-ci = { repository = "bspeice/dtparse" }
appveyor = { repository = "bspeice/dtparse" }
maintenance = { status = "passively-maintained" }
[lib]
name = "dtparse"
[dependencies]
chrono = "0.4"
lazy_static = "1.0"
lazy_static = "1.1"
num-traits = "0.2"
rust_decimal = "0.9"
rust_decimal = "^0.10.1"

View File

@ -1,27 +1,75 @@
# dtparse
A [dateutil](https://github.com/dateutil/dateutil)-compatible timestamp parser for Rust
## Where it stands
[![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/)
The library works really well at the moment, and passes the vast majority of `dateutil`s parser
test suite. This isn't mission-critical ready, but is more than ready for hobbyist projects.
The issues to be resolved before version 1.0:
The fully-featured "even I couldn't understand that" time parser.
Designed to take in strings and give back sensible dates and times.
**Functionality**:
dtparse has its foundations in the [`dateutil`](dateutil) library for
Python, which excels at taking "interesting" strings and trying to make
sense of the dates and times they contain. A couple of quick examples
from the test cases should give some context:
1. ~~We don't support weekday parsing. In the Python side this is accomplished via `dateutil.relativedelta`~~
Supported in v0.8
```rust
extern crate chrono;
extern crate dtparse;
use chrono::prelude::*;
use dtparse::parse;
2. Named timezones aren't supported very well. [chrono_tz](https://github.com/chronotope/chrono-tz)
theoretically would provide support, but I'd also like some helper things available (e.g. "EST" is not a named zone in `chrono-tz`).
Explicit time zones (i.e. "00:00:00 -0300") are working as expected.
assert_eq!(
parse("2008.12.30"),
Ok((NaiveDate::from_ymd(2008, 12, 30).and_hms(0, 0, 0), None))
);
3. ~~"Fuzzy" and "Fuzzy with tokens" modes haven't been tested. The code should work, but I need to get the
test cases added to the auto-generation suite~~
// It can even handle timezones!
assert_eq!(
parse("January 4, 2024; 18:30:04 +02:00"),
Ok((
NaiveDate::from_ymd(2024, 1, 4).and_hms(18, 30, 4),
Some(FixedOffset::east(7200))
))
);
```
**Non-functional**: This library is intended to be a direct port from Python, and thus the code
looks a lot more like Python than it does Rust. There are a ton of `TODO` comments in the code
that need cleaned up, things that could be converted to enums, etc.
And we can even handle fuzzy strings where dates/times aren't the
only content if we dig into the implementation a bit!
In addition, some more documentation would be incredibly helpful. It's, uh, sparse at the moment.
```rust
extern crate chrono;
extern crate dtparse;
use chrono::prelude::*;
use dtparse::Parser;
use std::collections::HashMap;
let mut p = Parser::default();
assert_eq!(
p.parse(
"I first released this library on the 17th of June, 2018.",
None, None,
true /* turns on fuzzy mode */,
true /* gives us the tokens that weren't recognized */,
None, false, &HashMap::new()
),
Ok((
NaiveDate::from_ymd(2018, 6, 17).and_hms(0, 0, 0),
None,
Some(vec!["I first released this library on the ",
" of ", ", "].iter().map(|&s| s.into()).collect())
))
);
```
Further examples can be found in the [examples](examples) directory on international usage.
# Usage
`dtparse` requires a minimum Rust version of 1.21 to build, but is tested on Windows, OSX,
BSD, Linux, and WASM. The build is also compiled against the iOS and Android SDK's, but is not
tested against them.
[dateutil]: https://github.com/dateutil/dateutil
[examples]: https://github.com/bspeice/dtparse/tree/master/examples

121
appveyor.yml Normal file
View File

@ -0,0 +1,121 @@
# 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%

View File

@ -1,4 +1,5 @@
from dateutil.parser import parse
from dateutil.tz import tzutc
from datetime import datetime
tests = {
@ -48,7 +49,9 @@ tests = {
'test_parse_offset': [
'Thu, 25 Sep 2003 10:49:41 -0300', '2003-09-25T10:49:41.5-03:00',
'2003-09-25T10:49:41-03:00', '20030925T104941.5-0300',
'20030925T104941-0300'
'20030925T104941-0300',
# dtparse-specific
"2018-08-10 10:00:00 UTC+3", "2018-08-10 03:36:47 PM GMT-4", "2018-08-10 04:15:00 AM Z-02:00"
],
'test_parse_dayfirst': [
'10-09-2003', '10.09.2003', '10/09/2003', '10 09 2003',
@ -189,6 +192,10 @@ def test_fuzzy_simple(i, s):
# Here lies all the ugly junk.
TEST_HEADER = '''
//! This code has been generated by running the `build_pycompat.py` script
//! in the repository root. Please do not edit it, as your edits will be destroyed
//! upon re-running code generation.
extern crate chrono;
use chrono::Datelike;
@ -222,7 +229,7 @@ fn parse_and_assert(
fuzzy_with_tokens: bool,
default: Option<&NaiveDateTime>,
ignoretz: bool,
tzinfos: HashMap<String, i32>,
tzinfos: &HashMap<String, i32>,
) {
let mut parser = Parser::new(info);
@ -272,7 +279,7 @@ fn parse_fuzzy_and_assert(
fuzzy_with_tokens: bool,
default: Option<&NaiveDateTime>,
ignoretz: bool,
tzinfos: HashMap<String, i32>,
tzinfos: &HashMap<String, i32>,
) {
let mut parser = Parser::new(info);
@ -316,7 +323,7 @@ fn test_parse_default{i}() {{
micros: {d.microsecond}, tzo: None
}};
parse_and_assert(pdt, info, "{s}", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}}\n'''
TEST_PARSE_SIMPLE = '''
@ -340,7 +347,7 @@ fn test_parse_tzinfo{i}() {{
micros: {d.microsecond}, tzo: Some({offset}),
}};
parse_and_assert(pdt, info, "{s}", None, None, false, false,
None, false, rs_tzinfo_map!());
None, false, &rs_tzinfo_map!());
}}\n'''
TEST_PARSE_OFFSET = '''
@ -353,7 +360,7 @@ fn test_parse_offset{i}() {{
micros: {d.microsecond}, tzo: Some({offset}),
}};
parse_and_assert(pdt, info, "{s}", None, None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}}\n'''
TEST_PARSE_DAYFIRST = '''
@ -366,7 +373,7 @@ fn test_parse_dayfirst{i}() {{
micros: {d.microsecond}, tzo: None,
}};
parse_and_assert(pdt, info, "{s}", Some(true), None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}}\n'''
TEST_PARSE_YEARFIRST = '''
@ -379,7 +386,7 @@ fn test_parse_yearfirst{i}() {{
micros: {d.microsecond}, tzo: None,
}};
parse_and_assert(pdt, info, "{s}", None, Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}}\n'''
TEST_PARSE_DFYF = '''
@ -392,7 +399,7 @@ fn test_parse_dfyf{i}() {{
micros: {d.microsecond}, tzo: None,
}};
parse_and_assert(pdt, info, "{s}", Some(true), Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}}\n'''
TEST_UNSPECIFIED_FALLBACK = '''
@ -406,7 +413,7 @@ fn test_unspecified_fallback{i}() {{
micros: {d.microsecond}, tzo: None
}};
parse_and_assert(pdt, info, "{s}", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}}\n'''
TEST_PARSE_IGNORETZ = '''
@ -419,7 +426,7 @@ fn test_parse_ignoretz{i}() {{
micros: {d.microsecond}, tzo: None
}};
parse_and_assert(pdt, info, "{s}", None, None, false, false,
None, true, HashMap::new());
None, true, &HashMap::new());
}}\n'''
TEST_PARSE_DEFAULT_IGNORE = '''
@ -434,7 +441,7 @@ fn test_parse_default_ignore{i}() {{
micros: {d.microsecond}, tzo: None
}};
parse_and_assert(pdt, info, "{s}", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}}\n'''
TEST_FUZZY_TZINFO = '''
@ -447,7 +454,7 @@ fn test_fuzzy_tzinfo{i}() {{
micros: {d.microsecond}, tzo: Some({offset})
}};
parse_fuzzy_and_assert(pdt, None, info, "{s}", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}}\n'''
TEST_FUZZY_TOKENS_TZINFO = '''
@ -461,7 +468,7 @@ fn test_fuzzy_tokens_tzinfo{i}() {{
}};
let tokens = vec![{tokens}];
parse_fuzzy_and_assert(pdt, Some(tokens), info, "{s}", None, None, true, true,
None, false, HashMap::new());
None, false, &HashMap::new());
}}\n'''
TEST_FUZZY_SIMPLE = '''
@ -474,7 +481,7 @@ fn test_fuzzy_simple{i}() {{
micros: {d.microsecond}, tzo: None
}};
parse_fuzzy_and_assert(pdt, None, info, "{s}", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}}\n'''

View File

@ -24,6 +24,10 @@ fn test_tokenize{i}() {{
TEST_HEADER = '''
//! This code has been generated by running the `build_pycompat_tokenizer.py` script
//! in the repository root. Please do not edit it, as your edits will be destroyed
//! upon re-running code generation.
use tokenize::Tokenizer;
fn tokenize_assert(test_str: &str, comparison: Vec<&str>) {

47
ci/install.sh Executable file
View File

@ -0,0 +1,47 @@
set -ex
main() {
local target=
if [ $TRAVIS_OS_NAME = linux ]; then
target=x86_64-unknown-linux-musl
sort=sort
else
target=x86_64-apple-darwin
sort=gsort # for `sort --sort-version`, from brew's coreutils.
fi
# Builds for iOS are done on OSX, but require the specific target to be
# installed.
case $TARGET in
aarch64-apple-ios)
rustup target install aarch64-apple-ios
;;
armv7-apple-ios)
rustup target install armv7-apple-ios
;;
armv7s-apple-ios)
rustup target install armv7s-apple-ios
;;
i386-apple-ios)
rustup target install i386-apple-ios
;;
x86_64-apple-ios)
rustup target install x86_64-apple-ios
;;
esac
# This fetches latest stable release
local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
| cut -d/ -f3 \
| grep -E '^v[0.1.0-9.]+$' \
| $sort --version-sort \
| tail -n1)
curl -LSfs https://japaric.github.io/trust/install.sh | \
sh -s -- \
--force \
--git japaric/cross \
--tag $tag \
--target $target
}
main

40
ci/script.sh Normal file
View File

@ -0,0 +1,40 @@
# This script takes care of testing your crate
set -ex
main() {
cross build --target $TARGET
cross build --target $TARGET --release
if [ ! -z $DISABLE_TESTS ]; then
return
fi
cross test --target $TARGET
cross test --target $TARGET --release
}
main_web() {
CARGO_WEB_RELEASE="$(curl -L -s -H 'Accept: application/json' https://github.com/koute/cargo-web/releases/latest)"
CARGO_WEB_VERSION="$(echo $CARGO_WEB_RELEASE | sed -e 's/.*"tag_name":"\([^"]*\)".*/\1/')"
CARGO_WEB_URL="https://github.com/koute/cargo-web/releases/download/$CARGO_WEB_VERSION/cargo-web-x86_64-unknown-linux-gnu.gz"
echo "Downloading cargo-web from: $CARGO_WEB_URL"
curl -L "$CARGO_WEB_URL" | gzip -d > cargo-web
chmod +x cargo-web
mkdir -p ~/.cargo/bin
mv cargo-web ~/.cargo/bin
cargo web build --target $TARGET
cargo web test --target $TARGET --release
}
# we don't run the "test phase" when doing deploys
if [ -z $TRAVIS_TAG ]; then
if [ -z "$USE_CARGO_WEB" ]; then
main
else
main_web
fi
fi

39
examples/russian.rs Normal file
View File

@ -0,0 +1,39 @@
extern crate chrono;
extern crate dtparse;
use chrono::NaiveDate;
use dtparse::parse_info;
use dtparse::Parser;
use dtparse::ParserInfo;
use std::collections::HashMap;
fn main() {
// In this example, we'll just swap the default "months" parameter
// with a version in Russian. Lovingly taken from:
// https://github.com/dateutil/dateutil/blob/99f5770e7c63aa049b28abe465d7f1cc25b63fd2/dateutil/test/test_parser.py#L244
let mut info = ParserInfo::default();
info.months = parse_info(vec![
vec!["янв", "Январь"],
vec!["фев", "Февраль"],
vec!["мар", "Март"],
vec!["апр", "Апрель"],
vec!["май", "Май"],
vec!["июн", "Июнь"],
vec!["июл", "Июль"],
vec!["авг", "Август"],
vec!["сен", "Сентябрь"],
vec!["окт", "Октябрь"],
vec!["ноя", "Ноябрь"],
vec!["дек", "Декабрь"]
]);
let mut p = Parser::new(info);
assert_eq!(
p.parse("10 Сентябрь 2015 10:20", None, None, false, false, None, false, &HashMap::new())
.unwrap().0,
NaiveDate::from_ymd(2015, 9, 10).and_hms(10, 20, 0)
);
}

5
fuzz/.gitignore vendored
View File

@ -1,5 +0,0 @@
target
libfuzzer
corpus
artifacts

View File

@ -1,22 +0,0 @@
[package]
name = "dtparse-fuzz"
version = "0.0.1"
authors = ["Automatically generated"]
publish = false
[package.metadata]
cargo-fuzz = true
[dependencies.dtparse]
path = ".."
[dependencies.libfuzzer-sys]
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "fuzzer_script_1"
path = "fuzzers/fuzzer_script_1.rs"

View File

@ -1,10 +0,0 @@
#![no_main]
extern crate libfuzzer_sys;
extern crate dtparse;
use dtparse::parse;
#[export_name="rust_fuzzer_test_input"]
pub extern fn go(data: &[u8]) {
if let Ok(s) = std::str::from_utf8(data) {
parse(s);
}
}

View File

@ -1,3 +1,74 @@
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
//! # dtparse
//! The fully-featured "even I couldn't understand that" time parser.
//! Designed to take in strings and give back sensible dates and times.
//!
//! dtparse has its foundations in the [`dateutil`](dateutil) library for
//! Python, which excels at taking "interesting" strings and trying to make
//! sense of the dates and times they contain. A couple of quick examples
//! from the test cases should give some context:
//!
//! ```rust,ignore (tests-dont-compile-on-old-rust)
//! # extern crate chrono;
//! # extern crate dtparse;
//! use chrono::prelude::*;
//! use dtparse::parse;
//!
//! assert_eq!(
//! parse("2008.12.30"),
//! Ok((NaiveDate::from_ymd(2008, 12, 30).and_hms(0, 0, 0), None))
//! );
//!
//! // It can even handle timezones!
//! assert_eq!(
//! parse("January 4, 2024; 18:30:04 +02:00"),
//! Ok((
//! NaiveDate::from_ymd(2024, 1, 4).and_hms(18, 30, 4),
//! Some(FixedOffset::east(7200))
//! ))
//! );
//! ```
//!
//! And we can even handle fuzzy strings where dates/times aren't the
//! only content if we dig into the implementation a bit!
//!
//! ```rust,ignore (tests-dont-compile-on-old-rust)
//! # extern crate chrono;
//! # extern crate dtparse;
//! use chrono::prelude::*;
//! use dtparse::Parser;
//! # use std::collections::HashMap;
//!
//! let mut p = Parser::default();
//! assert_eq!(
//! p.parse(
//! "I first released this library on the 17th of June, 2018.",
//! None, None,
//! true /* turns on fuzzy mode */,
//! true /* gives us the tokens that weren't recognized */,
//! None, false, &HashMap::new()
//! ),
//! Ok((
//! NaiveDate::from_ymd(2018, 6, 17).and_hms(0, 0, 0),
//! None,
//! Some(vec!["I first released this library on the ",
//! " of ", ", "].iter().map(|&s| s.into()).collect())
//! ))
//! );
//! ```
//!
//! Further examples can be found in the `examples` directory on international usage.
//!
//! # Usage
//!
//! `dtparse` requires a minimum Rust version of 1.21 to build, but is tested on Windows, OSX,
//! BSD, Linux, and WASM. The build is also compiled against the iOS and Android SDK's, but is not
//! tested against them.
//!
//! [dateutil]: https://github.com/dateutil/dateutil
#[macro_use]
extern crate lazy_static;
@ -39,58 +110,51 @@ lazy_static! {
static ref SIXTY: Decimal = Decimal::new(60, 0);
}
#[derive(Debug, PartialEq)]
pub enum ParseInternalError {
// Errors that indicate internal bugs
YMDEarlyResolve,
YMDValueUnset(Vec<YMDLabel>),
ParseIndexError,
InvalidDecimal,
InvalidInteger,
// Python-style errors
ValueError(String),
}
impl From<DecimalError> for ParseInternalError {
fn from(_err: DecimalError) -> Self {
ParseInternalError::InvalidDecimal
impl From<DecimalError> for ParseError {
fn from(err: DecimalError) -> Self {
ParseError::InvalidNumeric(format!("{}", err))
}
}
impl From<ParseIntError> for ParseInternalError {
fn from(_err: ParseIntError) -> Self {
ParseInternalError::InvalidInteger
impl From<ParseIntError> for ParseError {
fn from(err: ParseIntError) -> Self {
ParseError::InvalidNumeric(format!("{}", err))
}
}
/// Potential errors that come up when trying to parse time strings
#[derive(Debug, PartialEq)]
pub enum ParseError {
AmbiguousWeekday,
InternalError(ParseInternalError),
InvalidMonth,
UnrecognizedToken(String),
InvalidParseResult(ParsingResult),
/// Attempted to specify "AM" or "PM" without indicating an hour
AmPmWithoutHour,
TimezoneUnsupported,
/// Impossible value for a category; the 32nd day of a month is impossible
ImpossibleTimestamp(&'static str),
}
impl From<ParseInternalError> for ParseError {
fn from(err: ParseInternalError) -> Self {
ParseError::InternalError(err)
}
/// Unable to parse a numeric value from a token expected to be numeric
InvalidNumeric(String),
/// Generally unrecognized date string; please report to maintainer so
/// new test cases can be developed
UnrecognizedFormat,
/// A token the parser did not recognize was in the string, and fuzzy mode was off
UnrecognizedToken(String),
/// A timezone could not be handled; please report to maintainer as the timestring
/// likely exposes a bug in the implementation
TimezoneUnsupported,
/// 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),
}
type ParseResult<I> = Result<I, ParseError>;
type ParseIResult<I> = Result<I, ParseInternalError>;
pub fn tokenize(parse_string: &str) -> Vec<String> {
pub(crate) fn tokenize(parse_string: &str) -> Vec<String> {
let tokenizer = Tokenizer::new(parse_string);
tokenizer.collect()
}
fn parse_info(vec: Vec<Vec<&str>>) -> HashMap<String, usize> {
/// 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 {
@ -99,7 +163,7 @@ fn parse_info(vec: Vec<Vec<&str>>) -> HashMap<String, usize> {
}
} else {
for (i, val_vec) in vec.into_iter().enumerate() {
for val in val_vec.into_iter() {
for val in val_vec {
m.insert(val.to_lowercase(), i);
}
}
@ -108,23 +172,56 @@ fn parse_info(vec: Vec<Vec<&str>>) -> HashMap<String, usize> {
m
}
/// Container for specific tokens to be recognized during parsing.
///
/// - `jump`: Values that indicate the end of a token for parsing and can be ignored
/// - `weekday`: Names of the days of the week
/// - `months`: Names of the months
/// - `hms`: Names for the units of time - hours, minutes, seconds in English
/// - `ampm`: AM and PM tokens
/// - `utczone`: Tokens indicating a UTC-timezone string
/// - `pertain`: Tokens indicating a "belongs to" relationship; in English this is just "of"
/// - `tzoffset`:
/// - `dayfirst`: Upon encountering an ambiguous date, treat the first value as the day
/// - `yearfirst`: Upon encountering an ambiguous date, treat the first value as the year
/// - `year`: The current year
/// - `century`: The first year in the current century
///
/// Please note that if both `dayfirst` and `yearfirst` are true, years take precedence
/// and will be parsed as "YDM"
#[derive(Debug, PartialEq)]
pub struct ParserInfo {
jump: HashMap<String, usize>,
weekday: HashMap<String, usize>,
months: HashMap<String, usize>,
hms: HashMap<String, usize>,
ampm: HashMap<String, usize>,
utczone: HashMap<String, usize>,
pertain: HashMap<String, usize>,
tzoffset: HashMap<String, usize>,
dayfirst: bool,
yearfirst: bool,
year: i32,
century: i32,
/// Tokens that can be safely ignored
pub jump: HashMap<String, usize>,
/// Names of all seven weekdays
pub weekday: HashMap<String, usize>,
/// Names of all twelve months
pub months: HashMap<String, usize>,
/// Tokens to indicate a value is in units of hours, minutes, or seconds
pub hms: HashMap<String, usize>,
/// Tokens to indicate a value refers to AM or PM time
pub ampm: HashMap<String, usize>,
/// Tokens to indicate our timestamp is in the UTC timezone
pub utczone: HashMap<String, usize>,
/// Tokens to indicate values "belonging" to other tokens (e.g. 3rd *of* March)
pub pertain: HashMap<String, usize>,
/// Map of timezone names to their offset in seconds
pub tzoffset: HashMap<String, usize>,
/// For ambiguous year/month/day values, and `dayfirst` was not specified as
/// an argument to `Parser`, treat the first observed value as the day.
pub dayfirst: bool,
/// For ambiguous year/month/day values, and `dayfirst` was not specified as
/// an argument to `Parser`, treat the first observed value as the day.
/// Takes priority over `dayfirst`
pub yearfirst: bool,
/// The current year we are parsing values for
pub year: i32,
/// The current year we are parsing values for *modulo* 100
pub century: i32,
}
impl Default for ParserInfo {
/// Create a basic `ParserInfo` object suitable for parsing dates in English
fn default() -> Self {
let year = Local::now().year();
let century = year / 100 * 100;
@ -170,30 +267,30 @@ impl Default for ParserInfo {
tzoffset: parse_info(vec![vec![]]),
dayfirst: false,
yearfirst: false,
year: year,
century: century,
year,
century,
}
}
}
impl ParserInfo {
fn get_jump(&self, name: &str) -> bool {
fn jump_index(&self, name: &str) -> bool {
self.jump.contains_key(&name.to_lowercase())
}
fn get_weekday(&self, name: &str) -> Option<usize> {
self.weekday.get(&name.to_lowercase()).map(|i| *i)
fn weekday_index(&self, name: &str) -> Option<usize> {
self.weekday.get(&name.to_lowercase()).cloned()
}
fn get_month(&self, name: &str) -> Option<usize> {
fn month_index(&self, name: &str) -> Option<usize> {
self.months.get(&name.to_lowercase()).map(|u| u + 1)
}
fn get_hms(&self, name: &str) -> Option<usize> {
self.hms.get(&name.to_lowercase()).map(|i| *i)
fn hms_index(&self, name: &str) -> Option<usize> {
self.hms.get(&name.to_lowercase()).cloned()
}
fn get_ampm(&self, name: &str) -> Option<bool> {
fn ampm_index(&self, name: &str) -> Option<bool> {
if let Some(v) = self.ampm.get(&name.to_lowercase()) {
// Python technically uses numbers here, but given that the numbers are
// only 0 and 1, it's easier to use booleans
@ -203,15 +300,15 @@ impl ParserInfo {
}
}
fn get_pertain(&self, name: &str) -> bool {
fn pertain_index(&self, name: &str) -> bool {
self.pertain.contains_key(&name.to_lowercase())
}
fn get_utczone(&self, name: &str) -> bool {
fn utczone_index(&self, name: &str) -> bool {
self.utczone.contains_key(&name.to_lowercase())
}
fn get_tzoffset(&self, name: &str) -> Option<usize> {
fn tzoffset_index(&self, name: &str) -> Option<usize> {
if self.utczone.contains_key(&name.to_lowercase()) {
Some(0)
} else {
@ -244,7 +341,7 @@ impl ParserInfo {
res.tzname = Some("UTC".to_owned());
res.tzoffset = Some(0);
} else if res.tzoffset != Some(0) && res.tzname.is_some()
&& self.get_utczone(res.tzname.as_ref().unwrap())
&& self.utczone_index(res.tzname.as_ref().unwrap())
{
res.tzoffset = Some(0);
}
@ -268,13 +365,13 @@ fn days_in_month(year: i32, month: i32) -> Result<u32, ParseError> {
1 | 3 | 5 | 7 | 8 | 10 | 12 => Ok(31),
4 | 6 | 9 | 11 => Ok(30),
_ => {
Err(ParseError::InvalidMonth)
Err(ParseError::ImpossibleTimestamp("Invalid month"))
}
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum YMDLabel {
enum YMDLabel {
Year,
Month,
Day,
@ -311,7 +408,7 @@ impl YMD {
}
}
fn append(&mut self, val: i32, token: &str, label: Option<YMDLabel>) -> ParseIResult<()> {
fn append(&mut self, val: i32, token: &str, label: Option<YMDLabel>) -> ParseResult<()> {
let mut label = label;
// Python auto-detects strings using the '__len__' function here.
@ -320,12 +417,11 @@ impl YMD {
self.century_specified = true;
match label {
None | Some(YMDLabel::Year) => label = Some(YMDLabel::Year),
_ => {
return Err(ParseInternalError::ValueError(format!(
"Invalid label {:?} for token {:?}",
label,
token
)))
Some(YMDLabel::Month) => {
return Err(ParseError::ImpossibleTimestamp("Invalid month"))
}
Some(YMDLabel::Day) => {
return Err(ParseError::ImpossibleTimestamp("Invalid day"))
}
}
}
@ -335,12 +431,11 @@ impl YMD {
match label {
None => label = Some(YMDLabel::Year),
Some(YMDLabel::Year) => (),
_ => {
return Err(ParseInternalError::ValueError(format!(
"Invalid label {:?} for token {:?}",
label,
token
)))
Some(YMDLabel::Month) => {
return Err(ParseError::ImpossibleTimestamp("Invalid month"))
}
Some(YMDLabel::Day) => {
return Err(ParseError::ImpossibleTimestamp("Invalid day"))
}
}
}
@ -350,9 +445,7 @@ impl YMD {
match label {
Some(YMDLabel::Month) => {
if self.mstridx.is_some() {
Err(ParseInternalError::ValueError(
"Month already set.".to_owned(),
))
Err(ParseError::YearMonthDayError("Month already set"))
} else {
self.mstridx = Some(self._ymd.len() - 1);
Ok(())
@ -360,9 +453,7 @@ impl YMD {
}
Some(YMDLabel::Day) => {
if self.dstridx.is_some() {
Err(ParseInternalError::ValueError(
"Day already set.".to_owned(),
))
Err(ParseError::YearMonthDayError("Day already set"))
} else {
self.dstridx = Some(self._ymd.len() - 1);
Ok(())
@ -370,9 +461,7 @@ impl YMD {
}
Some(YMDLabel::Year) => {
if self.ystridx.is_some() {
Err(ParseInternalError::ValueError(
"Year already set.".to_owned(),
))
Err(ParseError::YearMonthDayError("Year already set"))
} else {
self.ystridx = Some(self._ymd.len() - 1);
Ok(())
@ -385,7 +474,7 @@ impl YMD {
fn resolve_from_stridxs(
&mut self,
strids: &mut HashMap<YMDLabel, usize>,
) -> ParseIResult<(Option<i32>, Option<i32>, Option<i32>)> {
) -> ParseResult<(Option<i32>, Option<i32>, Option<i32>)> {
if self._ymd.len() == 3 && strids.len() == 2 {
let missing_key = if !strids.contains_key(&YMDLabel::Year) {
YMDLabel::Year
@ -395,7 +484,7 @@ impl YMD {
YMDLabel::Day
};
let strids_vals: Vec<usize> = strids.values().map(|u| u.clone()).collect();
let strids_vals: Vec<usize> = strids.values().cloned().collect();
let missing_val = if !strids_vals.contains(&0) {
0
} else if !strids_vals.contains(&1) {
@ -408,7 +497,7 @@ impl YMD {
}
if self._ymd.len() != strids.len() {
return Err(ParseInternalError::YMDEarlyResolve);
return Err(ParseError::YearMonthDayError("Tried to resolve year, month, and day without enough information"));
}
Ok((
@ -424,32 +513,32 @@ impl YMD {
))
}
#[cfg_attr(feature = "cargo-clippy", allow(needless_return))]
fn resolve_ymd(
&mut self,
yearfirst: bool,
dayfirst: bool,
) -> ParseIResult<(Option<i32>, Option<i32>, Option<i32>)> {
) -> ParseResult<(Option<i32>, Option<i32>, Option<i32>)> {
let len_ymd = self._ymd.len();
let mut strids: HashMap<YMDLabel, usize> = HashMap::new();
self.ystridx
.map(|u| strids.insert(YMDLabel::Year, u.clone()));
.map(|u| strids.insert(YMDLabel::Year, u));
self.mstridx
.map(|u| strids.insert(YMDLabel::Month, u.clone()));
.map(|u| strids.insert(YMDLabel::Month, u));
self.dstridx
.map(|u| strids.insert(YMDLabel::Day, u.clone()));
.map(|u| strids.insert(YMDLabel::Day, u));
// TODO: More Rustiomatic way of doing this?
if len_ymd == strids.len() && strids.len() > 0
if len_ymd == strids.len() && !strids.is_empty()
|| (len_ymd == 3 && strids.len() == 2)
{
return self.resolve_from_stridxs(&mut strids);
};
// Received year, month, day, and ???
if len_ymd > 3 {
return Err(ParseInternalError::ValueError(
"More than three YMD values".to_owned(),
));
return Err(ParseError::YearMonthDayError("Received extra tokens in resolving year, month, and day"));
}
match (len_ymd, self.mstridx) {
@ -515,7 +604,7 @@ impl YMD {
}
#[derive(Default, Debug, PartialEq)]
pub struct ParsingResult {
struct ParsingResult {
year: Option<i32>,
month: Option<i32>,
day: Option<i32>,
@ -531,16 +620,55 @@ pub struct ParsingResult {
any_unused_tokens: Vec<String>,
}
/// 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.
#[derive(Default)]
pub struct Parser {
info: ParserInfo,
}
impl Parser {
/// Create a new `Parser` instance using the provided `ParserInfo`.
///
/// This method allows you to set up a parser to handle different
/// names for days of the week, months, etc., enabling customization
/// for different languages or extra values.
pub fn new(info: ParserInfo) -> Self {
Parser { info }
}
/// Main method to trigger parsing of a string using the previously-provided
/// parser information. Returns a naive timestamp along with timezone and
/// unused tokens if available.
///
/// `dayfirst` and `yearfirst` force parser behavior in the event of ambiguous
/// dates. Consider the following scenarios where we parse the string '01.02.03'
///
/// - `dayfirst=Some(true)`, `yearfirst=None`: Results in `February 2, 2003`
/// - `dayfirst=None`, `yearfirst=Some(true)`: Results in `February 3, 2001`
/// - `dayfirst=Some(true)`, `yearfirst=Some(true)`: Results in `March 2, 2001`
///
/// `fuzzy` enables fuzzy parsing mode, allowing the parser to skip tokens if
/// they are unrecognized. However, the unused tokens will not be returned
/// unless `fuzzy_with_tokens` is set as `true`.
///
/// `default` is the timestamp used to infer missing values, and is midnight
/// of the current day by default. For example, when parsing the text '2003',
/// we will use the current month and day as a default value, leading to a
/// result of 'March 3, 2003' if the function was run using a default of
/// March 3rd.
///
/// `ignoretz` forces the parser to ignore timezone information even if it
/// is recognized in the time string
///
/// `tzinfos` is a map of timezone names to the offset seconds. For example,
/// the parser would ignore the 'EST' part of the string in '10 AM EST'
/// unless you added a `tzinfos` map of `{"EST": "14400"}`. Please note that
/// 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
pub fn parse(
&mut self,
timestr: &str,
@ -550,7 +678,7 @@ impl Parser {
fuzzy_with_tokens: bool,
default: Option<&NaiveDateTime>,
ignoretz: bool,
tzinfos: HashMap<String, i32>,
tzinfos: &HashMap<String, i32>,
) -> ParseResult<(NaiveDateTime, Option<FixedOffset>, Option<Vec<String>>)> {
let default_date = default.unwrap_or(&Local::now().naive_local()).date();
@ -569,6 +697,7 @@ impl Parser {
}
}
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] // Imitating Python API is priority
fn parse_with_tokens(
&mut self,
timestr: &str,
@ -605,9 +734,9 @@ 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]) {
} else if let Some(value) = self.info.weekday_index(&l[i]) {
res.weekday = Some(value);
} else if let Some(value) = self.info.get_month(&l[i]) {
} else if let Some(value) = self.info.month_index(&l[i]) {
ymd.append(value as i32, &l[i], Some(YMDLabel::Month))?;
if i + 1 < len_l {
@ -625,10 +754,10 @@ impl Parser {
i += 2;
} else if i + 4 < len_l && l[i + 1] == l[i + 3] && l[i + 3] == " "
&& self.info.get_pertain(&l[i + 2])
&& self.info.pertain_index(&l[i + 2])
{
// Jan of 01
if let Some(value) = l[i + 4].parse::<i32>().ok() {
if let Ok(value) = l[i + 4].parse::<i32>() {
let year = self.info.convertyear(value, false);
ymd.append(year, &l[i + 4], Some(YMDLabel::Year))?;
}
@ -636,7 +765,7 @@ impl Parser {
i += 4;
}
}
} else if let Some(value) = self.info.get_ampm(&l[i]) {
} else if let Some(value) = self.info.ampm_index(&l[i]) {
let is_ampm = self.ampm_valid(res.hour, res.ampm, fuzzy);
if is_ampm == Ok(true) {
@ -645,11 +774,11 @@ impl Parser {
} else if fuzzy {
skipped_idxs.push(i);
}
} else if self.could_be_tzname(res.hour, res.tzname.clone(), res.tzoffset, &l[i]) {
} else if self.could_be_tzname(res.hour, &res.tzname, res.tzoffset, &l[i]) {
res.tzname = Some(l[i].clone());
let tzname = res.tzname.clone().unwrap();
res.tzoffset = self.info.get_tzoffset(&tzname).map(|t| t as i32);
res.tzoffset = self.info.tzoffset_index(&tzname).map(|t| t as i32);
if i + 1 < len_l && (l[i + 1] == "+" || l[i + 1] == "-") {
// GMT+3
@ -660,14 +789,13 @@ impl Parser {
let item = if l[i + 1] == "+" {
"-".to_owned()
} else {
"-".to_owned()
"+".to_owned()
};
l.remove(i + 1);
l.insert(i + 1, item);
l[i+1] = item;
res.tzoffset = None;
if self.info.get_utczone(&tzname) {
if self.info.utczone_index(&tzname) {
res.tzname = None;
}
}
@ -690,7 +818,8 @@ impl Parser {
i += 2;
} else if len_li <= 2 {
// -[0]3
hour_offset = Some(l[i + 1][..2].parse::<i32>().unwrap());
let range_len = min(l[i + 1].len(), 2);
hour_offset = Some(l[i + 1][..range_len].parse::<i32>().unwrap());
min_offset = Some(0);
}
@ -698,9 +827,9 @@ impl Parser {
Some(signal * (hour_offset.unwrap() * 3600 + min_offset.unwrap() * 60));
let tzname = res.tzname.clone();
if i + 5 < len_l && self.info.get_jump(&l[i + 2]) && l[i + 3] == "("
if i + 5 < len_l && self.info.jump_index(&l[i + 2]) && l[i + 3] == "("
&& l[i + 5] == ")" && 3 <= l[i + 4].len()
&& self.could_be_tzname(res.hour, tzname, None, &l[i + 4])
&& self.could_be_tzname(res.hour, &tzname, None, &l[i + 4])
{
// (GMT)
res.tzname = Some(l[i + 4].clone());
@ -708,7 +837,7 @@ impl Parser {
}
i += 1;
} else if !(self.info.get_jump(&l[i]) || fuzzy) {
} else if !(self.info.jump_index(&l[i]) || fuzzy) {
return Err(ParseError::UnrecognizedToken(l[i].clone()));
} else {
skipped_idxs.push(i);
@ -725,7 +854,7 @@ impl Parser {
res.day = day;
if !self.info.validate(&mut res) {
Err(ParseError::InvalidParseResult(res))
Err(ParseError::UnrecognizedFormat)
} else if fuzzy_with_tokens {
let skipped_tokens = self.recombine_skipped(skipped_idxs, l);
Ok((res, Some(skipped_tokens)))
@ -737,23 +866,20 @@ impl Parser {
fn could_be_tzname(
&self,
hour: Option<i32>,
tzname: Option<String>,
tzname: &Option<String>,
tzoffset: Option<i32>,
token: &str,
) -> bool {
let all_ascii_upper = token
.chars()
.all(|c| 65u8 as char <= c && c <= 90u8 as char);
return hour.is_some() && tzname.is_none() && tzoffset.is_none() && token.len() <= 5
&& all_ascii_upper;
hour.is_some() && tzname.is_none() && tzoffset.is_none() && token.len() <= 5
&& all_ascii_upper
}
fn ampm_valid(&self, hour: Option<i32>, ampm: Option<bool>, fuzzy: bool) -> ParseResult<bool> {
let mut val_is_ampm = true;
if fuzzy && ampm.is_some() {
val_is_ampm = false;
}
let mut val_is_ampm = !(fuzzy && ampm.is_some());
if hour.is_none() {
if fuzzy {
@ -773,8 +899,8 @@ impl Parser {
}
fn build_naive(&self, res: &ParsingResult, default: &NaiveDateTime) -> ParseResult<NaiveDateTime> {
let y = res.year.unwrap_or(default.year());
let m = res.month.unwrap_or(default.month() as i32) as u32;
let y = res.year.unwrap_or_else(|| default.year());
let m = res.month.unwrap_or_else(|| default.month() as i32) as u32;
let d_offset = if res.weekday.is_some() && res.day.is_none() {
// TODO: Unwrap not justified
@ -783,7 +909,7 @@ impl Parser {
// 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)
Duration::days(i64::from(dow.difference(&other)))
} else {
Duration::days(0)
};
@ -823,10 +949,11 @@ impl Parser {
&self,
_dt: &NaiveDateTime,
res: &ParsingResult,
tzinfos: HashMap<String, i32>,
tzinfos: &HashMap<String, i32>,
) -> ParseResult<Option<FixedOffset>> {
// TODO: Actual timezone support
if let Some(offset) = res.tzoffset {
println!("offset={}", offset);
Ok(Some(FixedOffset::east(offset)))
} else if res.tzoffset == None
&& (res.tzname == Some(" ".to_owned()) || res.tzname == Some(".".to_owned())
@ -835,7 +962,7 @@ impl Parser {
Ok(None)
} else if res.tzname.is_some() && tzinfos.contains_key(res.tzname.as_ref().unwrap()) {
Ok(Some(FixedOffset::east(
tzinfos.get(res.tzname.as_ref().unwrap()).unwrap().clone(),
*tzinfos.get(res.tzname.as_ref().unwrap()).unwrap(),
)))
} else if res.tzname.is_some() {
// TODO: Dateutil issues a warning/deprecation notice here. Should we force the issue?
@ -848,13 +975,13 @@ impl Parser {
fn parse_numeric_token(
&self,
tokens: &Vec<String>,
tokens: &[String],
idx: usize,
info: &ParserInfo,
ymd: &mut YMD,
res: &mut ParsingResult,
fuzzy: bool,
) -> Result<usize, ParseInternalError> {
) -> ParseResult<usize> {
let mut idx = idx;
let value_repr = &tokens[idx];
let mut value = Decimal::from_str(&value_repr).unwrap();
@ -866,7 +993,7 @@ impl Parser {
// TODO: Decompose this logic a bit
if ymd.len() == 3 && (len_li == 2 || len_li == 4) && res.hour.is_none()
&& (idx + 1 >= len_l
|| (tokens[idx + 1] != ":" && info.get_hms(&tokens[idx + 1]).is_none()))
|| (tokens[idx + 1] != ":" && info.hms_index(&tokens[idx + 1]).is_none()))
{
// 1990101T32[59]
let s = &tokens[idx];
@ -875,11 +1002,11 @@ impl Parser {
if len_li == 4 {
res.minute = Some(s[2..4].parse::<i32>()?)
}
} else if len_li == 6 || (len_li > 6 && tokens[idx].find(".") == Some(6)) {
} else if len_li == 6 || (len_li > 6 && tokens[idx].find('.') == Some(6)) {
// YYMMDD or HHMMSS[.ss]
let s = &tokens[idx];
if ymd.len() == 0 && tokens[idx].find(".") == None {
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)?;
@ -941,22 +1068,20 @@ impl Parser {
let sep = &tokens[idx + 1];
ymd.append(value_repr.parse::<i32>().unwrap(), &value_repr, None)?;
if idx + 2 < len_l && !info.get_jump(&tokens[idx + 2]) {
if idx + 2 < len_l && !info.jump_index(&tokens[idx + 2]) {
if let Ok(val) = tokens[idx + 2].parse::<i32>() {
ymd.append(val, &tokens[idx + 2], None)?;
} else if let Some(val) = info.get_month(&tokens[idx + 2]) {
} else if let Some(val) = info.month_index(&tokens[idx + 2]) {
ymd.append(val as i32, &tokens[idx + 2], Some(YMDLabel::Month))?;
}
if idx + 3 < len_l && &tokens[idx + 3] == sep {
if let Some(value) = info.get_month(&tokens[idx + 4]) {
if let Some(value) = info.month_index(&tokens[idx + 4]) {
ymd.append(value as i32, &tokens[idx + 4], Some(YMDLabel::Month))?;
} else {
if let Ok(val) = tokens[idx + 4].parse::<i32>() {
} else if let Ok(val) = tokens[idx + 4].parse::<i32>() {
ymd.append(val, &tokens[idx + 4], None)?;
} else {
return Err(ParseInternalError::ValueError("Unknown string format".to_owned()));
}
return Err(ParseError::UnrecognizedFormat);
}
idx += 2;
@ -966,10 +1091,10 @@ impl Parser {
}
idx += 1
} else if idx + 1 >= len_l || info.get_jump(&tokens[idx + 1]) {
if idx + 2 < len_l && info.get_ampm(&tokens[idx + 2]).is_some() {
} else if idx + 1 >= len_l || info.jump_index(&tokens[idx + 1]) {
if idx + 2 < len_l && info.ampm_index(&tokens[idx + 2]).is_some() {
let hour = value.to_i64().unwrap() as i32;
let ampm = info.get_ampm(&tokens[idx + 2]).unwrap();
let ampm = info.ampm_index(&tokens[idx + 2]).unwrap();
res.hour = Some(self.adjust_ampm(hour, ampm));
idx += 1;
} else {
@ -977,17 +1102,17 @@ impl Parser {
}
idx += 1;
} else if info.get_ampm(&tokens[idx + 1]).is_some()
} else if info.ampm_index(&tokens[idx + 1]).is_some()
&& (*ZERO <= value && value < *TWENTY_FOUR)
{
// 12am
let hour = value.to_i64().unwrap() as i32;
res.hour = Some(self.adjust_ampm(hour, info.get_ampm(&tokens[idx + 1]).unwrap()));
res.hour = Some(self.adjust_ampm(hour, info.ampm_index(&tokens[idx + 1]).unwrap()));
idx += 1;
} else if ymd.could_be_day(value.to_i64().unwrap() as i32) {
ymd.append(value.to_i64().unwrap() as i32, &value_repr, None)?;
} else if !fuzzy {
return Err(ParseInternalError::ValueError("".to_owned()));
return Err(ParseError::UnrecognizedFormat);
}
Ok(idx)
@ -1003,9 +1128,9 @@ impl Parser {
}
}
fn parsems(&self, seconds_str: &str) -> Result<(i32, i32), ParseInternalError> {
if seconds_str.contains(".") {
let split: Vec<&str> = seconds_str.split(".").collect();
fn parsems(&self, seconds_str: &str) -> ParseResult<(i32, i32)> {
if seconds_str.contains('.') {
let split: Vec<&str> = seconds_str.split('.').collect();
let (i, f): (&str, &str) = (split[0], split[1]);
let i_parse = i.parse::<i32>()?;
@ -1019,7 +1144,7 @@ impl Parser {
fn find_hms_index(
&self,
idx: usize,
tokens: &Vec<String>,
tokens: &[String],
info: &ParserInfo,
allow_jump: bool,
) -> Option<usize> {
@ -1041,16 +1166,16 @@ impl Parser {
0
};
if idx + 1 < len_l && info.get_hms(&tokens[idx + 1]).is_some() {
if idx + 1 < len_l && info.hms_index(&tokens[idx + 1]).is_some() {
hms_idx = Some(idx + 1)
} else if allow_jump && idx + 2 < len_l && tokens[idx + 1] == " "
&& info.get_hms(&tokens[idx + 2]).is_some()
&& info.hms_index(&tokens[idx + 2]).is_some()
{
hms_idx = Some(idx + 2)
} else if idx > 0 && info.get_hms(&tokens[idx - 1]).is_some() {
} else if idx > 0 && info.hms_index(&tokens[idx - 1]).is_some() {
hms_idx = Some(idx - 1)
} else if len_l > 0 && idx > 0 && idx == len_l - 1 && tokens[idx - 1] == " "
&& info.get_hms(&tokens[idx_minus_two]).is_some()
&& info.hms_index(&tokens[idx_minus_two]).is_some()
{
hms_idx = Some(idx - 2)
}
@ -1061,7 +1186,7 @@ impl Parser {
fn parse_hms(
&self,
idx: usize,
tokens: &Vec<String>,
tokens: &[String],
info: &ParserInfo,
hms_index: Option<usize>,
) -> (usize, Option<usize>) {
@ -1070,12 +1195,12 @@ impl Parser {
} else if hms_index.unwrap() > idx {
(
hms_index.unwrap(),
info.get_hms(&tokens[hms_index.unwrap()]),
info.hms_index(&tokens[hms_index.unwrap()]),
)
} else {
(
idx,
info.get_hms(&tokens[hms_index.unwrap()]).map(|u| u + 1),
info.hms_index(&tokens[hms_index.unwrap()]).map(|u| u + 1),
)
}
}
@ -1117,6 +1242,7 @@ 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![];
@ -1127,10 +1253,10 @@ impl Parser {
if i > 0 && idx - 1 == skipped_idxs[i - 1] {
// UNWRAP: Having an initial value and unconditional push at end guarantees value
let mut t = skipped_tokens.pop().unwrap();
t.push_str(tokens[idx.clone()].as_ref());
t.push_str(tokens[*idx].as_ref());
skipped_tokens.push(t);
} else {
skipped_tokens.push(tokens[idx.clone()].to_owned());
skipped_tokens.push(tokens[*idx].to_owned());
}
}
@ -1150,6 +1276,12 @@ fn ljust(s: &str, chars: usize, replace: char) -> String {
}
}
/// Main entry point for using `dtparse`. The parse function is responsible for
/// taking in a string representing some time value, and turning it into
/// a timestamp with optional timezone information if it can be identified.
///
/// The default implementation assumes English values for names of months,
/// days of the week, etc. It is equivalent to Python's `dateutil.parser.parse()`
pub fn parse(timestr: &str) -> ParseResult<(NaiveDateTime, Option<FixedOffset>)> {
let res = Parser::default().parse(
timestr,
@ -1159,7 +1291,7 @@ pub fn parse(timestr: &str) -> ParseResult<(NaiveDateTime, Option<FixedOffset>)>
false,
None,
false,
HashMap::new(),
&HashMap::new(),
)?;
Ok((res.0, res.1))

View File

@ -3,21 +3,21 @@ use std::collections::HashMap;
use parse;
use ParseError;
use ParseInternalError;
use Parser;
#[test]
fn test_fuzz() {
assert_eq!(parse("\x2D\x38\x31\x39\x34\x38\x34"), Err(ParseError::InvalidMonth));
assert_eq!(parse("\x2D\x38\x31\x39\x34\x38\x34"), Err(ParseError::ImpossibleTimestamp("Invalid month")));
// Garbage in the third delimited field
assert_eq!(parse("2..\x00\x000d\x00+\x010d\x01\x00\x00\x00+"),
Err(ParseError::InternalError(ParseInternalError::ValueError("Unknown string format".to_owned()))));
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 mut p = Parser::default();
let res = p.parse("\x0D\x31", None, None, false, false, Some(&default), false, HashMap::new()).unwrap();
let res = p.parse("\x0D\x31", None, None, false, false, Some(&default), false, &HashMap::new()).unwrap();
assert_eq!(res.0, default);
assert_eq!(parse("\x2D\x2D\x32\x31\x38\x6D"), Err(ParseError::ImpossibleTimestamp("Invalid minute")));

View File

@ -1,4 +1,8 @@
//! This code has been generated by running the `build_pycompat.py` script
//! in the repository root. Please do not edit it, as your edits will be destroyed
//! upon re-running code generation.
extern crate chrono;
use chrono::Datelike;
@ -32,7 +36,7 @@ fn parse_and_assert(
fuzzy_with_tokens: bool,
default: Option<&NaiveDateTime>,
ignoretz: bool,
tzinfos: HashMap<String, i32>,
tzinfos: &HashMap<String, i32>,
) {
let mut parser = Parser::new(info);
@ -82,7 +86,7 @@ fn parse_fuzzy_and_assert(
fuzzy_with_tokens: bool,
default: Option<&NaiveDateTime>,
ignoretz: bool,
tzinfos: HashMap<String, i32>,
tzinfos: &HashMap<String, i32>,
) {
let mut parser = Parser::new(info);
@ -125,7 +129,7 @@ fn test_parse_default0() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Thu Sep 25 10:36:28", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -138,7 +142,7 @@ fn test_parse_default1() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Sep 10:36:28", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -151,7 +155,7 @@ fn test_parse_default2() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:36:28", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -164,7 +168,7 @@ fn test_parse_default3() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:36", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -177,7 +181,7 @@ fn test_parse_default4() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Sep 2003", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -190,7 +194,7 @@ fn test_parse_default5() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Sep", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -203,7 +207,7 @@ fn test_parse_default6() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "2003", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -216,7 +220,7 @@ fn test_parse_default7() {
micros: 500000, tzo: None
};
parse_and_assert(pdt, info, "10h36m28.5s", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -229,7 +233,7 @@ fn test_parse_default8() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10h36m28s", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -242,7 +246,7 @@ fn test_parse_default9() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10h36m", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -255,7 +259,7 @@ fn test_parse_default10() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10h", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -268,7 +272,7 @@ fn test_parse_default11() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10 h 36", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -281,7 +285,7 @@ fn test_parse_default12() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10 h 36.5", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -294,7 +298,7 @@ fn test_parse_default13() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "36 m 5", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -307,7 +311,7 @@ fn test_parse_default14() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "36 m 5 s", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -320,7 +324,7 @@ fn test_parse_default15() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "36 m 05", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -333,7 +337,7 @@ fn test_parse_default16() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "36 m 05 s", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -346,7 +350,7 @@ fn test_parse_default17() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10h am", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -359,7 +363,7 @@ fn test_parse_default18() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10h pm", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -372,7 +376,7 @@ fn test_parse_default19() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10am", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -385,7 +389,7 @@ fn test_parse_default20() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10pm", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -398,7 +402,7 @@ fn test_parse_default21() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:00 am", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -411,7 +415,7 @@ fn test_parse_default22() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:00 pm", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -424,7 +428,7 @@ fn test_parse_default23() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:00am", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -437,7 +441,7 @@ fn test_parse_default24() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:00pm", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -450,7 +454,7 @@ fn test_parse_default25() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:00a.m", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -463,7 +467,7 @@ fn test_parse_default26() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:00p.m", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -476,7 +480,7 @@ fn test_parse_default27() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:00a.m.", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -489,7 +493,7 @@ fn test_parse_default28() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "10:00p.m.", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -502,7 +506,7 @@ fn test_parse_default29() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "October", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -515,7 +519,7 @@ fn test_parse_default30() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "31-Dec-00", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -528,7 +532,7 @@ fn test_parse_default31() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "0:01:02", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -541,7 +545,7 @@ fn test_parse_default32() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "12h 01m02s am", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -554,7 +558,7 @@ fn test_parse_default33() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "12:08 PM", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -567,7 +571,7 @@ fn test_parse_default34() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "01h02m03", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -580,7 +584,7 @@ fn test_parse_default35() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "01h02", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -593,7 +597,7 @@ fn test_parse_default36() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "01h02s", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -606,7 +610,7 @@ fn test_parse_default37() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "01m02", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -619,7 +623,7 @@ fn test_parse_default38() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "01m02h", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -632,7 +636,7 @@ fn test_parse_default39() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "2004 10 Apr 11h30m", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -645,7 +649,7 @@ fn test_parse_default40() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Sep 03", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -658,7 +662,7 @@ fn test_parse_default41() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Sep of 03", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -671,7 +675,7 @@ fn test_parse_default42() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "02:17NOV2017", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -684,7 +688,7 @@ fn test_parse_default43() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Thu Sep 10:36:28", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -697,7 +701,7 @@ fn test_parse_default44() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Thu 10:36:28", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -710,7 +714,7 @@ fn test_parse_default45() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Wed", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -723,7 +727,7 @@ fn test_parse_default46() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Wednesday", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -1355,7 +1359,7 @@ fn test_parse_tzinfo0() {
micros: 0, tzo: Some(-10800),
};
parse_and_assert(pdt, info, "Thu Sep 25 10:36:28 BRST 2003", None, None, false, false,
None, false, rs_tzinfo_map!());
None, false, &rs_tzinfo_map!());
}
#[test]
@ -1367,7 +1371,7 @@ fn test_parse_tzinfo1() {
micros: 0, tzo: Some(-10800),
};
parse_and_assert(pdt, info, "2003 10:36:28 BRST 25 Sep Thu", None, None, false, false,
None, false, rs_tzinfo_map!());
None, false, &rs_tzinfo_map!());
}
#[test]
@ -1379,7 +1383,7 @@ fn test_parse_offset0() {
micros: 0, tzo: Some(-10800),
};
parse_and_assert(pdt, info, "Thu, 25 Sep 2003 10:49:41 -0300", None, None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1391,7 +1395,7 @@ fn test_parse_offset1() {
micros: 500000, tzo: Some(-10800),
};
parse_and_assert(pdt, info, "2003-09-25T10:49:41.5-03:00", None, None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1403,7 +1407,7 @@ fn test_parse_offset2() {
micros: 0, tzo: Some(-10800),
};
parse_and_assert(pdt, info, "2003-09-25T10:49:41-03:00", None, None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1415,7 +1419,7 @@ fn test_parse_offset3() {
micros: 500000, tzo: Some(-10800),
};
parse_and_assert(pdt, info, "20030925T104941.5-0300", None, None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1427,7 +1431,43 @@ fn test_parse_offset4() {
micros: 0, tzo: Some(-10800),
};
parse_and_assert(pdt, info, "20030925T104941-0300", None, None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
fn test_parse_offset5() {
let info = ParserInfo::default();
let pdt = PyDateTime {
year: 2018, month: 8, day: 10,
hour: 10, minute: 0, second: 0,
micros: 0, tzo: Some(-10800),
};
parse_and_assert(pdt, info, "2018-08-10 10:00:00 UTC+3", None, None, false, false,
None, false, &HashMap::new());
}
#[test]
fn test_parse_offset6() {
let info = ParserInfo::default();
let pdt = PyDateTime {
year: 2018, month: 8, day: 10,
hour: 15, minute: 36, second: 47,
micros: 0, tzo: Some(14400),
};
parse_and_assert(pdt, info, "2018-08-10 03:36:47 PM GMT-4", None, None, false, false,
None, false, &HashMap::new());
}
#[test]
fn test_parse_offset7() {
let info = ParserInfo::default();
let pdt = PyDateTime {
year: 2018, month: 8, day: 10,
hour: 4, minute: 15, second: 0,
micros: 0, tzo: Some(7200),
};
parse_and_assert(pdt, info, "2018-08-10 04:15:00 AM Z-02:00", None, None, false, false,
None, false, &HashMap::new());
}
#[test]
@ -1439,7 +1479,7 @@ fn test_parse_dayfirst0() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "10-09-2003", Some(true), None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1451,7 +1491,7 @@ fn test_parse_dayfirst1() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "10.09.2003", Some(true), None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1463,7 +1503,7 @@ fn test_parse_dayfirst2() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "10/09/2003", Some(true), None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1475,7 +1515,7 @@ fn test_parse_dayfirst3() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "10 09 2003", Some(true), None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1487,7 +1527,7 @@ fn test_parse_dayfirst4() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "090107", Some(true), None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1499,7 +1539,7 @@ fn test_parse_dayfirst5() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "2015 09 25", Some(true), None, false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1511,7 +1551,7 @@ fn test_parse_yearfirst0() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "10-09-03", None, Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1523,7 +1563,7 @@ fn test_parse_yearfirst1() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "10.09.03", None, Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1535,7 +1575,7 @@ fn test_parse_yearfirst2() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "10/09/03", None, Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1547,7 +1587,7 @@ fn test_parse_yearfirst3() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "10 09 03", None, Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1559,7 +1599,7 @@ fn test_parse_yearfirst4() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "090107", None, Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1571,7 +1611,7 @@ fn test_parse_yearfirst5() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "2015 09 25", None, Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1583,7 +1623,7 @@ fn test_parse_dfyf0() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "090107", Some(true), Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1595,7 +1635,7 @@ fn test_parse_dfyf1() {
micros: 0, tzo: None,
};
parse_and_assert(pdt, info, "2015 09 25", Some(true), Some(true), false, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1608,7 +1648,7 @@ fn test_unspecified_fallback0() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "April 2009", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -1621,7 +1661,7 @@ fn test_unspecified_fallback1() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Feb 2007", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -1634,7 +1674,7 @@ fn test_unspecified_fallback2() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Feb 2008", None, None, false, false,
Some(default_rsdate), false, HashMap::new());
Some(default_rsdate), false, &HashMap::new());
}
#[test]
@ -1646,7 +1686,7 @@ fn test_parse_ignoretz0() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Thu Sep 25 10:36:28 BRST 2003", None, None, false, false,
None, true, HashMap::new());
None, true, &HashMap::new());
}
#[test]
@ -1658,7 +1698,7 @@ fn test_parse_ignoretz1() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "1996.07.10 AD at 15:08:56 PDT", None, None, false, false,
None, true, HashMap::new());
None, true, &HashMap::new());
}
#[test]
@ -1670,7 +1710,7 @@ fn test_parse_ignoretz2() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "Tuesday, April 12, 1952 AD 3:30:42pm PST", None, None, false, false,
None, true, HashMap::new());
None, true, &HashMap::new());
}
#[test]
@ -1682,7 +1722,7 @@ fn test_parse_ignoretz3() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "November 5, 1994, 8:15:30 am EST", None, None, false, false,
None, true, HashMap::new());
None, true, &HashMap::new());
}
#[test]
@ -1694,7 +1734,7 @@ fn test_parse_ignoretz4() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "1994-11-05T08:15:30-05:00", None, None, false, false,
None, true, HashMap::new());
None, true, &HashMap::new());
}
#[test]
@ -1706,7 +1746,7 @@ fn test_parse_ignoretz5() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "1994-11-05T08:15:30Z", None, None, false, false,
None, true, HashMap::new());
None, true, &HashMap::new());
}
#[test]
@ -1718,7 +1758,7 @@ fn test_parse_ignoretz6() {
micros: 0, tzo: None
};
parse_and_assert(pdt, info, "1976-07-04T00:01:02Z", None, None, false, false,
None, true, HashMap::new());
None, true, &HashMap::new());
}
#[test]
@ -1730,7 +1770,7 @@ fn test_parse_ignoretz7() {
micros: 0, tzo: None
};
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]
@ -1742,7 +1782,7 @@ fn test_fuzzy_tzinfo0() {
micros: 0, tzo: Some(-10800)
};
parse_fuzzy_and_assert(pdt, None, info, "Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1755,7 +1795,7 @@ fn test_fuzzy_tokens_tzinfo0() {
};
let tokens = vec!["Today is ".to_owned(), "of ".to_owned(), ", exactly at ".to_owned(), " with timezone ".to_owned(), ".".to_owned()];
parse_fuzzy_and_assert(pdt, Some(tokens), info, "Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.", None, None, true, true,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1767,7 +1807,7 @@ fn test_fuzzy_simple0() {
micros: 0, tzo: None
};
parse_fuzzy_and_assert(pdt, None, info, "I have a meeting on March 1, 1974", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1779,7 +1819,7 @@ fn test_fuzzy_simple1() {
micros: 0, tzo: None
};
parse_fuzzy_and_assert(pdt, None, info, "On June 8th, 2020, I am going to be the first man on Mars", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1791,7 +1831,7 @@ fn test_fuzzy_simple2() {
micros: 0, tzo: None
};
parse_fuzzy_and_assert(pdt, None, info, "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1803,7 +1843,7 @@ fn test_fuzzy_simple3() {
micros: 0, tzo: None
};
parse_fuzzy_and_assert(pdt, None, info, "Meet me at 3:00 AM on December 3rd, 2003 at the AM/PM on Sunset", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1815,7 +1855,7 @@ fn test_fuzzy_simple4() {
micros: 0, tzo: None
};
parse_fuzzy_and_assert(pdt, None, info, "Jan 29, 1945 14:45 AM I going to see you there?", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}
#[test]
@ -1827,5 +1867,5 @@ fn test_fuzzy_simple5() {
micros: 0, tzo: None
};
parse_fuzzy_and_assert(pdt, None, info, "2017-07-17 06:15:", None, None, true, false,
None, false, HashMap::new());
None, false, &HashMap::new());
}

View File

@ -1,4 +1,8 @@
//! This code has been generated by running the `build_pycompat_tokenizer.py` script
//! in the repository root. Please do not edit it, as your edits will be destroyed
//! upon re-running code generation.
use tokenize::Tokenizer;
fn tokenize_assert(test_str: &str, comparison: Vec<&str>) {
@ -704,198 +708,216 @@ fn test_tokenize115() {
#[test]
fn test_tokenize116() {
let comp = vec!["2018", "-", "08", "-", "10", " ", "10", ":", "00", ":", "00", " ", "UTC", "+", "3"];
tokenize_assert("2018-08-10 10:00:00 UTC+3", comp);
}
#[test]
fn test_tokenize117() {
let comp = vec!["2018", "-", "08", "-", "10", " ", "03", ":", "36", ":", "47", " ", "PM", " ", "GMT", "-", "4"];
tokenize_assert("2018-08-10 03:36:47 PM GMT-4", comp);
}
#[test]
fn test_tokenize118() {
let comp = vec!["2018", "-", "08", "-", "10", " ", "04", ":", "15", ":", "00", " ", "AM", " ", "Z", "-", "02", ":", "00"];
tokenize_assert("2018-08-10 04:15:00 AM Z-02:00", comp);
}
#[test]
fn test_tokenize119() {
let comp = vec!["10", "-", "09", "-", "2003"];
tokenize_assert("10-09-2003", comp);
}
#[test]
fn test_tokenize117() {
fn test_tokenize120() {
let comp = vec!["10", ".", "09", ".", "2003"];
tokenize_assert("10.09.2003", comp);
}
#[test]
fn test_tokenize118() {
fn test_tokenize121() {
let comp = vec!["10", "/", "09", "/", "2003"];
tokenize_assert("10/09/2003", comp);
}
#[test]
fn test_tokenize119() {
fn test_tokenize122() {
let comp = vec!["10", " ", "09", " ", "2003"];
tokenize_assert("10 09 2003", comp);
}
#[test]
fn test_tokenize120() {
fn test_tokenize123() {
let comp = vec!["090107"];
tokenize_assert("090107", comp);
}
#[test]
fn test_tokenize121() {
fn test_tokenize124() {
let comp = vec!["2015", " ", "09", " ", "25"];
tokenize_assert("2015 09 25", comp);
}
#[test]
fn test_tokenize122() {
fn test_tokenize125() {
let comp = vec!["10", "-", "09", "-", "03"];
tokenize_assert("10-09-03", comp);
}
#[test]
fn test_tokenize123() {
fn test_tokenize126() {
let comp = vec!["10", ".", "09", ".", "03"];
tokenize_assert("10.09.03", comp);
}
#[test]
fn test_tokenize124() {
fn test_tokenize127() {
let comp = vec!["10", "/", "09", "/", "03"];
tokenize_assert("10/09/03", comp);
}
#[test]
fn test_tokenize125() {
fn test_tokenize128() {
let comp = vec!["10", " ", "09", " ", "03"];
tokenize_assert("10 09 03", comp);
}
#[test]
fn test_tokenize126() {
let comp = vec!["090107"];
tokenize_assert("090107", comp);
}
#[test]
fn test_tokenize127() {
let comp = vec!["2015", " ", "09", " ", "25"];
tokenize_assert("2015 09 25", comp);
}
#[test]
fn test_tokenize128() {
let comp = vec!["090107"];
tokenize_assert("090107", comp);
}
#[test]
fn test_tokenize129() {
let comp = vec!["2015", " ", "09", " ", "25"];
tokenize_assert("2015 09 25", comp);
let comp = vec!["090107"];
tokenize_assert("090107", comp);
}
#[test]
fn test_tokenize130() {
let comp = vec!["2015", " ", "09", " ", "25"];
tokenize_assert("2015 09 25", comp);
}
#[test]
fn test_tokenize131() {
let comp = vec!["090107"];
tokenize_assert("090107", comp);
}
#[test]
fn test_tokenize132() {
let comp = vec!["2015", " ", "09", " ", "25"];
tokenize_assert("2015 09 25", comp);
}
#[test]
fn test_tokenize133() {
let comp = vec!["April", " ", "2009"];
tokenize_assert("April 2009", comp);
}
#[test]
fn test_tokenize131() {
fn test_tokenize134() {
let comp = vec!["Feb", " ", "2007"];
tokenize_assert("Feb 2007", comp);
}
#[test]
fn test_tokenize132() {
fn test_tokenize135() {
let comp = vec!["Feb", " ", "2008"];
tokenize_assert("Feb 2008", comp);
}
#[test]
fn test_tokenize133() {
fn test_tokenize136() {
let comp = vec!["Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", " ", "BRST", " ", "2003"];
tokenize_assert("Thu Sep 25 10:36:28 BRST 2003", comp);
}
#[test]
fn test_tokenize134() {
fn test_tokenize137() {
let comp = vec!["1996", ".", "07", ".", "10", " ", "AD", " ", "at", " ", "15", ":", "08", ":", "56", " ", "PDT"];
tokenize_assert("1996.07.10 AD at 15:08:56 PDT", comp);
}
#[test]
fn test_tokenize135() {
fn test_tokenize138() {
let comp = vec!["Tuesday", ",", " ", "April", " ", "12", ",", " ", "1952", " ", "AD", " ", "3", ":", "30", ":", "42", "pm", " ", "PST"];
tokenize_assert("Tuesday, April 12, 1952 AD 3:30:42pm PST", comp);
}
#[test]
fn test_tokenize136() {
fn test_tokenize139() {
let comp = vec!["November", " ", "5", ",", " ", "1994", ",", " ", "8", ":", "15", ":", "30", " ", "am", " ", "EST"];
tokenize_assert("November 5, 1994, 8:15:30 am EST", comp);
}
#[test]
fn test_tokenize137() {
fn test_tokenize140() {
let comp = vec!["1994", "-", "11", "-", "05", "T", "08", ":", "15", ":", "30", "-", "05", ":", "00"];
tokenize_assert("1994-11-05T08:15:30-05:00", comp);
}
#[test]
fn test_tokenize138() {
fn test_tokenize141() {
let comp = vec!["1994", "-", "11", "-", "05", "T", "08", ":", "15", ":", "30", "Z"];
tokenize_assert("1994-11-05T08:15:30Z", comp);
}
#[test]
fn test_tokenize139() {
fn test_tokenize142() {
let comp = vec!["1976", "-", "07", "-", "04", "T", "00", ":", "01", ":", "02", "Z"];
tokenize_assert("1976-07-04T00:01:02Z", comp);
}
#[test]
fn test_tokenize140() {
fn test_tokenize143() {
let comp = vec!["Tue", " ", "Apr", " ", "4", " ", "00", ":", "22", ":", "12", " ", "PDT", " ", "1995"];
tokenize_assert("Tue Apr 4 00:22:12 PDT 1995", comp);
}
#[test]
fn test_tokenize141() {
fn test_tokenize144() {
let comp = vec!["Today", " ", "is", " ", "25", " ", "of", " ", "September", " ", "of", " ", "2003", ",", " ", "exactly", " ", "at", " ", "10", ":", "49", ":", "41", " ", "with", " ", "timezone", " ", "-", "03", ":", "00", "."];
tokenize_assert("Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.", comp);
}
#[test]
fn test_tokenize142() {
fn test_tokenize145() {
let comp = vec!["Today", " ", "is", " ", "25", " ", "of", " ", "September", " ", "of", " ", "2003", ",", " ", "exactly", " ", "at", " ", "10", ":", "49", ":", "41", " ", "with", " ", "timezone", " ", "-", "03", ":", "00", "."];
tokenize_assert("Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.", comp);
}
#[test]
fn test_tokenize143() {
fn test_tokenize146() {
let comp = vec!["I", " ", "have", " ", "a", " ", "meeting", " ", "on", " ", "March", " ", "1", ",", " ", "1974"];
tokenize_assert("I have a meeting on March 1, 1974", comp);
}
#[test]
fn test_tokenize144() {
fn test_tokenize147() {
let comp = vec!["On", " ", "June", " ", "8", "th", ",", " ", "2020", ",", " ", "I", " ", "am", " ", "going", " ", "to", " ", "be", " ", "the", " ", "first", " ", "man", " ", "on", " ", "Mars"];
tokenize_assert("On June 8th, 2020, I am going to be the first man on Mars", comp);
}
#[test]
fn test_tokenize145() {
fn test_tokenize148() {
let comp = vec!["Meet", " ", "me", " ", "at", " ", "the", " ", "AM", "/", "PM", " ", "on", " ", "Sunset", " ", "at", " ", "3", ":", "00", " ", "AM", " ", "on", " ", "December", " ", "3", "rd", ",", " ", "2003"];
tokenize_assert("Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003", comp);
}
#[test]
fn test_tokenize146() {
fn test_tokenize149() {
let comp = vec!["Meet", " ", "me", " ", "at", " ", "3", ":", "00", " ", "AM", " ", "on", " ", "December", " ", "3", "rd", ",", " ", "2003", " ", "at", " ", "the", " ", "AM", "/", "PM", " ", "on", " ", "Sunset"];
tokenize_assert("Meet me at 3:00 AM on December 3rd, 2003 at the AM/PM on Sunset", comp);
}
#[test]
fn test_tokenize147() {
fn test_tokenize150() {
let comp = vec!["Jan", " ", "29", ",", " ", "1945", " ", "14", ":", "45", " ", "AM", " ", "I", " ", "going", " ", "to", " ", "see", " ", "you", " ", "there", "?"];
tokenize_assert("Jan 29, 1945 14:45 AM I going to see you there?", comp);
}
#[test]
fn test_tokenize148() {
fn test_tokenize151() {
let comp = vec!["2017", "-", "07", "-", "17", " ", "06", ":", "15", ":"];
tokenize_assert("2017-07-17 06:15:", comp);
}

View File

@ -15,7 +15,7 @@ pub enum DayOfWeek {
impl DayOfWeek {
pub fn to_numeral(&self) -> u32 {
match self {
match *self {
DayOfWeek::Sunday => 0,
DayOfWeek::Monday => 1,
DayOfWeek::Tuesday => 2,
@ -40,7 +40,7 @@ impl DayOfWeek {
}
/// Given the current day of the week, how many days until the next day?
pub fn difference(&self, other: DayOfWeek) -> u32 {
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;
@ -64,7 +64,7 @@ pub fn day_of_week(year: u32, month: u32, day: u32) -> ParseResult<DayOfWeek> {
let c = (year - 1) / 100;
(c, year - 1 - 100 * c)
},
_ => return Err(ParseError::InvalidMonth)
_ => return Err(ParseError::ImpossibleTimestamp("Invalid month"))
};
let e = match month {
@ -115,18 +115,18 @@ mod test {
#[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);
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);
}
}