1
0
mirror of https://github.com/bspeice/dtparse synced 2025-04-03 04:21:31 -04:00

Compare commits

...

62 Commits

Author SHA1 Message Date
081cd7bea0 Release version 2.1.0 2024-08-18 20:43:00 +00:00
c3d6730e9c #45: Fix index issue 2024-08-18 20:42:20 +00:00
61d2aa60c6 Publish 2.0.0 2023-08-24 19:07:54 -04:00
bspeice
92a2e16577
Merge pull request #44 from nibon7/chrono
Don't use `oldtime` feature of chrono
2023-08-24 19:06:15 -04:00
nibon7
dbd2eab7d4
Don't use oldtime feature of chrono 2023-06-30 18:53:01 +08:00
849ad33a5d Publish 1.5.0 2023-06-09 18:25:52 -04:00
bspeice
1f37b60cca
Merge pull request #42 from michel-slm/dont-ship-python
Don't ship Python scripts
2023-06-09 17:32:47 -04:00
bspeice
dec2e948db
Merge pull request #43 from michel-slm/add-license
Add Apache license text
2023-06-09 17:31:25 -04:00
Michel Alexandre Salim
3367312509
Add Apache license text
Include the license text as well, not just the license notice

See https://www.apache.org/foundation/license-faq.html#Apply-My-Software

```
michel in dtparse on  add-license [?] is 📦 v1.4.0 via 🐍 v3.11.3 via 🦀 v1.69.0
⬢ [fedora-toolbox:38] ❯ cargo package --allow-dirty --no-verify
   Packaging dtparse v1.4.0 (/home/michel/src/github/bspeice/dtparse)
    Updating crates.io index
    Packaged 20 files, 186.1KiB (34.2KiB compressed)

michel in dtparse on  add-license [?] is 📦 v1.4.0 via 🐍 v3.11.3 via 🦀 v1.69.0
⬢ [fedora-toolbox:38] ❯ tar tf target/package/dtparse-1.4.0.crate | grep LICENSE
dtparse-1.4.0/LICENSE
dtparse-1.4.0/LICENSE-APACHE
```

Signed-off-by: Michel Alexandre Salim <salimma@fedoraproject.org>
2023-06-09 16:02:49 -05:00
Michel Alexandre Salim
49a8a70bea
Don't ship Python scripts
This complicates distribution packaging in Fedora as the presence of these
files led the dependency generator to assume the crate depends on Python

```
michel in dtparse on  dont-ship-python is 📦 v1.4.0 via 🐍 v3.11.3 via 🦀 v1.69.0 took 6s
⬢ [fedora-toolbox:38] ❯ cargo package --allow-dirty --no-verify
   Packaging dtparse v1.4.0 (/home/michel/src/github/bspeice/dtparse)
    Updating crates.io index
    Packaged 19 files, 175.0KiB (30.7KiB compressed)

michel in dtparse on  dont-ship-python is 📦 v1.4.0 via 🐍 v3.11.3 via 🦀 v1.69.0 took 2s
⬢ [fedora-toolbox:38] ❯ tar tf target/package/dtparse-1.4.0.crate | grep '.py'
dtparse-1.4.0/build_pycompat.py
dtparse-1.4.0/build_pycompat_tokenizer.py
```

```
michel in dtparse on  dont-ship-python [!] is 📦 v1.4.0 via 🐍 v3.11.3 via 🦀 v1.69.0 took 9s
⬢ [fedora-toolbox:38] ❯ cargo package --allow-dirty --no-verify
   Packaging dtparse v1.4.0 (/home/michel/src/github/bspeice/dtparse)
    Updating crates.io index
    Packaged 17 files, 157.4KiB (27.4KiB compressed)

michel in dtparse on  dont-ship-python [!] is 📦 v1.4.0 via 🐍 v3.11.3 via 🦀 v1.69.0
⬢ [fedora-toolbox:38] ❯ tar tf target/package/dtparse-1.4.0.crate | grep '\.py'

michel in dtparse on  dont-ship-python [!] is 📦 v1.4.0 via 🐍 v3.11.3 via 🦀 v1.69.0
⬢ [fedora-toolbox:38] ❯
```

Signed-off-by: Michel Alexandre Salim <salimma@fedoraproject.org>
2023-06-09 14:31:39 -05:00
bspeice
dd0ef6eb91
Merge pull request #41 from bspeice/nanosecond
Nanosecond precision
2023-03-24 22:31:03 -04:00
b925b968ca Fix deprecations 2023-03-25 02:29:55 +00:00
3a9e2674a6 #40: Nanosecond precision 2023-03-25 02:12:57 +00:00
ed919e84ef #38: Release new dateutil version 2022-06-15 22:21:25 -04:00
4d8ade4b05 #36: Add test to assert no longer panics 2022-06-15 22:20:37 -04:00
6a88885ef5 #35: Add test to make sure it does not panic 2022-06-15 22:18:20 -04:00
a193a79afa Add a test for issue 34 2022-06-15 22:16:15 -04:00
bspeice
5b3be160f6
Merge pull request #37 from ahkrr/master
upgrade dependency rust_decimal ^0.10.1 -> 1.17.0
2022-03-24 21:47:10 -04:00
ahkrr
8f6a3b179d upgrade dependency rust_decimal ^0.10.1 -> 1.17.0 2021-11-14 14:14:11 +01:00
028c45e3fe Fix #32 and #33 2021-08-23 22:36:03 -04:00
bspeice
f1ca4e4129
Merge pull request #31 from sbrocket/delete-unneeded-dep
Delete unused chrono-tz dependency
2021-06-03 22:56:34 -04:00
Bryan Henry
61d3ed025e Delete unused chrono-tz dependency 2021-06-03 19:45:47 -07:00
440ba918f6 Version 1.2.0 2020-10-04 20:10:30 -04:00
bors[bot]
fe773b0d9f
Merge #30
30: Handle panic from large integers r=bspeice a=gillespiecd

1. Opt to use `NaiveDate::from_ymd_opt` so that a panic can be avoided, this will handle the issue with large integers causing panics.

I added the test case from the issue (and another), which should hopefully fix #26 .

Co-authored-by: Chris Gillespie <6572184+gillespiecd@users.noreply.github.com>
2020-10-05 00:01:02 +00:00
Chris Gillespie
bf456f466f Handle panic from large integers 2020-10-04 16:26:50 -07:00
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
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
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
b098f54f8b Convert clippy lints 2020-06-11 13:33:09 -04:00
61022c323e Cargo fmt 2020-06-11 13:11:52 -04:00
4079b3ce2f Fix ENV naming 2020-06-11 13:06:26 -04:00
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
22b6a321e6 Remove Appveyor badge from README 2020-06-11 12:25:55 -04:00
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
2a2f1e7fbd Fix #21 2020-05-29 14:23:54 -04:00
bspeice
e9c994a755
Merge pull request #20 from bspeice/timezone
Remove timezone handling
2019-11-30 08:41:11 -05:00
d6fc72459e Mark unused 2019-11-29 18:19:13 -05:00
d7ff381d7f Bugfix 2019-11-29 17:58:40 -05:00
d5e0a5d46a Remove timezone handling
There are too many issues in chrono-tz to make it worth supporting.
2019-11-29 16:49:29 -05:00
bspeice
9f1b8d4971
Merge pull request #19 from bspeice/tz_fix
Attempt to read timezones from chrono-tz
2019-11-29 15:45:44 -05:00
0f7ac8538c Remove WASM from Travis 2019-11-25 23:11:19 -05:00
b81a8d9541 Use 1.28 as minimum Rust version 2019-11-25 20:57:56 -05:00
030ca4fced Rustfmt 2019-11-13 23:18:37 -05:00
142712900f Attempt to read timezones from chrono-tz 2019-11-13 23:12:47 -05:00
c310cbaa0d Fix an example warning 2018-09-18 23:06:38 -04:00
ef3ea38834 Release version 1.0.3 2018-09-18 23:04:07 -04:00
741afa3451 Remove a last println and use a static default parser
Heap profilers are fun
2018-09-17 23:14:50 -04:00
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
Bradlee Speice
0ef35527d9 Release 1.0.1 2018-08-11 13:13:12 -04:00
Bradlee Speice
b5fa1d89ef Fix new tests not being date-neutral 2018-08-11 10:38:46 -04:00
Bradlee Speice
246b389ac9 Clippy caught a bug
And now I have test cases for it!
2018-08-10 23:24:02 -04:00
Bradlee Speice
4d48885f4b Add old rust versions to test agains 2018-08-10 22:15:27 -04:00
Bradlee Speice
48705339e6 Add one final CONTRIBUTOR 2018-08-03 23:43:24 -04:00
20 changed files with 1066 additions and 694 deletions

