├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── Cargo.toml ├── LICENSE ├── LICENSE-APACHE ├── README.md ├── build_pycompat.py ├── build_pycompat_tokenizer.py ├── examples └── russian.rs └── src ├── lib.rs ├── tests ├── fuzzing.rs ├── mod.rs ├── pycompat_parser.rs └── pycompat_tokenizer.rs ├── tokenize.rs └── weekday.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | .vscode 6 | *.pyc 7 | .idea/ 8 | *.swp 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version 1.0.3 (2018-09-18) 2 | ========================== 3 | 4 | Misc 5 | ---- 6 | 7 | - Changed the default `parse` function to use a static parser 8 | 9 | Version 1.0.2 (2018-08-14) 10 | ========================== 11 | 12 | Misc 13 | ---- 14 | 15 | - Add tests for WASM 16 | 17 | Version 1.0.1 (2018-08-11) 18 | ========================== 19 | 20 | Bugfixes 21 | -------- 22 | 23 | - Fixed an issue with "GMT+3" not being handled correctly 24 | 25 | Misc 26 | ---- 27 | 28 | - Upgrade `lazy_static` and `rust_decimal` dependencies 29 | 30 | Version 1.0.0 (2018-08-03) 31 | ========================== 32 | 33 | Initial release. Passes all relevant unit tests from Python's 34 | `dateutil` project. 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The `dtparse` crate is better for the contributions made by members of the open source community, 4 | and seeks to make it easy to contribute back to the community it comes from. The goals are 5 | fairly straight-forward, but here are the ways that would be most beneficial: 6 | 7 | ## Bug Reports 8 | 9 | The testing suite for `dtparse` is built using tests derived from the [`dateutil`](https://github.com/dateutil/dateutil) 10 | package in Python. Some Rust-specific behavior may show up though, for example in how 11 | Rust handles nanoseconds where Python's standard library will only go to microseconds. 12 | 13 | If you believe that behavior is improper, you are encouraged to file an issue; there are no dumb 14 | issues or suggestions, and the world is a better place for having your input. 15 | 16 | ## Testing/Fuzzing 17 | 18 | `dtparse`'s history as a port of Python software has led to some behavior being shown in Rust 19 | that would not otherwise be an issue in Python. Testing for these issues to prevent panics 20 | is greatly appreciated, and some great work has already happened surrounding fuzzing. 21 | 22 | New test cases built either by fuzzers or humans are welcome. 23 | 24 | ## Feature Requests 25 | 26 | Handling weird date formats and quirks is the name of the game. Any ideas on how to improve that 27 | or utilities useful in handling the mapping of human time to computers is appreciated. 28 | 29 | Writing code to implement the feature is never mandatory (though always appreciated); if there's 30 | something you believe `dtparse` should do that it doesn't currently support, let's make that happen. 31 | 32 | # Development Setup 33 | 34 | The setup requirements for `dtparse` should be fairly straightforward - the project can be built 35 | and deployed using only the `cargo` tool in Rust. 36 | 37 | Much of the test coee is generated from Python code, and then the generated versions are stored 38 | in version control. Thi is to ensure that all users can run the tests even without 39 | installing Python or the other necessary packages. 40 | 41 | To regenerate the tests, please use Python 3.6 with the `dateutil` package installed, and run: 42 | 43 | - `python build_pycompat.py` 44 | - `python build_pycompat_tokenizer.py` 45 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | This project benefits from the Rust and open source communities, but most specifically from these people: 2 | 3 | # Contributors: 4 | 5 | - [@messense](https://github.com/messense) 6 | - [@mjmeehan](https://github.com/mjmeehan) 7 | - [@neosilky](https://github.com/neosilky) -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dtparse" 3 | version = "2.0.1" 4 | authors = ["Bradlee Speice "] 5 | description = "A dateutil-compatible timestamp parser for Rust" 6 | repository = "https://github.com/bspeice/dtparse.git" 7 | readme = "README.md" 8 | categories = ["date-and-time"] 9 | license = "Apache-2.0" 10 | exclude = ["/*.py"] 11 | 12 | [badges] 13 | travis-ci = { repository = "bspeice/dtparse" } 14 | maintenance = { status = "passively-maintained" } 15 | 16 | [lib] 17 | name = "dtparse" 18 | 19 | [dependencies] 20 | chrono = { version = "0.4.24", default-features = false, features = ["clock"] } 21 | lazy_static = "1.4.0" 22 | num-traits = "0.2.15" 23 | rust_decimal = { version = "1.29.1", default-features = false } 24 | 25 | [dev-dependencies] 26 | base64 = "0.21.0" 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Bradlee Speice (bradlee@speice.io) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dtparse 2 | 3 | [![crates.io](https://img.shields.io/crates/v/dtparse.svg)](https://crates.io/crates/dtparse) 4 | [![docs.rs](https://docs.rs/dtparse/badge.svg)](https://docs.rs/dtparse/) 5 | 6 | 7 | The fully-featured "even I couldn't understand that" time parser. 8 | Designed to take in strings and give back sensible dates and times. 9 | 10 | dtparse has its foundations in the [`dateutil`](dateutil) library for 11 | Python, which excels at taking "interesting" strings and trying to make 12 | sense of the dates and times they contain. A couple of quick examples 13 | from the test cases should give some context: 14 | 15 | ```rust 16 | extern crate chrono; 17 | extern crate dtparse; 18 | use chrono::prelude::*; 19 | use dtparse::parse; 20 | 21 | assert_eq!( 22 | parse("2008.12.30"), 23 | Ok((NaiveDate::from_ymd(2008, 12, 30).and_hms(0, 0, 0), None)) 24 | ); 25 | 26 | // It can even handle timezones! 27 | assert_eq!( 28 | parse("January 4, 2024; 18:30:04 +02:00"), 29 | Ok(( 30 | NaiveDate::from_ymd(2024, 1, 4).and_hms(18, 30, 4), 31 | Some(FixedOffset::east(7200)) 32 | )) 33 | ); 34 | ``` 35 | 36 | And we can even handle fuzzy strings where dates/times aren't the 37 | only content if we dig into the implementation a bit! 38 | 39 | ```rust 40 | extern crate chrono; 41 | extern crate dtparse; 42 | use chrono::prelude::*; 43 | use dtparse::Parser; 44 | use std::collections::HashMap; 45 | 46 | let mut p = Parser::default(); 47 | assert_eq!( 48 | p.parse( 49 | "I first released this library on the 17th of June, 2018.", 50 | None, None, 51 | true /* turns on fuzzy mode */, 52 | true /* gives us the tokens that weren't recognized */, 53 | None, false, &HashMap::new() 54 | ), 55 | Ok(( 56 | NaiveDate::from_ymd(2018, 6, 17).and_hms(0, 0, 0), 57 | None, 58 | Some(vec!["I first released this library on the ", 59 | " of ", ", "].iter().map(|&s| s.into()).collect()) 60 | )) 61 | ); 62 | ``` 63 | 64 | Further examples can be found in the [examples](examples) directory on international usage. 65 | 66 | # Usage 67 | 68 | `dtparse` requires a minimum Rust version of 1.28 to build, but is tested on Windows, OSX, 69 | BSD, Linux, and WASM. The build is also compiled against the iOS and Android SDK's, but is not 70 | tested against them. 71 | 72 | [dateutil]: https://github.com/dateutil/dateutil 73 | [examples]: https://github.com/bspeice/dtparse/tree/master/examples 74 | -------------------------------------------------------------------------------- /build_pycompat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from dateutil.parser import parse 3 | from dateutil.tz import tzutc 4 | from datetime import datetime 5 | 6 | tests = { 7 | 'test_parse_default': [ 8 | "Thu Sep 25 10:36:28", 9 | "Sep 10:36:28", "10:36:28", "10:36", "Sep 2003", "Sep", "2003", 10 | "10h36m28.5s", "10h36m28s", "10h36m", "10h", "10 h 36", "10 h 36.5", 11 | "36 m 5", "36 m 5 s", "36 m 05", "36 m 05 s", "10h am", "10h pm", 12 | "10am", "10pm", "10:00 am", "10:00 pm", "10:00am", "10:00pm", 13 | "10:00a.m", "10:00p.m", "10:00a.m.", "10:00p.m.", 14 | "October", "31-Dec-00", "0:01:02", "12h 01m02s am", "12:08 PM", 15 | "01h02m03", "01h02", "01h02s", "01m02", "01m02h", "2004 10 Apr 11h30m", 16 | # testPertain 17 | 'Sep 03', 'Sep of 03', 18 | # test_hmBY - Note: This appears to be Python 3 only, no idea why 19 | '02:17NOV2017', 20 | # Weekdays 21 | "Thu Sep 10:36:28", "Thu 10:36:28", "Wed", "Wednesday" 22 | ], 23 | 'test_parse_simple': [ 24 | "Thu Sep 25 10:36:28 2003", "Thu Sep 25 2003", "2003-09-25T10:49:41", 25 | "2003-09-25T10:49", "2003-09-25T10", "2003-09-25", "20030925T104941", 26 | "20030925T1049", "20030925T10", "20030925", "2003-09-25 10:49:41,502", 27 | "199709020908", "19970902090807", "2003-09-25", "09-25-2003", 28 | "25-09-2003", "10-09-2003", "10-09-03", "2003.09.25", "09.25.2003", 29 | "25.09.2003", "10.09.2003", "10.09.03", "2003/09/25", "09/25/2003", 30 | "25/09/2003", "10/09/2003", "10/09/03", "2003 09 25", "09 25 2003", 31 | "25 09 2003", "10 09 2003", "10 09 03", "25 09 03", "03 25 Sep", 32 | "25 03 Sep", " July 4 , 1976 12:01:02 am ", 33 | "Wed, July 10, '96", "1996.July.10 AD 12:08 PM", "July 4, 1976", 34 | "7 4 1976", "4 jul 1976", "7-4-76", "19760704", 35 | "0:01:02 on July 4, 1976", "0:01:02 on July 4, 1976", 36 | "July 4, 1976 12:01:02 am", "Mon Jan 2 04:24:27 1995", 37 | "04.04.95 00:22", "Jan 1 1999 11:23:34.578", "950404 122212", 38 | "3rd of May 2001", "5th of March 2001", "1st of May 2003", 39 | '0099-01-01T00:00:00', '0031-01-01T00:00:00', 40 | "20080227T21:26:01.123456789", '13NOV2017', '0003-03-04', 41 | 'December.0031.30', 42 | # testNoYearFirstNoDayFirst 43 | '090107', 44 | # test_mstridx 45 | '2015-15-May', 46 | ], 47 | 'test_parse_tzinfo': [ 48 | 'Thu Sep 25 10:36:28 BRST 2003', '2003 10:36:28 BRST 25 Sep Thu', 49 | ], 50 | 'test_parse_offset': [ 51 | 'Thu, 25 Sep 2003 10:49:41 -0300', '2003-09-25T10:49:41.5-03:00', 52 | '2003-09-25T10:49:41-03:00', '20030925T104941.5-0300', 53 | '20030925T104941-0300', 54 | # dtparse-specific 55 | "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" 56 | ], 57 | 'test_parse_dayfirst': [ 58 | '10-09-2003', '10.09.2003', '10/09/2003', '10 09 2003', 59 | # testDayFirst 60 | '090107', 61 | # testUnambiguousDayFirst 62 | '2015 09 25' 63 | ], 64 | 'test_parse_yearfirst': [ 65 | '10-09-03', '10.09.03', '10/09/03', '10 09 03', 66 | # testYearFirst 67 | '090107', 68 | # testUnambiguousYearFirst 69 | '2015 09 25' 70 | ], 71 | 'test_parse_dfyf': [ 72 | # testDayFirstYearFirst 73 | '090107', 74 | # testUnambiguousDayFirstYearFirst 75 | '2015 09 25' 76 | ], 77 | 'test_unspecified_fallback': [ 78 | 'April 2009', 'Feb 2007', 'Feb 2008' 79 | ], 80 | 'test_parse_ignoretz': [ 81 | 'Thu Sep 25 10:36:28 BRST 2003', '1996.07.10 AD at 15:08:56 PDT', 82 | 'Tuesday, April 12, 1952 AD 3:30:42pm PST', 83 | 'November 5, 1994, 8:15:30 am EST', '1994-11-05T08:15:30-05:00', 84 | '1994-11-05T08:15:30Z', '1976-07-04T00:01:02Z', '1986-07-05T08:15:30z', 85 | 'Tue Apr 4 00:22:12 PDT 1995' 86 | ], 87 | 'test_fuzzy_tzinfo': [ 88 | 'Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.' 89 | ], 90 | 'test_fuzzy_tokens_tzinfo': [ 91 | 'Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.' 92 | ], 93 | 'test_fuzzy_simple': [ 94 | 'I have a meeting on March 1, 1974', # testFuzzyAMPMProblem 95 | 'On June 8th, 2020, I am going to be the first man on Mars', # testFuzzyAMPMProblem 96 | 'Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003', # testFuzzyAMPMProblem 97 | 'Meet me at 3:00 AM on December 3rd, 2003 at the AM/PM on Sunset', # testFuzzyAMPMProblem 98 | 'Jan 29, 1945 14:45 AM I going to see you there?', # testFuzzyIgnoreAMPM 99 | '2017-07-17 06:15:', # test_idx_check 100 | ], 101 | 'test_parse_default_ignore': [ 102 | ], 103 | } 104 | 105 | def main(): 106 | with open('src/tests/pycompat_parser.rs', 'w+') as handle: 107 | handle.write(TEST_HEADER) 108 | 109 | for test_name, test_strings in tests.items(): 110 | for i, s in enumerate(test_strings): 111 | handle.write(globals()[test_name].__call__(i, s)) 112 | 113 | 114 | def test_parse_default(i, s): 115 | default = datetime(2003, 9, 25) 116 | d = parse(s, default=default) 117 | 118 | return TEST_PARSE_DEFAULT.format(i=i, d=d, s=s) 119 | 120 | 121 | def test_parse_simple(i, s): 122 | d = parse(s) 123 | 124 | return TEST_PARSE_SIMPLE.format(i=i, d=d, s=s) 125 | 126 | 127 | def test_parse_tzinfo(i, s): 128 | tzinfo = {'BRST': -10800} 129 | d = parse(s, tzinfos=tzinfo) 130 | 131 | return TEST_PARSE_TZINFO.format(i=i, d=d, s=s, offset=int(d.tzinfo._offset.total_seconds())) 132 | 133 | 134 | def test_parse_offset(i, s): 135 | d = parse(s) 136 | return TEST_PARSE_OFFSET.format(i=i, d=d, s=s, offset=int(d.tzinfo._offset.total_seconds())) 137 | 138 | 139 | def test_parse_dayfirst(i, s): 140 | d = parse(s, dayfirst=True) 141 | return TEST_PARSE_DAYFIRST.format(i=i, d=d, s=s) 142 | 143 | 144 | def test_parse_yearfirst(i, s): 145 | d = parse(s, yearfirst=True) 146 | return TEST_PARSE_YEARFIRST.format(i=i, d=d, s=s) 147 | 148 | 149 | def test_parse_dfyf(i, s): 150 | d = parse(s, dayfirst=True, yearfirst=True) 151 | return TEST_PARSE_DFYF.format(i=i, d=d, s=s) 152 | 153 | 154 | def test_unspecified_fallback(i, s): 155 | d = parse(s, default=datetime(2010, 1, 31)) 156 | return TEST_UNSPECIFIED_FALLBACK.format(i=i, d=d, s=s) 157 | 158 | 159 | def test_parse_ignoretz(i, s): 160 | d = parse(s, ignoretz=True) 161 | return TEST_PARSE_IGNORETZ.format(i=i, d=d, s=s) 162 | 163 | 164 | def test_parse_default_ignore(i, s): 165 | default = datetime(2003, 9, 25) 166 | d = parse(s, default=default) 167 | 168 | return TEST_PARSE_DEFAULT_IGNORE.format(i=i, d=d, s=s) 169 | 170 | 171 | def test_fuzzy_tzinfo(i, s): 172 | d = parse(s, fuzzy=True) 173 | 174 | return TEST_FUZZY_TZINFO.format(i=i, d=d, s=s, offset=int(d.tzinfo._offset.total_seconds())) 175 | 176 | 177 | def test_fuzzy_tokens_tzinfo(i, s): 178 | d, tokens = parse(s, fuzzy_with_tokens=True) 179 | 180 | r_tokens = ", ".join(list(map(lambda s: f'"{s}".to_owned()', tokens))) 181 | 182 | return TEST_FUZZY_TOKENS_TZINFO.format( 183 | i=i, d=d, s=s, offset=int(d.tzinfo._offset.total_seconds()), 184 | tokens=r_tokens 185 | ) 186 | 187 | 188 | def test_fuzzy_simple(i, s): 189 | d = parse(s, fuzzy=True) 190 | 191 | return TEST_FUZZY_SIMPLE.format(i=i, d=d, s=s) 192 | 193 | 194 | # Here lies all the ugly junk. 195 | TEST_HEADER = ''' 196 | //! This code has been generated by running the `build_pycompat.py` script 197 | //! in the repository root. Please do not edit it, as your edits will be destroyed 198 | //! upon re-running code generation. 199 | 200 | extern crate chrono; 201 | 202 | use chrono::Datelike; 203 | use chrono::NaiveDate; 204 | use chrono::NaiveDateTime; 205 | use chrono::Timelike; 206 | use std::collections::HashMap; 207 | 208 | use Parser; 209 | use ParserInfo; 210 | use parse; 211 | 212 | struct PyDateTime { 213 | year: i32, 214 | month: u32, 215 | day: u32, 216 | hour: u32, 217 | minute: u32, 218 | second: u32, 219 | micros: u32, 220 | tzo: Option 221 | } 222 | 223 | fn parse_and_assert( 224 | pdt: PyDateTime, 225 | info: ParserInfo, 226 | s: &str, 227 | dayfirst: Option, 228 | yearfirst: Option, 229 | fuzzy: bool, 230 | fuzzy_with_tokens: bool, 231 | default: Option<&NaiveDateTime>, 232 | ignoretz: bool, 233 | tzinfos: &HashMap, 234 | ) { 235 | 236 | let parser = Parser::new(info); 237 | let rs_parsed = parser.parse( 238 | s, 239 | dayfirst, 240 | yearfirst, 241 | fuzzy, 242 | fuzzy_with_tokens, 243 | default, 244 | ignoretz, 245 | tzinfos).expect(&format!("Unable to parse date in Rust '{}'", s)); 246 | 247 | assert_eq!(pdt.year, rs_parsed.0.year(), "Year mismatch for '{}'", s); 248 | assert_eq!(pdt.month, rs_parsed.0.month(), "Month mismatch for '{}'", s); 249 | assert_eq!(pdt.day, rs_parsed.0.day(), "Day mismatch for '{}'", s); 250 | assert_eq!(pdt.hour, rs_parsed.0.hour(), "Hour mismatch for '{}'", s); 251 | assert_eq!(pdt.minute, rs_parsed.0.minute(), "Minute mismatch f'or' {}", s); 252 | assert_eq!(pdt.second, rs_parsed.0.second(), "Second mismatch for '{}'", s); 253 | assert_eq!(pdt.micros, rs_parsed.0.timestamp_subsec_micros(), "Microsecond mismatch for '{}'", s); 254 | assert_eq!(pdt.tzo, rs_parsed.1.map(|u| u.local_minus_utc()), "Timezone Offset mismatch for '{}'", s); 255 | } 256 | 257 | fn parse_and_assert_simple( 258 | pdt: PyDateTime, 259 | s: &str, 260 | ) { 261 | let rs_parsed = parse(s).expect(&format!("Unable to parse date in Rust '{}'", s)); 262 | assert_eq!(pdt.year, rs_parsed.0.year(), "Year mismatch for '{}'", s); 263 | assert_eq!(pdt.month, rs_parsed.0.month(), "Month mismatch for '{}'", s); 264 | assert_eq!(pdt.day, rs_parsed.0.day(), "Day mismatch for '{}'", s); 265 | assert_eq!(pdt.hour, rs_parsed.0.hour(), "Hour mismatch for '{}'", s); 266 | assert_eq!(pdt.minute, rs_parsed.0.minute(), "Minute mismatch for '{}'", s); 267 | assert_eq!(pdt.second, rs_parsed.0.second(), "Second mismatch for '{}'", s); 268 | assert_eq!(pdt.micros, rs_parsed.0.timestamp_subsec_micros(), "Microsecond mismatch for '{}'", s); 269 | assert_eq!(pdt.tzo, rs_parsed.1.map(|u| u.local_minus_utc()), "Timezone Offset mismatch for '{}'", s); 270 | } 271 | 272 | fn parse_fuzzy_and_assert( 273 | pdt: PyDateTime, 274 | ptokens: Option>, 275 | info: ParserInfo, 276 | s: &str, 277 | dayfirst: Option, 278 | yearfirst: Option, 279 | fuzzy: bool, 280 | fuzzy_with_tokens: bool, 281 | default: Option<&NaiveDateTime>, 282 | ignoretz: bool, 283 | tzinfos: &HashMap, 284 | ) { 285 | 286 | let parser = Parser::new(info); 287 | let rs_parsed = parser.parse( 288 | s, 289 | dayfirst, 290 | yearfirst, 291 | fuzzy, 292 | fuzzy_with_tokens, 293 | default, 294 | ignoretz, 295 | tzinfos).expect(&format!("Unable to parse date in Rust '{}'", s)); 296 | 297 | assert_eq!(pdt.year, rs_parsed.0.year(), "Year mismatch for '{}'", s); 298 | assert_eq!(pdt.month, rs_parsed.0.month(), "Month mismatch for '{}'", s); 299 | assert_eq!(pdt.day, rs_parsed.0.day(), "Day mismatch for '{}'", s); 300 | assert_eq!(pdt.hour, rs_parsed.0.hour(), "Hour mismatch for '{}'", s); 301 | assert_eq!(pdt.minute, rs_parsed.0.minute(), "Minute mismatch f'or' {}", s); 302 | assert_eq!(pdt.second, rs_parsed.0.second(), "Second mismatch for '{}'", s); 303 | assert_eq!(pdt.micros, rs_parsed.0.timestamp_subsec_micros(), "Microsecond mismatch for '{}'", s); 304 | assert_eq!(pdt.tzo, rs_parsed.1.map(|u| u.local_minus_utc()), "Timezone Offset mismatch for '{}'", s); 305 | assert_eq!(ptokens, rs_parsed.2, "Tokens mismatch for '{}'", s); 306 | } 307 | 308 | macro_rules! rs_tzinfo_map { 309 | () => ({ 310 | let mut h = HashMap::new(); 311 | h.insert("BRST".to_owned(), -10800); 312 | h 313 | }); 314 | }\n''' 315 | 316 | TEST_PARSE_DEFAULT = ''' 317 | #[test] 318 | fn test_parse_default{i}() {{ 319 | let info = ParserInfo::default(); 320 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 321 | let pdt = PyDateTime {{ 322 | year: {d.year}, month: {d.month}, day: {d.day}, 323 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 324 | micros: {d.microsecond}, tzo: None 325 | }}; 326 | parse_and_assert(pdt, info, "{s}", None, None, false, false, 327 | Some(default_rsdate), false, &HashMap::new()); 328 | }}\n''' 329 | 330 | TEST_PARSE_SIMPLE = ''' 331 | #[test] 332 | fn test_parse_simple{i}() {{ 333 | let pdt = PyDateTime {{ 334 | year: {d.year}, month: {d.month}, day: {d.day}, 335 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 336 | micros: {d.microsecond}, tzo: None, 337 | }}; 338 | parse_and_assert_simple(pdt, "{s}"); 339 | }}\n''' 340 | 341 | TEST_PARSE_TZINFO = ''' 342 | #[test] 343 | fn test_parse_tzinfo{i}() {{ 344 | let info = ParserInfo::default(); 345 | let pdt = PyDateTime {{ 346 | year: {d.year}, month: {d.month}, day: {d.day}, 347 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 348 | micros: {d.microsecond}, tzo: Some({offset}), 349 | }}; 350 | parse_and_assert(pdt, info, "{s}", None, None, false, false, 351 | None, false, &rs_tzinfo_map!()); 352 | }}\n''' 353 | 354 | TEST_PARSE_OFFSET = ''' 355 | #[test] 356 | fn test_parse_offset{i}() {{ 357 | let info = ParserInfo::default(); 358 | let pdt = PyDateTime {{ 359 | year: {d.year}, month: {d.month}, day: {d.day}, 360 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 361 | micros: {d.microsecond}, tzo: Some({offset}), 362 | }}; 363 | parse_and_assert(pdt, info, "{s}", None, None, false, false, 364 | None, false, &HashMap::new()); 365 | }}\n''' 366 | 367 | TEST_PARSE_DAYFIRST = ''' 368 | #[test] 369 | fn test_parse_dayfirst{i}() {{ 370 | let info = ParserInfo::default(); 371 | let pdt = PyDateTime {{ 372 | year: {d.year}, month: {d.month}, day: {d.day}, 373 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 374 | micros: {d.microsecond}, tzo: None, 375 | }}; 376 | parse_and_assert(pdt, info, "{s}", Some(true), None, false, false, 377 | None, false, &HashMap::new()); 378 | }}\n''' 379 | 380 | TEST_PARSE_YEARFIRST = ''' 381 | #[test] 382 | fn test_parse_yearfirst{i}() {{ 383 | let info = ParserInfo::default(); 384 | let pdt = PyDateTime {{ 385 | year: {d.year}, month: {d.month}, day: {d.day}, 386 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 387 | micros: {d.microsecond}, tzo: None, 388 | }}; 389 | parse_and_assert(pdt, info, "{s}", None, Some(true), false, false, 390 | None, false, &HashMap::new()); 391 | }}\n''' 392 | 393 | TEST_PARSE_DFYF = ''' 394 | #[test] 395 | fn test_parse_dfyf{i}() {{ 396 | let info = ParserInfo::default(); 397 | let pdt = PyDateTime {{ 398 | year: {d.year}, month: {d.month}, day: {d.day}, 399 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 400 | micros: {d.microsecond}, tzo: None, 401 | }}; 402 | parse_and_assert(pdt, info, "{s}", Some(true), Some(true), false, false, 403 | None, false, &HashMap::new()); 404 | }}\n''' 405 | 406 | TEST_UNSPECIFIED_FALLBACK = ''' 407 | #[test] 408 | fn test_unspecified_fallback{i}() {{ 409 | let info = ParserInfo::default(); 410 | let default_rsdate = &NaiveDate::from_ymd_opt(2010, 1, 31).unwrap().and_hms_opt(0, 0, 0).unwrap(); 411 | let pdt = PyDateTime {{ 412 | year: {d.year}, month: {d.month}, day: {d.day}, 413 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 414 | micros: {d.microsecond}, tzo: None 415 | }}; 416 | parse_and_assert(pdt, info, "{s}", None, None, false, false, 417 | Some(default_rsdate), false, &HashMap::new()); 418 | }}\n''' 419 | 420 | TEST_PARSE_IGNORETZ = ''' 421 | #[test] 422 | fn test_parse_ignoretz{i}() {{ 423 | let info = ParserInfo::default(); 424 | let pdt = PyDateTime {{ 425 | year: {d.year}, month: {d.month}, day: {d.day}, 426 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 427 | micros: {d.microsecond}, tzo: None 428 | }}; 429 | parse_and_assert(pdt, info, "{s}", None, None, false, false, 430 | None, true, &HashMap::new()); 431 | }}\n''' 432 | 433 | TEST_PARSE_DEFAULT_IGNORE = ''' 434 | #[test] 435 | #[ignore] 436 | fn test_parse_default_ignore{i}() {{ 437 | let info = ParserInfo::default(); 438 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 439 | let pdt = PyDateTime {{ 440 | year: {d.year}, month: {d.month}, day: {d.day}, 441 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 442 | micros: {d.microsecond}, tzo: None 443 | }}; 444 | parse_and_assert(pdt, info, "{s}", None, None, false, false, 445 | Some(default_rsdate), false, &HashMap::new()); 446 | }}\n''' 447 | 448 | TEST_FUZZY_TZINFO = ''' 449 | #[test] 450 | fn test_fuzzy_tzinfo{i}() {{ 451 | let info = ParserInfo::default(); 452 | let pdt = PyDateTime {{ 453 | year: {d.year}, month: {d.month}, day: {d.day}, 454 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 455 | micros: {d.microsecond}, tzo: Some({offset}) 456 | }}; 457 | parse_fuzzy_and_assert(pdt, None, info, "{s}", None, None, true, false, 458 | None, false, &HashMap::new()); 459 | }}\n''' 460 | 461 | TEST_FUZZY_TOKENS_TZINFO = ''' 462 | #[test] 463 | fn test_fuzzy_tokens_tzinfo{i}() {{ 464 | let info = ParserInfo::default(); 465 | let pdt = PyDateTime {{ 466 | year: {d.year}, month: {d.month}, day: {d.day}, 467 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 468 | micros: {d.microsecond}, tzo: Some({offset}) 469 | }}; 470 | let tokens = vec![{tokens}]; 471 | parse_fuzzy_and_assert(pdt, Some(tokens), info, "{s}", None, None, true, true, 472 | None, false, &HashMap::new()); 473 | }}\n''' 474 | 475 | TEST_FUZZY_SIMPLE = ''' 476 | #[test] 477 | fn test_fuzzy_simple{i}() {{ 478 | let info = ParserInfo::default(); 479 | let pdt = PyDateTime {{ 480 | year: {d.year}, month: {d.month}, day: {d.day}, 481 | hour: {d.hour}, minute: {d.minute}, second: {d.second}, 482 | micros: {d.microsecond}, tzo: None 483 | }}; 484 | parse_fuzzy_and_assert(pdt, None, info, "{s}", None, None, true, false, 485 | None, false, &HashMap::new()); 486 | }}\n''' 487 | 488 | 489 | if __name__ == '__main__': 490 | main() -------------------------------------------------------------------------------- /build_pycompat_tokenizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from dateutil.parser import _timelex 3 | 4 | from build_pycompat import tests 5 | 6 | def main(): 7 | with open('src/tests/pycompat_tokenizer.rs', 'w+') as handle: 8 | handle.write(TEST_HEADER) 9 | 10 | counter = 0 11 | for _, test_strings in tests.items(): 12 | for s in test_strings: 13 | handle.write(build_test(counter, s)) 14 | counter += 1 15 | 16 | def build_test(i, test_string): 17 | python_tokens = list(_timelex(test_string)) 18 | formatted_tokens = 'vec!["' + '", "'.join(python_tokens) + '"]' 19 | return f''' 20 | #[test] 21 | fn test_tokenize{i}() {{ 22 | let comp = {formatted_tokens}; 23 | tokenize_assert("{test_string}", comp); 24 | }}\n''' 25 | 26 | 27 | TEST_HEADER = ''' 28 | //! This code has been generated by running the `build_pycompat_tokenizer.py` script 29 | //! in the repository root. Please do not edit it, as your edits will be destroyed 30 | //! upon re-running code generation. 31 | 32 | use tokenize::Tokenizer; 33 | 34 | fn tokenize_assert(test_str: &str, comparison: Vec<&str>) { 35 | let tokens: Vec = Tokenizer::new(test_str).collect(); 36 | assert_eq!(tokens, comparison, "Tokenizing mismatch for `{}`", test_str); 37 | }\n''' 38 | 39 | if __name__ == '__main__': 40 | main() -------------------------------------------------------------------------------- /examples/russian.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono; 2 | extern crate dtparse; 3 | 4 | use chrono::NaiveDate; 5 | use dtparse::parse_info; 6 | use dtparse::Parser; 7 | use dtparse::ParserInfo; 8 | use std::collections::HashMap; 9 | 10 | fn main() { 11 | // In this example, we'll just swap the default "months" parameter 12 | // with a version in Russian. Lovingly taken from: 13 | // https://github.com/dateutil/dateutil/blob/99f5770e7c63aa049b28abe465d7f1cc25b63fd2/dateutil/test/test_parser.py#L244 14 | 15 | let mut info = ParserInfo::default(); 16 | info.months = parse_info(vec![ 17 | vec!["янв", "Январь"], 18 | vec!["фев", "Февраль"], 19 | vec!["мар", "Март"], 20 | vec!["апр", "Апрель"], 21 | vec!["май", "Май"], 22 | vec!["июн", "Июнь"], 23 | vec!["июл", "Июль"], 24 | vec!["авг", "Август"], 25 | vec!["сен", "Сентябрь"], 26 | vec!["окт", "Октябрь"], 27 | vec!["ноя", "Ноябрь"], 28 | vec!["дек", "Декабрь"], 29 | ]); 30 | 31 | let p = Parser::new(info); 32 | 33 | assert_eq!( 34 | p.parse( 35 | "10 Сентябрь 2015 10:20", 36 | None, 37 | None, 38 | false, 39 | false, 40 | None, 41 | false, 42 | &HashMap::new() 43 | ) 44 | .unwrap() 45 | .0, 46 | NaiveDate::from_ymd_opt(2015, 9, 10).unwrap().and_hms_opt(10, 20, 0).unwrap() 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![cfg_attr(test, allow(unknown_lints))] 3 | #![cfg_attr(test, deny(warnings))] 4 | 5 | //! # dtparse 6 | //! The fully-featured "even I couldn't understand that" time parser. 7 | //! Designed to take in strings and give back sensible dates and times. 8 | //! 9 | //! dtparse has its foundations in the [`dateutil`](dateutil) library for 10 | //! Python, which excels at taking "interesting" strings and trying to make 11 | //! sense of the dates and times they contain. A couple of quick examples 12 | //! from the test cases should give some context: 13 | //! 14 | //! ```rust,ignore (tests-dont-compile-on-old-rust) 15 | //! # extern crate chrono; 16 | //! # extern crate dtparse; 17 | //! use chrono::prelude::*; 18 | //! use dtparse::parse; 19 | //! 20 | //! assert_eq!( 21 | //! parse("2008.12.30"), 22 | //! Ok((NaiveDate::from_ymd(2008, 12, 30).and_hms(0, 0, 0), None)) 23 | //! ); 24 | //! 25 | //! // It can even handle timezones! 26 | //! assert_eq!( 27 | //! parse("January 4, 2024; 18:30:04 +02:00"), 28 | //! Ok(( 29 | //! NaiveDate::from_ymd(2024, 1, 4).and_hms(18, 30, 4), 30 | //! Some(FixedOffset::east(7200)) 31 | //! )) 32 | //! ); 33 | //! ``` 34 | //! 35 | //! And we can even handle fuzzy strings where dates/times aren't the 36 | //! only content if we dig into the implementation a bit! 37 | //! 38 | //! ```rust,ignore (tests-dont-compile-on-old-rust) 39 | //! # extern crate chrono; 40 | //! # extern crate dtparse; 41 | //! use chrono::prelude::*; 42 | //! use dtparse::Parser; 43 | //! # use std::collections::HashMap; 44 | //! 45 | //! let mut p = Parser::default(); 46 | //! assert_eq!( 47 | //! p.parse( 48 | //! "I first released this library on the 17th of June, 2018.", 49 | //! None, None, 50 | //! true /* turns on fuzzy mode */, 51 | //! true /* gives us the tokens that weren't recognized */, 52 | //! None, false, &HashMap::new() 53 | //! ), 54 | //! Ok(( 55 | //! NaiveDate::from_ymd(2018, 6, 17).and_hms(0, 0, 0), 56 | //! None, 57 | //! Some(vec!["I first released this library on the ", 58 | //! " of ", ", "].iter().map(|&s| s.into()).collect()) 59 | //! )) 60 | //! ); 61 | //! ``` 62 | //! 63 | //! Further examples can be found in the `examples` directory on international usage. 64 | //! 65 | //! # Usage 66 | //! 67 | //! `dtparse` requires a minimum Rust version of 1.28 to build, but is tested on Windows, OSX, 68 | //! BSD, Linux, and WASM. The build is also compiled against the iOS and Android SDK's, but is not 69 | //! tested against them. 70 | //! 71 | //! [dateutil]: https://github.com/dateutil/dateutil 72 | 73 | #[macro_use] 74 | extern crate lazy_static; 75 | 76 | extern crate chrono; 77 | extern crate num_traits; 78 | extern crate rust_decimal; 79 | 80 | #[cfg(test)] 81 | extern crate base64; 82 | 83 | use chrono::Datelike; 84 | use chrono::Duration; 85 | use chrono::FixedOffset; 86 | use chrono::Local; 87 | use chrono::NaiveDate; 88 | use chrono::NaiveDateTime; 89 | use chrono::NaiveTime; 90 | use chrono::Timelike; 91 | use num_traits::cast::ToPrimitive; 92 | use rust_decimal::Decimal; 93 | use rust_decimal::Error as DecimalError; 94 | use std::cmp::min; 95 | use std::collections::HashMap; 96 | use std::error::Error; 97 | use std::fmt; 98 | use std::num::ParseIntError; 99 | use std::str::FromStr; 100 | use std::vec::Vec; 101 | 102 | mod tokenize; 103 | mod weekday; 104 | 105 | #[cfg(test)] 106 | mod tests; 107 | 108 | use tokenize::Tokenizer; 109 | use weekday::day_of_week; 110 | use weekday::DayOfWeek; 111 | 112 | lazy_static! { 113 | static ref ZERO: Decimal = Decimal::new(0, 0); 114 | static ref ONE: Decimal = Decimal::new(1, 0); 115 | static ref TWENTY_FOUR: Decimal = Decimal::new(24, 0); 116 | static ref SIXTY: Decimal = Decimal::new(60, 0); 117 | static ref DEFAULT_PARSER: Parser = Parser::default(); 118 | } 119 | 120 | impl From for ParseError { 121 | fn from(err: DecimalError) -> Self { 122 | ParseError::InvalidNumeric(format!("{}", err)) 123 | } 124 | } 125 | 126 | impl From for ParseError { 127 | fn from(err: ParseIntError) -> Self { 128 | ParseError::InvalidNumeric(format!("{}", err)) 129 | } 130 | } 131 | 132 | /// Potential errors that come up when trying to parse time strings 133 | #[derive(Debug, PartialEq)] 134 | pub enum ParseError { 135 | /// Attempted to specify "AM" or "PM" without indicating an hour 136 | AmPmWithoutHour, 137 | /// Impossible value for a category; the 32nd day of a month is impossible 138 | ImpossibleTimestamp(&'static str), 139 | /// Unable to parse a numeric value from a token expected to be numeric 140 | InvalidNumeric(String), 141 | /// Generally unrecognized date string; please report to maintainer so 142 | /// new test cases can be developed 143 | UnrecognizedFormat, 144 | /// A token the parser did not recognize was in the string, and fuzzy mode was off 145 | UnrecognizedToken(String), 146 | /// A timezone could not be handled; please report to maintainer as the timestring 147 | /// likely exposes a bug in the implementation 148 | TimezoneUnsupported, 149 | /// Parser unable to make sense of year/month/day parameters in the time string; 150 | /// please report to maintainer as the timestring likely exposes a bug in implementation 151 | YearMonthDayError(&'static str), 152 | /// Parser unable to find any date/time-related content in the supplied string 153 | NoDate, 154 | } 155 | 156 | impl fmt::Display for ParseError { 157 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 158 | write!(f, "{:?}", self) 159 | } 160 | } 161 | 162 | impl Error for ParseError {} 163 | 164 | type ParseResult = Result; 165 | 166 | pub(crate) fn tokenize(parse_string: &str) -> Vec { 167 | let tokenizer = Tokenizer::new(parse_string); 168 | tokenizer.collect() 169 | } 170 | 171 | /// Utility function for `ParserInfo` that helps in constructing 172 | /// the attributes that make up the `ParserInfo` container 173 | pub fn parse_info(vec: Vec>) -> HashMap { 174 | let mut m = HashMap::new(); 175 | 176 | if vec.len() == 1 { 177 | for (i, val) in vec.get(0).unwrap().iter().enumerate() { 178 | m.insert(val.to_lowercase(), i); 179 | } 180 | } else { 181 | for (i, val_vec) in vec.iter().enumerate() { 182 | for val in val_vec { 183 | m.insert(val.to_lowercase(), i); 184 | } 185 | } 186 | } 187 | 188 | m 189 | } 190 | 191 | /// Container for specific tokens to be recognized during parsing. 192 | /// 193 | /// - `jump`: Values that indicate the end of a token for parsing and can be ignored 194 | /// - `weekday`: Names of the days of the week 195 | /// - `months`: Names of the months 196 | /// - `hms`: Names for the units of time - hours, minutes, seconds in English 197 | /// - `ampm`: AM and PM tokens 198 | /// - `utczone`: Tokens indicating a UTC-timezone string 199 | /// - `pertain`: Tokens indicating a "belongs to" relationship; in English this is just "of" 200 | /// - `tzoffset`: 201 | /// - `dayfirst`: Upon encountering an ambiguous date, treat the first value as the day 202 | /// - `yearfirst`: Upon encountering an ambiguous date, treat the first value as the year 203 | /// - `year`: The current year 204 | /// - `century`: The first year in the current century 205 | /// 206 | /// Please note that if both `dayfirst` and `yearfirst` are true, years take precedence 207 | /// and will be parsed as "YDM" 208 | #[derive(Debug, PartialEq)] 209 | pub struct ParserInfo { 210 | /// Tokens that can be safely ignored 211 | pub jump: HashMap, 212 | /// Names of all seven weekdays 213 | pub weekday: HashMap, 214 | /// Names of all twelve months 215 | pub months: HashMap, 216 | /// Tokens to indicate a value is in units of hours, minutes, or seconds 217 | pub hms: HashMap, 218 | /// Tokens to indicate a value refers to AM or PM time 219 | pub ampm: HashMap, 220 | /// Tokens to indicate our timestamp is in the UTC timezone 221 | pub utczone: HashMap, 222 | /// Tokens to indicate values "belonging" to other tokens (e.g. 3rd *of* March) 223 | pub pertain: HashMap, 224 | /// Map of timezone names to their offset in seconds 225 | pub tzoffset: HashMap, 226 | /// For ambiguous year/month/day values, and `dayfirst` was not specified as 227 | /// an argument to `Parser`, treat the first observed value as the day. 228 | pub dayfirst: bool, 229 | /// For ambiguous year/month/day values, and `dayfirst` was not specified as 230 | /// an argument to `Parser`, treat the first observed value as the day. 231 | /// Takes priority over `dayfirst` 232 | pub yearfirst: bool, 233 | /// The current year we are parsing values for 234 | pub year: i32, 235 | /// The current year we are parsing values for *modulo* 100 236 | pub century: i32, 237 | } 238 | 239 | impl Default for ParserInfo { 240 | /// Create a basic `ParserInfo` object suitable for parsing dates in English 241 | fn default() -> Self { 242 | let year = Local::now().year(); 243 | let century = year / 100 * 100; 244 | 245 | ParserInfo { 246 | jump: parse_info(vec![vec![ 247 | " ", ".", ",", ";", "-", "/", "'", "at", "on", "and", "ad", "m", "t", "of", "st", 248 | "nd", "rd", "th", 249 | ]]), 250 | weekday: parse_info(vec![ 251 | vec!["Mon", "Monday"], 252 | vec!["Tue", "Tues", "Tuesday"], 253 | vec!["Wed", "Wednesday"], 254 | vec!["Thu", "Thurs", "Thursday"], 255 | vec!["Fri", "Friday"], 256 | vec!["Sat", "Saturday"], 257 | vec!["Sun", "Sunday"], 258 | ]), 259 | months: parse_info(vec![ 260 | vec!["Jan", "January"], 261 | vec!["Feb", "February"], 262 | vec!["Mar", "March"], 263 | vec!["Apr", "April"], 264 | vec!["May"], 265 | vec!["Jun", "June"], 266 | vec!["Jul", "July"], 267 | vec!["Aug", "August"], 268 | vec!["Sep", "Sept", "September"], 269 | vec!["Oct", "October"], 270 | vec!["Nov", "November"], 271 | vec!["Dec", "December"], 272 | ]), 273 | hms: parse_info(vec![ 274 | vec!["h", "hour", "hours"], 275 | vec!["m", "minute", "minutes"], 276 | vec!["s", "second", "seconds"], 277 | ]), 278 | ampm: parse_info(vec![vec!["am", "a"], vec!["pm", "p"]]), 279 | utczone: parse_info(vec![vec!["UTC", "GMT", "Z"]]), 280 | pertain: parse_info(vec![vec!["of"]]), 281 | tzoffset: parse_info(vec![vec![]]), 282 | dayfirst: false, 283 | yearfirst: false, 284 | year, 285 | century, 286 | } 287 | } 288 | } 289 | 290 | impl ParserInfo { 291 | fn jump_index(&self, name: &str) -> bool { 292 | self.jump.contains_key(&name.to_lowercase()) 293 | } 294 | 295 | fn weekday_index(&self, name: &str) -> Option { 296 | self.weekday.get(&name.to_lowercase()).cloned() 297 | } 298 | 299 | fn month_index(&self, name: &str) -> Option { 300 | self.months.get(&name.to_lowercase()).map(|u| u + 1) 301 | } 302 | 303 | fn hms_index(&self, name: &str) -> Option { 304 | self.hms.get(&name.to_lowercase()).cloned() 305 | } 306 | 307 | fn ampm_index(&self, name: &str) -> Option { 308 | if let Some(v) = self.ampm.get(&name.to_lowercase()) { 309 | // Python technically uses numbers here, but given that the numbers are 310 | // only 0 and 1, it's easier to use booleans 311 | Some(*v == 1) 312 | } else { 313 | None 314 | } 315 | } 316 | 317 | fn pertain_index(&self, name: &str) -> bool { 318 | self.pertain.contains_key(&name.to_lowercase()) 319 | } 320 | 321 | fn utczone_index(&self, name: &str) -> bool { 322 | self.utczone.contains_key(&name.to_lowercase()) 323 | } 324 | 325 | fn tzoffset_index(&self, name: &str) -> Option { 326 | if self.utczone.contains_key(&name.to_lowercase()) { 327 | Some(0) 328 | } else { 329 | self.tzoffset.get(&name.to_lowercase()).cloned() 330 | } 331 | } 332 | 333 | fn convertyear(&self, year: i32, century_specified: bool) -> i32 { 334 | let mut year = year; 335 | 336 | if year < 100 && !century_specified { 337 | year += self.century; 338 | if year >= self.year + 50 { 339 | year -= 100; 340 | } else if year < self.year - 50 { 341 | year += 100 342 | } 343 | } 344 | 345 | year 346 | } 347 | 348 | // TODO: Should this be moved elsewhere? 349 | fn validate(&self, res: &mut ParsingResult) -> bool { 350 | if let Some(y) = res.year { 351 | res.year = Some(self.convertyear(y, res.century_specified)) 352 | }; 353 | 354 | if (res.tzoffset == Some(0) && res.tzname.is_none()) 355 | || (res.tzname == Some("Z".to_owned()) || res.tzname == Some("z".to_owned())) 356 | { 357 | res.tzname = Some("UTC".to_owned()); 358 | res.tzoffset = Some(0); 359 | } else if res.tzoffset != Some(0) 360 | && res.tzname.is_some() 361 | && self.utczone_index(res.tzname.as_ref().unwrap()) 362 | { 363 | res.tzoffset = Some(0); 364 | } 365 | 366 | true 367 | } 368 | } 369 | 370 | fn days_in_month(year: i32, month: i32) -> Result { 371 | let leap_year = match year % 4 { 372 | 0 => year % 400 != 0, 373 | _ => false, 374 | }; 375 | 376 | match month { 377 | 2 => { 378 | if leap_year { 379 | Ok(29) 380 | } else { 381 | Ok(28) 382 | } 383 | } 384 | 1 | 3 | 5 | 7 | 8 | 10 | 12 => Ok(31), 385 | 4 | 6 | 9 | 11 => Ok(30), 386 | _ => Err(ParseError::ImpossibleTimestamp("Invalid month")), 387 | } 388 | } 389 | 390 | #[derive(Debug, Hash, PartialEq, Eq)] 391 | enum YMDLabel { 392 | Year, 393 | Month, 394 | Day, 395 | } 396 | 397 | #[derive(Debug, Default)] 398 | struct YMD { 399 | _ymd: Vec, // TODO: This seems like a super weird way to store things 400 | century_specified: bool, 401 | dstridx: Option, 402 | mstridx: Option, 403 | ystridx: Option, 404 | } 405 | 406 | impl YMD { 407 | fn len(&self) -> usize { 408 | self._ymd.len() 409 | } 410 | 411 | fn could_be_day(&self, val: i32) -> bool { 412 | if self.dstridx.is_some() { 413 | false 414 | } else if self.mstridx.is_none() { 415 | (1 <= val) && (val <= 31) 416 | } else if self.ystridx.is_none() { 417 | // UNWRAP: Earlier condition catches mstridx missing 418 | let month = self._ymd[self.mstridx.unwrap()]; 419 | 1 <= val && (val <= days_in_month(2000, month).unwrap() as i32) 420 | } else { 421 | // UNWRAP: Earlier conditions prevent us from unsafely unwrapping 422 | let month = self._ymd[self.mstridx.unwrap()]; 423 | let year = self._ymd[self.ystridx.unwrap()]; 424 | 1 <= val && (val <= days_in_month(year, month).unwrap() as i32) 425 | } 426 | } 427 | 428 | fn append(&mut self, val: i32, token: &str, label: Option) -> ParseResult<()> { 429 | let mut label = label; 430 | 431 | // Python auto-detects strings using the '__len__' function here. 432 | // We instead take in both and handle as necessary. 433 | if Decimal::from_str(token).is_ok() && token.len() > 2 { 434 | self.century_specified = true; 435 | match label { 436 | None | Some(YMDLabel::Year) => label = Some(YMDLabel::Year), 437 | Some(YMDLabel::Month) => { 438 | return Err(ParseError::ImpossibleTimestamp("Invalid month")) 439 | } 440 | Some(YMDLabel::Day) => return Err(ParseError::ImpossibleTimestamp("Invalid day")), 441 | } 442 | } 443 | 444 | if val > 100 { 445 | self.century_specified = true; 446 | match label { 447 | None => label = Some(YMDLabel::Year), 448 | Some(YMDLabel::Year) => (), 449 | Some(YMDLabel::Month) => { 450 | return Err(ParseError::ImpossibleTimestamp("Invalid month")) 451 | } 452 | Some(YMDLabel::Day) => return Err(ParseError::ImpossibleTimestamp("Invalid day")), 453 | } 454 | } 455 | 456 | self._ymd.push(val); 457 | 458 | match label { 459 | Some(YMDLabel::Month) => { 460 | if self.mstridx.is_some() { 461 | Err(ParseError::YearMonthDayError("Month already set")) 462 | } else { 463 | self.mstridx = Some(self._ymd.len() - 1); 464 | Ok(()) 465 | } 466 | } 467 | Some(YMDLabel::Day) => { 468 | if self.dstridx.is_some() { 469 | Err(ParseError::YearMonthDayError("Day already set")) 470 | } else { 471 | self.dstridx = Some(self._ymd.len() - 1); 472 | Ok(()) 473 | } 474 | } 475 | Some(YMDLabel::Year) => { 476 | if self.ystridx.is_some() { 477 | Err(ParseError::YearMonthDayError("Year already set")) 478 | } else { 479 | self.ystridx = Some(self._ymd.len() - 1); 480 | Ok(()) 481 | } 482 | } 483 | None => Ok(()), 484 | } 485 | } 486 | 487 | fn resolve_from_stridxs( 488 | &mut self, 489 | strids: &mut HashMap, 490 | ) -> ParseResult<(Option, Option, Option)> { 491 | if self._ymd.len() == 3 && strids.len() == 2 { 492 | let missing_key = if !strids.contains_key(&YMDLabel::Year) { 493 | YMDLabel::Year 494 | } else if !strids.contains_key(&YMDLabel::Month) { 495 | YMDLabel::Month 496 | } else { 497 | YMDLabel::Day 498 | }; 499 | 500 | let strids_vals: Vec = strids.values().cloned().collect(); 501 | let missing_val = if !strids_vals.contains(&0) { 502 | 0 503 | } else if !strids_vals.contains(&1) { 504 | 1 505 | } else { 506 | 2 507 | }; 508 | 509 | strids.insert(missing_key, missing_val); 510 | } 511 | 512 | if self._ymd.len() != strids.len() { 513 | return Err(ParseError::YearMonthDayError( 514 | "Tried to resolve year, month, and day without enough information", 515 | )); 516 | } 517 | 518 | Ok(( 519 | strids.get(&YMDLabel::Year).map(|i| self._ymd[*i]), 520 | strids.get(&YMDLabel::Month).map(|i| self._ymd[*i]), 521 | strids.get(&YMDLabel::Day).map(|i| self._ymd[*i]), 522 | )) 523 | } 524 | 525 | #[allow(clippy::needless_return)] 526 | fn resolve_ymd( 527 | &mut self, 528 | yearfirst: bool, 529 | dayfirst: bool, 530 | ) -> ParseResult<(Option, Option, Option)> { 531 | let len_ymd = self._ymd.len(); 532 | 533 | let mut strids: HashMap = HashMap::new(); 534 | self.ystridx.map(|u| strids.insert(YMDLabel::Year, u)); 535 | self.mstridx.map(|u| strids.insert(YMDLabel::Month, u)); 536 | self.dstridx.map(|u| strids.insert(YMDLabel::Day, u)); 537 | 538 | // TODO: More Rustiomatic way of doing this? 539 | if len_ymd == strids.len() && !strids.is_empty() || (len_ymd == 3 && strids.len() == 2) { 540 | return self.resolve_from_stridxs(&mut strids); 541 | }; 542 | 543 | // Received year, month, day, and ??? 544 | if len_ymd > 3 { 545 | return Err(ParseError::YearMonthDayError( 546 | "Received extra tokens in resolving year, month, and day", 547 | )); 548 | } 549 | 550 | match (len_ymd, self.mstridx) { 551 | (1, Some(val)) | (2, Some(val)) => { 552 | let other = if len_ymd == 1 { 553 | self._ymd[0] 554 | } else { 555 | self._ymd[1 - val] 556 | }; 557 | if other > 31 { 558 | return Ok((Some(other), Some(self._ymd[val]), None)); 559 | } 560 | return Ok((None, Some(self._ymd[val]), Some(other))); 561 | } 562 | (2, None) => { 563 | if self._ymd[0] > 31 { 564 | return Ok((Some(self._ymd[0]), Some(self._ymd[1]), None)); 565 | } 566 | if self._ymd[1] > 31 { 567 | return Ok((Some(self._ymd[1]), Some(self._ymd[0]), None)); 568 | } 569 | if dayfirst && self._ymd[1] <= 12 { 570 | return Ok((None, Some(self._ymd[1]), Some(self._ymd[0]))); 571 | } 572 | return Ok((None, Some(self._ymd[0]), Some(self._ymd[1]))); 573 | } 574 | (3, Some(0)) => { 575 | if self._ymd[1] > 31 { 576 | return Ok((Some(self._ymd[1]), Some(self._ymd[0]), Some(self._ymd[2]))); 577 | } 578 | return Ok((Some(self._ymd[2]), Some(self._ymd[0]), Some(self._ymd[1]))); 579 | } 580 | (3, Some(1)) => { 581 | if self._ymd[0] > 31 || (yearfirst && self._ymd[2] <= 31) { 582 | return Ok((Some(self._ymd[0]), Some(self._ymd[1]), Some(self._ymd[2]))); 583 | } 584 | return Ok((Some(self._ymd[2]), Some(self._ymd[1]), Some(self._ymd[0]))); 585 | } 586 | (3, Some(2)) => { 587 | // It was in the original docs, so: WTF!? 588 | if self._ymd[1] > 31 { 589 | return Ok((Some(self._ymd[2]), Some(self._ymd[1]), Some(self._ymd[0]))); 590 | } 591 | return Ok((Some(self._ymd[0]), Some(self._ymd[2]), Some(self._ymd[1]))); 592 | } 593 | (3, None) => { 594 | if self._ymd[0] > 31 595 | || self.ystridx == Some(0) 596 | || (yearfirst && self._ymd[1] <= 12 && self._ymd[2] <= 31) 597 | { 598 | if dayfirst && self._ymd[2] <= 12 { 599 | return Ok((Some(self._ymd[0]), Some(self._ymd[2]), Some(self._ymd[1]))); 600 | } 601 | return Ok((Some(self._ymd[0]), Some(self._ymd[1]), Some(self._ymd[2]))); 602 | } else if self._ymd[0] > 12 || (dayfirst && self._ymd[1] <= 12) { 603 | return Ok((Some(self._ymd[2]), Some(self._ymd[1]), Some(self._ymd[0]))); 604 | } 605 | return Ok((Some(self._ymd[2]), Some(self._ymd[0]), Some(self._ymd[1]))); 606 | } 607 | (_, _) => { 608 | return Ok((None, None, None)); 609 | } 610 | } 611 | } 612 | } 613 | 614 | #[derive(Default, Debug, PartialEq)] 615 | struct ParsingResult { 616 | year: Option, 617 | month: Option, 618 | day: Option, 619 | weekday: Option, 620 | hour: Option, 621 | minute: Option, 622 | second: Option, 623 | nanosecond: Option, 624 | tzname: Option, 625 | tzoffset: Option, 626 | ampm: Option, 627 | century_specified: bool, 628 | any_unused_tokens: Vec, 629 | } 630 | 631 | macro_rules! option_len { 632 | ($o:expr) => {{ 633 | if $o.is_some() { 634 | 1 635 | } else { 636 | 0 637 | } 638 | }}; 639 | } 640 | 641 | impl ParsingResult { 642 | fn len(&self) -> usize { 643 | option_len!(self.year) 644 | + option_len!(self.month) 645 | + option_len!(self.day) 646 | + option_len!(self.weekday) 647 | + option_len!(self.hour) 648 | + option_len!(self.minute) 649 | + option_len!(self.second) 650 | + option_len!(self.nanosecond) 651 | + option_len!(self.tzname) 652 | + option_len!(self.ampm) 653 | } 654 | } 655 | 656 | /// Parser is responsible for doing the actual work of understanding a time string. 657 | /// The root level `parse` function is responsible for constructing a default `Parser` 658 | /// and triggering its behavior. 659 | #[derive(Default)] 660 | pub struct Parser { 661 | info: ParserInfo, 662 | } 663 | 664 | impl Parser { 665 | /// Create a new `Parser` instance using the provided `ParserInfo`. 666 | /// 667 | /// This method allows you to set up a parser to handle different 668 | /// names for days of the week, months, etc., enabling customization 669 | /// for different languages or extra values. 670 | pub fn new(info: ParserInfo) -> Self { 671 | Parser { info } 672 | } 673 | 674 | /// Main method to trigger parsing of a string using the previously-provided 675 | /// parser information. Returns a naive timestamp along with timezone and 676 | /// unused tokens if available. 677 | /// 678 | /// `dayfirst` and `yearfirst` force parser behavior in the event of ambiguous 679 | /// dates. Consider the following scenarios where we parse the string '01.02.03' 680 | /// 681 | /// - `dayfirst=Some(true)`, `yearfirst=None`: Results in `February 2, 2003` 682 | /// - `dayfirst=None`, `yearfirst=Some(true)`: Results in `February 3, 2001` 683 | /// - `dayfirst=Some(true)`, `yearfirst=Some(true)`: Results in `March 2, 2001` 684 | /// 685 | /// `fuzzy` enables fuzzy parsing mode, allowing the parser to skip tokens if 686 | /// they are unrecognized. However, the unused tokens will not be returned 687 | /// unless `fuzzy_with_tokens` is set as `true`. 688 | /// 689 | /// `default` is the timestamp used to infer missing values, and is midnight 690 | /// of the current day by default. For example, when parsing the text '2003', 691 | /// we will use the current month and day as a default value, leading to a 692 | /// result of 'March 3, 2003' if the function was run using a default of 693 | /// March 3rd. 694 | /// 695 | /// `ignoretz` forces the parser to ignore timezone information even if it 696 | /// is recognized in the time string 697 | /// 698 | /// `tzinfos` is a map of timezone names to the offset seconds. For example, 699 | /// the parser would ignore the 'EST' part of the string in '10 AM EST' 700 | /// unless you added a `tzinfos` map of `{"EST": "14400"}`. Please note that 701 | /// timezone name support (i.e. "EST", "BRST") is not available by default 702 | /// at the moment, they must be added through `tzinfos` at the moment in 703 | /// order to be resolved. 704 | #[allow(clippy::too_many_arguments)] 705 | pub fn parse( 706 | &self, 707 | timestr: &str, 708 | dayfirst: Option, 709 | yearfirst: Option, 710 | fuzzy: bool, 711 | fuzzy_with_tokens: bool, 712 | default: Option<&NaiveDateTime>, 713 | ignoretz: bool, 714 | tzinfos: &HashMap, 715 | ) -> ParseResult<(NaiveDateTime, Option, Option>)> { 716 | let default_date = default.unwrap_or(&Local::now().naive_local()).date(); 717 | 718 | let default_ts = NaiveDateTime::new(default_date, NaiveTime::from_hms_opt(0, 0, 0).unwrap()); 719 | 720 | let (res, tokens) = 721 | self.parse_with_tokens(timestr, dayfirst, yearfirst, fuzzy, fuzzy_with_tokens)?; 722 | 723 | if res.len() == 0 { 724 | return Err(ParseError::NoDate); 725 | } 726 | 727 | let naive = self.build_naive(&res, &default_ts)?; 728 | 729 | if !ignoretz { 730 | let offset = self.build_tzaware(&naive, &res, tzinfos)?; 731 | Ok((naive, offset, tokens)) 732 | } else { 733 | Ok((naive, None, tokens)) 734 | } 735 | } 736 | 737 | #[allow(clippy::cognitive_complexity)] // Imitating Python API is priority 738 | fn parse_with_tokens( 739 | &self, 740 | timestr: &str, 741 | dayfirst: Option, 742 | yearfirst: Option, 743 | fuzzy: bool, 744 | fuzzy_with_tokens: bool, 745 | ) -> Result<(ParsingResult, Option>), ParseError> { 746 | let fuzzy = if fuzzy_with_tokens { true } else { fuzzy }; 747 | // This is probably a stylistic abomination 748 | let dayfirst = if let Some(dayfirst) = dayfirst { 749 | dayfirst 750 | } else { 751 | self.info.dayfirst 752 | }; 753 | let yearfirst = if let Some(yearfirst) = yearfirst { 754 | yearfirst 755 | } else { 756 | self.info.yearfirst 757 | }; 758 | 759 | let mut res = ParsingResult::default(); 760 | 761 | let mut l = tokenize(×tr); 762 | let mut skipped_idxs: Vec = Vec::new(); 763 | 764 | let mut ymd = YMD::default(); 765 | 766 | let len_l = l.len(); 767 | let mut i = 0; 768 | 769 | while i < len_l { 770 | let value_repr = l[i].clone(); 771 | 772 | if let Ok(_v) = Decimal::from_str(&value_repr) { 773 | i = self.parse_numeric_token(&l, i, &self.info, &mut ymd, &mut res, fuzzy)?; 774 | } else if let Some(value) = self.info.weekday_index(&l[i]) { 775 | res.weekday = Some(value); 776 | } else if let Some(value) = self.info.month_index(&l[i]) { 777 | ymd.append(value as i32, &l[i], Some(YMDLabel::Month))?; 778 | 779 | if i + 1 < len_l { 780 | if l[i + 1] == "-" || l[i + 1] == "/" { 781 | // Jan-01[-99] 782 | let sep = &l[i + 1]; 783 | // TODO: This seems like a very unsafe unwrap 784 | ymd.append(l[i + 2].parse::()?, &l[i + 2], None)?; 785 | 786 | if i + 3 < len_l && &l[i + 3] == sep { 787 | // Jan-01-99 788 | ymd.append(l[i + 4].parse::()?, &l[i + 4], None)?; 789 | i += 2; 790 | } 791 | 792 | i += 2; 793 | } else if i + 4 < len_l 794 | && l[i + 1] == l[i + 3] 795 | && l[i + 3] == " " 796 | && self.info.pertain_index(&l[i + 2]) 797 | { 798 | // Jan of 01 799 | if let Ok(value) = l[i + 4].parse::() { 800 | let year = self.info.convertyear(value, false); 801 | ymd.append(year, &l[i + 4], Some(YMDLabel::Year))?; 802 | } 803 | 804 | i += 4; 805 | } 806 | } 807 | } else if let Some(value) = self.info.ampm_index(&l[i]) { 808 | let is_ampm = self.ampm_valid(res.hour, res.ampm, fuzzy); 809 | 810 | if is_ampm == Ok(true) { 811 | res.hour = res.hour.map(|h| self.adjust_ampm(h, value)); 812 | res.ampm = Some(value); 813 | } else if fuzzy { 814 | skipped_idxs.push(i); 815 | } 816 | } else if self.could_be_tzname(res.hour, &res.tzname, res.tzoffset, &l[i]) { 817 | res.tzname = Some(l[i].clone()); 818 | 819 | let tzname = res.tzname.clone().unwrap(); 820 | res.tzoffset = self.info.tzoffset_index(&tzname).map(|t| t as i32); 821 | 822 | if i + 1 < len_l && (l[i + 1] == "+" || l[i + 1] == "-") { 823 | // GMT+3 824 | // According to dateutil docs - reverse the size, as GMT+3 means 825 | // "my time +3 is GMT" not "GMT +3 is my time" 826 | 827 | // TODO: Is there a better way of in-place modifying a vector? 828 | let item = if l[i + 1] == "+" { 829 | "-".to_owned() 830 | } else { 831 | "+".to_owned() 832 | }; 833 | l[i + 1] = item; 834 | 835 | res.tzoffset = None; 836 | 837 | if self.info.utczone_index(&tzname) { 838 | res.tzname = None; 839 | } 840 | } 841 | } else if res.hour.is_some() && (l[i] == "+" || l[i] == "-") { 842 | let signal = if l[i] == "+" { 1 } else { -1 }; 843 | let len_li = l[i].len(); 844 | 845 | let mut hour_offset: Option = None; 846 | let mut min_offset: Option = None; 847 | 848 | // TODO: check that l[i + 1] is integer? 849 | if len_li == 4 { 850 | // -0300 851 | hour_offset = Some(l[i + 1][..2].parse::()?); 852 | min_offset = Some(l[i + 1][2..4].parse::()?); 853 | } else if i + 2 < len_l && l[i + 2] == ":" { 854 | // -03:00 855 | hour_offset = Some(l[i + 1].parse::()?); 856 | min_offset = Some(l[i + 3].parse::()?); 857 | i += 2; 858 | } else if len_li <= 2 { 859 | // -[0]3 860 | let range_len = min(l[i + 1].len(), 2); 861 | hour_offset = Some(l[i + 1][..range_len].parse::()?); 862 | min_offset = Some(0); 863 | } 864 | 865 | res.tzoffset = 866 | Some(signal * (hour_offset.unwrap() * 3600 + min_offset.unwrap() * 60)); 867 | 868 | let tzname = res.tzname.clone(); 869 | if i + 5 < len_l 870 | && self.info.jump_index(&l[i + 2]) 871 | && l[i + 3] == "(" 872 | && l[i + 5] == ")" 873 | && 3 <= l[i + 4].len() 874 | && self.could_be_tzname(res.hour, &tzname, None, &l[i + 4]) 875 | { 876 | // (GMT) 877 | res.tzname = Some(l[i + 4].clone()); 878 | i += 4; 879 | } 880 | 881 | i += 1; 882 | } else if !(self.info.jump_index(&l[i]) || fuzzy) { 883 | return Err(ParseError::UnrecognizedToken(l[i].clone())); 884 | } else { 885 | skipped_idxs.push(i); 886 | } 887 | 888 | i += 1; 889 | } 890 | 891 | let (year, month, day) = ymd.resolve_ymd(yearfirst, dayfirst)?; 892 | 893 | res.century_specified = ymd.century_specified; 894 | res.year = year; 895 | res.month = month; 896 | res.day = day; 897 | 898 | if !self.info.validate(&mut res) { 899 | Err(ParseError::UnrecognizedFormat) 900 | } else if fuzzy_with_tokens { 901 | let skipped_tokens = self.recombine_skipped(skipped_idxs, l); 902 | Ok((res, Some(skipped_tokens))) 903 | } else { 904 | Ok((res, None)) 905 | } 906 | } 907 | 908 | fn could_be_tzname( 909 | &self, 910 | hour: Option, 911 | tzname: &Option, 912 | tzoffset: Option, 913 | token: &str, 914 | ) -> bool { 915 | let all_ascii_upper = token 916 | .chars() 917 | .all(|c| 65u8 as char <= c && c <= 90u8 as char); 918 | 919 | hour.is_some() 920 | && tzname.is_none() 921 | && tzoffset.is_none() 922 | && token.len() <= 5 923 | && (all_ascii_upper || self.info.utczone.contains_key(token)) 924 | } 925 | 926 | #[allow(clippy::unnecessary_unwrap)] 927 | fn ampm_valid(&self, hour: Option, ampm: Option, fuzzy: bool) -> ParseResult { 928 | let mut val_is_ampm = !(fuzzy && ampm.is_some()); 929 | 930 | if hour.is_none() { 931 | if fuzzy { 932 | val_is_ampm = false; 933 | } else { 934 | return Err(ParseError::AmPmWithoutHour); 935 | } 936 | } else if !(0 <= hour.unwrap() && hour.unwrap() <= 12) { 937 | if fuzzy { 938 | val_is_ampm = false; 939 | } else { 940 | return Err(ParseError::ImpossibleTimestamp("Invalid hour")); 941 | } 942 | } 943 | 944 | Ok(val_is_ampm) 945 | } 946 | 947 | fn build_naive( 948 | &self, 949 | res: &ParsingResult, 950 | default: &NaiveDateTime, 951 | ) -> ParseResult { 952 | let y = res.year.unwrap_or_else(|| default.year()); 953 | let m = res.month.unwrap_or_else(|| default.month() as i32) as u32; 954 | 955 | let d_offset = if res.weekday.is_some() && res.day.is_none() { 956 | let dow = day_of_week(y as u32, m, default.day())?; 957 | 958 | // UNWRAP: We've already check res.weekday() is some 959 | let actual_weekday = (res.weekday.unwrap() + 1) % 7; 960 | let other = DayOfWeek::from_numeral(actual_weekday as u32); 961 | Duration::days(i64::from(dow.difference(&other))) 962 | } else { 963 | Duration::days(0) 964 | }; 965 | 966 | // TODO: Change month/day to u32 967 | let d = NaiveDate::from_ymd_opt( 968 | y, 969 | m, 970 | min( 971 | res.day.unwrap_or(default.day() as i32) as u32, 972 | days_in_month(y, m as i32)?, 973 | ), 974 | ) 975 | .ok_or_else(|| ParseError::ImpossibleTimestamp("Invalid date range given"))?; 976 | 977 | let d = d + d_offset; 978 | 979 | let hour = res.hour.unwrap_or(default.hour() as i32) as u32; 980 | let minute = res.minute.unwrap_or(default.minute() as i32) as u32; 981 | let second = res.second.unwrap_or(default.second() as i32) as u32; 982 | let nanosecond = res 983 | .nanosecond 984 | .unwrap_or(default.timestamp_subsec_nanos() as i64) as u32; 985 | let t = 986 | NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond).ok_or_else(|| { 987 | if hour >= 24 { 988 | ParseError::ImpossibleTimestamp("Invalid hour") 989 | } else if minute >= 60 { 990 | ParseError::ImpossibleTimestamp("Invalid minute") 991 | } else if second >= 60 { 992 | ParseError::ImpossibleTimestamp("Invalid second") 993 | } else if nanosecond >= 2_000_000_000 { 994 | ParseError::ImpossibleTimestamp("Invalid microsecond") 995 | } else { 996 | unreachable!(); 997 | } 998 | })?; 999 | 1000 | Ok(NaiveDateTime::new(d, t)) 1001 | } 1002 | 1003 | fn build_tzaware( 1004 | &self, 1005 | _dt: &NaiveDateTime, 1006 | res: &ParsingResult, 1007 | tzinfos: &HashMap, 1008 | ) -> ParseResult> { 1009 | if let Some(offset) = res.tzoffset { 1010 | Ok(FixedOffset::east_opt(offset)) 1011 | } else if res.tzoffset == None 1012 | && (res.tzname == Some(" ".to_owned()) 1013 | || res.tzname == Some(".".to_owned()) 1014 | || res.tzname == Some("-".to_owned()) 1015 | || res.tzname == None) 1016 | { 1017 | Ok(None) 1018 | } else if res.tzname.is_some() && tzinfos.contains_key(res.tzname.as_ref().unwrap()) { 1019 | Ok(FixedOffset::east_opt( 1020 | *tzinfos.get(res.tzname.as_ref().unwrap()).unwrap(), 1021 | )) 1022 | } else if let Some(tzname) = res.tzname.as_ref() { 1023 | println!("tzname {} identified but not understood.", tzname); 1024 | Ok(None) 1025 | } else { 1026 | Err(ParseError::TimezoneUnsupported) 1027 | } 1028 | } 1029 | 1030 | #[allow(clippy::unnecessary_unwrap)] 1031 | fn parse_numeric_token( 1032 | &self, 1033 | tokens: &[String], 1034 | idx: usize, 1035 | info: &ParserInfo, 1036 | ymd: &mut YMD, 1037 | res: &mut ParsingResult, 1038 | fuzzy: bool, 1039 | ) -> ParseResult { 1040 | let mut idx = idx; 1041 | let value_repr = &tokens[idx]; 1042 | let mut value = Decimal::from_str(&value_repr).unwrap(); 1043 | 1044 | let len_li = value_repr.len(); 1045 | let len_l = tokens.len(); 1046 | 1047 | // TODO: I miss the `x in y` syntax 1048 | // TODO: Decompose this logic a bit 1049 | if ymd.len() == 3 1050 | && (len_li == 2 || len_li == 4) 1051 | && res.hour.is_none() 1052 | && (idx + 1 >= len_l 1053 | || (tokens[idx + 1] != ":" && info.hms_index(&tokens[idx + 1]).is_none())) 1054 | { 1055 | // 1990101T32[59] 1056 | let s = &tokens[idx]; 1057 | res.hour = s[0..2].parse::().ok(); 1058 | 1059 | if len_li == 4 { 1060 | res.minute = Some(s[2..4].parse::()?) 1061 | } 1062 | } else if len_li == 6 || (len_li > 6 && tokens[idx].find('.') == Some(6)) { 1063 | // YYMMDD or HHMMSS[.ss] 1064 | let s = &tokens[idx]; 1065 | 1066 | if ymd.len() == 0 && tokens[idx].find('.') == None { 1067 | ymd.append(s[0..2].parse::()?, &s[0..2], None)?; 1068 | ymd.append(s[2..4].parse::()?, &s[2..4], None)?; 1069 | ymd.append(s[4..6].parse::()?, &s[4..6], None)?; 1070 | } else { 1071 | // 19990101T235959[.59] 1072 | res.hour = s[0..2].parse::().ok(); 1073 | res.minute = s[2..4].parse::().ok(); 1074 | 1075 | let t = self.parsems(&s[4..])?; 1076 | res.second = Some(t.0); 1077 | res.nanosecond = Some(t.1); 1078 | } 1079 | } else if vec![8, 12, 14].contains(&len_li) { 1080 | // YYMMDD 1081 | let s = &tokens[idx]; 1082 | ymd.append(s[..4].parse::()?, &s[..4], Some(YMDLabel::Year))?; 1083 | ymd.append(s[4..6].parse::()?, &s[4..6], None)?; 1084 | ymd.append(s[6..8].parse::()?, &s[6..8], None)?; 1085 | 1086 | if len_li > 8 { 1087 | res.hour = Some(s[8..10].parse::()?); 1088 | res.minute = Some(s[10..12].parse::()?); 1089 | 1090 | if len_li > 12 { 1091 | res.second = Some(s[12..].parse::()?); 1092 | } 1093 | } 1094 | } else if let Some(hms_idx) = self.find_hms_index(idx, tokens, info, true) { 1095 | // HH[ ]h or MM[ ]m or SS[.ss][ ]s 1096 | let (new_idx, hms) = self.parse_hms(idx, tokens, info, Some(hms_idx)); 1097 | if let Some(hms) = hms { 1098 | self.assign_hms(res, value_repr, hms)?; 1099 | } 1100 | idx = new_idx; 1101 | } else if idx + 2 < len_l && tokens[idx + 1] == ":" { 1102 | // HH:MM[:SS[.ss]] 1103 | // TODO: Better story around Decimal handling 1104 | res.hour = Some(value.floor().to_i64().unwrap() as i32); 1105 | // TODO: Rescope `value` here? 1106 | value = self.to_decimal(&tokens[idx + 2])?; 1107 | let min_sec = self.parse_min_sec(value); 1108 | res.minute = Some(min_sec.0); 1109 | res.second = min_sec.1; 1110 | 1111 | if idx + 4 < len_l && tokens[idx + 3] == ":" { 1112 | // TODO: (x, y) = (a, b) syntax? 1113 | let ms = self.parsems(&tokens[idx + 4]).unwrap(); 1114 | res.second = Some(ms.0); 1115 | res.nanosecond = Some(ms.1); 1116 | 1117 | idx += 2; 1118 | } 1119 | idx += 2; 1120 | } else if idx + 1 < len_l 1121 | && (tokens[idx + 1] == "-" || tokens[idx + 1] == "/" || tokens[idx + 1] == ".") 1122 | { 1123 | // TODO: There's got to be a better way of handling the condition above 1124 | let sep = &tokens[idx + 1]; 1125 | ymd.append(value_repr.parse::()?, &value_repr, None)?; 1126 | 1127 | if idx + 2 < len_l && !info.jump_index(&tokens[idx + 2]) { 1128 | if let Ok(val) = tokens[idx + 2].parse::() { 1129 | ymd.append(val, &tokens[idx + 2], None)?; 1130 | } else if let Some(val) = info.month_index(&tokens[idx + 2]) { 1131 | ymd.append(val as i32, &tokens[idx + 2], Some(YMDLabel::Month))?; 1132 | } 1133 | 1134 | if idx + 3 < len_l && &tokens[idx + 3] == sep { 1135 | if tokens.len() <= idx + 4 { 1136 | return Err(ParseError::UnrecognizedFormat); 1137 | } else if let Some(value) = info.month_index(&tokens[idx + 4]) { 1138 | ymd.append(value as i32, &tokens[idx + 4], Some(YMDLabel::Month))?; 1139 | } else if let Ok(val) = tokens[idx + 4].parse::() { 1140 | ymd.append(val, &tokens[idx + 4], None)?; 1141 | } else { 1142 | return Err(ParseError::UnrecognizedFormat); 1143 | } 1144 | 1145 | idx += 2; 1146 | } 1147 | 1148 | idx += 1; 1149 | } 1150 | 1151 | idx += 1 1152 | } else if idx + 1 >= len_l || info.jump_index(&tokens[idx + 1]) { 1153 | if idx + 2 < len_l && info.ampm_index(&tokens[idx + 2]).is_some() { 1154 | let hour = value.to_i64().unwrap() as i32; 1155 | let ampm = info.ampm_index(&tokens[idx + 2]).unwrap(); 1156 | res.hour = Some(self.adjust_ampm(hour, ampm)); 1157 | idx += 1; 1158 | } else { 1159 | //let value = value.floor().to_i32().ok_or(Err(ParseError::InvalidNumeric())) 1160 | let value = value.floor().to_i32().ok_or_else(|| ParseError::InvalidNumeric(value_repr.to_owned()))?; 1161 | ymd.append(value, &value_repr, None)?; 1162 | } 1163 | 1164 | idx += 1; 1165 | } else if info.ampm_index(&tokens[idx + 1]).is_some() 1166 | && (*ZERO <= value && value < *TWENTY_FOUR) 1167 | { 1168 | // 12am 1169 | let hour = value.to_i64().unwrap() as i32; 1170 | res.hour = Some(self.adjust_ampm(hour, info.ampm_index(&tokens[idx + 1]).unwrap())); 1171 | idx += 1; 1172 | } else if ymd.could_be_day(value.to_i64().unwrap() as i32) { 1173 | ymd.append(value.to_i64().unwrap() as i32, &value_repr, None)?; 1174 | } else if !fuzzy { 1175 | return Err(ParseError::UnrecognizedFormat); 1176 | } 1177 | 1178 | Ok(idx) 1179 | } 1180 | 1181 | fn adjust_ampm(&self, hour: i32, ampm: bool) -> i32 { 1182 | if hour < 12 && ampm { 1183 | hour + 12 1184 | } else if hour == 12 && !ampm { 1185 | 0 1186 | } else { 1187 | hour 1188 | } 1189 | } 1190 | 1191 | fn parsems(&self, seconds_str: &str) -> ParseResult<(i32, i64)> { 1192 | if seconds_str.contains('.') { 1193 | let split: Vec<&str> = seconds_str.split('.').collect(); 1194 | let (i, f): (&str, &str) = (split[0], split[1]); 1195 | 1196 | let i_parse = i.parse::()?; 1197 | let f_parse = ljust(f, 9, '0').parse::()?; 1198 | Ok((i_parse, f_parse)) 1199 | } else { 1200 | Ok((seconds_str.parse::()?, 0)) 1201 | } 1202 | } 1203 | 1204 | fn find_hms_index( 1205 | &self, 1206 | idx: usize, 1207 | tokens: &[String], 1208 | info: &ParserInfo, 1209 | allow_jump: bool, 1210 | ) -> Option { 1211 | let len_l = tokens.len(); 1212 | let mut hms_idx = None; 1213 | 1214 | // There's a super weird edge case that can happen 1215 | // because Python safely handles negative array indices, 1216 | // and Rust (because of usize) does not. 1217 | let idx_minus_two = if idx == 1 && len_l > 0 { 1218 | len_l - 1 1219 | } else if idx == 0 && len_l > 1 { 1220 | len_l - 2 1221 | } else if idx > 1 { 1222 | idx - 2 1223 | } else if len_l == 0 { 1224 | panic!("Attempting to find_hms_index() wih no tokens."); 1225 | } else { 1226 | 0 1227 | }; 1228 | 1229 | if idx + 1 < len_l && info.hms_index(&tokens[idx + 1]).is_some() { 1230 | hms_idx = Some(idx + 1) 1231 | } else if allow_jump 1232 | && idx + 2 < len_l 1233 | && tokens[idx + 1] == " " 1234 | && info.hms_index(&tokens[idx + 2]).is_some() 1235 | { 1236 | hms_idx = Some(idx + 2) 1237 | } else if idx > 0 && info.hms_index(&tokens[idx - 1]).is_some() { 1238 | hms_idx = Some(idx - 1) 1239 | } else if len_l > 0 1240 | && idx > 0 1241 | && idx == len_l - 1 1242 | && tokens[idx - 1] == " " 1243 | && info.hms_index(&tokens[idx_minus_two]).is_some() 1244 | { 1245 | hms_idx = Some(idx - 2) 1246 | } 1247 | 1248 | hms_idx 1249 | } 1250 | 1251 | #[allow(clippy::unnecessary_unwrap)] 1252 | fn parse_hms( 1253 | &self, 1254 | idx: usize, 1255 | tokens: &[String], 1256 | info: &ParserInfo, 1257 | hms_index: Option, 1258 | ) -> (usize, Option) { 1259 | if hms_index.is_none() { 1260 | (idx, None) 1261 | } else if hms_index.unwrap() > idx { 1262 | ( 1263 | hms_index.unwrap(), 1264 | info.hms_index(&tokens[hms_index.unwrap()]), 1265 | ) 1266 | } else { 1267 | ( 1268 | idx, 1269 | info.hms_index(&tokens[hms_index.unwrap()]).map(|u| u + 1), 1270 | ) 1271 | } 1272 | } 1273 | 1274 | fn assign_hms(&self, res: &mut ParsingResult, value_repr: &str, hms: usize) -> ParseResult<()> { 1275 | let value = self.to_decimal(value_repr)?; 1276 | 1277 | if hms == 0 { 1278 | res.hour = value.to_i32(); 1279 | if !close_to_integer(&value) { 1280 | res.minute = Some((*SIXTY * (value % *ONE)).to_i64().unwrap() as i32); 1281 | } 1282 | } else if hms == 1 { 1283 | let (min, sec) = self.parse_min_sec(value); 1284 | res.minute = Some(min); 1285 | res.second = sec; 1286 | } else if hms == 2 { 1287 | let (sec, micro) = self.parsems(value_repr).unwrap(); 1288 | res.second = Some(sec); 1289 | res.nanosecond = Some(micro); 1290 | } 1291 | 1292 | Ok(()) 1293 | } 1294 | 1295 | fn to_decimal(&self, value: &str) -> ParseResult { 1296 | Decimal::from_str(value).or_else(|_| Err(ParseError::InvalidNumeric(value.to_owned()))) 1297 | } 1298 | 1299 | fn parse_min_sec(&self, value: Decimal) -> (i32, Option) { 1300 | // UNWRAP: i64 guaranteed to be fine because of preceding floor 1301 | let minute = value.floor().to_i64().unwrap() as i32; 1302 | let mut second = None; 1303 | 1304 | let sec_remainder = value - value.floor(); 1305 | if sec_remainder != *ZERO { 1306 | second = Some((*SIXTY * sec_remainder).floor().to_i64().unwrap() as i32); 1307 | } 1308 | 1309 | (minute, second) 1310 | } 1311 | 1312 | fn recombine_skipped(&self, skipped_idxs: Vec, tokens: Vec) -> Vec { 1313 | let mut skipped_tokens: Vec = vec![]; 1314 | 1315 | let mut sorted_idxs = skipped_idxs.clone(); 1316 | sorted_idxs.sort(); 1317 | 1318 | for (i, idx) in sorted_idxs.iter().enumerate() { 1319 | if i > 0 && idx - 1 == skipped_idxs[i - 1] { 1320 | // UNWRAP: Having an initial value and unconditional push at end guarantees value 1321 | let mut t = skipped_tokens.pop().unwrap(); 1322 | t.push_str(tokens[*idx].as_ref()); 1323 | skipped_tokens.push(t); 1324 | } else { 1325 | skipped_tokens.push(tokens[*idx].to_owned()); 1326 | } 1327 | } 1328 | 1329 | skipped_tokens 1330 | } 1331 | } 1332 | 1333 | fn close_to_integer(value: &Decimal) -> bool { 1334 | value % *ONE == *ZERO 1335 | } 1336 | 1337 | fn ljust(s: &str, chars: usize, replace: char) -> String { 1338 | if s.len() >= chars { 1339 | s[..chars].to_owned() 1340 | } else { 1341 | format!("{}{}", s, replace.to_string().repeat(chars - s.len())) 1342 | } 1343 | } 1344 | 1345 | /// Main entry point for using `dtparse`. The parse function is responsible for 1346 | /// taking in a string representing some time value, and turning it into 1347 | /// a timestamp with optional timezone information if it can be identified. 1348 | /// 1349 | /// The default implementation assumes English values for names of months, 1350 | /// days of the week, etc. It is equivalent to Python's `dateutil.parser.parse()` 1351 | pub fn parse(timestr: &str) -> ParseResult<(NaiveDateTime, Option)> { 1352 | let res = DEFAULT_PARSER.parse( 1353 | timestr, 1354 | None, 1355 | None, 1356 | false, 1357 | false, 1358 | None, 1359 | false, 1360 | &HashMap::new(), 1361 | )?; 1362 | 1363 | Ok((res.0, res.1)) 1364 | } 1365 | -------------------------------------------------------------------------------- /src/tests/fuzzing.rs: -------------------------------------------------------------------------------- 1 | use base64::Engine; 2 | use base64::engine::general_purpose::STANDARD; 3 | use chrono::NaiveDate; 4 | use std::collections::HashMap; 5 | use std::str; 6 | 7 | use parse; 8 | use ParseError; 9 | use Parser; 10 | 11 | #[test] 12 | fn test_fuzz() { 13 | assert_eq!( 14 | parse("\x2D\x38\x31\x39\x34\x38\x34"), 15 | Err(ParseError::ImpossibleTimestamp("Invalid month")) 16 | ); 17 | 18 | // Garbage in the third delimited field 19 | assert_eq!( 20 | parse("2..\x00\x000d\x00+\x010d\x01\x00\x00\x00+"), 21 | Err(ParseError::UnrecognizedFormat) 22 | ); 23 | 24 | let default = NaiveDate::from_ymd_opt(2016, 6, 29).unwrap().and_hms_opt(0, 0, 0).unwrap(); 25 | let p = Parser::default(); 26 | let res = p.parse( 27 | "\x0D\x31", 28 | None, 29 | None, 30 | false, 31 | false, 32 | Some(&default), 33 | false, 34 | &HashMap::new(), 35 | ); 36 | assert_eq!(res, Err(ParseError::NoDate)); 37 | 38 | assert_eq!( 39 | parse("\x2D\x2D\x32\x31\x38\x6D"), 40 | Err(ParseError::ImpossibleTimestamp("Invalid minute")) 41 | ); 42 | } 43 | 44 | #[test] 45 | fn large_int() { 46 | let parse_result = parse("1412409095009.jpg"); 47 | assert!(parse_result.is_err()); 48 | } 49 | 50 | #[test] 51 | fn another_large_int() { 52 | let parse_result = parse("1412409095009"); 53 | assert!(parse_result.is_err()); 54 | } 55 | 56 | #[test] 57 | fn an_even_larger_int() { 58 | let parse_result = parse("1566997680962280"); 59 | assert!(parse_result.is_err()); 60 | } 61 | 62 | #[test] 63 | fn empty_string() { 64 | assert_eq!(parse(""), Err(ParseError::NoDate)) 65 | } 66 | 67 | #[test] 68 | fn github_33() { 69 | assert_eq!(parse("66:'"), Err(ParseError::InvalidNumeric("'".to_owned()))) 70 | } 71 | 72 | #[test] 73 | fn github_32() { 74 | assert_eq!(parse("99999999999999999999999"), Err(ParseError::InvalidNumeric("99999999999999999999999".to_owned()))) 75 | } 76 | 77 | #[test] 78 | fn github_34() { 79 | let parse_vec = STANDARD.decode("KTMuLjYpGDYvLjZTNiouNjYuHzZpLjY/NkwuNh42Ry42PzYnKTMuNk02NjY2NjA2NjY2NjY2NjYTNjY2Ni82NjY2NlAuNlAuNlNI").unwrap(); 80 | let parse_str = str::from_utf8(&parse_vec).unwrap(); 81 | let parse_result = parse(parse_str); 82 | assert!(parse_result.is_err()); 83 | } 84 | 85 | #[test] 86 | fn github_35() { 87 | let parse_vec = STANDARD.decode("KTY6LjYqNio6KjYn").unwrap(); 88 | let parse_str = str::from_utf8(&parse_vec).unwrap(); 89 | let parse_result = parse(parse_str); 90 | assert!(parse_result.is_err()); 91 | } 92 | 93 | #[test] 94 | fn github_36() { 95 | let parse_vec = STANDARD.decode("KTYuLg==").unwrap(); 96 | let parse_str = str::from_utf8(&parse_vec).unwrap(); 97 | let parse_result = parse(parse_str); 98 | assert!(parse_result.is_err()); 99 | } 100 | 101 | #[test] 102 | fn github_45() { 103 | assert!(parse("/2018-fifa-").is_err()); 104 | assert!(parse("/2009/07/").is_err()); 105 | assert!(parse("2021-09-").is_err()); 106 | } -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod fuzzing; 2 | mod pycompat_parser; 3 | mod pycompat_tokenizer; 4 | 5 | use chrono::NaiveDate; 6 | use crate::parse; 7 | 8 | #[test] 9 | fn nanosecond_precision() { 10 | assert_eq!( 11 | parse("2008.12.29T08:09:10.123456789").unwrap(), 12 | (NaiveDate::from_ymd_opt(2008, 12, 29).unwrap().and_hms_nano_opt(8, 9, 10, 123_456_789).unwrap(), None) 13 | ) 14 | } -------------------------------------------------------------------------------- /src/tests/pycompat_parser.rs: -------------------------------------------------------------------------------- 1 | 2 | //! This code has been generated by running the `build_pycompat.py` script 3 | //! in the repository root. Please do not edit it, as your edits will be destroyed 4 | //! upon re-running code generation. 5 | 6 | extern crate chrono; 7 | 8 | use chrono::Datelike; 9 | use chrono::NaiveDate; 10 | use chrono::NaiveDateTime; 11 | use chrono::Timelike; 12 | use std::collections::HashMap; 13 | 14 | use Parser; 15 | use ParserInfo; 16 | use parse; 17 | 18 | struct PyDateTime { 19 | year: i32, 20 | month: u32, 21 | day: u32, 22 | hour: u32, 23 | minute: u32, 24 | second: u32, 25 | micros: u32, 26 | tzo: Option 27 | } 28 | 29 | fn parse_and_assert( 30 | pdt: PyDateTime, 31 | info: ParserInfo, 32 | s: &str, 33 | dayfirst: Option, 34 | yearfirst: Option, 35 | fuzzy: bool, 36 | fuzzy_with_tokens: bool, 37 | default: Option<&NaiveDateTime>, 38 | ignoretz: bool, 39 | tzinfos: &HashMap, 40 | ) { 41 | 42 | let parser = Parser::new(info); 43 | let rs_parsed = parser.parse( 44 | s, 45 | dayfirst, 46 | yearfirst, 47 | fuzzy, 48 | fuzzy_with_tokens, 49 | default, 50 | ignoretz, 51 | tzinfos).expect(&format!("Unable to parse date in Rust '{}'", s)); 52 | 53 | assert_eq!(pdt.year, rs_parsed.0.year(), "Year mismatch for '{}'", s); 54 | assert_eq!(pdt.month, rs_parsed.0.month(), "Month mismatch for '{}'", s); 55 | assert_eq!(pdt.day, rs_parsed.0.day(), "Day mismatch for '{}'", s); 56 | assert_eq!(pdt.hour, rs_parsed.0.hour(), "Hour mismatch for '{}'", s); 57 | assert_eq!(pdt.minute, rs_parsed.0.minute(), "Minute mismatch f'or' {}", s); 58 | assert_eq!(pdt.second, rs_parsed.0.second(), "Second mismatch for '{}'", s); 59 | assert_eq!(pdt.micros, rs_parsed.0.timestamp_subsec_micros(), "Microsecond mismatch for '{}'", s); 60 | assert_eq!(pdt.tzo, rs_parsed.1.map(|u| u.local_minus_utc()), "Timezone Offset mismatch for '{}'", s); 61 | } 62 | 63 | fn parse_and_assert_simple( 64 | pdt: PyDateTime, 65 | s: &str, 66 | ) { 67 | let rs_parsed = parse(s).expect(&format!("Unable to parse date in Rust '{}'", s)); 68 | assert_eq!(pdt.year, rs_parsed.0.year(), "Year mismatch for '{}'", s); 69 | assert_eq!(pdt.month, rs_parsed.0.month(), "Month mismatch for '{}'", s); 70 | assert_eq!(pdt.day, rs_parsed.0.day(), "Day mismatch for '{}'", s); 71 | assert_eq!(pdt.hour, rs_parsed.0.hour(), "Hour mismatch for '{}'", s); 72 | assert_eq!(pdt.minute, rs_parsed.0.minute(), "Minute mismatch for '{}'", s); 73 | assert_eq!(pdt.second, rs_parsed.0.second(), "Second mismatch for '{}'", s); 74 | assert_eq!(pdt.micros, rs_parsed.0.timestamp_subsec_micros(), "Microsecond mismatch for '{}'", s); 75 | assert_eq!(pdt.tzo, rs_parsed.1.map(|u| u.local_minus_utc()), "Timezone Offset mismatch for '{}'", s); 76 | } 77 | 78 | fn parse_fuzzy_and_assert( 79 | pdt: PyDateTime, 80 | ptokens: Option>, 81 | info: ParserInfo, 82 | s: &str, 83 | dayfirst: Option, 84 | yearfirst: Option, 85 | fuzzy: bool, 86 | fuzzy_with_tokens: bool, 87 | default: Option<&NaiveDateTime>, 88 | ignoretz: bool, 89 | tzinfos: &HashMap, 90 | ) { 91 | 92 | let parser = Parser::new(info); 93 | let rs_parsed = parser.parse( 94 | s, 95 | dayfirst, 96 | yearfirst, 97 | fuzzy, 98 | fuzzy_with_tokens, 99 | default, 100 | ignoretz, 101 | tzinfos).expect(&format!("Unable to parse date in Rust '{}'", s)); 102 | 103 | assert_eq!(pdt.year, rs_parsed.0.year(), "Year mismatch for '{}'", s); 104 | assert_eq!(pdt.month, rs_parsed.0.month(), "Month mismatch for '{}'", s); 105 | assert_eq!(pdt.day, rs_parsed.0.day(), "Day mismatch for '{}'", s); 106 | assert_eq!(pdt.hour, rs_parsed.0.hour(), "Hour mismatch for '{}'", s); 107 | assert_eq!(pdt.minute, rs_parsed.0.minute(), "Minute mismatch f'or' {}", s); 108 | assert_eq!(pdt.second, rs_parsed.0.second(), "Second mismatch for '{}'", s); 109 | assert_eq!(pdt.micros, rs_parsed.0.timestamp_subsec_micros(), "Microsecond mismatch for '{}'", s); 110 | assert_eq!(pdt.tzo, rs_parsed.1.map(|u| u.local_minus_utc()), "Timezone Offset mismatch for '{}'", s); 111 | assert_eq!(ptokens, rs_parsed.2, "Tokens mismatch for '{}'", s); 112 | } 113 | 114 | macro_rules! rs_tzinfo_map { 115 | () => ({ 116 | let mut h = HashMap::new(); 117 | h.insert("BRST".to_owned(), -10800); 118 | h 119 | }); 120 | } 121 | 122 | #[test] 123 | fn test_parse_default0() { 124 | let info = ParserInfo::default(); 125 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 126 | let pdt = PyDateTime { 127 | year: 2003, month: 9, day: 25, 128 | hour: 10, minute: 36, second: 28, 129 | micros: 0, tzo: None 130 | }; 131 | parse_and_assert(pdt, info, "Thu Sep 25 10:36:28", None, None, false, false, 132 | Some(default_rsdate), false, &HashMap::new()); 133 | } 134 | 135 | #[test] 136 | fn test_parse_default1() { 137 | let info = ParserInfo::default(); 138 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 139 | let pdt = PyDateTime { 140 | year: 2003, month: 9, day: 25, 141 | hour: 10, minute: 36, second: 28, 142 | micros: 0, tzo: None 143 | }; 144 | parse_and_assert(pdt, info, "Sep 10:36:28", None, None, false, false, 145 | Some(default_rsdate), false, &HashMap::new()); 146 | } 147 | 148 | #[test] 149 | fn test_parse_default2() { 150 | let info = ParserInfo::default(); 151 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 152 | let pdt = PyDateTime { 153 | year: 2003, month: 9, day: 25, 154 | hour: 10, minute: 36, second: 28, 155 | micros: 0, tzo: None 156 | }; 157 | parse_and_assert(pdt, info, "10:36:28", None, None, false, false, 158 | Some(default_rsdate), false, &HashMap::new()); 159 | } 160 | 161 | #[test] 162 | fn test_parse_default3() { 163 | let info = ParserInfo::default(); 164 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 165 | let pdt = PyDateTime { 166 | year: 2003, month: 9, day: 25, 167 | hour: 10, minute: 36, second: 0, 168 | micros: 0, tzo: None 169 | }; 170 | parse_and_assert(pdt, info, "10:36", None, None, false, false, 171 | Some(default_rsdate), false, &HashMap::new()); 172 | } 173 | 174 | #[test] 175 | fn test_parse_default4() { 176 | let info = ParserInfo::default(); 177 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 178 | let pdt = PyDateTime { 179 | year: 2003, month: 9, day: 25, 180 | hour: 0, minute: 0, second: 0, 181 | micros: 0, tzo: None 182 | }; 183 | parse_and_assert(pdt, info, "Sep 2003", None, None, false, false, 184 | Some(default_rsdate), false, &HashMap::new()); 185 | } 186 | 187 | #[test] 188 | fn test_parse_default5() { 189 | let info = ParserInfo::default(); 190 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 191 | let pdt = PyDateTime { 192 | year: 2003, month: 9, day: 25, 193 | hour: 0, minute: 0, second: 0, 194 | micros: 0, tzo: None 195 | }; 196 | parse_and_assert(pdt, info, "Sep", None, None, false, false, 197 | Some(default_rsdate), false, &HashMap::new()); 198 | } 199 | 200 | #[test] 201 | fn test_parse_default6() { 202 | let info = ParserInfo::default(); 203 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 204 | let pdt = PyDateTime { 205 | year: 2003, month: 9, day: 25, 206 | hour: 0, minute: 0, second: 0, 207 | micros: 0, tzo: None 208 | }; 209 | parse_and_assert(pdt, info, "2003", None, None, false, false, 210 | Some(default_rsdate), false, &HashMap::new()); 211 | } 212 | 213 | #[test] 214 | fn test_parse_default7() { 215 | let info = ParserInfo::default(); 216 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 217 | let pdt = PyDateTime { 218 | year: 2003, month: 9, day: 25, 219 | hour: 10, minute: 36, second: 28, 220 | micros: 500000, tzo: None 221 | }; 222 | parse_and_assert(pdt, info, "10h36m28.5s", None, None, false, false, 223 | Some(default_rsdate), false, &HashMap::new()); 224 | } 225 | 226 | #[test] 227 | fn test_parse_default8() { 228 | let info = ParserInfo::default(); 229 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 230 | let pdt = PyDateTime { 231 | year: 2003, month: 9, day: 25, 232 | hour: 10, minute: 36, second: 28, 233 | micros: 0, tzo: None 234 | }; 235 | parse_and_assert(pdt, info, "10h36m28s", None, None, false, false, 236 | Some(default_rsdate), false, &HashMap::new()); 237 | } 238 | 239 | #[test] 240 | fn test_parse_default9() { 241 | let info = ParserInfo::default(); 242 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 243 | let pdt = PyDateTime { 244 | year: 2003, month: 9, day: 25, 245 | hour: 10, minute: 36, second: 0, 246 | micros: 0, tzo: None 247 | }; 248 | parse_and_assert(pdt, info, "10h36m", None, None, false, false, 249 | Some(default_rsdate), false, &HashMap::new()); 250 | } 251 | 252 | #[test] 253 | fn test_parse_default10() { 254 | let info = ParserInfo::default(); 255 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 256 | let pdt = PyDateTime { 257 | year: 2003, month: 9, day: 25, 258 | hour: 10, minute: 0, second: 0, 259 | micros: 0, tzo: None 260 | }; 261 | parse_and_assert(pdt, info, "10h", None, None, false, false, 262 | Some(default_rsdate), false, &HashMap::new()); 263 | } 264 | 265 | #[test] 266 | fn test_parse_default11() { 267 | let info = ParserInfo::default(); 268 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 269 | let pdt = PyDateTime { 270 | year: 2003, month: 9, day: 25, 271 | hour: 10, minute: 36, second: 0, 272 | micros: 0, tzo: None 273 | }; 274 | parse_and_assert(pdt, info, "10 h 36", None, None, false, false, 275 | Some(default_rsdate), false, &HashMap::new()); 276 | } 277 | 278 | #[test] 279 | fn test_parse_default12() { 280 | let info = ParserInfo::default(); 281 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 282 | let pdt = PyDateTime { 283 | year: 2003, month: 9, day: 25, 284 | hour: 10, minute: 36, second: 30, 285 | micros: 0, tzo: None 286 | }; 287 | parse_and_assert(pdt, info, "10 h 36.5", None, None, false, false, 288 | Some(default_rsdate), false, &HashMap::new()); 289 | } 290 | 291 | #[test] 292 | fn test_parse_default13() { 293 | let info = ParserInfo::default(); 294 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 295 | let pdt = PyDateTime { 296 | year: 2003, month: 9, day: 25, 297 | hour: 0, minute: 36, second: 5, 298 | micros: 0, tzo: None 299 | }; 300 | parse_and_assert(pdt, info, "36 m 5", None, None, false, false, 301 | Some(default_rsdate), false, &HashMap::new()); 302 | } 303 | 304 | #[test] 305 | fn test_parse_default14() { 306 | let info = ParserInfo::default(); 307 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 308 | let pdt = PyDateTime { 309 | year: 2003, month: 9, day: 25, 310 | hour: 0, minute: 36, second: 5, 311 | micros: 0, tzo: None 312 | }; 313 | parse_and_assert(pdt, info, "36 m 5 s", None, None, false, false, 314 | Some(default_rsdate), false, &HashMap::new()); 315 | } 316 | 317 | #[test] 318 | fn test_parse_default15() { 319 | let info = ParserInfo::default(); 320 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 321 | let pdt = PyDateTime { 322 | year: 2003, month: 9, day: 25, 323 | hour: 0, minute: 36, second: 5, 324 | micros: 0, tzo: None 325 | }; 326 | parse_and_assert(pdt, info, "36 m 05", None, None, false, false, 327 | Some(default_rsdate), false, &HashMap::new()); 328 | } 329 | 330 | #[test] 331 | fn test_parse_default16() { 332 | let info = ParserInfo::default(); 333 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 334 | let pdt = PyDateTime { 335 | year: 2003, month: 9, day: 25, 336 | hour: 0, minute: 36, second: 5, 337 | micros: 0, tzo: None 338 | }; 339 | parse_and_assert(pdt, info, "36 m 05 s", None, None, false, false, 340 | Some(default_rsdate), false, &HashMap::new()); 341 | } 342 | 343 | #[test] 344 | fn test_parse_default17() { 345 | let info = ParserInfo::default(); 346 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 347 | let pdt = PyDateTime { 348 | year: 2003, month: 9, day: 25, 349 | hour: 10, minute: 0, second: 0, 350 | micros: 0, tzo: None 351 | }; 352 | parse_and_assert(pdt, info, "10h am", None, None, false, false, 353 | Some(default_rsdate), false, &HashMap::new()); 354 | } 355 | 356 | #[test] 357 | fn test_parse_default18() { 358 | let info = ParserInfo::default(); 359 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 360 | let pdt = PyDateTime { 361 | year: 2003, month: 9, day: 25, 362 | hour: 22, minute: 0, second: 0, 363 | micros: 0, tzo: None 364 | }; 365 | parse_and_assert(pdt, info, "10h pm", None, None, false, false, 366 | Some(default_rsdate), false, &HashMap::new()); 367 | } 368 | 369 | #[test] 370 | fn test_parse_default19() { 371 | let info = ParserInfo::default(); 372 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 373 | let pdt = PyDateTime { 374 | year: 2003, month: 9, day: 25, 375 | hour: 10, minute: 0, second: 0, 376 | micros: 0, tzo: None 377 | }; 378 | parse_and_assert(pdt, info, "10am", None, None, false, false, 379 | Some(default_rsdate), false, &HashMap::new()); 380 | } 381 | 382 | #[test] 383 | fn test_parse_default20() { 384 | let info = ParserInfo::default(); 385 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 386 | let pdt = PyDateTime { 387 | year: 2003, month: 9, day: 25, 388 | hour: 22, minute: 0, second: 0, 389 | micros: 0, tzo: None 390 | }; 391 | parse_and_assert(pdt, info, "10pm", None, None, false, false, 392 | Some(default_rsdate), false, &HashMap::new()); 393 | } 394 | 395 | #[test] 396 | fn test_parse_default21() { 397 | let info = ParserInfo::default(); 398 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 399 | let pdt = PyDateTime { 400 | year: 2003, month: 9, day: 25, 401 | hour: 10, minute: 0, second: 0, 402 | micros: 0, tzo: None 403 | }; 404 | parse_and_assert(pdt, info, "10:00 am", None, None, false, false, 405 | Some(default_rsdate), false, &HashMap::new()); 406 | } 407 | 408 | #[test] 409 | fn test_parse_default22() { 410 | let info = ParserInfo::default(); 411 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 412 | let pdt = PyDateTime { 413 | year: 2003, month: 9, day: 25, 414 | hour: 22, minute: 0, second: 0, 415 | micros: 0, tzo: None 416 | }; 417 | parse_and_assert(pdt, info, "10:00 pm", None, None, false, false, 418 | Some(default_rsdate), false, &HashMap::new()); 419 | } 420 | 421 | #[test] 422 | fn test_parse_default23() { 423 | let info = ParserInfo::default(); 424 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 425 | let pdt = PyDateTime { 426 | year: 2003, month: 9, day: 25, 427 | hour: 10, minute: 0, second: 0, 428 | micros: 0, tzo: None 429 | }; 430 | parse_and_assert(pdt, info, "10:00am", None, None, false, false, 431 | Some(default_rsdate), false, &HashMap::new()); 432 | } 433 | 434 | #[test] 435 | fn test_parse_default24() { 436 | let info = ParserInfo::default(); 437 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 438 | let pdt = PyDateTime { 439 | year: 2003, month: 9, day: 25, 440 | hour: 22, minute: 0, second: 0, 441 | micros: 0, tzo: None 442 | }; 443 | parse_and_assert(pdt, info, "10:00pm", None, None, false, false, 444 | Some(default_rsdate), false, &HashMap::new()); 445 | } 446 | 447 | #[test] 448 | fn test_parse_default25() { 449 | let info = ParserInfo::default(); 450 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 451 | let pdt = PyDateTime { 452 | year: 2003, month: 9, day: 25, 453 | hour: 10, minute: 0, second: 0, 454 | micros: 0, tzo: None 455 | }; 456 | parse_and_assert(pdt, info, "10:00a.m", None, None, false, false, 457 | Some(default_rsdate), false, &HashMap::new()); 458 | } 459 | 460 | #[test] 461 | fn test_parse_default26() { 462 | let info = ParserInfo::default(); 463 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 464 | let pdt = PyDateTime { 465 | year: 2003, month: 9, day: 25, 466 | hour: 22, minute: 0, second: 0, 467 | micros: 0, tzo: None 468 | }; 469 | parse_and_assert(pdt, info, "10:00p.m", None, None, false, false, 470 | Some(default_rsdate), false, &HashMap::new()); 471 | } 472 | 473 | #[test] 474 | fn test_parse_default27() { 475 | let info = ParserInfo::default(); 476 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 477 | let pdt = PyDateTime { 478 | year: 2003, month: 9, day: 25, 479 | hour: 10, minute: 0, second: 0, 480 | micros: 0, tzo: None 481 | }; 482 | parse_and_assert(pdt, info, "10:00a.m.", None, None, false, false, 483 | Some(default_rsdate), false, &HashMap::new()); 484 | } 485 | 486 | #[test] 487 | fn test_parse_default28() { 488 | let info = ParserInfo::default(); 489 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 490 | let pdt = PyDateTime { 491 | year: 2003, month: 9, day: 25, 492 | hour: 22, minute: 0, second: 0, 493 | micros: 0, tzo: None 494 | }; 495 | parse_and_assert(pdt, info, "10:00p.m.", None, None, false, false, 496 | Some(default_rsdate), false, &HashMap::new()); 497 | } 498 | 499 | #[test] 500 | fn test_parse_default29() { 501 | let info = ParserInfo::default(); 502 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 503 | let pdt = PyDateTime { 504 | year: 2003, month: 10, day: 25, 505 | hour: 0, minute: 0, second: 0, 506 | micros: 0, tzo: None 507 | }; 508 | parse_and_assert(pdt, info, "October", None, None, false, false, 509 | Some(default_rsdate), false, &HashMap::new()); 510 | } 511 | 512 | #[test] 513 | fn test_parse_default30() { 514 | let info = ParserInfo::default(); 515 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 516 | let pdt = PyDateTime { 517 | year: 2000, month: 12, day: 31, 518 | hour: 0, minute: 0, second: 0, 519 | micros: 0, tzo: None 520 | }; 521 | parse_and_assert(pdt, info, "31-Dec-00", None, None, false, false, 522 | Some(default_rsdate), false, &HashMap::new()); 523 | } 524 | 525 | #[test] 526 | fn test_parse_default31() { 527 | let info = ParserInfo::default(); 528 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 529 | let pdt = PyDateTime { 530 | year: 2003, month: 9, day: 25, 531 | hour: 0, minute: 1, second: 2, 532 | micros: 0, tzo: None 533 | }; 534 | parse_and_assert(pdt, info, "0:01:02", None, None, false, false, 535 | Some(default_rsdate), false, &HashMap::new()); 536 | } 537 | 538 | #[test] 539 | fn test_parse_default32() { 540 | let info = ParserInfo::default(); 541 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 542 | let pdt = PyDateTime { 543 | year: 2003, month: 9, day: 25, 544 | hour: 0, minute: 1, second: 2, 545 | micros: 0, tzo: None 546 | }; 547 | parse_and_assert(pdt, info, "12h 01m02s am", None, None, false, false, 548 | Some(default_rsdate), false, &HashMap::new()); 549 | } 550 | 551 | #[test] 552 | fn test_parse_default33() { 553 | let info = ParserInfo::default(); 554 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 555 | let pdt = PyDateTime { 556 | year: 2003, month: 9, day: 25, 557 | hour: 12, minute: 8, second: 0, 558 | micros: 0, tzo: None 559 | }; 560 | parse_and_assert(pdt, info, "12:08 PM", None, None, false, false, 561 | Some(default_rsdate), false, &HashMap::new()); 562 | } 563 | 564 | #[test] 565 | fn test_parse_default34() { 566 | let info = ParserInfo::default(); 567 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 568 | let pdt = PyDateTime { 569 | year: 2003, month: 9, day: 25, 570 | hour: 1, minute: 2, second: 3, 571 | micros: 0, tzo: None 572 | }; 573 | parse_and_assert(pdt, info, "01h02m03", None, None, false, false, 574 | Some(default_rsdate), false, &HashMap::new()); 575 | } 576 | 577 | #[test] 578 | fn test_parse_default35() { 579 | let info = ParserInfo::default(); 580 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 581 | let pdt = PyDateTime { 582 | year: 2003, month: 9, day: 25, 583 | hour: 1, minute: 2, second: 0, 584 | micros: 0, tzo: None 585 | }; 586 | parse_and_assert(pdt, info, "01h02", None, None, false, false, 587 | Some(default_rsdate), false, &HashMap::new()); 588 | } 589 | 590 | #[test] 591 | fn test_parse_default36() { 592 | let info = ParserInfo::default(); 593 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 594 | let pdt = PyDateTime { 595 | year: 2003, month: 9, day: 25, 596 | hour: 1, minute: 0, second: 2, 597 | micros: 0, tzo: None 598 | }; 599 | parse_and_assert(pdt, info, "01h02s", None, None, false, false, 600 | Some(default_rsdate), false, &HashMap::new()); 601 | } 602 | 603 | #[test] 604 | fn test_parse_default37() { 605 | let info = ParserInfo::default(); 606 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 607 | let pdt = PyDateTime { 608 | year: 2003, month: 9, day: 25, 609 | hour: 0, minute: 1, second: 2, 610 | micros: 0, tzo: None 611 | }; 612 | parse_and_assert(pdt, info, "01m02", None, None, false, false, 613 | Some(default_rsdate), false, &HashMap::new()); 614 | } 615 | 616 | #[test] 617 | fn test_parse_default38() { 618 | let info = ParserInfo::default(); 619 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 620 | let pdt = PyDateTime { 621 | year: 2003, month: 9, day: 25, 622 | hour: 2, minute: 1, second: 0, 623 | micros: 0, tzo: None 624 | }; 625 | parse_and_assert(pdt, info, "01m02h", None, None, false, false, 626 | Some(default_rsdate), false, &HashMap::new()); 627 | } 628 | 629 | #[test] 630 | fn test_parse_default39() { 631 | let info = ParserInfo::default(); 632 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 633 | let pdt = PyDateTime { 634 | year: 2004, month: 4, day: 10, 635 | hour: 11, minute: 30, second: 0, 636 | micros: 0, tzo: None 637 | }; 638 | parse_and_assert(pdt, info, "2004 10 Apr 11h30m", None, None, false, false, 639 | Some(default_rsdate), false, &HashMap::new()); 640 | } 641 | 642 | #[test] 643 | fn test_parse_default40() { 644 | let info = ParserInfo::default(); 645 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 646 | let pdt = PyDateTime { 647 | year: 2003, month: 9, day: 3, 648 | hour: 0, minute: 0, second: 0, 649 | micros: 0, tzo: None 650 | }; 651 | parse_and_assert(pdt, info, "Sep 03", None, None, false, false, 652 | Some(default_rsdate), false, &HashMap::new()); 653 | } 654 | 655 | #[test] 656 | fn test_parse_default41() { 657 | let info = ParserInfo::default(); 658 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 659 | let pdt = PyDateTime { 660 | year: 2003, month: 9, day: 25, 661 | hour: 0, minute: 0, second: 0, 662 | micros: 0, tzo: None 663 | }; 664 | parse_and_assert(pdt, info, "Sep of 03", None, None, false, false, 665 | Some(default_rsdate), false, &HashMap::new()); 666 | } 667 | 668 | #[test] 669 | fn test_parse_default42() { 670 | let info = ParserInfo::default(); 671 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 672 | let pdt = PyDateTime { 673 | year: 2017, month: 11, day: 25, 674 | hour: 2, minute: 17, second: 0, 675 | micros: 0, tzo: None 676 | }; 677 | parse_and_assert(pdt, info, "02:17NOV2017", None, None, false, false, 678 | Some(default_rsdate), false, &HashMap::new()); 679 | } 680 | 681 | #[test] 682 | fn test_parse_default43() { 683 | let info = ParserInfo::default(); 684 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 685 | let pdt = PyDateTime { 686 | year: 2003, month: 9, day: 25, 687 | hour: 10, minute: 36, second: 28, 688 | micros: 0, tzo: None 689 | }; 690 | parse_and_assert(pdt, info, "Thu Sep 10:36:28", None, None, false, false, 691 | Some(default_rsdate), false, &HashMap::new()); 692 | } 693 | 694 | #[test] 695 | fn test_parse_default44() { 696 | let info = ParserInfo::default(); 697 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 698 | let pdt = PyDateTime { 699 | year: 2003, month: 9, day: 25, 700 | hour: 10, minute: 36, second: 28, 701 | micros: 0, tzo: None 702 | }; 703 | parse_and_assert(pdt, info, "Thu 10:36:28", None, None, false, false, 704 | Some(default_rsdate), false, &HashMap::new()); 705 | } 706 | 707 | #[test] 708 | fn test_parse_default45() { 709 | let info = ParserInfo::default(); 710 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 711 | let pdt = PyDateTime { 712 | year: 2003, month: 10, day: 1, 713 | hour: 0, minute: 0, second: 0, 714 | micros: 0, tzo: None 715 | }; 716 | parse_and_assert(pdt, info, "Wed", None, None, false, false, 717 | Some(default_rsdate), false, &HashMap::new()); 718 | } 719 | 720 | #[test] 721 | fn test_parse_default46() { 722 | let info = ParserInfo::default(); 723 | let default_rsdate = &NaiveDate::from_ymd_opt(2003, 9, 25).unwrap().and_hms_opt(0, 0, 0).unwrap(); 724 | let pdt = PyDateTime { 725 | year: 2003, month: 10, day: 1, 726 | hour: 0, minute: 0, second: 0, 727 | micros: 0, tzo: None 728 | }; 729 | parse_and_assert(pdt, info, "Wednesday", None, None, false, false, 730 | Some(default_rsdate), false, &HashMap::new()); 731 | } 732 | 733 | #[test] 734 | fn test_parse_simple0() { 735 | let pdt = PyDateTime { 736 | year: 2003, month: 9, day: 25, 737 | hour: 10, minute: 36, second: 28, 738 | micros: 0, tzo: None, 739 | }; 740 | parse_and_assert_simple(pdt, "Thu Sep 25 10:36:28 2003"); 741 | } 742 | 743 | #[test] 744 | fn test_parse_simple1() { 745 | let pdt = PyDateTime { 746 | year: 2003, month: 9, day: 25, 747 | hour: 0, minute: 0, second: 0, 748 | micros: 0, tzo: None, 749 | }; 750 | parse_and_assert_simple(pdt, "Thu Sep 25 2003"); 751 | } 752 | 753 | #[test] 754 | fn test_parse_simple2() { 755 | let pdt = PyDateTime { 756 | year: 2003, month: 9, day: 25, 757 | hour: 10, minute: 49, second: 41, 758 | micros: 0, tzo: None, 759 | }; 760 | parse_and_assert_simple(pdt, "2003-09-25T10:49:41"); 761 | } 762 | 763 | #[test] 764 | fn test_parse_simple3() { 765 | let pdt = PyDateTime { 766 | year: 2003, month: 9, day: 25, 767 | hour: 10, minute: 49, second: 0, 768 | micros: 0, tzo: None, 769 | }; 770 | parse_and_assert_simple(pdt, "2003-09-25T10:49"); 771 | } 772 | 773 | #[test] 774 | fn test_parse_simple4() { 775 | let pdt = PyDateTime { 776 | year: 2003, month: 9, day: 25, 777 | hour: 10, minute: 0, second: 0, 778 | micros: 0, tzo: None, 779 | }; 780 | parse_and_assert_simple(pdt, "2003-09-25T10"); 781 | } 782 | 783 | #[test] 784 | fn test_parse_simple5() { 785 | let pdt = PyDateTime { 786 | year: 2003, month: 9, day: 25, 787 | hour: 0, minute: 0, second: 0, 788 | micros: 0, tzo: None, 789 | }; 790 | parse_and_assert_simple(pdt, "2003-09-25"); 791 | } 792 | 793 | #[test] 794 | fn test_parse_simple6() { 795 | let pdt = PyDateTime { 796 | year: 2003, month: 9, day: 25, 797 | hour: 10, minute: 49, second: 41, 798 | micros: 0, tzo: None, 799 | }; 800 | parse_and_assert_simple(pdt, "20030925T104941"); 801 | } 802 | 803 | #[test] 804 | fn test_parse_simple7() { 805 | let pdt = PyDateTime { 806 | year: 2003, month: 9, day: 25, 807 | hour: 10, minute: 49, second: 0, 808 | micros: 0, tzo: None, 809 | }; 810 | parse_and_assert_simple(pdt, "20030925T1049"); 811 | } 812 | 813 | #[test] 814 | fn test_parse_simple8() { 815 | let pdt = PyDateTime { 816 | year: 2003, month: 9, day: 25, 817 | hour: 10, minute: 0, second: 0, 818 | micros: 0, tzo: None, 819 | }; 820 | parse_and_assert_simple(pdt, "20030925T10"); 821 | } 822 | 823 | #[test] 824 | fn test_parse_simple9() { 825 | let pdt = PyDateTime { 826 | year: 2003, month: 9, day: 25, 827 | hour: 0, minute: 0, second: 0, 828 | micros: 0, tzo: None, 829 | }; 830 | parse_and_assert_simple(pdt, "20030925"); 831 | } 832 | 833 | #[test] 834 | fn test_parse_simple10() { 835 | let pdt = PyDateTime { 836 | year: 2003, month: 9, day: 25, 837 | hour: 10, minute: 49, second: 41, 838 | micros: 502000, tzo: None, 839 | }; 840 | parse_and_assert_simple(pdt, "2003-09-25 10:49:41,502"); 841 | } 842 | 843 | #[test] 844 | fn test_parse_simple11() { 845 | let pdt = PyDateTime { 846 | year: 1997, month: 9, day: 2, 847 | hour: 9, minute: 8, second: 0, 848 | micros: 0, tzo: None, 849 | }; 850 | parse_and_assert_simple(pdt, "199709020908"); 851 | } 852 | 853 | #[test] 854 | fn test_parse_simple12() { 855 | let pdt = PyDateTime { 856 | year: 1997, month: 9, day: 2, 857 | hour: 9, minute: 8, second: 7, 858 | micros: 0, tzo: None, 859 | }; 860 | parse_and_assert_simple(pdt, "19970902090807"); 861 | } 862 | 863 | #[test] 864 | fn test_parse_simple13() { 865 | let pdt = PyDateTime { 866 | year: 2003, month: 9, day: 25, 867 | hour: 0, minute: 0, second: 0, 868 | micros: 0, tzo: None, 869 | }; 870 | parse_and_assert_simple(pdt, "2003-09-25"); 871 | } 872 | 873 | #[test] 874 | fn test_parse_simple14() { 875 | let pdt = PyDateTime { 876 | year: 2003, month: 9, day: 25, 877 | hour: 0, minute: 0, second: 0, 878 | micros: 0, tzo: None, 879 | }; 880 | parse_and_assert_simple(pdt, "09-25-2003"); 881 | } 882 | 883 | #[test] 884 | fn test_parse_simple15() { 885 | let pdt = PyDateTime { 886 | year: 2003, month: 9, day: 25, 887 | hour: 0, minute: 0, second: 0, 888 | micros: 0, tzo: None, 889 | }; 890 | parse_and_assert_simple(pdt, "25-09-2003"); 891 | } 892 | 893 | #[test] 894 | fn test_parse_simple16() { 895 | let pdt = PyDateTime { 896 | year: 2003, month: 10, day: 9, 897 | hour: 0, minute: 0, second: 0, 898 | micros: 0, tzo: None, 899 | }; 900 | parse_and_assert_simple(pdt, "10-09-2003"); 901 | } 902 | 903 | #[test] 904 | fn test_parse_simple17() { 905 | let pdt = PyDateTime { 906 | year: 2003, month: 10, day: 9, 907 | hour: 0, minute: 0, second: 0, 908 | micros: 0, tzo: None, 909 | }; 910 | parse_and_assert_simple(pdt, "10-09-03"); 911 | } 912 | 913 | #[test] 914 | fn test_parse_simple18() { 915 | let pdt = PyDateTime { 916 | year: 2003, month: 9, day: 25, 917 | hour: 0, minute: 0, second: 0, 918 | micros: 0, tzo: None, 919 | }; 920 | parse_and_assert_simple(pdt, "2003.09.25"); 921 | } 922 | 923 | #[test] 924 | fn test_parse_simple19() { 925 | let pdt = PyDateTime { 926 | year: 2003, month: 9, day: 25, 927 | hour: 0, minute: 0, second: 0, 928 | micros: 0, tzo: None, 929 | }; 930 | parse_and_assert_simple(pdt, "09.25.2003"); 931 | } 932 | 933 | #[test] 934 | fn test_parse_simple20() { 935 | let pdt = PyDateTime { 936 | year: 2003, month: 9, day: 25, 937 | hour: 0, minute: 0, second: 0, 938 | micros: 0, tzo: None, 939 | }; 940 | parse_and_assert_simple(pdt, "25.09.2003"); 941 | } 942 | 943 | #[test] 944 | fn test_parse_simple21() { 945 | let pdt = PyDateTime { 946 | year: 2003, month: 10, day: 9, 947 | hour: 0, minute: 0, second: 0, 948 | micros: 0, tzo: None, 949 | }; 950 | parse_and_assert_simple(pdt, "10.09.2003"); 951 | } 952 | 953 | #[test] 954 | fn test_parse_simple22() { 955 | let pdt = PyDateTime { 956 | year: 2003, month: 10, day: 9, 957 | hour: 0, minute: 0, second: 0, 958 | micros: 0, tzo: None, 959 | }; 960 | parse_and_assert_simple(pdt, "10.09.03"); 961 | } 962 | 963 | #[test] 964 | fn test_parse_simple23() { 965 | let pdt = PyDateTime { 966 | year: 2003, month: 9, day: 25, 967 | hour: 0, minute: 0, second: 0, 968 | micros: 0, tzo: None, 969 | }; 970 | parse_and_assert_simple(pdt, "2003/09/25"); 971 | } 972 | 973 | #[test] 974 | fn test_parse_simple24() { 975 | let pdt = PyDateTime { 976 | year: 2003, month: 9, day: 25, 977 | hour: 0, minute: 0, second: 0, 978 | micros: 0, tzo: None, 979 | }; 980 | parse_and_assert_simple(pdt, "09/25/2003"); 981 | } 982 | 983 | #[test] 984 | fn test_parse_simple25() { 985 | let pdt = PyDateTime { 986 | year: 2003, month: 9, day: 25, 987 | hour: 0, minute: 0, second: 0, 988 | micros: 0, tzo: None, 989 | }; 990 | parse_and_assert_simple(pdt, "25/09/2003"); 991 | } 992 | 993 | #[test] 994 | fn test_parse_simple26() { 995 | let pdt = PyDateTime { 996 | year: 2003, month: 10, day: 9, 997 | hour: 0, minute: 0, second: 0, 998 | micros: 0, tzo: None, 999 | }; 1000 | parse_and_assert_simple(pdt, "10/09/2003"); 1001 | } 1002 | 1003 | #[test] 1004 | fn test_parse_simple27() { 1005 | let pdt = PyDateTime { 1006 | year: 2003, month: 10, day: 9, 1007 | hour: 0, minute: 0, second: 0, 1008 | micros: 0, tzo: None, 1009 | }; 1010 | parse_and_assert_simple(pdt, "10/09/03"); 1011 | } 1012 | 1013 | #[test] 1014 | fn test_parse_simple28() { 1015 | let pdt = PyDateTime { 1016 | year: 2003, month: 9, day: 25, 1017 | hour: 0, minute: 0, second: 0, 1018 | micros: 0, tzo: None, 1019 | }; 1020 | parse_and_assert_simple(pdt, "2003 09 25"); 1021 | } 1022 | 1023 | #[test] 1024 | fn test_parse_simple29() { 1025 | let pdt = PyDateTime { 1026 | year: 2003, month: 9, day: 25, 1027 | hour: 0, minute: 0, second: 0, 1028 | micros: 0, tzo: None, 1029 | }; 1030 | parse_and_assert_simple(pdt, "09 25 2003"); 1031 | } 1032 | 1033 | #[test] 1034 | fn test_parse_simple30() { 1035 | let pdt = PyDateTime { 1036 | year: 2003, month: 9, day: 25, 1037 | hour: 0, minute: 0, second: 0, 1038 | micros: 0, tzo: None, 1039 | }; 1040 | parse_and_assert_simple(pdt, "25 09 2003"); 1041 | } 1042 | 1043 | #[test] 1044 | fn test_parse_simple31() { 1045 | let pdt = PyDateTime { 1046 | year: 2003, month: 10, day: 9, 1047 | hour: 0, minute: 0, second: 0, 1048 | micros: 0, tzo: None, 1049 | }; 1050 | parse_and_assert_simple(pdt, "10 09 2003"); 1051 | } 1052 | 1053 | #[test] 1054 | fn test_parse_simple32() { 1055 | let pdt = PyDateTime { 1056 | year: 2003, month: 10, day: 9, 1057 | hour: 0, minute: 0, second: 0, 1058 | micros: 0, tzo: None, 1059 | }; 1060 | parse_and_assert_simple(pdt, "10 09 03"); 1061 | } 1062 | 1063 | #[test] 1064 | fn test_parse_simple33() { 1065 | let pdt = PyDateTime { 1066 | year: 2003, month: 9, day: 25, 1067 | hour: 0, minute: 0, second: 0, 1068 | micros: 0, tzo: None, 1069 | }; 1070 | parse_and_assert_simple(pdt, "25 09 03"); 1071 | } 1072 | 1073 | #[test] 1074 | fn test_parse_simple34() { 1075 | let pdt = PyDateTime { 1076 | year: 2003, month: 9, day: 25, 1077 | hour: 0, minute: 0, second: 0, 1078 | micros: 0, tzo: None, 1079 | }; 1080 | parse_and_assert_simple(pdt, "03 25 Sep"); 1081 | } 1082 | 1083 | #[test] 1084 | fn test_parse_simple35() { 1085 | let pdt = PyDateTime { 1086 | year: 2025, month: 9, day: 3, 1087 | hour: 0, minute: 0, second: 0, 1088 | micros: 0, tzo: None, 1089 | }; 1090 | parse_and_assert_simple(pdt, "25 03 Sep"); 1091 | } 1092 | 1093 | #[test] 1094 | fn test_parse_simple36() { 1095 | let pdt = PyDateTime { 1096 | year: 1976, month: 7, day: 4, 1097 | hour: 0, minute: 1, second: 2, 1098 | micros: 0, tzo: None, 1099 | }; 1100 | parse_and_assert_simple(pdt, " July 4 , 1976 12:01:02 am "); 1101 | } 1102 | 1103 | #[test] 1104 | fn test_parse_simple37() { 1105 | let pdt = PyDateTime { 1106 | year: 1996, month: 7, day: 10, 1107 | hour: 0, minute: 0, second: 0, 1108 | micros: 0, tzo: None, 1109 | }; 1110 | parse_and_assert_simple(pdt, "Wed, July 10, '96"); 1111 | } 1112 | 1113 | #[test] 1114 | fn test_parse_simple38() { 1115 | let pdt = PyDateTime { 1116 | year: 1996, month: 7, day: 10, 1117 | hour: 12, minute: 8, second: 0, 1118 | micros: 0, tzo: None, 1119 | }; 1120 | parse_and_assert_simple(pdt, "1996.July.10 AD 12:08 PM"); 1121 | } 1122 | 1123 | #[test] 1124 | fn test_parse_simple39() { 1125 | let pdt = PyDateTime { 1126 | year: 1976, month: 7, day: 4, 1127 | hour: 0, minute: 0, second: 0, 1128 | micros: 0, tzo: None, 1129 | }; 1130 | parse_and_assert_simple(pdt, "July 4, 1976"); 1131 | } 1132 | 1133 | #[test] 1134 | fn test_parse_simple40() { 1135 | let pdt = PyDateTime { 1136 | year: 1976, month: 7, day: 4, 1137 | hour: 0, minute: 0, second: 0, 1138 | micros: 0, tzo: None, 1139 | }; 1140 | parse_and_assert_simple(pdt, "7 4 1976"); 1141 | } 1142 | 1143 | #[test] 1144 | fn test_parse_simple41() { 1145 | let pdt = PyDateTime { 1146 | year: 1976, month: 7, day: 4, 1147 | hour: 0, minute: 0, second: 0, 1148 | micros: 0, tzo: None, 1149 | }; 1150 | parse_and_assert_simple(pdt, "4 jul 1976"); 1151 | } 1152 | 1153 | #[test] 1154 | fn test_parse_simple42() { 1155 | let pdt = PyDateTime { 1156 | year: 1976, month: 7, day: 4, 1157 | hour: 0, minute: 0, second: 0, 1158 | micros: 0, tzo: None, 1159 | }; 1160 | parse_and_assert_simple(pdt, "7-4-76"); 1161 | } 1162 | 1163 | #[test] 1164 | fn test_parse_simple43() { 1165 | let pdt = PyDateTime { 1166 | year: 1976, month: 7, day: 4, 1167 | hour: 0, minute: 0, second: 0, 1168 | micros: 0, tzo: None, 1169 | }; 1170 | parse_and_assert_simple(pdt, "19760704"); 1171 | } 1172 | 1173 | #[test] 1174 | fn test_parse_simple44() { 1175 | let pdt = PyDateTime { 1176 | year: 1976, month: 7, day: 4, 1177 | hour: 0, minute: 1, second: 2, 1178 | micros: 0, tzo: None, 1179 | }; 1180 | parse_and_assert_simple(pdt, "0:01:02 on July 4, 1976"); 1181 | } 1182 | 1183 | #[test] 1184 | fn test_parse_simple45() { 1185 | let pdt = PyDateTime { 1186 | year: 1976, month: 7, day: 4, 1187 | hour: 0, minute: 1, second: 2, 1188 | micros: 0, tzo: None, 1189 | }; 1190 | parse_and_assert_simple(pdt, "0:01:02 on July 4, 1976"); 1191 | } 1192 | 1193 | #[test] 1194 | fn test_parse_simple46() { 1195 | let pdt = PyDateTime { 1196 | year: 1976, month: 7, day: 4, 1197 | hour: 0, minute: 1, second: 2, 1198 | micros: 0, tzo: None, 1199 | }; 1200 | parse_and_assert_simple(pdt, "July 4, 1976 12:01:02 am"); 1201 | } 1202 | 1203 | #[test] 1204 | fn test_parse_simple47() { 1205 | let pdt = PyDateTime { 1206 | year: 1995, month: 1, day: 2, 1207 | hour: 4, minute: 24, second: 27, 1208 | micros: 0, tzo: None, 1209 | }; 1210 | parse_and_assert_simple(pdt, "Mon Jan 2 04:24:27 1995"); 1211 | } 1212 | 1213 | #[test] 1214 | fn test_parse_simple48() { 1215 | let pdt = PyDateTime { 1216 | year: 1995, month: 4, day: 4, 1217 | hour: 0, minute: 22, second: 0, 1218 | micros: 0, tzo: None, 1219 | }; 1220 | parse_and_assert_simple(pdt, "04.04.95 00:22"); 1221 | } 1222 | 1223 | #[test] 1224 | fn test_parse_simple49() { 1225 | let pdt = PyDateTime { 1226 | year: 1999, month: 1, day: 1, 1227 | hour: 11, minute: 23, second: 34, 1228 | micros: 578000, tzo: None, 1229 | }; 1230 | parse_and_assert_simple(pdt, "Jan 1 1999 11:23:34.578"); 1231 | } 1232 | 1233 | #[test] 1234 | fn test_parse_simple50() { 1235 | let pdt = PyDateTime { 1236 | year: 1995, month: 4, day: 4, 1237 | hour: 12, minute: 22, second: 12, 1238 | micros: 0, tzo: None, 1239 | }; 1240 | parse_and_assert_simple(pdt, "950404 122212"); 1241 | } 1242 | 1243 | #[test] 1244 | fn test_parse_simple51() { 1245 | let pdt = PyDateTime { 1246 | year: 2001, month: 5, day: 3, 1247 | hour: 0, minute: 0, second: 0, 1248 | micros: 0, tzo: None, 1249 | }; 1250 | parse_and_assert_simple(pdt, "3rd of May 2001"); 1251 | } 1252 | 1253 | #[test] 1254 | fn test_parse_simple52() { 1255 | let pdt = PyDateTime { 1256 | year: 2001, month: 3, day: 5, 1257 | hour: 0, minute: 0, second: 0, 1258 | micros: 0, tzo: None, 1259 | }; 1260 | parse_and_assert_simple(pdt, "5th of March 2001"); 1261 | } 1262 | 1263 | #[test] 1264 | fn test_parse_simple53() { 1265 | let pdt = PyDateTime { 1266 | year: 2003, month: 5, day: 1, 1267 | hour: 0, minute: 0, second: 0, 1268 | micros: 0, tzo: None, 1269 | }; 1270 | parse_and_assert_simple(pdt, "1st of May 2003"); 1271 | } 1272 | 1273 | #[test] 1274 | fn test_parse_simple54() { 1275 | let pdt = PyDateTime { 1276 | year: 99, month: 1, day: 1, 1277 | hour: 0, minute: 0, second: 0, 1278 | micros: 0, tzo: None, 1279 | }; 1280 | parse_and_assert_simple(pdt, "0099-01-01T00:00:00"); 1281 | } 1282 | 1283 | #[test] 1284 | fn test_parse_simple55() { 1285 | let pdt = PyDateTime { 1286 | year: 31, month: 1, day: 1, 1287 | hour: 0, minute: 0, second: 0, 1288 | micros: 0, tzo: None, 1289 | }; 1290 | parse_and_assert_simple(pdt, "0031-01-01T00:00:00"); 1291 | } 1292 | 1293 | #[test] 1294 | fn test_parse_simple56() { 1295 | let pdt = PyDateTime { 1296 | year: 2008, month: 2, day: 27, 1297 | hour: 21, minute: 26, second: 1, 1298 | micros: 123456, tzo: None, 1299 | }; 1300 | parse_and_assert_simple(pdt, "20080227T21:26:01.123456789"); 1301 | } 1302 | 1303 | #[test] 1304 | fn test_parse_simple57() { 1305 | let pdt = PyDateTime { 1306 | year: 2017, month: 11, day: 13, 1307 | hour: 0, minute: 0, second: 0, 1308 | micros: 0, tzo: None, 1309 | }; 1310 | parse_and_assert_simple(pdt, "13NOV2017"); 1311 | } 1312 | 1313 | #[test] 1314 | fn test_parse_simple58() { 1315 | let pdt = PyDateTime { 1316 | year: 3, month: 3, day: 4, 1317 | hour: 0, minute: 0, second: 0, 1318 | micros: 0, tzo: None, 1319 | }; 1320 | parse_and_assert_simple(pdt, "0003-03-04"); 1321 | } 1322 | 1323 | #[test] 1324 | fn test_parse_simple59() { 1325 | let pdt = PyDateTime { 1326 | year: 31, month: 12, day: 30, 1327 | hour: 0, minute: 0, second: 0, 1328 | micros: 0, tzo: None, 1329 | }; 1330 | parse_and_assert_simple(pdt, "December.0031.30"); 1331 | } 1332 | 1333 | #[test] 1334 | fn test_parse_simple60() { 1335 | let pdt = PyDateTime { 1336 | year: 2007, month: 9, day: 1, 1337 | hour: 0, minute: 0, second: 0, 1338 | micros: 0, tzo: None, 1339 | }; 1340 | parse_and_assert_simple(pdt, "090107"); 1341 | } 1342 | 1343 | #[test] 1344 | fn test_parse_simple61() { 1345 | let pdt = PyDateTime { 1346 | year: 2015, month: 5, day: 15, 1347 | hour: 0, minute: 0, second: 0, 1348 | micros: 0, tzo: None, 1349 | }; 1350 | parse_and_assert_simple(pdt, "2015-15-May"); 1351 | } 1352 | 1353 | #[test] 1354 | fn test_parse_tzinfo0() { 1355 | let info = ParserInfo::default(); 1356 | let pdt = PyDateTime { 1357 | year: 2003, month: 9, day: 25, 1358 | hour: 10, minute: 36, second: 28, 1359 | micros: 0, tzo: Some(-10800), 1360 | }; 1361 | parse_and_assert(pdt, info, "Thu Sep 25 10:36:28 BRST 2003", None, None, false, false, 1362 | None, false, &rs_tzinfo_map!()); 1363 | } 1364 | 1365 | #[test] 1366 | fn test_parse_tzinfo1() { 1367 | let info = ParserInfo::default(); 1368 | let pdt = PyDateTime { 1369 | year: 2003, month: 9, day: 25, 1370 | hour: 10, minute: 36, second: 28, 1371 | micros: 0, tzo: Some(-10800), 1372 | }; 1373 | parse_and_assert(pdt, info, "2003 10:36:28 BRST 25 Sep Thu", None, None, false, false, 1374 | None, false, &rs_tzinfo_map!()); 1375 | } 1376 | 1377 | #[test] 1378 | fn test_parse_offset0() { 1379 | let info = ParserInfo::default(); 1380 | let pdt = PyDateTime { 1381 | year: 2003, month: 9, day: 25, 1382 | hour: 10, minute: 49, second: 41, 1383 | micros: 0, tzo: Some(-10800), 1384 | }; 1385 | parse_and_assert(pdt, info, "Thu, 25 Sep 2003 10:49:41 -0300", None, None, false, false, 1386 | None, false, &HashMap::new()); 1387 | } 1388 | 1389 | #[test] 1390 | fn test_parse_offset1() { 1391 | let info = ParserInfo::default(); 1392 | let pdt = PyDateTime { 1393 | year: 2003, month: 9, day: 25, 1394 | hour: 10, minute: 49, second: 41, 1395 | micros: 500000, tzo: Some(-10800), 1396 | }; 1397 | parse_and_assert(pdt, info, "2003-09-25T10:49:41.5-03:00", None, None, false, false, 1398 | None, false, &HashMap::new()); 1399 | } 1400 | 1401 | #[test] 1402 | fn test_parse_offset2() { 1403 | let info = ParserInfo::default(); 1404 | let pdt = PyDateTime { 1405 | year: 2003, month: 9, day: 25, 1406 | hour: 10, minute: 49, second: 41, 1407 | micros: 0, tzo: Some(-10800), 1408 | }; 1409 | parse_and_assert(pdt, info, "2003-09-25T10:49:41-03:00", None, None, false, false, 1410 | None, false, &HashMap::new()); 1411 | } 1412 | 1413 | #[test] 1414 | fn test_parse_offset3() { 1415 | let info = ParserInfo::default(); 1416 | let pdt = PyDateTime { 1417 | year: 2003, month: 9, day: 25, 1418 | hour: 10, minute: 49, second: 41, 1419 | micros: 500000, tzo: Some(-10800), 1420 | }; 1421 | parse_and_assert(pdt, info, "20030925T104941.5-0300", None, None, false, false, 1422 | None, false, &HashMap::new()); 1423 | } 1424 | 1425 | #[test] 1426 | fn test_parse_offset4() { 1427 | let info = ParserInfo::default(); 1428 | let pdt = PyDateTime { 1429 | year: 2003, month: 9, day: 25, 1430 | hour: 10, minute: 49, second: 41, 1431 | micros: 0, tzo: Some(-10800), 1432 | }; 1433 | parse_and_assert(pdt, info, "20030925T104941-0300", None, None, false, false, 1434 | None, false, &HashMap::new()); 1435 | } 1436 | 1437 | #[test] 1438 | fn test_parse_offset5() { 1439 | let info = ParserInfo::default(); 1440 | let pdt = PyDateTime { 1441 | year: 2018, month: 8, day: 10, 1442 | hour: 10, minute: 0, second: 0, 1443 | micros: 0, tzo: Some(-10800), 1444 | }; 1445 | parse_and_assert(pdt, info, "2018-08-10 10:00:00 UTC+3", None, None, false, false, 1446 | None, false, &HashMap::new()); 1447 | } 1448 | 1449 | #[test] 1450 | fn test_parse_offset6() { 1451 | let info = ParserInfo::default(); 1452 | let pdt = PyDateTime { 1453 | year: 2018, month: 8, day: 10, 1454 | hour: 15, minute: 36, second: 47, 1455 | micros: 0, tzo: Some(14400), 1456 | }; 1457 | parse_and_assert(pdt, info, "2018-08-10 03:36:47 PM GMT-4", None, None, false, false, 1458 | None, false, &HashMap::new()); 1459 | } 1460 | 1461 | #[test] 1462 | fn test_parse_offset7() { 1463 | let info = ParserInfo::default(); 1464 | let pdt = PyDateTime { 1465 | year: 2018, month: 8, day: 10, 1466 | hour: 4, minute: 15, second: 0, 1467 | micros: 0, tzo: Some(7200), 1468 | }; 1469 | parse_and_assert(pdt, info, "2018-08-10 04:15:00 AM Z-02:00", None, None, false, false, 1470 | None, false, &HashMap::new()); 1471 | } 1472 | 1473 | #[test] 1474 | fn test_parse_dayfirst0() { 1475 | let info = ParserInfo::default(); 1476 | let pdt = PyDateTime { 1477 | year: 2003, month: 9, day: 10, 1478 | hour: 0, minute: 0, second: 0, 1479 | micros: 0, tzo: None, 1480 | }; 1481 | parse_and_assert(pdt, info, "10-09-2003", Some(true), None, false, false, 1482 | None, false, &HashMap::new()); 1483 | } 1484 | 1485 | #[test] 1486 | fn test_parse_dayfirst1() { 1487 | let info = ParserInfo::default(); 1488 | let pdt = PyDateTime { 1489 | year: 2003, month: 9, day: 10, 1490 | hour: 0, minute: 0, second: 0, 1491 | micros: 0, tzo: None, 1492 | }; 1493 | parse_and_assert(pdt, info, "10.09.2003", Some(true), None, false, false, 1494 | None, false, &HashMap::new()); 1495 | } 1496 | 1497 | #[test] 1498 | fn test_parse_dayfirst2() { 1499 | let info = ParserInfo::default(); 1500 | let pdt = PyDateTime { 1501 | year: 2003, month: 9, day: 10, 1502 | hour: 0, minute: 0, second: 0, 1503 | micros: 0, tzo: None, 1504 | }; 1505 | parse_and_assert(pdt, info, "10/09/2003", Some(true), None, false, false, 1506 | None, false, &HashMap::new()); 1507 | } 1508 | 1509 | #[test] 1510 | fn test_parse_dayfirst3() { 1511 | let info = ParserInfo::default(); 1512 | let pdt = PyDateTime { 1513 | year: 2003, month: 9, day: 10, 1514 | hour: 0, minute: 0, second: 0, 1515 | micros: 0, tzo: None, 1516 | }; 1517 | parse_and_assert(pdt, info, "10 09 2003", Some(true), None, false, false, 1518 | None, false, &HashMap::new()); 1519 | } 1520 | 1521 | #[test] 1522 | fn test_parse_dayfirst4() { 1523 | let info = ParserInfo::default(); 1524 | let pdt = PyDateTime { 1525 | year: 2007, month: 1, day: 9, 1526 | hour: 0, minute: 0, second: 0, 1527 | micros: 0, tzo: None, 1528 | }; 1529 | parse_and_assert(pdt, info, "090107", Some(true), None, false, false, 1530 | None, false, &HashMap::new()); 1531 | } 1532 | 1533 | #[test] 1534 | fn test_parse_dayfirst5() { 1535 | let info = ParserInfo::default(); 1536 | let pdt = PyDateTime { 1537 | year: 2015, month: 9, day: 25, 1538 | hour: 0, minute: 0, second: 0, 1539 | micros: 0, tzo: None, 1540 | }; 1541 | parse_and_assert(pdt, info, "2015 09 25", Some(true), None, false, false, 1542 | None, false, &HashMap::new()); 1543 | } 1544 | 1545 | #[test] 1546 | fn test_parse_yearfirst0() { 1547 | let info = ParserInfo::default(); 1548 | let pdt = PyDateTime { 1549 | year: 2010, month: 9, day: 3, 1550 | hour: 0, minute: 0, second: 0, 1551 | micros: 0, tzo: None, 1552 | }; 1553 | parse_and_assert(pdt, info, "10-09-03", None, Some(true), false, false, 1554 | None, false, &HashMap::new()); 1555 | } 1556 | 1557 | #[test] 1558 | fn test_parse_yearfirst1() { 1559 | let info = ParserInfo::default(); 1560 | let pdt = PyDateTime { 1561 | year: 2010, month: 9, day: 3, 1562 | hour: 0, minute: 0, second: 0, 1563 | micros: 0, tzo: None, 1564 | }; 1565 | parse_and_assert(pdt, info, "10.09.03", None, Some(true), false, false, 1566 | None, false, &HashMap::new()); 1567 | } 1568 | 1569 | #[test] 1570 | fn test_parse_yearfirst2() { 1571 | let info = ParserInfo::default(); 1572 | let pdt = PyDateTime { 1573 | year: 2010, month: 9, day: 3, 1574 | hour: 0, minute: 0, second: 0, 1575 | micros: 0, tzo: None, 1576 | }; 1577 | parse_and_assert(pdt, info, "10/09/03", None, Some(true), false, false, 1578 | None, false, &HashMap::new()); 1579 | } 1580 | 1581 | #[test] 1582 | fn test_parse_yearfirst3() { 1583 | let info = ParserInfo::default(); 1584 | let pdt = PyDateTime { 1585 | year: 2010, month: 9, day: 3, 1586 | hour: 0, minute: 0, second: 0, 1587 | micros: 0, tzo: None, 1588 | }; 1589 | parse_and_assert(pdt, info, "10 09 03", None, Some(true), false, false, 1590 | None, false, &HashMap::new()); 1591 | } 1592 | 1593 | #[test] 1594 | fn test_parse_yearfirst4() { 1595 | let info = ParserInfo::default(); 1596 | let pdt = PyDateTime { 1597 | year: 2009, month: 1, day: 7, 1598 | hour: 0, minute: 0, second: 0, 1599 | micros: 0, tzo: None, 1600 | }; 1601 | parse_and_assert(pdt, info, "090107", None, Some(true), false, false, 1602 | None, false, &HashMap::new()); 1603 | } 1604 | 1605 | #[test] 1606 | fn test_parse_yearfirst5() { 1607 | let info = ParserInfo::default(); 1608 | let pdt = PyDateTime { 1609 | year: 2015, month: 9, day: 25, 1610 | hour: 0, minute: 0, second: 0, 1611 | micros: 0, tzo: None, 1612 | }; 1613 | parse_and_assert(pdt, info, "2015 09 25", None, Some(true), false, false, 1614 | None, false, &HashMap::new()); 1615 | } 1616 | 1617 | #[test] 1618 | fn test_parse_dfyf0() { 1619 | let info = ParserInfo::default(); 1620 | let pdt = PyDateTime { 1621 | year: 2009, month: 7, day: 1, 1622 | hour: 0, minute: 0, second: 0, 1623 | micros: 0, tzo: None, 1624 | }; 1625 | parse_and_assert(pdt, info, "090107", Some(true), Some(true), false, false, 1626 | None, false, &HashMap::new()); 1627 | } 1628 | 1629 | #[test] 1630 | fn test_parse_dfyf1() { 1631 | let info = ParserInfo::default(); 1632 | let pdt = PyDateTime { 1633 | year: 2015, month: 9, day: 25, 1634 | hour: 0, minute: 0, second: 0, 1635 | micros: 0, tzo: None, 1636 | }; 1637 | parse_and_assert(pdt, info, "2015 09 25", Some(true), Some(true), false, false, 1638 | None, false, &HashMap::new()); 1639 | } 1640 | 1641 | #[test] 1642 | fn test_unspecified_fallback0() { 1643 | let info = ParserInfo::default(); 1644 | let default_rsdate = &NaiveDate::from_ymd_opt(2010, 1, 31).unwrap().and_hms_opt(0, 0, 0).unwrap(); 1645 | let pdt = PyDateTime { 1646 | year: 2009, month: 4, day: 30, 1647 | hour: 0, minute: 0, second: 0, 1648 | micros: 0, tzo: None 1649 | }; 1650 | parse_and_assert(pdt, info, "April 2009", None, None, false, false, 1651 | Some(default_rsdate), false, &HashMap::new()); 1652 | } 1653 | 1654 | #[test] 1655 | fn test_unspecified_fallback1() { 1656 | let info = ParserInfo::default(); 1657 | let default_rsdate = &NaiveDate::from_ymd_opt(2010, 1, 31).unwrap().and_hms_opt(0, 0, 0).unwrap(); 1658 | let pdt = PyDateTime { 1659 | year: 2007, month: 2, day: 28, 1660 | hour: 0, minute: 0, second: 0, 1661 | micros: 0, tzo: None 1662 | }; 1663 | parse_and_assert(pdt, info, "Feb 2007", None, None, false, false, 1664 | Some(default_rsdate), false, &HashMap::new()); 1665 | } 1666 | 1667 | #[test] 1668 | fn test_unspecified_fallback2() { 1669 | let info = ParserInfo::default(); 1670 | let default_rsdate = &NaiveDate::from_ymd_opt(2010, 1, 31).unwrap().and_hms_opt(0, 0, 0).unwrap(); 1671 | let pdt = PyDateTime { 1672 | year: 2008, month: 2, day: 29, 1673 | hour: 0, minute: 0, second: 0, 1674 | micros: 0, tzo: None 1675 | }; 1676 | parse_and_assert(pdt, info, "Feb 2008", None, None, false, false, 1677 | Some(default_rsdate), false, &HashMap::new()); 1678 | } 1679 | 1680 | #[test] 1681 | fn test_parse_ignoretz0() { 1682 | let info = ParserInfo::default(); 1683 | let pdt = PyDateTime { 1684 | year: 2003, month: 9, day: 25, 1685 | hour: 10, minute: 36, second: 28, 1686 | micros: 0, tzo: None 1687 | }; 1688 | parse_and_assert(pdt, info, "Thu Sep 25 10:36:28 BRST 2003", None, None, false, false, 1689 | None, true, &HashMap::new()); 1690 | } 1691 | 1692 | #[test] 1693 | fn test_parse_ignoretz1() { 1694 | let info = ParserInfo::default(); 1695 | let pdt = PyDateTime { 1696 | year: 1996, month: 7, day: 10, 1697 | hour: 15, minute: 8, second: 56, 1698 | micros: 0, tzo: None 1699 | }; 1700 | parse_and_assert(pdt, info, "1996.07.10 AD at 15:08:56 PDT", None, None, false, false, 1701 | None, true, &HashMap::new()); 1702 | } 1703 | 1704 | #[test] 1705 | fn test_parse_ignoretz2() { 1706 | let info = ParserInfo::default(); 1707 | let pdt = PyDateTime { 1708 | year: 1952, month: 4, day: 12, 1709 | hour: 15, minute: 30, second: 42, 1710 | micros: 0, tzo: None 1711 | }; 1712 | parse_and_assert(pdt, info, "Tuesday, April 12, 1952 AD 3:30:42pm PST", None, None, false, false, 1713 | None, true, &HashMap::new()); 1714 | } 1715 | 1716 | #[test] 1717 | fn test_parse_ignoretz3() { 1718 | let info = ParserInfo::default(); 1719 | let pdt = PyDateTime { 1720 | year: 1994, month: 11, day: 5, 1721 | hour: 8, minute: 15, second: 30, 1722 | micros: 0, tzo: None 1723 | }; 1724 | parse_and_assert(pdt, info, "November 5, 1994, 8:15:30 am EST", None, None, false, false, 1725 | None, true, &HashMap::new()); 1726 | } 1727 | 1728 | #[test] 1729 | fn test_parse_ignoretz4() { 1730 | let info = ParserInfo::default(); 1731 | let pdt = PyDateTime { 1732 | year: 1994, month: 11, day: 5, 1733 | hour: 8, minute: 15, second: 30, 1734 | micros: 0, tzo: None 1735 | }; 1736 | parse_and_assert(pdt, info, "1994-11-05T08:15:30-05:00", None, None, false, false, 1737 | None, true, &HashMap::new()); 1738 | } 1739 | 1740 | #[test] 1741 | fn test_parse_ignoretz5() { 1742 | let info = ParserInfo::default(); 1743 | let pdt = PyDateTime { 1744 | year: 1994, month: 11, day: 5, 1745 | hour: 8, minute: 15, second: 30, 1746 | micros: 0, tzo: None 1747 | }; 1748 | parse_and_assert(pdt, info, "1994-11-05T08:15:30Z", None, None, false, false, 1749 | None, true, &HashMap::new()); 1750 | } 1751 | 1752 | #[test] 1753 | fn test_parse_ignoretz6() { 1754 | let info = ParserInfo::default(); 1755 | let pdt = PyDateTime { 1756 | year: 1976, month: 7, day: 4, 1757 | hour: 0, minute: 1, second: 2, 1758 | micros: 0, tzo: None 1759 | }; 1760 | parse_and_assert(pdt, info, "1976-07-04T00:01:02Z", None, None, false, false, 1761 | None, true, &HashMap::new()); 1762 | } 1763 | 1764 | #[test] 1765 | fn test_parse_ignoretz7() { 1766 | let info = ParserInfo::default(); 1767 | let pdt = PyDateTime { 1768 | year: 1986, month: 7, day: 5, 1769 | hour: 8, minute: 15, second: 30, 1770 | micros: 0, tzo: None 1771 | }; 1772 | parse_and_assert(pdt, info, "1986-07-05T08:15:30z", None, None, false, false, 1773 | None, true, &HashMap::new()); 1774 | } 1775 | 1776 | #[test] 1777 | fn test_parse_ignoretz8() { 1778 | let info = ParserInfo::default(); 1779 | let pdt = PyDateTime { 1780 | year: 1995, month: 4, day: 4, 1781 | hour: 0, minute: 22, second: 12, 1782 | micros: 0, tzo: None 1783 | }; 1784 | parse_and_assert(pdt, info, "Tue Apr 4 00:22:12 PDT 1995", None, None, false, false, 1785 | None, true, &HashMap::new()); 1786 | } 1787 | 1788 | #[test] 1789 | fn test_fuzzy_tzinfo0() { 1790 | let info = ParserInfo::default(); 1791 | let pdt = PyDateTime { 1792 | year: 2003, month: 9, day: 25, 1793 | hour: 10, minute: 49, second: 41, 1794 | micros: 0, tzo: Some(-10800) 1795 | }; 1796 | parse_fuzzy_and_assert(pdt, None, info, "Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.", None, None, true, false, 1797 | None, false, &HashMap::new()); 1798 | } 1799 | 1800 | #[test] 1801 | fn test_fuzzy_tokens_tzinfo0() { 1802 | let info = ParserInfo::default(); 1803 | let pdt = PyDateTime { 1804 | year: 2003, month: 9, day: 25, 1805 | hour: 10, minute: 49, second: 41, 1806 | micros: 0, tzo: Some(-10800) 1807 | }; 1808 | let tokens = vec!["Today is ".to_owned(), "of ".to_owned(), ", exactly at ".to_owned(), " with timezone ".to_owned(), ".".to_owned()]; 1809 | parse_fuzzy_and_assert(pdt, Some(tokens), info, "Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.", None, None, true, true, 1810 | None, false, &HashMap::new()); 1811 | } 1812 | 1813 | #[test] 1814 | fn test_fuzzy_simple0() { 1815 | let info = ParserInfo::default(); 1816 | let pdt = PyDateTime { 1817 | year: 1974, month: 3, day: 1, 1818 | hour: 0, minute: 0, second: 0, 1819 | micros: 0, tzo: None 1820 | }; 1821 | parse_fuzzy_and_assert(pdt, None, info, "I have a meeting on March 1, 1974", None, None, true, false, 1822 | None, false, &HashMap::new()); 1823 | } 1824 | 1825 | #[test] 1826 | fn test_fuzzy_simple1() { 1827 | let info = ParserInfo::default(); 1828 | let pdt = PyDateTime { 1829 | year: 2020, month: 6, day: 8, 1830 | hour: 0, minute: 0, second: 0, 1831 | micros: 0, tzo: None 1832 | }; 1833 | parse_fuzzy_and_assert(pdt, None, info, "On June 8th, 2020, I am going to be the first man on Mars", None, None, true, false, 1834 | None, false, &HashMap::new()); 1835 | } 1836 | 1837 | #[test] 1838 | fn test_fuzzy_simple2() { 1839 | let info = ParserInfo::default(); 1840 | let pdt = PyDateTime { 1841 | year: 2003, month: 12, day: 3, 1842 | hour: 3, minute: 0, second: 0, 1843 | micros: 0, tzo: None 1844 | }; 1845 | parse_fuzzy_and_assert(pdt, None, info, "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003", None, None, true, false, 1846 | None, false, &HashMap::new()); 1847 | } 1848 | 1849 | #[test] 1850 | fn test_fuzzy_simple3() { 1851 | let info = ParserInfo::default(); 1852 | let pdt = PyDateTime { 1853 | year: 2003, month: 12, day: 3, 1854 | hour: 3, minute: 0, second: 0, 1855 | micros: 0, tzo: None 1856 | }; 1857 | parse_fuzzy_and_assert(pdt, None, info, "Meet me at 3:00 AM on December 3rd, 2003 at the AM/PM on Sunset", None, None, true, false, 1858 | None, false, &HashMap::new()); 1859 | } 1860 | 1861 | #[test] 1862 | fn test_fuzzy_simple4() { 1863 | let info = ParserInfo::default(); 1864 | let pdt = PyDateTime { 1865 | year: 1945, month: 1, day: 29, 1866 | hour: 14, minute: 45, second: 0, 1867 | micros: 0, tzo: None 1868 | }; 1869 | parse_fuzzy_and_assert(pdt, None, info, "Jan 29, 1945 14:45 AM I going to see you there?", None, None, true, false, 1870 | None, false, &HashMap::new()); 1871 | } 1872 | 1873 | #[test] 1874 | fn test_fuzzy_simple5() { 1875 | let info = ParserInfo::default(); 1876 | let pdt = PyDateTime { 1877 | year: 2017, month: 7, day: 17, 1878 | hour: 6, minute: 15, second: 0, 1879 | micros: 0, tzo: None 1880 | }; 1881 | parse_fuzzy_and_assert(pdt, None, info, "2017-07-17 06:15:", None, None, true, false, 1882 | None, false, &HashMap::new()); 1883 | } 1884 | -------------------------------------------------------------------------------- /src/tests/pycompat_tokenizer.rs: -------------------------------------------------------------------------------- 1 | //! This code has been generated by running the `build_pycompat_tokenizer.py` script 2 | //! in the repository root. Please do not edit it, as your edits will be destroyed 3 | //! upon re-running code generation. 4 | 5 | use tokenize::Tokenizer; 6 | 7 | fn tokenize_assert(test_str: &str, comparison: Vec<&str>) { 8 | let tokens: Vec = Tokenizer::new(test_str).collect(); 9 | assert_eq!(tokens, comparison, "Tokenizing mismatch for `{}`", test_str); 10 | } 11 | 12 | #[test] 13 | fn test_tokenize0() { 14 | let comp = vec![ 15 | "Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", 16 | ]; 17 | tokenize_assert("Thu Sep 25 10:36:28", comp); 18 | } 19 | 20 | #[test] 21 | fn test_tokenize1() { 22 | let comp = vec!["Sep", " ", "10", ":", "36", ":", "28"]; 23 | tokenize_assert("Sep 10:36:28", comp); 24 | } 25 | 26 | #[test] 27 | fn test_tokenize2() { 28 | let comp = vec!["10", ":", "36", ":", "28"]; 29 | tokenize_assert("10:36:28", comp); 30 | } 31 | 32 | #[test] 33 | fn test_tokenize3() { 34 | let comp = vec!["10", ":", "36"]; 35 | tokenize_assert("10:36", comp); 36 | } 37 | 38 | #[test] 39 | fn test_tokenize4() { 40 | let comp = vec!["Sep", " ", "2003"]; 41 | tokenize_assert("Sep 2003", comp); 42 | } 43 | 44 | #[test] 45 | fn test_tokenize5() { 46 | let comp = vec!["Sep"]; 47 | tokenize_assert("Sep", comp); 48 | } 49 | 50 | #[test] 51 | fn test_tokenize6() { 52 | let comp = vec!["2003"]; 53 | tokenize_assert("2003", comp); 54 | } 55 | 56 | #[test] 57 | fn test_tokenize7() { 58 | let comp = vec!["10", "h", "36", "m", "28.5", "s"]; 59 | tokenize_assert("10h36m28.5s", comp); 60 | } 61 | 62 | #[test] 63 | fn test_tokenize8() { 64 | let comp = vec!["10", "h", "36", "m", "28", "s"]; 65 | tokenize_assert("10h36m28s", comp); 66 | } 67 | 68 | #[test] 69 | fn test_tokenize9() { 70 | let comp = vec!["10", "h", "36", "m"]; 71 | tokenize_assert("10h36m", comp); 72 | } 73 | 74 | #[test] 75 | fn test_tokenize10() { 76 | let comp = vec!["10", "h"]; 77 | tokenize_assert("10h", comp); 78 | } 79 | 80 | #[test] 81 | fn test_tokenize11() { 82 | let comp = vec!["10", " ", "h", " ", "36"]; 83 | tokenize_assert("10 h 36", comp); 84 | } 85 | 86 | #[test] 87 | fn test_tokenize12() { 88 | let comp = vec!["10", " ", "h", " ", "36.5"]; 89 | tokenize_assert("10 h 36.5", comp); 90 | } 91 | 92 | #[test] 93 | fn test_tokenize13() { 94 | let comp = vec!["36", " ", "m", " ", "5"]; 95 | tokenize_assert("36 m 5", comp); 96 | } 97 | 98 | #[test] 99 | fn test_tokenize14() { 100 | let comp = vec!["36", " ", "m", " ", "5", " ", "s"]; 101 | tokenize_assert("36 m 5 s", comp); 102 | } 103 | 104 | #[test] 105 | fn test_tokenize15() { 106 | let comp = vec!["36", " ", "m", " ", "05"]; 107 | tokenize_assert("36 m 05", comp); 108 | } 109 | 110 | #[test] 111 | fn test_tokenize16() { 112 | let comp = vec!["36", " ", "m", " ", "05", " ", "s"]; 113 | tokenize_assert("36 m 05 s", comp); 114 | } 115 | 116 | #[test] 117 | fn test_tokenize17() { 118 | let comp = vec!["10", "h", " ", "am"]; 119 | tokenize_assert("10h am", comp); 120 | } 121 | 122 | #[test] 123 | fn test_tokenize18() { 124 | let comp = vec!["10", "h", " ", "pm"]; 125 | tokenize_assert("10h pm", comp); 126 | } 127 | 128 | #[test] 129 | fn test_tokenize19() { 130 | let comp = vec!["10", "am"]; 131 | tokenize_assert("10am", comp); 132 | } 133 | 134 | #[test] 135 | fn test_tokenize20() { 136 | let comp = vec!["10", "pm"]; 137 | tokenize_assert("10pm", comp); 138 | } 139 | 140 | #[test] 141 | fn test_tokenize21() { 142 | let comp = vec!["10", ":", "00", " ", "am"]; 143 | tokenize_assert("10:00 am", comp); 144 | } 145 | 146 | #[test] 147 | fn test_tokenize22() { 148 | let comp = vec!["10", ":", "00", " ", "pm"]; 149 | tokenize_assert("10:00 pm", comp); 150 | } 151 | 152 | #[test] 153 | fn test_tokenize23() { 154 | let comp = vec!["10", ":", "00", "am"]; 155 | tokenize_assert("10:00am", comp); 156 | } 157 | 158 | #[test] 159 | fn test_tokenize24() { 160 | let comp = vec!["10", ":", "00", "pm"]; 161 | tokenize_assert("10:00pm", comp); 162 | } 163 | 164 | #[test] 165 | fn test_tokenize25() { 166 | let comp = vec!["10", ":", "00", "a", ".", "m"]; 167 | tokenize_assert("10:00a.m", comp); 168 | } 169 | 170 | #[test] 171 | fn test_tokenize26() { 172 | let comp = vec!["10", ":", "00", "p", ".", "m"]; 173 | tokenize_assert("10:00p.m", comp); 174 | } 175 | 176 | #[test] 177 | fn test_tokenize27() { 178 | let comp = vec!["10", ":", "00", "a", ".", "m", "."]; 179 | tokenize_assert("10:00a.m.", comp); 180 | } 181 | 182 | #[test] 183 | fn test_tokenize28() { 184 | let comp = vec!["10", ":", "00", "p", ".", "m", "."]; 185 | tokenize_assert("10:00p.m.", comp); 186 | } 187 | 188 | #[test] 189 | fn test_tokenize29() { 190 | let comp = vec!["October"]; 191 | tokenize_assert("October", comp); 192 | } 193 | 194 | #[test] 195 | fn test_tokenize30() { 196 | let comp = vec!["31", "-", "Dec", "-", "00"]; 197 | tokenize_assert("31-Dec-00", comp); 198 | } 199 | 200 | #[test] 201 | fn test_tokenize31() { 202 | let comp = vec!["0", ":", "01", ":", "02"]; 203 | tokenize_assert("0:01:02", comp); 204 | } 205 | 206 | #[test] 207 | fn test_tokenize32() { 208 | let comp = vec!["12", "h", " ", "01", "m", "02", "s", " ", "am"]; 209 | tokenize_assert("12h 01m02s am", comp); 210 | } 211 | 212 | #[test] 213 | fn test_tokenize33() { 214 | let comp = vec!["12", ":", "08", " ", "PM"]; 215 | tokenize_assert("12:08 PM", comp); 216 | } 217 | 218 | #[test] 219 | fn test_tokenize34() { 220 | let comp = vec!["01", "h", "02", "m", "03"]; 221 | tokenize_assert("01h02m03", comp); 222 | } 223 | 224 | #[test] 225 | fn test_tokenize35() { 226 | let comp = vec!["01", "h", "02"]; 227 | tokenize_assert("01h02", comp); 228 | } 229 | 230 | #[test] 231 | fn test_tokenize36() { 232 | let comp = vec!["01", "h", "02", "s"]; 233 | tokenize_assert("01h02s", comp); 234 | } 235 | 236 | #[test] 237 | fn test_tokenize37() { 238 | let comp = vec!["01", "m", "02"]; 239 | tokenize_assert("01m02", comp); 240 | } 241 | 242 | #[test] 243 | fn test_tokenize38() { 244 | let comp = vec!["01", "m", "02", "h"]; 245 | tokenize_assert("01m02h", comp); 246 | } 247 | 248 | #[test] 249 | fn test_tokenize39() { 250 | let comp = vec!["2004", " ", "10", " ", "Apr", " ", "11", "h", "30", "m"]; 251 | tokenize_assert("2004 10 Apr 11h30m", comp); 252 | } 253 | 254 | #[test] 255 | fn test_tokenize40() { 256 | let comp = vec!["Sep", " ", "03"]; 257 | tokenize_assert("Sep 03", comp); 258 | } 259 | 260 | #[test] 261 | fn test_tokenize41() { 262 | let comp = vec!["Sep", " ", "of", " ", "03"]; 263 | tokenize_assert("Sep of 03", comp); 264 | } 265 | 266 | #[test] 267 | fn test_tokenize42() { 268 | let comp = vec!["02", ":", "17", "NOV", "2017"]; 269 | tokenize_assert("02:17NOV2017", comp); 270 | } 271 | 272 | #[test] 273 | fn test_tokenize43() { 274 | let comp = vec!["Thu", " ", "Sep", " ", "10", ":", "36", ":", "28"]; 275 | tokenize_assert("Thu Sep 10:36:28", comp); 276 | } 277 | 278 | #[test] 279 | fn test_tokenize44() { 280 | let comp = vec!["Thu", " ", "10", ":", "36", ":", "28"]; 281 | tokenize_assert("Thu 10:36:28", comp); 282 | } 283 | 284 | #[test] 285 | fn test_tokenize45() { 286 | let comp = vec!["Wed"]; 287 | tokenize_assert("Wed", comp); 288 | } 289 | 290 | #[test] 291 | fn test_tokenize46() { 292 | let comp = vec!["Wednesday"]; 293 | tokenize_assert("Wednesday", comp); 294 | } 295 | 296 | #[test] 297 | fn test_tokenize47() { 298 | let comp = vec![ 299 | "Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", " ", "2003", 300 | ]; 301 | tokenize_assert("Thu Sep 25 10:36:28 2003", comp); 302 | } 303 | 304 | #[test] 305 | fn test_tokenize48() { 306 | let comp = vec!["Thu", " ", "Sep", " ", "25", " ", "2003"]; 307 | tokenize_assert("Thu Sep 25 2003", comp); 308 | } 309 | 310 | #[test] 311 | fn test_tokenize49() { 312 | let comp = vec![ 313 | "2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41", 314 | ]; 315 | tokenize_assert("2003-09-25T10:49:41", comp); 316 | } 317 | 318 | #[test] 319 | fn test_tokenize50() { 320 | let comp = vec!["2003", "-", "09", "-", "25", "T", "10", ":", "49"]; 321 | tokenize_assert("2003-09-25T10:49", comp); 322 | } 323 | 324 | #[test] 325 | fn test_tokenize51() { 326 | let comp = vec!["2003", "-", "09", "-", "25", "T", "10"]; 327 | tokenize_assert("2003-09-25T10", comp); 328 | } 329 | 330 | #[test] 331 | fn test_tokenize52() { 332 | let comp = vec!["2003", "-", "09", "-", "25"]; 333 | tokenize_assert("2003-09-25", comp); 334 | } 335 | 336 | #[test] 337 | fn test_tokenize53() { 338 | let comp = vec!["20030925", "T", "104941"]; 339 | tokenize_assert("20030925T104941", comp); 340 | } 341 | 342 | #[test] 343 | fn test_tokenize54() { 344 | let comp = vec!["20030925", "T", "1049"]; 345 | tokenize_assert("20030925T1049", comp); 346 | } 347 | 348 | #[test] 349 | fn test_tokenize55() { 350 | let comp = vec!["20030925", "T", "10"]; 351 | tokenize_assert("20030925T10", comp); 352 | } 353 | 354 | #[test] 355 | fn test_tokenize56() { 356 | let comp = vec!["20030925"]; 357 | tokenize_assert("20030925", comp); 358 | } 359 | 360 | #[test] 361 | fn test_tokenize57() { 362 | let comp = vec![ 363 | "2003", "-", "09", "-", "25", " ", "10", ":", "49", ":", "41.502", 364 | ]; 365 | tokenize_assert("2003-09-25 10:49:41,502", comp); 366 | } 367 | 368 | #[test] 369 | fn test_tokenize58() { 370 | let comp = vec!["199709020908"]; 371 | tokenize_assert("199709020908", comp); 372 | } 373 | 374 | #[test] 375 | fn test_tokenize59() { 376 | let comp = vec!["19970902090807"]; 377 | tokenize_assert("19970902090807", comp); 378 | } 379 | 380 | #[test] 381 | fn test_tokenize60() { 382 | let comp = vec!["2003", "-", "09", "-", "25"]; 383 | tokenize_assert("2003-09-25", comp); 384 | } 385 | 386 | #[test] 387 | fn test_tokenize61() { 388 | let comp = vec!["09", "-", "25", "-", "2003"]; 389 | tokenize_assert("09-25-2003", comp); 390 | } 391 | 392 | #[test] 393 | fn test_tokenize62() { 394 | let comp = vec!["25", "-", "09", "-", "2003"]; 395 | tokenize_assert("25-09-2003", comp); 396 | } 397 | 398 | #[test] 399 | fn test_tokenize63() { 400 | let comp = vec!["10", "-", "09", "-", "2003"]; 401 | tokenize_assert("10-09-2003", comp); 402 | } 403 | 404 | #[test] 405 | fn test_tokenize64() { 406 | let comp = vec!["10", "-", "09", "-", "03"]; 407 | tokenize_assert("10-09-03", comp); 408 | } 409 | 410 | #[test] 411 | fn test_tokenize65() { 412 | let comp = vec!["2003", ".", "09", ".", "25"]; 413 | tokenize_assert("2003.09.25", comp); 414 | } 415 | 416 | #[test] 417 | fn test_tokenize66() { 418 | let comp = vec!["09", ".", "25", ".", "2003"]; 419 | tokenize_assert("09.25.2003", comp); 420 | } 421 | 422 | #[test] 423 | fn test_tokenize67() { 424 | let comp = vec!["25", ".", "09", ".", "2003"]; 425 | tokenize_assert("25.09.2003", comp); 426 | } 427 | 428 | #[test] 429 | fn test_tokenize68() { 430 | let comp = vec!["10", ".", "09", ".", "2003"]; 431 | tokenize_assert("10.09.2003", comp); 432 | } 433 | 434 | #[test] 435 | fn test_tokenize69() { 436 | let comp = vec!["10", ".", "09", ".", "03"]; 437 | tokenize_assert("10.09.03", comp); 438 | } 439 | 440 | #[test] 441 | fn test_tokenize70() { 442 | let comp = vec!["2003", "/", "09", "/", "25"]; 443 | tokenize_assert("2003/09/25", comp); 444 | } 445 | 446 | #[test] 447 | fn test_tokenize71() { 448 | let comp = vec!["09", "/", "25", "/", "2003"]; 449 | tokenize_assert("09/25/2003", comp); 450 | } 451 | 452 | #[test] 453 | fn test_tokenize72() { 454 | let comp = vec!["25", "/", "09", "/", "2003"]; 455 | tokenize_assert("25/09/2003", comp); 456 | } 457 | 458 | #[test] 459 | fn test_tokenize73() { 460 | let comp = vec!["10", "/", "09", "/", "2003"]; 461 | tokenize_assert("10/09/2003", comp); 462 | } 463 | 464 | #[test] 465 | fn test_tokenize74() { 466 | let comp = vec!["10", "/", "09", "/", "03"]; 467 | tokenize_assert("10/09/03", comp); 468 | } 469 | 470 | #[test] 471 | fn test_tokenize75() { 472 | let comp = vec!["2003", " ", "09", " ", "25"]; 473 | tokenize_assert("2003 09 25", comp); 474 | } 475 | 476 | #[test] 477 | fn test_tokenize76() { 478 | let comp = vec!["09", " ", "25", " ", "2003"]; 479 | tokenize_assert("09 25 2003", comp); 480 | } 481 | 482 | #[test] 483 | fn test_tokenize77() { 484 | let comp = vec!["25", " ", "09", " ", "2003"]; 485 | tokenize_assert("25 09 2003", comp); 486 | } 487 | 488 | #[test] 489 | fn test_tokenize78() { 490 | let comp = vec!["10", " ", "09", " ", "2003"]; 491 | tokenize_assert("10 09 2003", comp); 492 | } 493 | 494 | #[test] 495 | fn test_tokenize79() { 496 | let comp = vec!["10", " ", "09", " ", "03"]; 497 | tokenize_assert("10 09 03", comp); 498 | } 499 | 500 | #[test] 501 | fn test_tokenize80() { 502 | let comp = vec!["25", " ", "09", " ", "03"]; 503 | tokenize_assert("25 09 03", comp); 504 | } 505 | 506 | #[test] 507 | fn test_tokenize81() { 508 | let comp = vec!["03", " ", "25", " ", "Sep"]; 509 | tokenize_assert("03 25 Sep", comp); 510 | } 511 | 512 | #[test] 513 | fn test_tokenize82() { 514 | let comp = vec!["25", " ", "03", " ", "Sep"]; 515 | tokenize_assert("25 03 Sep", comp); 516 | } 517 | 518 | #[test] 519 | fn test_tokenize83() { 520 | let comp = vec![ 521 | " ", " ", "July", " ", " ", " ", "4", " ", ",", " ", " ", "1976", " ", " ", " ", "12", ":", 522 | "01", ":", "02", " ", " ", " ", "am", " ", " ", 523 | ]; 524 | tokenize_assert(" July 4 , 1976 12:01:02 am ", comp); 525 | } 526 | 527 | #[test] 528 | fn test_tokenize84() { 529 | let comp = vec!["Wed", ",", " ", "July", " ", "10", ",", " ", "'", "96"]; 530 | tokenize_assert("Wed, July 10, '96", comp); 531 | } 532 | 533 | #[test] 534 | fn test_tokenize85() { 535 | let comp = vec![ 536 | "1996", ".", "July", ".", "10", " ", "AD", " ", "12", ":", "08", " ", "PM", 537 | ]; 538 | tokenize_assert("1996.July.10 AD 12:08 PM", comp); 539 | } 540 | 541 | #[test] 542 | fn test_tokenize86() { 543 | let comp = vec!["July", " ", "4", ",", " ", "1976"]; 544 | tokenize_assert("July 4, 1976", comp); 545 | } 546 | 547 | #[test] 548 | fn test_tokenize87() { 549 | let comp = vec!["7", " ", "4", " ", "1976"]; 550 | tokenize_assert("7 4 1976", comp); 551 | } 552 | 553 | #[test] 554 | fn test_tokenize88() { 555 | let comp = vec!["4", " ", "jul", " ", "1976"]; 556 | tokenize_assert("4 jul 1976", comp); 557 | } 558 | 559 | #[test] 560 | fn test_tokenize89() { 561 | let comp = vec!["7", "-", "4", "-", "76"]; 562 | tokenize_assert("7-4-76", comp); 563 | } 564 | 565 | #[test] 566 | fn test_tokenize90() { 567 | let comp = vec!["19760704"]; 568 | tokenize_assert("19760704", comp); 569 | } 570 | 571 | #[test] 572 | fn test_tokenize91() { 573 | let comp = vec![ 574 | "0", ":", "01", ":", "02", " ", "on", " ", "July", " ", "4", ",", " ", "1976", 575 | ]; 576 | tokenize_assert("0:01:02 on July 4, 1976", comp); 577 | } 578 | 579 | #[test] 580 | fn test_tokenize92() { 581 | let comp = vec![ 582 | "0", ":", "01", ":", "02", " ", "on", " ", "July", " ", "4", ",", " ", "1976", 583 | ]; 584 | tokenize_assert("0:01:02 on July 4, 1976", comp); 585 | } 586 | 587 | #[test] 588 | fn test_tokenize93() { 589 | let comp = vec![ 590 | "July", " ", "4", ",", " ", "1976", " ", "12", ":", "01", ":", "02", " ", "am", 591 | ]; 592 | tokenize_assert("July 4, 1976 12:01:02 am", comp); 593 | } 594 | 595 | #[test] 596 | fn test_tokenize94() { 597 | let comp = vec![ 598 | "Mon", " ", "Jan", " ", " ", "2", " ", "04", ":", "24", ":", "27", " ", "1995", 599 | ]; 600 | tokenize_assert("Mon Jan 2 04:24:27 1995", comp); 601 | } 602 | 603 | #[test] 604 | fn test_tokenize95() { 605 | let comp = vec!["04", ".", "04", ".", "95", " ", "00", ":", "22"]; 606 | tokenize_assert("04.04.95 00:22", comp); 607 | } 608 | 609 | #[test] 610 | fn test_tokenize96() { 611 | let comp = vec![ 612 | "Jan", " ", "1", " ", "1999", " ", "11", ":", "23", ":", "34.578", 613 | ]; 614 | tokenize_assert("Jan 1 1999 11:23:34.578", comp); 615 | } 616 | 617 | #[test] 618 | fn test_tokenize97() { 619 | let comp = vec!["950404", " ", "122212"]; 620 | tokenize_assert("950404 122212", comp); 621 | } 622 | 623 | #[test] 624 | fn test_tokenize98() { 625 | let comp = vec!["3", "rd", " ", "of", " ", "May", " ", "2001"]; 626 | tokenize_assert("3rd of May 2001", comp); 627 | } 628 | 629 | #[test] 630 | fn test_tokenize99() { 631 | let comp = vec!["5", "th", " ", "of", " ", "March", " ", "2001"]; 632 | tokenize_assert("5th of March 2001", comp); 633 | } 634 | 635 | #[test] 636 | fn test_tokenize100() { 637 | let comp = vec!["1", "st", " ", "of", " ", "May", " ", "2003"]; 638 | tokenize_assert("1st of May 2003", comp); 639 | } 640 | 641 | #[test] 642 | fn test_tokenize101() { 643 | let comp = vec![ 644 | "0099", "-", "01", "-", "01", "T", "00", ":", "00", ":", "00", 645 | ]; 646 | tokenize_assert("0099-01-01T00:00:00", comp); 647 | } 648 | 649 | #[test] 650 | fn test_tokenize102() { 651 | let comp = vec![ 652 | "0031", "-", "01", "-", "01", "T", "00", ":", "00", ":", "00", 653 | ]; 654 | tokenize_assert("0031-01-01T00:00:00", comp); 655 | } 656 | 657 | #[test] 658 | fn test_tokenize103() { 659 | let comp = vec!["20080227", "T", "21", ":", "26", ":", "01.123456789"]; 660 | tokenize_assert("20080227T21:26:01.123456789", comp); 661 | } 662 | 663 | #[test] 664 | fn test_tokenize104() { 665 | let comp = vec!["13", "NOV", "2017"]; 666 | tokenize_assert("13NOV2017", comp); 667 | } 668 | 669 | #[test] 670 | fn test_tokenize105() { 671 | let comp = vec!["0003", "-", "03", "-", "04"]; 672 | tokenize_assert("0003-03-04", comp); 673 | } 674 | 675 | #[test] 676 | fn test_tokenize106() { 677 | let comp = vec!["December", ".", "0031", ".", "30"]; 678 | tokenize_assert("December.0031.30", comp); 679 | } 680 | 681 | #[test] 682 | fn test_tokenize107() { 683 | let comp = vec!["090107"]; 684 | tokenize_assert("090107", comp); 685 | } 686 | 687 | #[test] 688 | fn test_tokenize108() { 689 | let comp = vec!["2015", "-", "15", "-", "May"]; 690 | tokenize_assert("2015-15-May", comp); 691 | } 692 | 693 | #[test] 694 | fn test_tokenize109() { 695 | let comp = vec![ 696 | "Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", " ", "BRST", " ", "2003", 697 | ]; 698 | tokenize_assert("Thu Sep 25 10:36:28 BRST 2003", comp); 699 | } 700 | 701 | #[test] 702 | fn test_tokenize110() { 703 | let comp = vec![ 704 | "2003", " ", "10", ":", "36", ":", "28", " ", "BRST", " ", "25", " ", "Sep", " ", "Thu", 705 | ]; 706 | tokenize_assert("2003 10:36:28 BRST 25 Sep Thu", comp); 707 | } 708 | 709 | #[test] 710 | fn test_tokenize111() { 711 | let comp = vec![ 712 | "Thu", ",", " ", "25", " ", "Sep", " ", "2003", " ", "10", ":", "49", ":", "41", " ", "-", 713 | "0300", 714 | ]; 715 | tokenize_assert("Thu, 25 Sep 2003 10:49:41 -0300", comp); 716 | } 717 | 718 | #[test] 719 | fn test_tokenize112() { 720 | let comp = vec![ 721 | "2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41.5", "-", "03", ":", "00", 722 | ]; 723 | tokenize_assert("2003-09-25T10:49:41.5-03:00", comp); 724 | } 725 | 726 | #[test] 727 | fn test_tokenize113() { 728 | let comp = vec![ 729 | "2003", "-", "09", "-", "25", "T", "10", ":", "49", ":", "41", "-", "03", ":", "00", 730 | ]; 731 | tokenize_assert("2003-09-25T10:49:41-03:00", comp); 732 | } 733 | 734 | #[test] 735 | fn test_tokenize114() { 736 | let comp = vec!["20030925", "T", "104941.5", "-", "0300"]; 737 | tokenize_assert("20030925T104941.5-0300", comp); 738 | } 739 | 740 | #[test] 741 | fn test_tokenize115() { 742 | let comp = vec!["20030925", "T", "104941", "-", "0300"]; 743 | tokenize_assert("20030925T104941-0300", comp); 744 | } 745 | 746 | #[test] 747 | fn test_tokenize116() { 748 | let comp = vec![ 749 | "2018", "-", "08", "-", "10", " ", "10", ":", "00", ":", "00", " ", "UTC", "+", "3", 750 | ]; 751 | tokenize_assert("2018-08-10 10:00:00 UTC+3", comp); 752 | } 753 | 754 | #[test] 755 | fn test_tokenize117() { 756 | let comp = vec![ 757 | "2018", "-", "08", "-", "10", " ", "03", ":", "36", ":", "47", " ", "PM", " ", "GMT", "-", 758 | "4", 759 | ]; 760 | tokenize_assert("2018-08-10 03:36:47 PM GMT-4", comp); 761 | } 762 | 763 | #[test] 764 | fn test_tokenize118() { 765 | let comp = vec![ 766 | "2018", "-", "08", "-", "10", " ", "04", ":", "15", ":", "00", " ", "AM", " ", "Z", "-", 767 | "02", ":", "00", 768 | ]; 769 | tokenize_assert("2018-08-10 04:15:00 AM Z-02:00", comp); 770 | } 771 | 772 | #[test] 773 | fn test_tokenize119() { 774 | let comp = vec!["10", "-", "09", "-", "2003"]; 775 | tokenize_assert("10-09-2003", comp); 776 | } 777 | 778 | #[test] 779 | fn test_tokenize120() { 780 | let comp = vec!["10", ".", "09", ".", "2003"]; 781 | tokenize_assert("10.09.2003", comp); 782 | } 783 | 784 | #[test] 785 | fn test_tokenize121() { 786 | let comp = vec!["10", "/", "09", "/", "2003"]; 787 | tokenize_assert("10/09/2003", comp); 788 | } 789 | 790 | #[test] 791 | fn test_tokenize122() { 792 | let comp = vec!["10", " ", "09", " ", "2003"]; 793 | tokenize_assert("10 09 2003", comp); 794 | } 795 | 796 | #[test] 797 | fn test_tokenize123() { 798 | let comp = vec!["090107"]; 799 | tokenize_assert("090107", comp); 800 | } 801 | 802 | #[test] 803 | fn test_tokenize124() { 804 | let comp = vec!["2015", " ", "09", " ", "25"]; 805 | tokenize_assert("2015 09 25", comp); 806 | } 807 | 808 | #[test] 809 | fn test_tokenize125() { 810 | let comp = vec!["10", "-", "09", "-", "03"]; 811 | tokenize_assert("10-09-03", comp); 812 | } 813 | 814 | #[test] 815 | fn test_tokenize126() { 816 | let comp = vec!["10", ".", "09", ".", "03"]; 817 | tokenize_assert("10.09.03", comp); 818 | } 819 | 820 | #[test] 821 | fn test_tokenize127() { 822 | let comp = vec!["10", "/", "09", "/", "03"]; 823 | tokenize_assert("10/09/03", comp); 824 | } 825 | 826 | #[test] 827 | fn test_tokenize128() { 828 | let comp = vec!["10", " ", "09", " ", "03"]; 829 | tokenize_assert("10 09 03", comp); 830 | } 831 | 832 | #[test] 833 | fn test_tokenize129() { 834 | let comp = vec!["090107"]; 835 | tokenize_assert("090107", comp); 836 | } 837 | 838 | #[test] 839 | fn test_tokenize130() { 840 | let comp = vec!["2015", " ", "09", " ", "25"]; 841 | tokenize_assert("2015 09 25", comp); 842 | } 843 | 844 | #[test] 845 | fn test_tokenize131() { 846 | let comp = vec!["090107"]; 847 | tokenize_assert("090107", comp); 848 | } 849 | 850 | #[test] 851 | fn test_tokenize132() { 852 | let comp = vec!["2015", " ", "09", " ", "25"]; 853 | tokenize_assert("2015 09 25", comp); 854 | } 855 | 856 | #[test] 857 | fn test_tokenize133() { 858 | let comp = vec!["April", " ", "2009"]; 859 | tokenize_assert("April 2009", comp); 860 | } 861 | 862 | #[test] 863 | fn test_tokenize134() { 864 | let comp = vec!["Feb", " ", "2007"]; 865 | tokenize_assert("Feb 2007", comp); 866 | } 867 | 868 | #[test] 869 | fn test_tokenize135() { 870 | let comp = vec!["Feb", " ", "2008"]; 871 | tokenize_assert("Feb 2008", comp); 872 | } 873 | 874 | #[test] 875 | fn test_tokenize136() { 876 | let comp = vec![ 877 | "Thu", " ", "Sep", " ", "25", " ", "10", ":", "36", ":", "28", " ", "BRST", " ", "2003", 878 | ]; 879 | tokenize_assert("Thu Sep 25 10:36:28 BRST 2003", comp); 880 | } 881 | 882 | #[test] 883 | fn test_tokenize137() { 884 | let comp = vec![ 885 | "1996", ".", "07", ".", "10", " ", "AD", " ", "at", " ", "15", ":", "08", ":", "56", " ", 886 | "PDT", 887 | ]; 888 | tokenize_assert("1996.07.10 AD at 15:08:56 PDT", comp); 889 | } 890 | 891 | #[test] 892 | fn test_tokenize138() { 893 | let comp = vec![ 894 | "Tuesday", ",", " ", "April", " ", "12", ",", " ", "1952", " ", "AD", " ", "3", ":", "30", 895 | ":", "42", "pm", " ", "PST", 896 | ]; 897 | tokenize_assert("Tuesday, April 12, 1952 AD 3:30:42pm PST", comp); 898 | } 899 | 900 | #[test] 901 | fn test_tokenize139() { 902 | let comp = vec![ 903 | "November", " ", "5", ",", " ", "1994", ",", " ", "8", ":", "15", ":", "30", " ", "am", 904 | " ", "EST", 905 | ]; 906 | tokenize_assert("November 5, 1994, 8:15:30 am EST", comp); 907 | } 908 | 909 | #[test] 910 | fn test_tokenize140() { 911 | let comp = vec![ 912 | "1994", "-", "11", "-", "05", "T", "08", ":", "15", ":", "30", "-", "05", ":", "00", 913 | ]; 914 | tokenize_assert("1994-11-05T08:15:30-05:00", comp); 915 | } 916 | 917 | #[test] 918 | fn test_tokenize141() { 919 | let comp = vec![ 920 | "1994", "-", "11", "-", "05", "T", "08", ":", "15", ":", "30", "Z", 921 | ]; 922 | tokenize_assert("1994-11-05T08:15:30Z", comp); 923 | } 924 | 925 | #[test] 926 | fn test_tokenize142() { 927 | let comp = vec![ 928 | "1976", "-", "07", "-", "04", "T", "00", ":", "01", ":", "02", "Z", 929 | ]; 930 | tokenize_assert("1976-07-04T00:01:02Z", comp); 931 | } 932 | 933 | #[test] 934 | fn test_tokenize143() { 935 | let comp = vec![ 936 | "Tue", " ", "Apr", " ", "4", " ", "00", ":", "22", ":", "12", " ", "PDT", " ", "1995", 937 | ]; 938 | tokenize_assert("Tue Apr 4 00:22:12 PDT 1995", comp); 939 | } 940 | 941 | #[test] 942 | fn test_tokenize144() { 943 | let comp = vec![ 944 | "Today", 945 | " ", 946 | "is", 947 | " ", 948 | "25", 949 | " ", 950 | "of", 951 | " ", 952 | "September", 953 | " ", 954 | "of", 955 | " ", 956 | "2003", 957 | ",", 958 | " ", 959 | "exactly", 960 | " ", 961 | "at", 962 | " ", 963 | "10", 964 | ":", 965 | "49", 966 | ":", 967 | "41", 968 | " ", 969 | "with", 970 | " ", 971 | "timezone", 972 | " ", 973 | "-", 974 | "03", 975 | ":", 976 | "00", 977 | ".", 978 | ]; 979 | tokenize_assert( 980 | "Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.", 981 | comp, 982 | ); 983 | } 984 | 985 | #[test] 986 | fn test_tokenize145() { 987 | let comp = vec![ 988 | "Today", 989 | " ", 990 | "is", 991 | " ", 992 | "25", 993 | " ", 994 | "of", 995 | " ", 996 | "September", 997 | " ", 998 | "of", 999 | " ", 1000 | "2003", 1001 | ",", 1002 | " ", 1003 | "exactly", 1004 | " ", 1005 | "at", 1006 | " ", 1007 | "10", 1008 | ":", 1009 | "49", 1010 | ":", 1011 | "41", 1012 | " ", 1013 | "with", 1014 | " ", 1015 | "timezone", 1016 | " ", 1017 | "-", 1018 | "03", 1019 | ":", 1020 | "00", 1021 | ".", 1022 | ]; 1023 | tokenize_assert( 1024 | "Today is 25 of September of 2003, exactly at 10:49:41 with timezone -03:00.", 1025 | comp, 1026 | ); 1027 | } 1028 | 1029 | #[test] 1030 | fn test_tokenize146() { 1031 | let comp = vec![ 1032 | "I", " ", "have", " ", "a", " ", "meeting", " ", "on", " ", "March", " ", "1", ",", " ", 1033 | "1974", 1034 | ]; 1035 | tokenize_assert("I have a meeting on March 1, 1974", comp); 1036 | } 1037 | 1038 | #[test] 1039 | fn test_tokenize147() { 1040 | let comp = vec![ 1041 | "On", " ", "June", " ", "8", "th", ",", " ", "2020", ",", " ", "I", " ", "am", " ", 1042 | "going", " ", "to", " ", "be", " ", "the", " ", "first", " ", "man", " ", "on", " ", 1043 | "Mars", 1044 | ]; 1045 | tokenize_assert( 1046 | "On June 8th, 2020, I am going to be the first man on Mars", 1047 | comp, 1048 | ); 1049 | } 1050 | 1051 | #[test] 1052 | fn test_tokenize148() { 1053 | let comp = vec![ 1054 | "Meet", " ", "me", " ", "at", " ", "the", " ", "AM", "/", "PM", " ", "on", " ", "Sunset", 1055 | " ", "at", " ", "3", ":", "00", " ", "AM", " ", "on", " ", "December", " ", "3", "rd", ",", 1056 | " ", "2003", 1057 | ]; 1058 | tokenize_assert( 1059 | "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003", 1060 | comp, 1061 | ); 1062 | } 1063 | 1064 | #[test] 1065 | fn test_tokenize149() { 1066 | let comp = vec![ 1067 | "Meet", " ", "me", " ", "at", " ", "3", ":", "00", " ", "AM", " ", "on", " ", "December", 1068 | " ", "3", "rd", ",", " ", "2003", " ", "at", " ", "the", " ", "AM", "/", "PM", " ", "on", 1069 | " ", "Sunset", 1070 | ]; 1071 | tokenize_assert( 1072 | "Meet me at 3:00 AM on December 3rd, 2003 at the AM/PM on Sunset", 1073 | comp, 1074 | ); 1075 | } 1076 | 1077 | #[test] 1078 | fn test_tokenize150() { 1079 | let comp = vec![ 1080 | "Jan", " ", "29", ",", " ", "1945", " ", "14", ":", "45", " ", "AM", " ", "I", " ", 1081 | "going", " ", "to", " ", "see", " ", "you", " ", "there", "?", 1082 | ]; 1083 | tokenize_assert("Jan 29, 1945 14:45 AM I going to see you there?", comp); 1084 | } 1085 | 1086 | #[test] 1087 | fn test_tokenize151() { 1088 | let comp = vec!["2017", "-", "07", "-", "17", " ", "06", ":", "15", ":"]; 1089 | tokenize_assert("2017-07-17 06:15:", comp); 1090 | } 1091 | -------------------------------------------------------------------------------- /src/tokenize.rs: -------------------------------------------------------------------------------- 1 | pub(crate) struct Tokenizer { 2 | token_stack: Vec, 3 | // TODO: Should this be more generic? io::Read for example? 4 | parse_string: String, 5 | } 6 | 7 | #[derive(Debug, PartialEq)] 8 | pub(crate) enum ParseState { 9 | Empty, 10 | Alpha, 11 | AlphaDecimal, 12 | Numeric, 13 | NumericDecimal, 14 | } 15 | 16 | impl Tokenizer { 17 | pub(crate) fn new(parse_string: &str) -> Self { 18 | Tokenizer { 19 | token_stack: vec![], 20 | parse_string: parse_string.chars().rev().collect(), 21 | } 22 | } 23 | 24 | fn isword(&self, c: char) -> bool { 25 | c.is_alphabetic() 26 | } 27 | 28 | fn isnum(&self, c: char) -> bool { 29 | c.is_numeric() 30 | } 31 | 32 | fn isspace(&self, c: char) -> bool { 33 | c.is_whitespace() 34 | } 35 | 36 | fn decimal_split(&self, s: &str) -> Vec { 37 | // Handles the same thing as Python's re.split() 38 | let mut tokens: Vec = vec!["".to_owned()]; 39 | 40 | for c in s.chars() { 41 | if c == '.' || c == ',' { 42 | tokens.push(c.to_string()); 43 | tokens.push("".to_owned()); 44 | } else { 45 | // UNWRAP: Initial setup guarantees we always have an item 46 | let mut t = tokens.pop().unwrap(); 47 | t.push(c); 48 | tokens.push(t); 49 | } 50 | } 51 | 52 | // TODO: Do I really have to use &String instead of &str? 53 | if tokens.last() == Some(&"".to_owned()) { 54 | tokens.pop(); 55 | } 56 | 57 | tokens 58 | } 59 | } 60 | 61 | impl Iterator for Tokenizer { 62 | type Item = String; 63 | 64 | fn next(&mut self) -> Option { 65 | if !self.token_stack.is_empty() { 66 | return Some(self.token_stack.remove(0)); 67 | } 68 | 69 | let mut seenletters = false; 70 | let mut token: Option = None; 71 | let mut state = ParseState::Empty; 72 | 73 | while !self.parse_string.is_empty() { 74 | // Dateutil uses a separate `charstack` to manage the incoming stream. 75 | // Because parse_string can have things pushed back onto it, we skip 76 | // a couple of steps related to the `charstack`. 77 | 78 | // UNWRAP: Just checked that parse_string isn't empty 79 | let nextchar = self.parse_string.pop().unwrap(); 80 | 81 | match state { 82 | ParseState::Empty => { 83 | token = Some(nextchar.to_string()); 84 | if self.isword(nextchar) { 85 | state = ParseState::Alpha; 86 | } else if self.isnum(nextchar) { 87 | state = ParseState::Numeric; 88 | } else if self.isspace(nextchar) { 89 | token = Some(" ".to_owned()); 90 | break; 91 | } else { 92 | break; 93 | } 94 | } 95 | ParseState::Alpha => { 96 | seenletters = true; 97 | if self.isword(nextchar) { 98 | // UNWRAP: Because we're in non-empty parse state, we're guaranteed to have a token 99 | token.as_mut().unwrap().push(nextchar); 100 | } else if nextchar == '.' { 101 | token.as_mut().unwrap().push(nextchar); 102 | state = ParseState::AlphaDecimal; 103 | } else { 104 | self.parse_string.push(nextchar); 105 | break; 106 | } 107 | } 108 | ParseState::Numeric => { 109 | if self.isnum(nextchar) { 110 | // UNWRAP: Because we're in non-empty parse state, we're guaranteed to have a token 111 | token.as_mut().unwrap().push(nextchar); 112 | } else if nextchar == '.' 113 | || (nextchar == ',' && token.as_ref().unwrap().len() >= 2) 114 | { 115 | token.as_mut().unwrap().push(nextchar); 116 | state = ParseState::NumericDecimal; 117 | } else { 118 | self.parse_string.push(nextchar); 119 | break; 120 | } 121 | } 122 | ParseState::AlphaDecimal => { 123 | seenletters = true; 124 | if nextchar == '.' || self.isword(nextchar) { 125 | // UNWRAP: Because we're in non-empty parse state, we're guaranteed to have a token 126 | token.as_mut().unwrap().push(nextchar); 127 | } else if self.isnum(nextchar) && token.as_ref().unwrap().ends_with('.') { 128 | token.as_mut().unwrap().push(nextchar); 129 | state = ParseState::NumericDecimal; 130 | } else { 131 | self.parse_string.push(nextchar); 132 | break; 133 | } 134 | } 135 | ParseState::NumericDecimal => { 136 | if nextchar == '.' || self.isnum(nextchar) { 137 | // UNWRAP: Because we're in non-empty parse state, we're guaranteed to have a token 138 | token.as_mut().unwrap().push(nextchar); 139 | } else if self.isword(nextchar) && token.as_ref().unwrap().ends_with('.') { 140 | token.as_mut().unwrap().push(nextchar); 141 | state = ParseState::AlphaDecimal; 142 | } else { 143 | self.parse_string.push(nextchar); 144 | break; 145 | } 146 | } 147 | } 148 | } 149 | 150 | // Python uses the state to short-circuit and make sure it doesn't run into issues with None 151 | // We do something slightly different to express the same logic 152 | if state == ParseState::AlphaDecimal || state == ParseState::NumericDecimal { 153 | // UNWRAP: The state check guarantees that we have a value 154 | let dot_count = token 155 | .as_ref() 156 | .unwrap() 157 | .chars() 158 | .filter(|c| *c == '.') 159 | .count(); 160 | let last_char = token.as_ref().unwrap().chars().last(); 161 | let last_splittable = last_char == Some('.') || last_char == Some(','); 162 | 163 | if seenletters || dot_count > 1 || last_splittable { 164 | let mut l = self.decimal_split(token.as_ref().unwrap()); 165 | let remaining = l.split_off(1); 166 | 167 | token = Some(l[0].clone()); 168 | for t in remaining { 169 | self.token_stack.push(t); 170 | } 171 | } 172 | 173 | if state == ParseState::NumericDecimal && dot_count == 0 { 174 | token = Some(token.unwrap().replace(',', ".")); 175 | } 176 | } 177 | 178 | token 179 | } 180 | } 181 | 182 | #[cfg(test)] 183 | mod tests { 184 | 185 | use Tokenizer; 186 | 187 | #[test] 188 | fn test_basic() { 189 | let tokens: Vec = Tokenizer::new("September of 2003,").collect(); 190 | assert_eq!(tokens, vec!["September", " ", "of", " ", "2003", ","]); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/weekday.rs: -------------------------------------------------------------------------------- 1 | use ParseError; 2 | use ParseResult; 3 | 4 | #[derive(Debug, PartialEq)] 5 | pub enum DayOfWeek { 6 | Sunday, 7 | Monday, 8 | Tuesday, 9 | Wednesday, 10 | Thursday, 11 | Friday, 12 | Saturday, 13 | } 14 | 15 | impl DayOfWeek { 16 | pub fn to_numeral(&self) -> u32 { 17 | match *self { 18 | DayOfWeek::Sunday => 0, 19 | DayOfWeek::Monday => 1, 20 | DayOfWeek::Tuesday => 2, 21 | DayOfWeek::Wednesday => 3, 22 | DayOfWeek::Thursday => 4, 23 | DayOfWeek::Friday => 5, 24 | DayOfWeek::Saturday => 6, 25 | } 26 | } 27 | 28 | pub fn from_numeral(num: u32) -> DayOfWeek { 29 | match num % 7 { 30 | 0 => DayOfWeek::Sunday, 31 | 1 => DayOfWeek::Monday, 32 | 2 => DayOfWeek::Tuesday, 33 | 3 => DayOfWeek::Wednesday, 34 | 4 => DayOfWeek::Thursday, 35 | 5 => DayOfWeek::Friday, 36 | 6 => DayOfWeek::Saturday, 37 | _ => panic!("Unreachable."), 38 | } 39 | } 40 | 41 | /// Given the current day of the week, how many days until the next day? 42 | pub fn difference(&self, other: &DayOfWeek) -> u32 { 43 | // Have to use i32 because of wraparound issues 44 | let s_num = self.to_numeral() as i32; 45 | let o_num = other.to_numeral() as i32; 46 | 47 | if o_num - s_num >= 0 { 48 | (o_num - s_num) as u32 49 | } else { 50 | (7 + o_num - s_num) as u32 51 | } 52 | } 53 | } 54 | 55 | pub fn day_of_week(year: u32, month: u32, day: u32) -> ParseResult { 56 | // From https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Schwerdtfeger's_method 57 | let (c, g) = match month { 58 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 => { 59 | let c = year / 100; 60 | (c, year - 100 * c) 61 | } 62 | 1 | 2 => { 63 | let c = (year - 1) / 100; 64 | (c, year - 1 - 100 * c) 65 | } 66 | _ => return Err(ParseError::ImpossibleTimestamp("Invalid month")), 67 | }; 68 | 69 | let e = match month { 70 | 1 | 5 => 0, 71 | 2 | 6 => 3, 72 | 3 | 11 => 2, 73 | 4 | 7 => 5, 74 | 8 => 1, 75 | 9 | 12 => 4, 76 | 10 => 6, 77 | _ => panic!("Unreachable."), 78 | }; 79 | 80 | // This implementation is Gregorian-only. 81 | let f = match c % 4 { 82 | 0 => 0, 83 | 1 => 5, 84 | 2 => 3, 85 | 3 => 1, 86 | _ => panic!("Unreachable."), 87 | }; 88 | 89 | match (day + e + f + g + g / 4) % 7 { 90 | 0 => Ok(DayOfWeek::Sunday), 91 | 1 => Ok(DayOfWeek::Monday), 92 | 2 => Ok(DayOfWeek::Tuesday), 93 | 3 => Ok(DayOfWeek::Wednesday), 94 | 4 => Ok(DayOfWeek::Thursday), 95 | 5 => Ok(DayOfWeek::Friday), 96 | 6 => Ok(DayOfWeek::Saturday), 97 | _ => panic!("Unreachable."), 98 | } 99 | } 100 | 101 | // Rust warns about unused imports here, but they're definitely used. 102 | #[allow(unused_imports)] 103 | mod test { 104 | 105 | use weekday::day_of_week; 106 | use weekday::DayOfWeek; 107 | 108 | #[test] 109 | fn day_of_week_examples() { 110 | assert_eq!(day_of_week(2018, 6, 24).unwrap(), DayOfWeek::Sunday); 111 | assert_eq!(day_of_week(2003, 9, 25).unwrap(), DayOfWeek::Thursday); 112 | } 113 | 114 | #[test] 115 | fn weekday_difference() { 116 | assert_eq!(DayOfWeek::Sunday.difference(&DayOfWeek::Sunday), 0); 117 | assert_eq!(DayOfWeek::Sunday.difference(&DayOfWeek::Monday), 1); 118 | assert_eq!(DayOfWeek::Sunday.difference(&DayOfWeek::Tuesday), 2); 119 | assert_eq!(DayOfWeek::Sunday.difference(&DayOfWeek::Wednesday), 3); 120 | assert_eq!(DayOfWeek::Sunday.difference(&DayOfWeek::Thursday), 4); 121 | assert_eq!(DayOfWeek::Sunday.difference(&DayOfWeek::Friday), 5); 122 | assert_eq!(DayOfWeek::Sunday.difference(&DayOfWeek::Saturday), 6); 123 | assert_eq!(DayOfWeek::Monday.difference(&DayOfWeek::Sunday), 6); 124 | assert_eq!(DayOfWeek::Tuesday.difference(&DayOfWeek::Sunday), 5); 125 | assert_eq!(DayOfWeek::Wednesday.difference(&DayOfWeek::Sunday), 4); 126 | assert_eq!(DayOfWeek::Thursday.difference(&DayOfWeek::Sunday), 3); 127 | assert_eq!(DayOfWeek::Friday.difference(&DayOfWeek::Sunday), 2); 128 | assert_eq!(DayOfWeek::Saturday.difference(&DayOfWeek::Sunday), 1); 129 | } 130 | } 131 | --------------------------------------------------------------------------------