4
.gitignore vendored
View File

@ -3,4 +3,6 @@
**/*.rs.bk
Cargo.lock
.vscode
*.pyc
*.pyc
.idea/
*.swp

View File

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

34
CHANGELOG.md Normal file
View File

@ -0,0 +1,34 @@
Version 1.0.3 (2018-09-18)
==========================
Misc
----
- Changed the default `parse` function to use a static parser
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.

View File

@ -3,4 +3,5 @@ This project benefits from the Rust and open source communities, but most specif
# Contributors:
- [@messense](https://github.com/messense)
- [@mjmeehan](https://github.com/mjmeehan)
- [@mjmeehan](https://github.com/mjmeehan)
- [@neosilky](https://github.com/neosilky)

View File

@ -1,23 +1,26 @@
[package]
name = "dtparse"
version = "1.0.0"
version = "2.0.1"
authors = ["Bradlee Speice <bradlee@speice.io>"]
description = "A dateutil-compatible timestamp parser for Rust"
repository = "https://github.com/bspeice/dtparse.git"
readme = "README.md"
categories = ["date-and-time"]
license = "Apache-2.0"
exclude = ["/*.py"]
[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"
num-traits = "0.2"
rust_decimal = "0.9"
chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
lazy_static = "1.4.0"
num-traits = "0.2.15"
rust_decimal = { version = "1.29.1", default-features = false }
[dev-dependencies]
base64 = "0.21.0"

202
LICENSE-APACHE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,7 +1,5 @@
# 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/)
@ -65,5 +63,11 @@ assert_eq!(
Further examples can be found in the [examples](examples) directory on international usage.
# Usage
`dtparse` requires a minimum Rust version of 1.28 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

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%

18
build_pycompat.py Normal file → Executable file
View File

@ -1,4 +1,6 @@
#!/usr/bin/python3
from dateutil.parser import parse
from dateutil.tz import tzutc
from datetime import datetime
tests = {
@ -48,7 +50,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',
@ -77,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': [
@ -229,7 +233,7 @@ fn parse_and_assert(
tzinfos: &HashMap<String, i32>,
) {
let mut parser = Parser::new(info);
let parser = Parser::new(info);
let rs_parsed = parser.parse(
s,
dayfirst,
@ -279,7 +283,7 @@ fn parse_fuzzy_and_assert(
tzinfos: &HashMap<String, i32>,
) {
let mut parser = Parser::new(info);
let parser = Parser::new(info);
let rs_parsed = parser.parse(
s,
dayfirst,
@ -313,7 +317,7 @@ TEST_PARSE_DEFAULT = '''
#[test]
fn test_parse_default{i}() {{
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {{
year: {d.year}, month: {d.month}, day: {d.day},
hour: {d.hour}, minute: {d.minute}, second: {d.second},
@ -403,7 +407,7 @@ TEST_UNSPECIFIED_FALLBACK = '''
#[test]
fn test_unspecified_fallback{i}() {{
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2010, 1, 31).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2010, 1, 31).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {{
year: {d.year}, month: {d.month}, day: {d.day},
hour: {d.hour}, minute: {d.minute}, second: {d.second},
@ -431,7 +435,7 @@ TEST_PARSE_DEFAULT_IGNORE = '''
#[ignore]
fn test_parse_default_ignore{i}() {{
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {{
year: {d.year}, month: {d.month}, day: {d.day},
hour: {d.hour}, minute: {d.minute}, second: {d.second},

1
build_pycompat_tokenizer.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/python3
from dateutil.parser import _timelex
from build_pycompat import tests

View File

@ -1,47 +0,0 @@
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

View File

@ -1,20 +0,0 @@
# 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
}
# we don't run the "test phase" when doing deploys
if [ -z $TRAVIS_TAG ]; then
main
fi

View File

@ -8,7 +8,6 @@ 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
@ -26,14 +25,24 @@ fn main() {
vec!["сен", "Сентябрь"],
vec!["окт", "Октябрь"],
vec!["ноя", "Ноябрь"],
vec!["дек", "Декабрь"]
vec!["дек", "Декабрь"],
]);
let mut p = Parser::new(info);
let 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)
p.parse(
"10 Сентябрь 2015 10:20",
None,
None,
false,
false,
None,
false,
&HashMap::new()
)
.unwrap()
.0,
NaiveDate::from_ymd_opt(2015, 9, 10).unwrap().and_hms_opt(10, 20, 0).unwrap()
);
}
}

View File

@ -1,26 +1,27 @@
#![deny(missing_docs)]
#![cfg_attr(test, allow(unknown_lints))]
#![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
//!
//! ```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"),
@ -30,17 +31,17 @@
//! ))
//! );
//! ```
//!
//!
//! And we can even handle fuzzy strings where dates/times aren't the
//! only content if we dig into the implementation a bit!
//!
//! ```rust
//!
//! ```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(
@ -58,9 +59,15 @@
//! ))
//! );
//! ```
//!
//!
//! Further examples can be found in the `examples` directory on international usage.
//!
//!
//! # Usage
//!
//! `dtparse` requires a minimum Rust version of 1.28 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]
@ -70,6 +77,9 @@ extern crate chrono;
extern crate num_traits;
extern crate rust_decimal;
#[cfg(test)]
extern crate base64;
use chrono::Datelike;
use chrono::Duration;
use chrono::FixedOffset;
@ -81,8 +91,10 @@ use chrono::Timelike;
use num_traits::cast::ToPrimitive;
use rust_decimal::Decimal;
use rust_decimal::Error as DecimalError;
use std::collections::HashMap;
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;
@ -102,6 +114,7 @@ lazy_static! {
static ref ONE: Decimal = Decimal::new(1, 0);
static ref TWENTY_FOUR: Decimal = Decimal::new(24, 0);
static ref SIXTY: Decimal = Decimal::new(60, 0);
static ref DEFAULT_PARSER: Parser = Parser::default();
}
impl From<DecimalError> for ParseError {
@ -136,8 +149,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> {
@ -151,12 +174,12 @@ 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 val in val_vec.into_iter() {
for (i, val_vec) in vec.iter().enumerate() {
for val in val_vec {
m.insert(val.to_lowercase(), i);
}
}
@ -166,7 +189,7 @@ pub fn parse_info(vec: Vec<Vec<&str>>) -> HashMap<String, usize> {
}
/// 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
@ -179,7 +202,7 @@ pub fn parse_info(vec: Vec<Vec<&str>>) -> HashMap<String, usize> {
/// - `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)]
@ -220,12 +243,10 @@ impl Default for ParserInfo {
let century = year / 100 * 100;
ParserInfo {
jump: parse_info(vec![
vec![
" ", ".", ",", ";", "-", "/", "'", "at", "on", "and", "ad", "m", "t", "of",
"st", "nd", "rd", "th",
],
]),
jump: parse_info(vec![vec![
" ", ".", ",", ";", "-", "/", "'", "at", "on", "and", "ad", "m", "t", "of", "st",
"nd", "rd", "th",
]]),
weekday: parse_info(vec![
vec!["Mon", "Monday"],
vec!["Tue", "Tues", "Tuesday"],
@ -260,8 +281,8 @@ impl Default for ParserInfo {
tzoffset: parse_info(vec![vec![]]),
dayfirst: false,
yearfirst: false,
year: year,
century: century,
year,
century,
}
}
}
@ -272,7 +293,7 @@ impl ParserInfo {
}
fn weekday_index(&self, name: &str) -> Option<usize> {
self.weekday.get(&name.to_lowercase()).map(|i| *i)
self.weekday.get(&name.to_lowercase()).cloned()
}
fn month_index(&self, name: &str) -> Option<usize> {
@ -280,7 +301,7 @@ impl ParserInfo {
}
fn hms_index(&self, name: &str) -> Option<usize> {
self.hms.get(&name.to_lowercase()).map(|i| *i)
self.hms.get(&name.to_lowercase()).cloned()
}
fn ampm_index(&self, name: &str) -> Option<bool> {
@ -330,10 +351,13 @@ 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) && res.tzname.is_some()
} else if res.tzoffset != Some(0)
&& res.tzname.is_some()
&& self.utczone_index(res.tzname.as_ref().unwrap())
{
res.tzoffset = Some(0);
@ -350,16 +374,16 @@ fn days_in_month(year: i32, month: i32) -> Result<u32, ParseError> {
};
match month {
2 => if leap_year {
Ok(29)
} else {
Ok(28)
},
2 => {
if leap_year {
Ok(29)
} else {
Ok(28)
}
}
1 | 3 | 5 | 7 | 8 | 10 | 12 => Ok(31),
4 | 6 | 9 | 11 => Ok(30),
_ => {
Err(ParseError::ImpossibleTimestamp("Invalid month"))
}
_ => Err(ParseError::ImpossibleTimestamp("Invalid month")),
}
}
@ -413,9 +437,7 @@ impl YMD {
Some(YMDLabel::Month) => {
return Err(ParseError::ImpossibleTimestamp("Invalid month"))
}
Some(YMDLabel::Day) => {
return Err(ParseError::ImpossibleTimestamp("Invalid day"))
}
Some(YMDLabel::Day) => return Err(ParseError::ImpossibleTimestamp("Invalid day")),
}
}
@ -427,9 +449,7 @@ impl YMD {
Some(YMDLabel::Month) => {
return Err(ParseError::ImpossibleTimestamp("Invalid month"))
}
Some(YMDLabel::Day) => {
return Err(ParseError::ImpossibleTimestamp("Invalid day"))
}
Some(YMDLabel::Day) => return Err(ParseError::ImpossibleTimestamp("Invalid day")),
}
}
@ -477,7 +497,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) {
@ -490,22 +510,19 @@ impl YMD {
}
if self._ymd.len() != strids.len() {
return Err(ParseError::YearMonthDayError("Tried to resolve year, month, and day without enough information"));
return Err(ParseError::YearMonthDayError(
"Tried to resolve year, month, and day without enough information",
));
}
Ok((
strids
.get(&YMDLabel::Year)
.map(|i| self._ymd[*i]),
strids
.get(&YMDLabel::Month)
.map(|i| self._ymd[*i]),
strids
.get(&YMDLabel::Day)
.map(|i| self._ymd[*i]),
strids.get(&YMDLabel::Year).map(|i| self._ymd[*i]),
strids.get(&YMDLabel::Month).map(|i| self._ymd[*i]),
strids.get(&YMDLabel::Day).map(|i| self._ymd[*i]),
))
}
#[allow(clippy::needless_return)]
fn resolve_ymd(
&mut self,
yearfirst: bool,
@ -514,28 +531,24 @@ impl YMD {
let len_ymd = self._ymd.len();
let mut strids: HashMap<YMDLabel, usize> = HashMap::new();
self.ystridx
.map(|u| strids.insert(YMDLabel::Year, u.clone()));
self.mstridx
.map(|u| strids.insert(YMDLabel::Month, u.clone()));
self.dstridx
.map(|u| strids.insert(YMDLabel::Day, u.clone()));
self.ystridx.map(|u| strids.insert(YMDLabel::Year, u));
self.mstridx.map(|u| strids.insert(YMDLabel::Month, u));
self.dstridx.map(|u| strids.insert(YMDLabel::Day, u));
// TODO: More Rustiomatic way of doing this?
if len_ymd == strids.len() && strids.len() > 0
|| (len_ymd == 3 && strids.len() == 2)
{
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(ParseError::YearMonthDayError("Received extra tokens in resolving year, month, and day"));
return Err(ParseError::YearMonthDayError(
"Received extra tokens in resolving year, month, and day",
));
}
match (len_ymd, self.mstridx) {
(1, Some(val)) |
(2, Some(val)) => {
(1, Some(val)) | (2, Some(val)) => {
let other = if len_ymd == 1 {
self._ymd[0]
} else {
@ -545,7 +558,7 @@ impl YMD {
return Ok((Some(other), Some(self._ymd[val]), None));
}
return Ok((None, Some(self._ymd[val]), Some(other)));
},
}
(2, None) => {
if self._ymd[0] > 31 {
return Ok((Some(self._ymd[0]), Some(self._ymd[1]), None));
@ -557,28 +570,29 @@ impl YMD {
return Ok((None, Some(self._ymd[1]), Some(self._ymd[0])));
}
return Ok((None, Some(self._ymd[0]), Some(self._ymd[1])));
},
}
(3, Some(0)) => {
if self._ymd[1] > 31 {
return Ok((Some(self._ymd[1]), Some(self._ymd[0]), Some(self._ymd[2])));
}
return Ok((Some(self._ymd[2]), Some(self._ymd[0]), Some(self._ymd[1])));
},
}
(3, Some(1)) => {
if self._ymd[0] > 31 || (yearfirst && self._ymd[2] <= 31) {
return Ok((Some(self._ymd[0]), Some(self._ymd[1]), Some(self._ymd[2])));
}
return Ok((Some(self._ymd[2]), Some(self._ymd[1]), Some(self._ymd[0])));
},
}
(3, Some(2)) => {
// It was in the original docs, so: WTF!?
if self._ymd[1] > 31 {
return Ok((Some(self._ymd[2]), Some(self._ymd[1]), Some(self._ymd[0])));
}
return Ok((Some(self._ymd[0]), Some(self._ymd[2]), Some(self._ymd[1])));
},
}
(3, None) => {
if self._ymd[0] > 31 || self.ystridx == Some(0)
if self._ymd[0] > 31
|| self.ystridx == Some(0)
|| (yearfirst && self._ymd[1] <= 12 && self._ymd[2] <= 31)
{
if dayfirst && self._ymd[2] <= 12 {
@ -589,8 +603,10 @@ impl YMD {
return Ok((Some(self._ymd[2]), Some(self._ymd[1]), Some(self._ymd[0])));
}
return Ok((Some(self._ymd[2]), Some(self._ymd[0]), Some(self._ymd[1])));
},
(_, _) => { return Ok((None, None, None)); },
}
(_, _) => {
return Ok((None, None, None));
}
}
}
}
@ -604,7 +620,7 @@ struct ParsingResult {
hour: Option<i32>,
minute: Option<i32>,
second: Option<i32>,
microsecond: Option<i32>,
nanosecond: Option<i64>,
tzname: Option<String>,
tzoffset: Option<i32>,
ampm: Option<bool>,
@ -612,6 +628,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.nanosecond)
+ 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.
@ -622,7 +663,7 @@ pub struct Parser {
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.
@ -633,35 +674,36 @@ impl Parser {
/// 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.
#[allow(clippy::too_many_arguments)]
pub fn parse(
&mut self,
&self,
timestr: &str,
dayfirst: Option<bool>,
yearfirst: Option<bool>,
@ -673,11 +715,15 @@ impl Parser {
) -> ParseResult<(NaiveDateTime, Option<FixedOffset>, Option<Vec<String>>)> {
let default_date = default.unwrap_or(&Local::now().naive_local()).date();
let default_ts = NaiveDateTime::new(default_date, NaiveTime::from_hms(0, 0, 0));
let default_ts = NaiveDateTime::new(default_date, NaiveTime::from_hms_opt(0, 0, 0).unwrap());
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 {
@ -688,8 +734,9 @@ impl Parser {
}
}
#[allow(clippy::cognitive_complexity)] // Imitating Python API is priority
fn parse_with_tokens(
&mut self,
&self,
timestr: &str,
dayfirst: Option<bool>,
yearfirst: Option<bool>,
@ -734,20 +781,22 @@ 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;
}
i += 2;
} else if i + 4 < len_l && l[i + 1] == l[i + 3] && l[i + 3] == " "
} else if i + 4 < len_l
&& l[i + 1] == l[i + 3]
&& l[i + 3] == " "
&& 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))?;
}
@ -764,7 +813,7 @@ 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();
@ -779,10 +828,9 @@ 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;
@ -800,16 +848,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
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>()?);
min_offset = Some(0);
}
@ -817,9 +866,12 @@ impl Parser {
Some(signal * (hour_offset.unwrap() * 3600 + min_offset.unwrap() * 60));
let tzname = res.tzname.clone();
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])
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])
{
// (GMT)
res.tzname = Some(l[i + 4].clone());
@ -856,23 +908,24 @@ 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 || 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 = 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 {
@ -891,49 +944,58 @@ impl Parser {
Ok(val_is_ampm)
}
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;
fn build_naive(
&self,
res: &ParsingResult,
default: &NaiveDateTime,
) -> ParseResult<NaiveDateTime> {
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
let dow = day_of_week(y as u32, m, default.day()).unwrap();
let dow = day_of_week(y as u32, m, default.day())?;
// 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)
};
// TODO: Change month/day to u32
let d = NaiveDate::from_ymd(
let d = NaiveDate::from_ymd_opt(
y,
m,
min(res.day.unwrap_or(default.day() as i32) as u32, days_in_month(y, m as i32)?)
);
min(
res.day.unwrap_or(default.day() as i32) as u32,
days_in_month(y, m as i32)?,
),
)
.ok_or_else(|| ParseError::ImpossibleTimestamp("Invalid date range given"))?;
let d = d + d_offset;
let hour = res.hour.unwrap_or(default.hour() as i32) as u32;
let minute = res.minute.unwrap_or(default.minute() as i32) as u32;
let second = res.second.unwrap_or(default.second() as i32) as u32;
let microsecond = res.microsecond
.unwrap_or(default.timestamp_subsec_micros() as i32) as u32;
let t = NaiveTime::from_hms_micro_opt(hour, minute, second, microsecond).ok_or_else(|| {
if hour >= 24 {
ParseError::ImpossibleTimestamp("Invalid hour")
} else if minute >= 60 {
ParseError::ImpossibleTimestamp("Invalid minute")
} else if second >= 60 {
ParseError::ImpossibleTimestamp("Invalid second")
} else if microsecond >= 2_000_000 {
ParseError::ImpossibleTimestamp("Invalid microsecond")
} else {
unreachable!();
}
})?;
let nanosecond = res
.nanosecond
.unwrap_or(default.timestamp_subsec_nanos() as i64) as u32;
let t =
NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond).ok_or_else(|| {
if hour >= 24 {
ParseError::ImpossibleTimestamp("Invalid hour")
} else if minute >= 60 {
ParseError::ImpossibleTimestamp("Invalid minute")
} else if second >= 60 {
ParseError::ImpossibleTimestamp("Invalid second")
} else if nanosecond >= 2_000_000_000 {
ParseError::ImpossibleTimestamp("Invalid microsecond")
} else {
unreachable!();
}
})?;
Ok(NaiveDateTime::new(d, t))
}
@ -944,30 +1006,31 @@ impl Parser {
res: &ParsingResult,
tzinfos: &HashMap<String, i32>,
) -> ParseResult<Option<FixedOffset>> {
// TODO: Actual timezone support
if let Some(offset) = res.tzoffset {
Ok(Some(FixedOffset::east(offset)))
Ok(FixedOffset::east_opt(offset))
} else if res.tzoffset == None
&& (res.tzname == Some(" ".to_owned()) || res.tzname == Some(".".to_owned())
|| res.tzname == Some("-".to_owned()) || res.tzname == None)
&& (res.tzname == Some(" ".to_owned())
|| res.tzname == Some(".".to_owned())
|| res.tzname == Some("-".to_owned())
|| res.tzname == None)
{
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(),
)))
} else if res.tzname.is_some() {
// TODO: Dateutil issues a warning/deprecation notice here. Should we force the issue?
println!("tzname {} identified but not understood. Ignoring for the time being, but behavior is subject to change.", res.tzname.as_ref().unwrap());
Ok(FixedOffset::east_opt(
*tzinfos.get(res.tzname.as_ref().unwrap()).unwrap(),
))
} else if let Some(tzname) = res.tzname.as_ref() {
println!("tzname {} identified but not understood.", tzname);
Ok(None)
} else {
Err(ParseError::TimezoneUnsupported)
}
}
#[allow(clippy::unnecessary_unwrap)]
fn parse_numeric_token(
&self,
tokens: &Vec<String>,
tokens: &[String],
idx: usize,
info: &ParserInfo,
ymd: &mut YMD,
@ -983,7 +1046,9 @@ impl Parser {
// TODO: I miss the `x in y` syntax
// TODO: Decompose this logic a bit
if ymd.len() == 3 && (len_li == 2 || len_li == 4) && res.hour.is_none()
if ymd.len() == 3
&& (len_li == 2 || len_li == 4)
&& res.hour.is_none()
&& (idx + 1 >= len_l
|| (tokens[idx + 1] != ":" && info.hms_index(&tokens[idx + 1]).is_none()))
{
@ -994,14 +1059,14 @@ 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 {
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)?;
if ymd.len() == 0 && tokens[idx].find('.') == 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();
@ -1009,14 +1074,14 @@ impl Parser {
let t = self.parsems(&s[4..])?;
res.second = Some(t.0);
res.microsecond = Some(t.1);
res.nanosecond = Some(t.1);
}
} 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>()?);
@ -1029,9 +1094,8 @@ impl Parser {
} else if let Some(hms_idx) = self.find_hms_index(idx, tokens, info, true) {
// HH[ ]h or MM[ ]m or SS[.ss][ ]s
let (new_idx, hms) = self.parse_hms(idx, tokens, info, Some(hms_idx));
if hms.is_some() {
// TODO: This unwrap is unjustified.
self.assign_hms(res, value_repr, hms.unwrap());
if let Some(hms) = hms {
self.assign_hms(res, value_repr, hms)?;
}
idx = new_idx;
} else if idx + 2 < len_l && tokens[idx + 1] == ":" {
@ -1039,7 +1103,7 @@ impl Parser {
// TODO: Better story around Decimal handling
res.hour = Some(value.floor().to_i64().unwrap() as i32);
// TODO: Rescope `value` here?
value = self.to_decimal(&tokens[idx + 2]);
value = self.to_decimal(&tokens[idx + 2])?;
let min_sec = self.parse_min_sec(value);
res.minute = Some(min_sec.0);
res.second = min_sec.1;
@ -1048,7 +1112,7 @@ impl Parser {
// TODO: (x, y) = (a, b) syntax?
let ms = self.parsems(&tokens[idx + 4]).unwrap();
res.second = Some(ms.0);
res.microsecond = Some(ms.1);
res.nanosecond = Some(ms.1);
idx += 2;
}
@ -1058,7 +1122,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>() {
@ -1068,14 +1132,14 @@ impl Parser {
}
if idx + 3 < len_l && &tokens[idx + 3] == sep {
if let Some(value) = info.month_index(&tokens[idx + 4]) {
if tokens.len() <= idx + 4 {
return Err(ParseError::UnrecognizedFormat);
} else 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>() {
ymd.append(val, &tokens[idx + 4], None)?;
} else {
if let Ok(val) = tokens[idx + 4].parse::<i32>() {
ymd.append(val, &tokens[idx + 4], None)?;
} else {
return Err(ParseError::UnrecognizedFormat);
}
return Err(ParseError::UnrecognizedFormat);
}
idx += 2;
@ -1092,7 +1156,9 @@ impl Parser {
res.hour = Some(self.adjust_ampm(hour, ampm));
idx += 1;
} else {
ymd.append(value.floor().to_i64().unwrap() as i32, &value_repr, None)?;
//let value = value.floor().to_i32().ok_or(Err(ParseError::InvalidNumeric()))
let value = value.floor().to_i32().ok_or_else(|| ParseError::InvalidNumeric(value_repr.to_owned()))?;
ymd.append(value, &value_repr, None)?;
}
idx += 1;
@ -1122,13 +1188,13 @@ impl Parser {
}
}
fn parsems(&self, seconds_str: &str) -> ParseResult<(i32, i32)> {
if seconds_str.contains(".") {
let split: Vec<&str> = seconds_str.split(".").collect();
fn parsems(&self, seconds_str: &str) -> ParseResult<(i32, i64)> {
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>()?;
let f_parse = ljust(f, 6, '0').parse::<i32>()?;
let f_parse = ljust(f, 9, '0').parse::<i64>()?;
Ok((i_parse, f_parse))
} else {
Ok((seconds_str.parse::<i32>()?, 0))
@ -1138,7 +1204,7 @@ impl Parser {
fn find_hms_index(
&self,
idx: usize,
tokens: &Vec<String>,
tokens: &[String],
info: &ParserInfo,
allow_jump: bool,
) -> Option<usize> {
@ -1154,7 +1220,7 @@ impl Parser {
len_l - 2
} else if idx > 1 {
idx - 2
} else if len_l == 0{
} else if len_l == 0 {
panic!("Attempting to find_hms_index() wih no tokens.");
} else {
0
@ -1162,13 +1228,18 @@ impl Parser {
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] == " "
} else if allow_jump
&& idx + 2 < len_l
&& tokens[idx + 1] == " "
&& info.hms_index(&tokens[idx + 2]).is_some()
{
hms_idx = Some(idx + 2)
} 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] == " "
} else if len_l > 0
&& idx > 0
&& idx == len_l - 1
&& tokens[idx - 1] == " "
&& info.hms_index(&tokens[idx_minus_two]).is_some()
{
hms_idx = Some(idx - 2)
@ -1177,10 +1248,11 @@ impl Parser {
hms_idx
}
#[allow(clippy::unnecessary_unwrap)]
fn parse_hms(
&self,
idx: usize,
tokens: &Vec<String>,
tokens: &[String],
info: &ParserInfo,
hms_index: Option<usize>,
) -> (usize, Option<usize>) {
@ -1199,11 +1271,11 @@ impl Parser {
}
}
fn assign_hms(&self, res: &mut ParsingResult, value_repr: &str, hms: usize) {
let value = self.to_decimal(value_repr);
fn assign_hms(&self, res: &mut ParsingResult, value_repr: &str, hms: usize) -> ParseResult<()> {
let value = self.to_decimal(value_repr)?;
if hms == 0 {
res.hour = Some(value.to_i64().unwrap() as i32);
res.hour = value.to_i32();
if !close_to_integer(&value) {
res.minute = Some((*SIXTY * (value % *ONE)).to_i64().unwrap() as i32);
}
@ -1214,13 +1286,14 @@ impl Parser {
} else if hms == 2 {
let (sec, micro) = self.parsems(value_repr).unwrap();
res.second = Some(sec);
res.microsecond = Some(micro);
res.nanosecond = Some(micro);
}
Ok(())
}
fn to_decimal(&self, value: &str) -> Decimal {
// TODO: Justify unwrap
Decimal::from_str(value).unwrap()
fn to_decimal(&self, value: &str) -> ParseResult<Decimal> {
Decimal::from_str(value).or_else(|_| Err(ParseError::InvalidNumeric(value.to_owned())))
}
fn parse_min_sec(&self, value: Decimal) -> (i32, Option<i32>) {
@ -1246,10 +1319,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());
}
}
@ -1272,11 +1345,11 @@ 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(
let res = DEFAULT_PARSER.parse(
timestr,
None,
None,

View File

@ -1,5 +1,8 @@
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use chrono::NaiveDate;
use std::collections::HashMap;
use std::str;
use parse;
use ParseError;
@ -7,18 +10,97 @@ use Parser;
#[test]
fn test_fuzz() {
assert_eq!(parse("\x2D\x38\x31\x39\x34\x38\x34"), Err(ParseError::ImpossibleTimestamp("Invalid month")));
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::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();
assert_eq!(res.0, default);
assert_eq!(
parse("2..\x00\x000d\x00+\x010d\x01\x00\x00\x00+"),
Err(ParseError::UnrecognizedFormat)
);
assert_eq!(parse("\x2D\x2D\x32\x31\x38\x6D"), Err(ParseError::ImpossibleTimestamp("Invalid minute")));
let default = NaiveDate::from_ymd_opt(2016, 6, 29).unwrap().and_hms_opt(0, 0, 0).unwrap();
let p = Parser::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 another_large_int() {
let parse_result = parse("1412409095009");
assert!(parse_result.is_err());
}
#[test]
fn an_even_larger_int() {
let parse_result = parse("1566997680962280");
assert!(parse_result.is_err());
}
#[test]
fn empty_string() {
assert_eq!(parse(""), Err(ParseError::NoDate))
}
#[test]
fn github_33() {
assert_eq!(parse("66:'"), Err(ParseError::InvalidNumeric("'".to_owned())))
}
#[test]
fn github_32() {
assert_eq!(parse("99999999999999999999999"), Err(ParseError::InvalidNumeric("99999999999999999999999".to_owned())))
}
#[test]
fn github_34() {
let parse_vec = STANDARD.decode("KTMuLjYpGDYvLjZTNiouNjYuHzZpLjY/NkwuNh42Ry42PzYnKTMuNk02NjY2NjA2NjY2NjY2NjYTNjY2Ni82NjY2NlAuNlAuNlNI").unwrap();
let parse_str = str::from_utf8(&parse_vec).unwrap();
let parse_result = parse(parse_str);
assert!(parse_result.is_err());
}
#[test]
fn github_35() {
let parse_vec = STANDARD.decode("KTY6LjYqNio6KjYn").unwrap();
let parse_str = str::from_utf8(&parse_vec).unwrap();
let parse_result = parse(parse_str);
assert!(parse_result.is_err());
}
#[test]
fn github_36() {
let parse_vec = STANDARD.decode("KTYuLg==").unwrap();
let parse_str = str::from_utf8(&parse_vec).unwrap();
let parse_result = parse(parse_str);
assert!(parse_result.is_err());
}
#[test]
fn github_45() {
assert!(parse("/2018-fifa-").is_err());
assert!(parse("/2009/07/").is_err());
assert!(parse("2021-09-").is_err());
}

View File

@ -1,3 +1,14 @@
mod fuzzing;
mod pycompat_parser;
mod pycompat_tokenizer;
use chrono::NaiveDate;
use crate::parse;
#[test]
fn nanosecond_precision() {
assert_eq!(
parse("2008.12.29T08:09:10.123456789").unwrap(),
(NaiveDate::from_ymd_opt(2008, 12, 29).unwrap().and_hms_nano_opt(8, 9, 10, 123_456_789).unwrap(), None)
)
}

View File

@ -39,7 +39,7 @@ fn parse_and_assert(
tzinfos: &HashMap<String, i32>,
) {
let mut parser = Parser::new(info);
let parser = Parser::new(info);
let rs_parsed = parser.parse(
s,
dayfirst,
@ -89,7 +89,7 @@ fn parse_fuzzy_and_assert(
tzinfos: &HashMap<String, i32>,
) {
let mut parser = Parser::new(info);
let parser = Parser::new(info);
let rs_parsed = parser.parse(
s,
dayfirst,
@ -122,7 +122,7 @@ macro_rules! rs_tzinfo_map {
#[test]
fn test_parse_default0() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 28,
@ -135,7 +135,7 @@ fn test_parse_default0() {
#[test]
fn test_parse_default1() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 28,
@ -148,7 +148,7 @@ fn test_parse_default1() {
#[test]
fn test_parse_default2() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 28,
@ -161,7 +161,7 @@ fn test_parse_default2() {
#[test]
fn test_parse_default3() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 0,
@ -174,7 +174,7 @@ fn test_parse_default3() {
#[test]
fn test_parse_default4() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 0, second: 0,
@ -187,7 +187,7 @@ fn test_parse_default4() {
#[test]
fn test_parse_default5() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 0, second: 0,
@ -200,7 +200,7 @@ fn test_parse_default5() {
#[test]
fn test_parse_default6() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 0, second: 0,
@ -213,7 +213,7 @@ fn test_parse_default6() {
#[test]
fn test_parse_default7() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 28,
@ -226,7 +226,7 @@ fn test_parse_default7() {
#[test]
fn test_parse_default8() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 28,
@ -239,7 +239,7 @@ fn test_parse_default8() {
#[test]
fn test_parse_default9() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 0,
@ -252,7 +252,7 @@ fn test_parse_default9() {
#[test]
fn test_parse_default10() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 0, second: 0,
@ -265,7 +265,7 @@ fn test_parse_default10() {
#[test]
fn test_parse_default11() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 0,
@ -278,7 +278,7 @@ fn test_parse_default11() {
#[test]
fn test_parse_default12() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 30,
@ -291,7 +291,7 @@ fn test_parse_default12() {
#[test]
fn test_parse_default13() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 36, second: 5,
@ -304,7 +304,7 @@ fn test_parse_default13() {
#[test]
fn test_parse_default14() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 36, second: 5,
@ -317,7 +317,7 @@ fn test_parse_default14() {
#[test]
fn test_parse_default15() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 36, second: 5,
@ -330,7 +330,7 @@ fn test_parse_default15() {
#[test]
fn test_parse_default16() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 36, second: 5,
@ -343,7 +343,7 @@ fn test_parse_default16() {
#[test]
fn test_parse_default17() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 0, second: 0,
@ -356,7 +356,7 @@ fn test_parse_default17() {
#[test]
fn test_parse_default18() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 22, minute: 0, second: 0,
@ -369,7 +369,7 @@ fn test_parse_default18() {
#[test]
fn test_parse_default19() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 0, second: 0,
@ -382,7 +382,7 @@ fn test_parse_default19() {
#[test]
fn test_parse_default20() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 22, minute: 0, second: 0,
@ -395,7 +395,7 @@ fn test_parse_default20() {
#[test]
fn test_parse_default21() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 0, second: 0,
@ -408,7 +408,7 @@ fn test_parse_default21() {
#[test]
fn test_parse_default22() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 22, minute: 0, second: 0,
@ -421,7 +421,7 @@ fn test_parse_default22() {
#[test]
fn test_parse_default23() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 0, second: 0,
@ -434,7 +434,7 @@ fn test_parse_default23() {
#[test]
fn test_parse_default24() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 22, minute: 0, second: 0,
@ -447,7 +447,7 @@ fn test_parse_default24() {
#[test]
fn test_parse_default25() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 0, second: 0,
@ -460,7 +460,7 @@ fn test_parse_default25() {
#[test]
fn test_parse_default26() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 22, minute: 0, second: 0,
@ -473,7 +473,7 @@ fn test_parse_default26() {
#[test]
fn test_parse_default27() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 0, second: 0,
@ -486,7 +486,7 @@ fn test_parse_default27() {
#[test]
fn test_parse_default28() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 22, minute: 0, second: 0,
@ -499,7 +499,7 @@ fn test_parse_default28() {
#[test]
fn test_parse_default29() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 10, day: 25,
hour: 0, minute: 0, second: 0,
@ -512,7 +512,7 @@ fn test_parse_default29() {
#[test]
fn test_parse_default30() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2000, month: 12, day: 31,
hour: 0, minute: 0, second: 0,
@ -525,7 +525,7 @@ fn test_parse_default30() {
#[test]
fn test_parse_default31() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 1, second: 2,
@ -538,7 +538,7 @@ fn test_parse_default31() {
#[test]
fn test_parse_default32() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 1, second: 2,
@ -551,7 +551,7 @@ fn test_parse_default32() {
#[test]
fn test_parse_default33() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 12, minute: 8, second: 0,
@ -564,7 +564,7 @@ fn test_parse_default33() {
#[test]
fn test_parse_default34() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 1, minute: 2, second: 3,
@ -577,7 +577,7 @@ fn test_parse_default34() {
#[test]
fn test_parse_default35() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 1, minute: 2, second: 0,
@ -590,7 +590,7 @@ fn test_parse_default35() {
#[test]
fn test_parse_default36() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 1, minute: 0, second: 2,
@ -603,7 +603,7 @@ fn test_parse_default36() {
#[test]
fn test_parse_default37() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 1, second: 2,
@ -616,7 +616,7 @@ fn test_parse_default37() {
#[test]
fn test_parse_default38() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 2, minute: 1, second: 0,
@ -629,7 +629,7 @@ fn test_parse_default38() {
#[test]
fn test_parse_default39() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2004, month: 4, day: 10,
hour: 11, minute: 30, second: 0,
@ -642,7 +642,7 @@ fn test_parse_default39() {
#[test]
fn test_parse_default40() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 3,
hour: 0, minute: 0, second: 0,
@ -655,7 +655,7 @@ fn test_parse_default40() {
#[test]
fn test_parse_default41() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 0, minute: 0, second: 0,
@ -668,7 +668,7 @@ fn test_parse_default41() {
#[test]
fn test_parse_default42() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2017, month: 11, day: 25,
hour: 2, minute: 17, second: 0,
@ -681,7 +681,7 @@ fn test_parse_default42() {
#[test]
fn test_parse_default43() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 28,
@ -694,7 +694,7 @@ fn test_parse_default43() {
#[test]
fn test_parse_default44() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 9, day: 25,
hour: 10, minute: 36, second: 28,
@ -707,7 +707,7 @@ fn test_parse_default44() {
#[test]
fn test_parse_default45() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 10, day: 1,
hour: 0, minute: 0, second: 0,
@ -720,7 +720,7 @@ fn test_parse_default45() {
#[test]
fn test_parse_default46() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2003, 9, 25).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2003, month: 10, day: 1,
hour: 0, minute: 0, second: 0,
@ -1434,6 +1434,42 @@ fn test_parse_offset4() {
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]
fn test_parse_dayfirst0() {
let info = ParserInfo::default();
@ -1605,7 +1641,7 @@ fn test_parse_dfyf1() {
#[test]
fn test_unspecified_fallback0() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2010, 1, 31).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2010, 1, 31).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2009, month: 4, day: 30,
hour: 0, minute: 0, second: 0,
@ -1618,7 +1654,7 @@ fn test_unspecified_fallback0() {
#[test]
fn test_unspecified_fallback1() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2010, 1, 31).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2010, 1, 31).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2007, month: 2, day: 28,
hour: 0, minute: 0, second: 0,
@ -1631,7 +1667,7 @@ fn test_unspecified_fallback1() {
#[test]
fn test_unspecified_fallback2() {
let info = ParserInfo::default();
let default_rsdate = &NaiveDate::from_ymd(2010, 1, 31).and_hms(0, 0, 0);
let default_rsdate = &NaiveDate::from_ymd_opt(2010, 1, 31).unwrap().and_hms_opt(0, 0, 0).unwrap();
let pdt = PyDateTime {
year: 2008, month: 2, day: 29,
hour: 0, minute: 0, second: 0,
@ -1727,6 +1763,18 @@ 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, month: 4, day: 4,

View File

@ -1,4 +1,3 @@
//! 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.
@ -12,7 +11,9 @@ fn tokenize_assert(test_str: &str, comparison: Vec<&str>) {
#[test]
fn test_tokenize0() {
let comp = vec!["Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28"];
let comp = vec![
"Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28",
];
tokenize_assert("Thu Sep 25 10:36:28", comp);
}
@ -294,7 +295,9 @@ fn test_tokenize46() {
#[test]
fn test_tokenize47() {
let comp = vec!["Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", " ", "2003"];
let comp = vec![
"Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", " ", "2003",
];
tokenize_assert("Thu Sep 25 10:36:28 2003", comp);
}
@ -306,7 +309,9 @@ fn test_tokenize48() {
#[test]
fn test_tokenize49() {
let comp = vec!["2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41"];
let comp = vec![
"2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41",
];
tokenize_assert("2003-09-25T10:49:41", comp);
}
@ -354,7 +359,9 @@ fn test_tokenize56() {
#[test]
fn test_tokenize57() {
let comp = vec!["2003", "-", "09", "-", "25", " ", "10", ":", "49", ":", "41.502"];
let comp = vec![
"2003", "-", "09", "-", "25", " ", "10", ":", "49", ":", "41.502",
];
tokenize_assert("2003-09-25 10:49:41,502", comp);
}
@ -510,7 +517,10 @@ fn test_tokenize82() {
#[test]
fn test_tokenize83() {
let comp = vec![" ", " ", "July", " ", " ", " ", "4", " ", ",", " ", " ", "1976", " ", " ", " ", "12", ":", "01", ":", "02", " ", " ", " ", "am", " ", " "];
let comp = vec![
" ", " ", "July", " ", " ", " ", "4", " ", ",", " ", " ", "1976", " ", " ", " ", "12", ":",
"01", ":", "02", " ", " ", " ", "am", " ", " ",
];
tokenize_assert(" July 4 , 1976 12:01:02 am ", comp);
}
@ -522,7 +532,9 @@ fn test_tokenize84() {
#[test]
fn test_tokenize85() {
let comp = vec!["1996", ".", "July", ".", "10", " ", "AD", " ", "12", ":", "08", " ", "PM"];
let comp = vec![
"1996", ".", "July", ".", "10", " ", "AD", " ", "12", ":", "08", " ", "PM",
];
tokenize_assert("1996.July.10 AD 12:08 PM", comp);
}
@ -558,25 +570,33 @@ fn test_tokenize90() {
#[test]
fn test_tokenize91() {
let comp = vec!["0", ":", "01", ":", "02", " ", "on", " ", "July", " ", "4", ",", " ", "1976"];
let comp = vec![
"0", ":", "01", ":", "02", " ", "on", " ", "July", " ", "4", ",", " ", "1976",
];
tokenize_assert("0:01:02 on July 4, 1976", comp);
}
#[test]
fn test_tokenize92() {
let comp = vec!["0", ":", "01", ":", "02", " ", "on", " ", "July", " ", "4", ",", " ", "1976"];
let comp = vec![
"0", ":", "01", ":", "02", " ", "on", " ", "July", " ", "4", ",", " ", "1976",
];
tokenize_assert("0:01:02 on July 4, 1976", comp);
}
#[test]
fn test_tokenize93() {
let comp = vec!["July", " ", "4", ",", " ", "1976", " ", "12", ":", "01", ":", "02", " ", "am"];
let comp = vec![
"July", " ", "4", ",", " ", "1976", " ", "12", ":", "01", ":", "02", " ", "am",
];
tokenize_assert("July 4, 1976 12:01:02 am", comp);
}
#[test]
fn test_tokenize94() {
let comp = vec!["Mon", " ", "Jan", " ", " ", "2", " ", "04", ":", "24", ":", "27", " ", "1995"];
let comp = vec![
"Mon", " ", "Jan", " ", " ", "2", " ", "04", ":", "24", ":", "27", " ", "1995",
];
tokenize_assert("Mon Jan 2 04:24:27 1995", comp);
}
@ -588,7 +608,9 @@ fn test_tokenize95() {
#[test]
fn test_tokenize96() {
let comp = vec!["Jan", " ", "1", " ", "1999", " ", "11", ":", "23", ":", "34.578"];
let comp = vec![
"Jan", " ", "1", " ", "1999", " ", "11", ":", "23", ":", "34.578",
];
tokenize_assert("Jan 1 1999 11:23:34.578", comp);
}
@ -618,13 +640,17 @@ fn test_tokenize100() {
#[test]
fn test_tokenize101() {
let comp = vec!["0099", "-", "01", "-", "01", "T", "00", ":", "00", ":", "00"];
let comp = vec![
"0099", "-", "01", "-", "01", "T", "00", ":", "00", ":", "00",
];
tokenize_assert("0099-01-01T00:00:00", comp);
}
#[test]
fn test_tokenize102() {
let comp = vec!["0031", "-", "01", "-", "01", "T", "00", ":", "00", ":", "00"];
let comp = vec![
"0031", "-", "01", "-", "01", "T", "00", ":", "00", ":", "00",
];
tokenize_assert("0031-01-01T00:00:00", comp);
}
@ -666,31 +692,42 @@ fn test_tokenize108() {
#[test]
fn test_tokenize109() {
let comp = vec!["Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", " ", "BRST", " ", "2003"];
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_tokenize110() {
let comp = vec!["2003", " ", "10", ":", "36", ":", "28", " ", "BRST", " ", "25", " ", "Sep", " ", "Thu"];
let comp = vec![
"2003", " ", "10", ":", "36", ":", "28", " ", "BRST", " ", "25", " ", "Sep", " ", "Thu",
];
tokenize_assert("2003 10:36:28 BRST 25 Sep Thu", comp);
}
#[test]
fn test_tokenize111() {
let comp = vec!["Thu", ",", " ", "25", " ", "Sep", " ", "2003", " ", "10", ":", "49", ":", "41", " ", "-", "0300"];
let comp = vec![
"Thu", ",", " ", "25", " ", "Sep", " ", "2003", " ", "10", ":", "49", ":", "41", " ", "-",
"0300",
];
tokenize_assert("Thu, 25 Sep 2003 10:49:41 -0300", comp);
}
#[test]
fn test_tokenize112() {
let comp = vec!["2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41.5", "-", "03", ":", "00"];
let comp = vec![
"2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41.5", "-", "03", ":", "00",
];
tokenize_assert("2003-09-25T10:49:41.5-03:00", comp);
}
#[test]
fn test_tokenize113() {
let comp = vec!["2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41", "-", "03", ":", "00"];
let comp = vec![
"2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41", "-", "03", ":", "00",
];
tokenize_assert("2003-09-25T10:49:41-03:00", comp);
}
@ -708,198 +745,346 @@ 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() {
let comp = vec!["Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", " ", "BRST", " ", "2003"];
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() {
let comp = vec!["1996", ".", "07", ".", "10", " ", "AD", " ", "at", " ", "15", ":", "08", ":", "56", " ", "PDT"];
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() {
let comp = vec!["Tuesday", ",", " ", "April", " ", "12", ",", " ", "1952", " ", "AD", " ", "3", ":", "30", ":", "42", "pm", " ", "PST"];
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() {
let comp = vec!["November", " ", "5", ",", " ", "1994", ",", " ", "8", ":", "15", ":", "30", " ", "am", " ", "EST"];
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() {
let comp = vec!["1994", "-", "11", "-", "05", "T", "08", ":", "15", ":", "30", "-", "05", ":", "00"];
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() {
let comp = vec!["1994", "-", "11", "-", "05", "T", "08", ":", "15", ":", "30", "Z"];
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() {
let comp = vec!["1976", "-", "07", "-", "04", "T", "00", ":", "01", ":", "02", "Z"];
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() {
let comp = vec!["Tue", " ", "Apr", " ", "4", " ", "00", ":", "22", ":", "12", " ", "PDT", " ", "1995"];
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() {
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() {
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() {
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() {
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);
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_tokenize145() {
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);
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_tokenize146() {
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);
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_tokenize147() {
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);
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_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_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_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_tokenize151() {
let comp = vec!["2017", "-", "07", "-", "17", " ", "06", ":", "15", ":"];
tokenize_assert("2017-07-17 06:15:", comp);
}

View File

@ -14,7 +14,6 @@ pub(crate) enum ParseState {
}
impl Tokenizer {
pub(crate) fn new(parse_string: &str) -> Self {
Tokenizer {
token_stack: vec![],
@ -92,7 +91,7 @@ impl Iterator for Tokenizer {
} else {
break;
}
},
}
ParseState::Alpha => {
seenletters = true;
if self.isword(nextchar) {
@ -105,19 +104,21 @@ impl Iterator for Tokenizer {
self.parse_string.push(nextchar);
break;
}
},
}
ParseState::Numeric => {
if self.isnum(nextchar) {
// UNWRAP: Because we're in non-empty parse state, we're guaranteed to have a token
token.as_mut().unwrap().push(nextchar);
} else if nextchar == '.' || (nextchar == ',' && token.as_ref().unwrap().len() >= 2) {
} else if nextchar == '.'
|| (nextchar == ',' && token.as_ref().unwrap().len() >= 2)
{
token.as_mut().unwrap().push(nextchar);
state = ParseState::NumericDecimal;
} else {
self.parse_string.push(nextchar);
break;
}
},
}
ParseState::AlphaDecimal => {
seenletters = true;
if nextchar == '.' || self.isword(nextchar) {
@ -130,7 +131,7 @@ impl Iterator for Tokenizer {
self.parse_string.push(nextchar);
break;
}
},
}
ParseState::NumericDecimal => {
if nextchar == '.' || self.isnum(nextchar) {
// UNWRAP: Because we're in non-empty parse state, we're guaranteed to have a token
@ -150,20 +151,25 @@ impl Iterator for Tokenizer {
// We do something slightly different to express the same logic
if state == ParseState::AlphaDecimal || state == ParseState::NumericDecimal {
// UNWRAP: The state check guarantees that we have a value
let dot_count = token.as_ref().unwrap().chars().filter(|c| *c == '.').count();
let dot_count = token
.as_ref()
.unwrap()
.chars()
.filter(|c| *c == '.')
.count();
let last_char = token.as_ref().unwrap().chars().last();
let last_splittable = last_char == Some('.') || last_char == Some(',');
if seenletters || dot_count > 1 || last_splittable {
let mut l = self.decimal_split(token.as_ref().unwrap());
let remaining = l.split_off(1);
token = Some(l[0].clone());
for t in remaining {
self.token_stack.push(t);
}
}
if state == ParseState::NumericDecimal && dot_count == 0 {
token = Some(token.unwrap().replace(',', "."));
}

View File

@ -1,5 +1,5 @@
use ParseResult;
use ParseError;
use ParseResult;
#[derive(Debug, PartialEq)]
pub enum DayOfWeek {
@ -9,13 +9,12 @@ pub enum DayOfWeek {
Wednesday,
Thursday,
Friday,
Saturday
Saturday,
}
impl DayOfWeek {
pub fn to_numeral(&self) -> u32 {
match self {
match *self {
DayOfWeek::Sunday => 0,
DayOfWeek::Monday => 1,
DayOfWeek::Tuesday => 2,
@ -35,12 +34,12 @@ impl DayOfWeek {
4 => DayOfWeek::Thursday,
5 => DayOfWeek::Friday,
6 => DayOfWeek::Saturday,
_ => panic!("Unreachable.")
_ => panic!("Unreachable."),
}
}
/// 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;
@ -59,12 +58,12 @@ pub fn day_of_week(year: u32, month: u32, day: u32) -> ParseResult<DayOfWeek> {
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 => {
let c = year / 100;
(c, year - 100 * c)
},
}
1 | 2 => {
let c = (year - 1) / 100;
(c, year - 1 - 100 * c)
},
_ => return Err(ParseError::ImpossibleTimestamp("Invalid month"))
}
_ => return Err(ParseError::ImpossibleTimestamp("Invalid month")),
};
let e = match month {
@ -75,7 +74,7 @@ pub fn day_of_week(year: u32, month: u32, day: u32) -> ParseResult<DayOfWeek> {
8 => 1,
9 | 12 => 4,
10 => 6,
_ => panic!("Unreachable.")
_ => panic!("Unreachable."),
};
// This implementation is Gregorian-only.
@ -84,7 +83,7 @@ pub fn day_of_week(year: u32, month: u32, day: u32) -> ParseResult<DayOfWeek> {
1 => 5,
2 => 3,
3 => 1,
_ => panic!("Unreachable.")
_ => panic!("Unreachable."),
};
match (day + e + f + g + g / 4) % 7 {
@ -95,7 +94,7 @@ pub fn day_of_week(year: u32, month: u32, day: u32) -> ParseResult<DayOfWeek> {
4 => Ok(DayOfWeek::Thursday),
5 => Ok(DayOfWeek::Friday),
6 => Ok(DayOfWeek::Saturday),
_ => panic!("Unreachable.")
_ => panic!("Unreachable."),
}
}
@ -114,19 +113,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);
}
}
}