dateparser-0.2.0/.cargo_vcs_info.json0000644000000001500000000000100131610ustar { "git": { "sha1": "3ed3200feee75ebb93ba384e717c8c4a928b24e9" }, "path_in_vcs": "dateparser" }dateparser-0.2.0/Cargo.lock0000644000000575100000000000100111500ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anyhow" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", "num-integer", "num-traits", "time", "wasm-bindgen", "winapi", ] [[package]] name = "chrono-tz" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" dependencies = [ "chrono", "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" dependencies = [ "parse-zoneinfo", "phf", "phf_codegen", ] [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", "textwrap", "unicode-width", ] [[package]] name = "codespan-reporting" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", "unicode-width", ] [[package]] name = "core-foundation-sys" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "criterion" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", "clap", "criterion-plot", "csv", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_cbor", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-channel" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] [[package]] name = "csv" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad" dependencies = [ "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ "memchr", ] [[package]] name = "cxx" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", "cxxbridge-macro", "link-cplusplus", ] [[package]] name = "cxx-build" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", "once_cell", "proc-macro2", "quote", "scratch", "syn 2.0.10", ] [[package]] name = "cxxbridge-flags" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", "syn 2.0.10", ] [[package]] name = "dateparser" version = "0.2.0" dependencies = [ "anyhow", "chrono", "chrono-tz", "criterion", "lazy_static", "regex", ] [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] [[package]] name = "iana-time-zone" version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ "cxx", "cxx-build", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "link-cplusplus" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "parse-zoneinfo" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" dependencies = [ "regex", ] [[package]] name = "phf" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_shared" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" dependencies = [ "siphasher", "uncased", ] [[package]] name = "plotters" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" dependencies = [ "plotters-backend", ] [[package]] name = "proc-macro2" version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rayon" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "regex" version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "serde" version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" [[package]] name = "serde_cbor" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", ] [[package]] name = "serde_derive" version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", "syn 2.0.10", ] [[package]] name = "serde_json" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "siphasher" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "time" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi", "winapi", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "uncased" version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" dependencies = [ "version_check", ] [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 1.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" dateparser-0.2.0/Cargo.toml0000644000000023170000000000100111660ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "dateparser" version = "0.2.0" authors = ["Rollie Ma "] description = "Parse dates in string formats that are commonly used" homepage = "https://github.com/waltzofpearls/dateparser" readme = "README.md" keywords = [ "date", "time", "datetime", "parser", "parse", ] license = "MIT" repository = "https://github.com/waltzofpearls/dateparser" resolver = "1" [[bench]] name = "parse" harness = false [dependencies.anyhow] version = "1.0.40" [dependencies.chrono] version = "0.4.24" [dependencies.lazy_static] version = "1.4.0" [dependencies.regex] version = "1.6.0" [dev-dependencies.chrono-tz] version = "0.6.3" [dev-dependencies.criterion] version = "0.3.6" features = ["html_reports"] dateparser-0.2.0/Cargo.toml.orig000064400000000000000000000011611046102023000146430ustar 00000000000000[package] name = "dateparser" version = "0.2.0" authors = ["Rollie Ma "] description = "Parse dates in string formats that are commonly used" readme = "README.md" homepage = "https://github.com/waltzofpearls/dateparser" repository = "https://github.com/waltzofpearls/dateparser" keywords = ["date", "time", "datetime", "parser", "parse"] license = "MIT" edition = "2021" [dependencies] anyhow = "1.0.40" chrono = "0.4.24" lazy_static = "1.4.0" regex = "1.6.0" [dev-dependencies] chrono-tz = "0.6.3" criterion = { version = "0.3.6", features = ["html_reports"] } [[bench]] name = "parse" harness = false dateparser-0.2.0/README.md000064400000000000000000000130671046102023000132430ustar 00000000000000# [dateparser](https://crates.io/crates/dateparser) [![Build Status][actions-badge]][actions-url] [![MIT licensed][mit-badge]][mit-url] [![Crates.io][cratesio-badge]][cratesio-url] [![Doc.rs][docrs-badge]][docrs-url] [actions-badge]: https://github.com/waltzofpearls/dateparser/workflows/ci/badge.svg [actions-url]: https://github.com/waltzofpearls/dateparser/actions?query=workflow%3Aci+branch%3Amain [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: https://github.com/waltzofpearls/dateparser/blob/main/LICENSE [cratesio-badge]: https://img.shields.io/crates/v/dateparser.svg [cratesio-url]: https://crates.io/crates/dateparser [docrs-badge]: https://docs.rs/dateparser/badge.svg [docrs-url]: https://docs.rs/crate/dateparser/ A rust library for parsing date strings in commonly used formats. Parsed date will be returned as `chrono`'s `DateTime`. ## Examples Add to your `Cargo.toml`: ```toml [dependencies] dateparser = "0.2.0" ``` And then use `dateparser` in your code: ```rust use dateparser::parse; use std::error::Error; fn main() -> Result<(), Box> { let parsed = parse("6:15pm")?; println!("{:#?}", parsed); Ok(()) } ``` Or use `str`'s `parse` method: ```rust use dateparser::DateTimeUtc; use std::error::Error; fn main() -> Result<(), Box> { let parsed = "2021-05-14 18:51 PDT".parse::()?.0; println!("{:#?}", parsed); Ok(()) } ``` Convert returned `DateTime` to pacific time zone datetime with `chrono-tz`: ```toml [dependencies] chrono-tz = "0.6.3" dateparser = "0.2.0" ``` ```rust use chrono_tz::US::Pacific; use dateparser::DateTimeUtc; use std::error::Error; fn main() -> Result<(), Box> { let parsed = "Wed, 02 Jun 2021 06:31:39 GMT".parse::()?.0; println!("{:#?}", parsed.with_timezone(&Pacific)); Ok(()) } ``` Parse using a custom timezone offset for a datetime string that doesn't come with a specific timezone: ```rust use dateparser::parse_with_timezone; use chrono::offset::{Local, Utc}; use chrono_tz::US::Pacific; use std::error::Error; fn main() -> Result<(), Box> { let parsed_in_local = parse_with_timezone("6:15pm", &Local)?; println!("{:#?}", parsed_in_local); let parsed_in_utc = parse_with_timezone("6:15pm", &Utc)?; println!("{:#?}", parsed_in_utc); let parsed_in_pacific = parse_with_timezone("6:15pm", &Pacific)?; println!("{:#?}", parsed_in_pacific); Ok(()) } ``` Parse with a custom timezone offset and default time when those are not given in datetime string. By default, `parse` and `parse_with_timezone` uses `Utc::now().time()` as `default_time`. ```rust use dateparser::parse_with; use chrono::{ offset::{Local, Utc}, naive::NaiveTime, }; use std::error::Error; fn main() -> Result<(), Box> { let parsed_in_local = parse_with("2021-10-09", &Local, NaiveTime::from_hms(0, 0, 0))?; println!("{:#?}", parsed_in_local); let parsed_in_utc = parse_with("2021-10-09", &Utc, NaiveTime::from_hms(0, 0, 0))?; println!("{:#?}", parsed_in_utc); Ok(()) } ``` ## Accepted date formats ```rust // unix timestamp "1511648546", "1620021848429", "1620024872717915000", // rfc3339 "2021-05-01T01:17:02.604456Z", "2017-11-25T22:34:50Z", // rfc2822 "Wed, 02 Jun 2021 06:31:39 GMT", // postgres timestamp yyyy-mm-dd hh:mm:ss z "2019-11-29 08:08-08", "2019-11-29 08:08:05-08", "2021-05-02 23:31:36.0741-07", "2021-05-02 23:31:39.12689-07", "2019-11-29 08:15:47.624504-08", "2017-07-19 03:21:51+00:00", // yyyy-mm-dd hh:mm:ss "2014-04-26 05:24:37 PM", "2021-04-30 21:14", "2021-04-30 21:14:10", "2021-04-30 21:14:10.052282", "2014-04-26 17:24:37.123", "2014-04-26 17:24:37.3186369", "2012-08-03 18:31:59.257000000", // yyyy-mm-dd hh:mm:ss z "2017-11-25 13:31:15 PST", "2017-11-25 13:31 PST", "2014-12-16 06:20:00 UTC", "2014-12-16 06:20:00 GMT", "2014-04-26 13:13:43 +0800", "2014-04-26 13:13:44 +09:00", "2012-08-03 18:31:59.257000000 +0000", "2015-09-30 18:48:56.35272715 UTC", // yyyy-mm-dd "2021-02-21", // yyyy-mm-dd z "2021-02-21 PST", "2021-02-21 UTC", "2020-07-20+08:00", // hh:mm:ss "01:06:06", "4:00pm", "6:00 AM", // hh:mm:ss z "01:06:06 PST", "4:00pm PST", "6:00 AM PST", "6:00pm UTC", // Mon dd hh:mm:ss "May 6 at 9:24 PM", "May 27 02:45:27", // Mon dd, yyyy, hh:mm:ss "May 8, 2009 5:57:51 PM", "September 17, 2012 10:09am", "September 17, 2012, 10:10:09", // Mon dd, yyyy hh:mm:ss z "May 02, 2021 15:51:31 UTC", "May 02, 2021 15:51 UTC", "May 26, 2021, 12:49 AM PDT", "September 17, 2012 at 10:09am PST", // yyyy-mon-dd "2021-Feb-21", // Mon dd, yyyy "May 25, 2021", "oct 7, 1970", "oct 7, 70", "oct. 7, 1970", "oct. 7, 70", "October 7, 1970", // dd Mon yyyy hh:mm:ss "12 Feb 2006, 19:17", "12 Feb 2006 19:17", "14 May 2019 19:11:40.164", // dd Mon yyyy "7 oct 70", "7 oct 1970", "03 February 2013", "1 July 2013", // mm/dd/yyyy hh:mm:ss "4/8/2014 22:05", "04/08/2014 22:05", "4/8/14 22:05", "04/2/2014 03:00:51", "8/8/1965 12:00:00 AM", "8/8/1965 01:00:01 PM", "8/8/1965 01:00 PM", "8/8/1965 1:00 PM", "8/8/1965 12:00 AM", "4/02/2014 03:00:51", "03/19/2012 10:11:59", "03/19/2012 10:11:59.3186369", // mm/dd/yyyy "3/31/2014", "03/31/2014", "08/21/71", "8/1/71", // yyyy/mm/dd hh:mm:ss "2014/4/8 22:05", "2014/04/08 22:05", "2014/04/2 03:00:51", "2014/4/02 03:00:51", "2012/03/19 10:11:59", "2012/03/19 10:11:59.3186369", // yyyy/mm/dd "2014/3/31", "2014/03/31", // mm.dd.yyyy "3.31.2014", "03.31.2014", "08.21.71", // yyyy.mm.dd "2014.03.30", "2014.03", // yymmdd hh:mm:ss mysql log "171113 14:14:20", // chinese yyyy mm dd hh mm ss "2014年04月08日11时25分18秒", // chinese yyyy mm dd "2014年04月08日", ``` dateparser-0.2.0/benches/parse.rs000064400000000000000000000043341046102023000150500ustar 00000000000000use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use dateparser::parse; use lazy_static::lazy_static; lazy_static! { static ref SELECTED: Vec<&'static str> = vec![ "1511648546", // unix_timestamp "2017-11-25T22:34:50Z", // rfc3339 "Wed, 02 Jun 2021 06:31:39 GMT", // rfc2822 "2019-11-29 08:08:05-08", // postgres_timestamp "2021-04-30 21:14:10", // ymd_hms "2017-11-25 13:31:15 PST", // ymd_hms_z "2021-02-21", // ymd "2021-02-21 PST", // ymd_z "4:00pm", // hms "6:00 AM PST", // hms_z "May 27 02:45:27", // month_md_hms "May 8, 2009 5:57:51 PM", // month_mdy_hms "May 02, 2021 15:51 UTC", // month_mdy_hms_z "2021-Feb-21", // month_ymd "May 25, 2021", // month_mdy "14 May 2019 19:11:40.164", // month_dmy_hms "1 July 2013", // month_dmy "03/19/2012 10:11:59", // slash_mdy_hms "08/21/71", // slash_mdy "2012/03/19 10:11:59", // slash_ymd_hms "2014/3/31", // slash_ymd "2014.03.30", // dot_mdy_or_ymd "171113 14:14:20", // mysql_log_timestamp "2014年04月08日11时25分18秒", // chinese_ymd_hms "2014年04月08日", // chinese_ymd ]; } fn bench_parse_all(c: &mut Criterion) { c.bench_with_input( BenchmarkId::new("parse_all", "accepted_formats"), &SELECTED, |b, all| { b.iter(|| { for date_str in all.iter() { let _ = parse(*date_str); } }) }, ); } fn bench_parse_each(c: &mut Criterion) { let mut group = c.benchmark_group("parse_each"); for date_str in SELECTED.iter() { group.bench_with_input(*date_str, *date_str, |b, input| b.iter(|| parse(input))); } group.finish(); } criterion_group!(benches, bench_parse_all, bench_parse_each); criterion_main!(benches); dateparser-0.2.0/examples/convert_to_pacific.rs000064400000000000000000000004131046102023000177770ustar 00000000000000use chrono_tz::US::Pacific; use dateparser::DateTimeUtc; use std::error::Error; fn main() -> Result<(), Box> { let parsed = "Wed, 02 Jun 2021 06:31:39 GMT".parse::()?.0; println!("{:#?}", parsed.with_timezone(&Pacific)); Ok(()) } dateparser-0.2.0/examples/parse.rs000064400000000000000000000002501046102023000152500ustar 00000000000000use dateparser::parse; use std::error::Error; fn main() -> Result<(), Box> { let parsed = parse("6:15pm")?; println!("{:#?}", parsed); Ok(()) } dateparser-0.2.0/examples/parse_with.rs000064400000000000000000000010061046102023000163030ustar 00000000000000use chrono::{ naive::NaiveTime, offset::{Local, Utc}, }; use dateparser::parse_with; use std::error::Error; fn main() -> Result<(), Box> { let parsed_in_local = parse_with( "2021-10-09", &Local, NaiveTime::from_hms_opt(0, 0, 0).unwrap(), )?; println!("{:#?}", parsed_in_local); let parsed_in_utc = parse_with( "2021-10-09", &Utc, NaiveTime::from_hms_opt(0, 0, 0).unwrap(), )?; println!("{:#?}", parsed_in_utc); Ok(()) } dateparser-0.2.0/examples/parse_with_timezone.rs000064400000000000000000000007631046102023000202260ustar 00000000000000use chrono::offset::{Local, Utc}; use chrono_tz::US::Pacific; use dateparser::parse_with_timezone; use std::error::Error; fn main() -> Result<(), Box> { let parsed_in_local = parse_with_timezone("6:15pm", &Local)?; println!("{:#?}", parsed_in_local); let parsed_in_utc = parse_with_timezone("6:15pm", &Utc)?; println!("{:#?}", parsed_in_utc); let parsed_in_pacific = parse_with_timezone("6:15pm", &Pacific)?; println!("{:#?}", parsed_in_pacific); Ok(()) } dateparser-0.2.0/examples/str_parse_method.rs000064400000000000000000000003161046102023000175030ustar 00000000000000use dateparser::DateTimeUtc; use std::error::Error; fn main() -> Result<(), Box> { let parsed = "2021-05-14 18:51 PDT".parse::()?.0; println!("{:#?}", parsed); Ok(()) } dateparser-0.2.0/src/datetime.rs000064400000000000000000001515121046102023000147130ustar 00000000000000#![allow(deprecated)] use crate::timezone; use anyhow::{anyhow, Result}; use chrono::prelude::*; use lazy_static::lazy_static; use regex::Regex; /// Parse struct has methods implemented parsers for accepted formats. pub struct Parse<'z, Tz2> { tz: &'z Tz2, default_time: Option, } impl<'z, Tz2> Parse<'z, Tz2> where Tz2: TimeZone, { /// Create a new instrance of [`Parse`] with a custom parsing timezone that handles the /// datetime string without time offset. pub fn new(tz: &'z Tz2, default_time: Option) -> Self { Self { tz, default_time } } /// This method tries to parse the input datetime string with a list of accepted formats. See /// more exmaples from [`Parse`], [`crate::parse()`] and [`crate::parse_with_timezone()`]. pub fn parse(&self, input: &str) -> Result> { self.unix_timestamp(input) .or_else(|| self.rfc2822(input)) .or_else(|| self.ymd_family(input)) .or_else(|| self.hms_family(input)) .or_else(|| self.month_ymd(input)) .or_else(|| self.month_mdy_family(input)) .or_else(|| self.month_dmy_family(input)) .or_else(|| self.slash_mdy_family(input)) .or_else(|| self.slash_ymd_family(input)) .or_else(|| self.dot_mdy_or_ymd(input)) .or_else(|| self.mysql_log_timestamp(input)) .or_else(|| self.chinese_ymd_family(input)) .unwrap_or_else(|| Err(anyhow!("{} did not match any formats.", input))) } fn ymd_family(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}-[0-9]{2}").unwrap(); } if !RE.is_match(input) { return None; } self.rfc3339(input) .or_else(|| self.postgres_timestamp(input)) .or_else(|| self.ymd_hms(input)) .or_else(|| self.ymd_hms_z(input)) .or_else(|| self.ymd(input)) .or_else(|| self.ymd_z(input)) } fn hms_family(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{1,2}:[0-9]{2}").unwrap(); } if !RE.is_match(input) { return None; } self.hms(input).or_else(|| self.hms_z(input)) } fn month_mdy_family(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[a-zA-Z]{3,9}\.?\s+[0-9]{1,2}").unwrap(); } if !RE.is_match(input) { return None; } self.month_md_hms(input) .or_else(|| self.month_mdy_hms(input)) .or_else(|| self.month_mdy_hms_z(input)) .or_else(|| self.month_mdy(input)) } fn month_dmy_family(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{1,2}\s+[a-zA-Z]{3,9}").unwrap(); } if !RE.is_match(input) { return None; } self.month_dmy_hms(input).or_else(|| self.month_dmy(input)) } fn slash_mdy_family(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{1,2}/[0-9]{1,2}").unwrap(); } if !RE.is_match(input) { return None; } self.slash_mdy_hms(input).or_else(|| self.slash_mdy(input)) } fn slash_ymd_family(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}/[0-9]{1,2}").unwrap(); } if !RE.is_match(input) { return None; } self.slash_ymd_hms(input).or_else(|| self.slash_ymd(input)) } fn chinese_ymd_family(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}年[0-9]{2}月").unwrap(); } if !RE.is_match(input) { return None; } self.chinese_ymd_hms(input) .or_else(|| self.chinese_ymd(input)) } // unix timestamp // - 1511648546 // - 1620021848429 // - 1620024872717915000 fn unix_timestamp(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{10,19}$").unwrap(); } if !RE.is_match(input) { return None; } input .parse::() .ok() .and_then(|timestamp| { match input.len() { 10 => Some(Utc.timestamp(timestamp, 0)), 13 => Some(Utc.timestamp_millis(timestamp)), 19 => Some(Utc.timestamp_nanos(timestamp)), _ => None, } .map(|datetime| datetime.with_timezone(&Utc)) }) .map(Ok) } // rfc3339 // - 2021-05-01T01:17:02.604456Z // - 2017-11-25T22:34:50Z fn rfc3339(&self, input: &str) -> Option>> { DateTime::parse_from_rfc3339(input) .ok() .map(|parsed| parsed.with_timezone(&Utc)) .map(Ok) } // rfc2822 // - Wed, 02 Jun 2021 06:31:39 GMT fn rfc2822(&self, input: &str) -> Option>> { DateTime::parse_from_rfc2822(input) .ok() .map(|parsed| parsed.with_timezone(&Utc)) .map(Ok) } // postgres timestamp yyyy-mm-dd hh:mm:ss z // - 2019-11-29 08:08-08 // - 2019-11-29 08:08:05-08 // - 2021-05-02 23:31:36.0741-07 // - 2021-05-02 23:31:39.12689-07 // - 2019-11-29 08:15:47.624504-08 // - 2017-07-19 03:21:51+00:00 fn postgres_timestamp(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?[+-:0-9]{3,6}$", ) .unwrap(); } if !RE.is_match(input) { return None; } DateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S%#z") .or_else(|_| DateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f%#z")) .or_else(|_| DateTime::parse_from_str(input, "%Y-%m-%d %H:%M%#z")) .ok() .map(|parsed| parsed.with_timezone(&Utc)) .map(Ok) } // yyyy-mm-dd hh:mm:ss // - 2014-04-26 05:24:37 PM // - 2021-04-30 21:14 // - 2021-04-30 21:14:10 // - 2021-04-30 21:14:10.052282 // - 2014-04-26 17:24:37.123 // - 2014-04-26 17:24:37.3186369 // - 2012-08-03 18:31:59.257000000 fn ymd_hms(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?\s*(am|pm|AM|PM)?$", ) .unwrap(); } if !RE.is_match(input) { return None; } self.tz .datetime_from_str(input, "%Y-%m-%d %H:%M:%S") .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %H:%M")) .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %H:%M:%S%.f")) .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %I:%M:%S %P")) .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %I:%M %P")) .ok() .map(|parsed| parsed.with_timezone(&Utc)) .map(Ok) } // yyyy-mm-dd hh:mm:ss z // - 2017-11-25 13:31:15 PST // - 2017-11-25 13:31 PST // - 2014-12-16 06:20:00 UTC // - 2014-12-16 06:20:00 GMT // - 2014-04-26 13:13:43 +0800 // - 2014-04-26 13:13:44 +09:00 // - 2012-08-03 18:31:59.257000000 +0000 // - 2015-09-30 18:48:56.35272715 UTC fn ymd_hms_z(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?(?P\s*[+-:a-zA-Z0-9]{3,6})$", ).unwrap(); } if !RE.is_match(input) { return None; } if let Some(caps) = RE.captures(input) { if let Some(matched_tz) = caps.name("tz") { let parse_from_str = NaiveDateTime::parse_from_str; return match timezone::parse(matched_tz.as_str().trim()) { Ok(offset) => parse_from_str(input, "%Y-%m-%d %H:%M:%S %Z") .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M %Z")) .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f %Z")) .ok() .and_then(|parsed| offset.from_local_datetime(&parsed).single()) .map(|datetime| datetime.with_timezone(&Utc)) .map(Ok), Err(err) => Some(Err(err)), }; } } None } // yyyy-mm-dd // - 2021-02-21 fn ymd(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$").unwrap(); } if !RE.is_match(input) { return None; } // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(self.tz).time(), }; NaiveDate::parse_from_str(input, "%Y-%m-%d") .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| self.tz.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // yyyy-mm-dd z // - 2021-02-21 PST // - 2021-02-21 UTC // - 2020-07-20+08:00 (yyyy-mm-dd-07:00) fn ymd_z(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}(?P\s*[+-:a-zA-Z0-9]{3,6})$").unwrap(); } if !RE.is_match(input) { return None; } if let Some(caps) = RE.captures(input) { if let Some(matched_tz) = caps.name("tz") { return match timezone::parse(matched_tz.as_str().trim()) { Ok(offset) => { // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(&offset).time(), }; NaiveDate::parse_from_str(input, "%Y-%m-%d %Z") .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| offset.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } Err(err) => Some(Err(err)), }; } } None } // hh:mm:ss // - 01:06:06 // - 4:00pm // - 6:00 AM fn hms(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?$").unwrap(); } if !RE.is_match(input) { return None; } let now = Utc::now().with_timezone(self.tz); NaiveTime::parse_from_str(input, "%H:%M:%S") .or_else(|_| NaiveTime::parse_from_str(input, "%H:%M")) .or_else(|_| NaiveTime::parse_from_str(input, "%I:%M:%S %P")) .or_else(|_| NaiveTime::parse_from_str(input, "%I:%M %P")) .ok() .and_then(|parsed| now.date().and_time(parsed)) .map(|datetime| datetime.with_timezone(&Utc)) .map(Ok) } // hh:mm:ss z // - 01:06:06 PST // - 4:00pm PST // - 6:00 AM PST // - 6:00pm UTC fn hms_z(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?(?P\s+[+-:a-zA-Z0-9]{3,6})$", ) .unwrap(); } if !RE.is_match(input) { return None; } if let Some(caps) = RE.captures(input) { if let Some(matched_tz) = caps.name("tz") { return match timezone::parse(matched_tz.as_str().trim()) { Ok(offset) => { let now = Utc::now().with_timezone(&offset); NaiveTime::parse_from_str(input, "%H:%M:%S %Z") .or_else(|_| NaiveTime::parse_from_str(input, "%H:%M %Z")) .or_else(|_| NaiveTime::parse_from_str(input, "%I:%M:%S %P %Z")) .or_else(|_| NaiveTime::parse_from_str(input, "%I:%M %P %Z")) .ok() .map(|parsed| now.date().naive_local().and_time(parsed)) .and_then(|datetime| offset.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } Err(err) => Some(Err(err)), }; } } None } // yyyy-mon-dd // - 2021-Feb-21 fn month_ymd(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}-[a-zA-Z]{3,9}-[0-9]{2}$").unwrap(); } if !RE.is_match(input) { return None; } // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(self.tz).time(), }; NaiveDate::parse_from_str(input, "%Y-%m-%d") .or_else(|_| NaiveDate::parse_from_str(input, "%Y-%b-%d")) .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| self.tz.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // Mon dd hh:mm:ss // - May 6 at 9:24 PM // - May 27 02:45:27 fn month_md_hms(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[a-zA-Z]{3}\s+[0-9]{1,2}\s*(at)?\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?$", ) .unwrap(); } if !RE.is_match(input) { return None; } let now = Utc::now().with_timezone(self.tz); let with_year = format!("{} {}", now.year(), input); self.tz .datetime_from_str(&with_year, "%Y %b %d at %I:%M %P") .or_else(|_| self.tz.datetime_from_str(&with_year, "%Y %b %d %H:%M:%S")) .ok() .map(|parsed| parsed.with_timezone(&Utc)) .map(Ok) } // Mon dd, yyyy, hh:mm:ss // - May 8, 2009 5:57:51 PM // - September 17, 2012 10:09am // - September 17, 2012, 10:10:09 fn month_mdy_hms(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[a-zA-Z]{3,9}\.?\s+[0-9]{1,2},\s+[0-9]{2,4},?\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?$", ).unwrap(); } if !RE.is_match(input) { return None; } let dt = input.replace(", ", " ").replace(". ", " "); self.tz .datetime_from_str(&dt, "%B %d %Y %H:%M:%S") .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %H:%M")) .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M:%S %P")) .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M %P")) .ok() .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // Mon dd, yyyy hh:mm:ss z // - May 02, 2021 15:51:31 UTC // - May 02, 2021 15:51 UTC // - May 26, 2021, 12:49 AM PDT // - September 17, 2012 at 10:09am PST fn month_mdy_hms_z(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[a-zA-Z]{3,9}\s+[0-9]{1,2},?\s+[0-9]{4}\s*,?(at)?\s+[0-9]{2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?(?P\s+[+-:a-zA-Z0-9]{3,6})$", ).unwrap(); } if !RE.is_match(input) { return None; } if let Some(caps) = RE.captures(input) { if let Some(matched_tz) = caps.name("tz") { let parse_from_str = NaiveDateTime::parse_from_str; return match timezone::parse(matched_tz.as_str().trim()) { Ok(offset) => { let dt = input.replace(',', "").replace("at", ""); parse_from_str(&dt, "%B %d %Y %H:%M:%S %Z") .or_else(|_| parse_from_str(&dt, "%B %d %Y %H:%M %Z")) .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M:%S %P %Z")) .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M %P %Z")) .ok() .and_then(|parsed| offset.from_local_datetime(&parsed).single()) .map(|datetime| datetime.with_timezone(&Utc)) .map(Ok) } Err(err) => Some(Err(err)), }; } } None } // Mon dd, yyyy // - May 25, 2021 // - oct 7, 1970 // - oct 7, 70 // - oct. 7, 1970 // - oct. 7, 70 // - October 7, 1970 fn month_mdy(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[a-zA-Z]{3,9}\.?\s+[0-9]{1,2},\s+[0-9]{2,4}$").unwrap(); } if !RE.is_match(input) { return None; } // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(self.tz).time(), }; let dt = input.replace(", ", " ").replace(". ", " "); NaiveDate::parse_from_str(&dt, "%B %d %y") .or_else(|_| NaiveDate::parse_from_str(&dt, "%B %d %Y")) .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| self.tz.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // dd Mon yyyy hh:mm:ss // - 12 Feb 2006, 19:17 // - 12 Feb 2006 19:17 // - 14 May 2019 19:11:40.164 fn month_dmy_hms(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[0-9]{1,2}\s+[a-zA-Z]{3,9}\s+[0-9]{2,4},?\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?$", ).unwrap(); } if !RE.is_match(input) { return None; } let dt = input.replace(", ", " "); self.tz .datetime_from_str(&dt, "%d %B %Y %H:%M:%S") .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M")) .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M:%S%.f")) .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M:%S %P")) .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M %P")) .ok() .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // dd Mon yyyy // - 7 oct 70 // - 7 oct 1970 // - 03 February 2013 // - 1 July 2013 fn month_dmy(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{1,2}\s+[a-zA-Z]{3,9}\s+[0-9]{2,4}$").unwrap(); } if !RE.is_match(input) { return None; } // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(self.tz).time(), }; NaiveDate::parse_from_str(input, "%d %B %y") .or_else(|_| NaiveDate::parse_from_str(input, "%d %B %Y")) .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| self.tz.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // mm/dd/yyyy hh:mm:ss // - 4/8/2014 22:05 // - 04/08/2014 22:05 // - 4/8/14 22:05 // - 04/2/2014 03:00:51 // - 8/8/1965 12:00:00 AM // - 8/8/1965 01:00:01 PM // - 8/8/1965 01:00 PM // - 8/8/1965 1:00 PM // - 8/8/1965 12:00 AM // - 4/02/2014 03:00:51 // - 03/19/2012 10:11:59 // - 03/19/2012 10:11:59.3186369 fn slash_mdy_hms(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?\s*(am|pm|AM|PM)?$" ) .unwrap(); } if !RE.is_match(input) { return None; } self.tz .datetime_from_str(input, "%m/%d/%y %H:%M:%S") .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M")) .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M:%S%.f")) .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M:%S %P")) .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M %P")) .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S")) .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M")) .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S%.f")) .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M:%S %P")) .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M %P")) .ok() .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // mm/dd/yyyy // - 3/31/2014 // - 03/31/2014 // - 08/21/71 // - 8/1/71 fn slash_mdy(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}$").unwrap(); } if !RE.is_match(input) { return None; } // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(self.tz).time(), }; NaiveDate::parse_from_str(input, "%m/%d/%y") .or_else(|_| NaiveDate::parse_from_str(input, "%m/%d/%Y")) .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| self.tz.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // yyyy/mm/dd hh:mm:ss // - 2014/4/8 22:05 // - 2014/04/08 22:05 // - 2014/04/2 03:00:51 // - 2014/4/02 03:00:51 // - 2012/03/19 10:11:59 // - 2012/03/19 10:11:59.3186369 fn slash_ymd_hms(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new( r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?\s*(am|pm|AM|PM)?$" ) .unwrap(); } if !RE.is_match(input) { return None; } self.tz .datetime_from_str(input, "%Y/%m/%d %H:%M:%S") .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M")) .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M:%S%.f")) .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M:%S %P")) .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M %P")) .ok() .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // yyyy/mm/dd // - 2014/3/31 // - 2014/03/31 fn slash_ymd(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$").unwrap(); } if !RE.is_match(input) { return None; } // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(self.tz).time(), }; NaiveDate::parse_from_str(input, "%Y/%m/%d") .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| self.tz.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // mm.dd.yyyy // - 3.31.2014 // - 03.31.2014 // - 08.21.71 // yyyy.mm.dd // - 2014.03.30 // - 2014.03 fn dot_mdy_or_ymd(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"[0-9]{1,4}.[0-9]{1,4}[0-9]{1,4}").unwrap(); } if !RE.is_match(input) { return None; } // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(self.tz).time(), }; NaiveDate::parse_from_str(input, "%m.%d.%y") .or_else(|_| NaiveDate::parse_from_str(input, "%m.%d.%Y")) .or_else(|_| NaiveDate::parse_from_str(input, "%Y.%m.%d")) .or_else(|_| { NaiveDate::parse_from_str(&format!("{}.{}", input, Utc::now().day()), "%Y.%m.%d") }) .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| self.tz.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // yymmdd hh:mm:ss mysql log // - 171113 14:14:20 fn mysql_log_timestamp(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"[0-9]{6}\s+[0-9]{2}:[0-9]{2}:[0-9]{2}").unwrap(); } if !RE.is_match(input) { return None; } self.tz .datetime_from_str(input, "%y%m%d %H:%M:%S") .ok() .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // chinese yyyy mm dd hh mm ss // - 2014年04月08日11时25分18秒 fn chinese_ymd_hms(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}年[0-9]{2}月[0-9]{2}日[0-9]{2}时[0-9]{2}分[0-9]{2}秒$") .unwrap(); } if !RE.is_match(input) { return None; } self.tz .datetime_from_str(input, "%Y年%m月%d日%H时%M分%S秒") .ok() .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } // chinese yyyy mm dd // - 2014年04月08日 fn chinese_ymd(&self, input: &str) -> Option>> { lazy_static! { static ref RE: Regex = Regex::new(r"^[0-9]{4}年[0-9]{2}月[0-9]{2}日$").unwrap(); } if !RE.is_match(input) { return None; } // set time to use let time = match self.default_time { Some(v) => v, None => Utc::now().with_timezone(self.tz).time(), }; NaiveDate::parse_from_str(input, "%Y年%m月%d日") .ok() .map(|parsed| parsed.and_time(time)) .and_then(|datetime| self.tz.from_local_datetime(&datetime).single()) .map(|at_tz| at_tz.with_timezone(&Utc)) .map(Ok) } } #[cfg(test)] mod tests { use super::*; #[test] fn unix_timestamp() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ("0000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)), ("0000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)), ("0000000000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)), ("1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26)), ( "1620021848429", Utc.ymd(2021, 5, 3).and_hms_milli(6, 4, 8, 429), ), ( "1620024872717915000", Utc.ymd(2021, 5, 3).and_hms_nano(6, 54, 32, 717915000), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.unix_timestamp(input).unwrap().unwrap(), want, "unix_timestamp/{}", input ) } assert!(parse.unix_timestamp("15116").is_none()); assert!(parse .unix_timestamp("16200248727179150001620024872717915000") .is_none()); assert!(parse.unix_timestamp("not-a-ts").is_none()); } #[test] fn rfc3339() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "2021-05-01T01:17:02.604456Z", Utc.ymd(2021, 5, 1).and_hms_nano(1, 17, 2, 604456000), ), ( "2017-11-25T22:34:50Z", Utc.ymd(2017, 11, 25).and_hms(22, 34, 50), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.rfc3339(input).unwrap().unwrap(), want, "rfc3339/{}", input ) } assert!(parse.rfc3339("2017-11-25 22:34:50").is_none()); assert!(parse.rfc3339("not-date-time").is_none()); } #[test] fn rfc2822() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "Wed, 02 Jun 2021 06:31:39 GMT", Utc.ymd(2021, 6, 2).and_hms(6, 31, 39), ), ( "Wed, 02 Jun 2021 06:31:39 PDT", Utc.ymd(2021, 6, 2).and_hms(13, 31, 39), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.rfc2822(input).unwrap().unwrap(), want, "rfc2822/{}", input ) } assert!(parse.rfc2822("02 Jun 2021 06:31:39").is_none()); assert!(parse.rfc2822("not-date-time").is_none()); } #[test] fn postgres_timestamp() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "2019-11-29 08:08-08", Utc.ymd(2019, 11, 29).and_hms(16, 8, 0), ), ( "2019-11-29 08:08:05-08", Utc.ymd(2019, 11, 29).and_hms(16, 8, 5), ), ( "2021-05-02 23:31:36.0741-07", Utc.ymd(2021, 5, 3).and_hms_micro(6, 31, 36, 74100), ), ( "2021-05-02 23:31:39.12689-07", Utc.ymd(2021, 5, 3).and_hms_micro(6, 31, 39, 126890), ), ( "2019-11-29 08:15:47.624504-08", Utc.ymd(2019, 11, 29).and_hms_micro(16, 15, 47, 624504), ), ( "2017-07-19 03:21:51+00:00", Utc.ymd(2017, 7, 19).and_hms(3, 21, 51), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.postgres_timestamp(input).unwrap().unwrap(), want, "postgres_timestamp/{}", input ) } assert!(parse.postgres_timestamp("not-date-time").is_none()); } #[test] fn ymd_hms() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ("2021-04-30 21:14", Utc.ymd(2021, 4, 30).and_hms(21, 14, 0)), ( "2021-04-30 21:14:10", Utc.ymd(2021, 4, 30).and_hms(21, 14, 10), ), ( "2021-04-30 21:14:10.052282", Utc.ymd(2021, 4, 30).and_hms_micro(21, 14, 10, 52282), ), ( "2014-04-26 05:24:37 PM", Utc.ymd(2014, 4, 26).and_hms(17, 24, 37), ), ( "2014-04-26 17:24:37.123", Utc.ymd(2014, 4, 26).and_hms_milli(17, 24, 37, 123), ), ( "2014-04-26 17:24:37.3186369", Utc.ymd(2014, 4, 26).and_hms_nano(17, 24, 37, 318636900), ), ( "2012-08-03 18:31:59.257000000", Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.ymd_hms(input).unwrap().unwrap(), want, "ymd_hms/{}", input ) } assert!(parse.ymd_hms("not-date-time").is_none()); } #[test] fn ymd_hms_z() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "2017-11-25 13:31:15 PST", Utc.ymd(2017, 11, 25).and_hms(21, 31, 15), ), ( "2017-11-25 13:31 PST", Utc.ymd(2017, 11, 25).and_hms(21, 31, 0), ), ( "2014-12-16 06:20:00 UTC", Utc.ymd(2014, 12, 16).and_hms(6, 20, 0), ), ( "2014-12-16 06:20:00 GMT", Utc.ymd(2014, 12, 16).and_hms(6, 20, 0), ), ( "2014-04-26 13:13:43 +0800", Utc.ymd(2014, 4, 26).and_hms(5, 13, 43), ), ( "2014-04-26 13:13:44 +09:00", Utc.ymd(2014, 4, 26).and_hms(4, 13, 44), ), ( "2012-08-03 18:31:59.257000000 +0000", Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000), ), ( "2015-09-30 18:48:56.35272715 UTC", Utc.ymd(2015, 9, 30).and_hms_nano(18, 48, 56, 352727150), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.ymd_hms_z(input).unwrap().unwrap(), want, "ymd_hms_z/{}", input ) } assert!(parse.ymd_hms_z("not-date-time").is_none()); } #[test] fn ymd() { let parse = Parse::new(&Utc, Some(Utc::now().time())); let test_cases = vec![( "2021-02-21", Utc.ymd(2021, 2, 21).and_time(Utc::now().time()), )]; for &(input, want) in test_cases.iter() { assert_eq!( parse .ymd(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "ymd/{}", input ) } assert!(parse.ymd("not-date-time").is_none()); } #[test] fn ymd_z() { let parse = Parse::new(&Utc, None); let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600)); let now_at_cst = Utc::now().with_timezone(&FixedOffset::east(8 * 3600)); let test_cases = vec![ ( "2021-02-21 PST", FixedOffset::west(8 * 3600) .ymd(2021, 2, 21) .and_time(now_at_pst.time()) .map(|dt| dt.with_timezone(&Utc)), ), ( "2021-02-21 UTC", FixedOffset::west(0) .ymd(2021, 2, 21) .and_time(Utc::now().time()) .map(|dt| dt.with_timezone(&Utc)), ), ( "2020-07-20+08:00", FixedOffset::east(8 * 3600) .ymd(2020, 7, 20) .and_time(now_at_cst.time()) .map(|dt| dt.with_timezone(&Utc)), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse .ymd_z(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "ymd_z/{}", input ) } assert!(parse.ymd_z("not-date-time").is_none()); } #[test] fn hms() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "01:06:06", Utc::now().date().and_time(NaiveTime::from_hms(1, 6, 6)), ), ( "4:00pm", Utc::now().date().and_time(NaiveTime::from_hms(16, 0, 0)), ), ( "6:00 AM", Utc::now().date().and_time(NaiveTime::from_hms(6, 0, 0)), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.hms(input).unwrap().unwrap(), want.unwrap(), "hms/{}", input ) } assert!(parse.hms("not-date-time").is_none()); } #[test] fn hms_z() { let parse = Parse::new(&Utc, None); let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600)); let test_cases = vec![ ( "01:06:06 PST", FixedOffset::west(8 * 3600) .from_local_date(&now_at_pst.date().naive_local()) .and_time(NaiveTime::from_hms(1, 6, 6)) .map(|dt| dt.with_timezone(&Utc)), ), ( "4:00pm PST", FixedOffset::west(8 * 3600) .from_local_date(&now_at_pst.date().naive_local()) .and_time(NaiveTime::from_hms(16, 0, 0)) .map(|dt| dt.with_timezone(&Utc)), ), ( "6:00 AM PST", FixedOffset::west(8 * 3600) .from_local_date(&now_at_pst.date().naive_local()) .and_time(NaiveTime::from_hms(6, 0, 0)) .map(|dt| dt.with_timezone(&Utc)), ), ( "6:00pm UTC", FixedOffset::west(0) .from_local_date(&Utc::now().date().naive_local()) .and_time(NaiveTime::from_hms(18, 0, 0)) .map(|dt| dt.with_timezone(&Utc)), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.hms_z(input).unwrap().unwrap(), want.unwrap(), "hms_z/{}", input ) } assert!(parse.hms_z("not-date-time").is_none()); } #[test] fn month_ymd() { let parse = Parse::new(&Utc, None); let test_cases = vec![( "2021-Feb-21", Utc.ymd(2021, 2, 21).and_time(Utc::now().time()), )]; for &(input, want) in test_cases.iter() { assert_eq!( parse .month_ymd(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "month_ymd/{}", input ) } assert!(parse.month_ymd("not-date-time").is_none()); } #[test] fn month_md_hms() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "May 6 at 9:24 PM", Utc.ymd(Utc::now().year(), 5, 6).and_hms(21, 24, 0), ), ( "May 27 02:45:27", Utc.ymd(Utc::now().year(), 5, 27).and_hms(2, 45, 27), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.month_md_hms(input).unwrap().unwrap(), want, "month_md_hms/{}", input ) } assert!(parse.month_md_hms("not-date-time").is_none()); } #[test] fn month_mdy_hms() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "May 8, 2009 5:57:51 PM", Utc.ymd(2009, 5, 8).and_hms(17, 57, 51), ), ( "September 17, 2012 10:09am", Utc.ymd(2012, 9, 17).and_hms(10, 9, 0), ), ( "September 17, 2012, 10:10:09", Utc.ymd(2012, 9, 17).and_hms(10, 10, 9), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.month_mdy_hms(input).unwrap().unwrap(), want, "month_mdy_hms/{}", input ) } assert!(parse.month_mdy_hms("not-date-time").is_none()); } #[test] fn month_mdy_hms_z() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "May 02, 2021 15:51:31 UTC", Utc.ymd(2021, 5, 2).and_hms(15, 51, 31), ), ( "May 02, 2021 15:51 UTC", Utc.ymd(2021, 5, 2).and_hms(15, 51, 0), ), ( "May 26, 2021, 12:49 AM PDT", Utc.ymd(2021, 5, 26).and_hms(7, 49, 0), ), ( "September 17, 2012 at 10:09am PST", Utc.ymd(2012, 9, 17).and_hms(18, 9, 0), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.month_mdy_hms_z(input).unwrap().unwrap(), want, "month_mdy_hms_z/{}", input ) } assert!(parse.month_mdy_hms_z("not-date-time").is_none()); } #[test] fn month_mdy() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "May 25, 2021", Utc.ymd(2021, 5, 25).and_time(Utc::now().time()), ), ( "oct 7, 1970", Utc.ymd(1970, 10, 7).and_time(Utc::now().time()), ), ( "oct 7, 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time()), ), ( "oct. 7, 1970", Utc.ymd(1970, 10, 7).and_time(Utc::now().time()), ), ( "oct. 7, 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time()), ), ( "October 7, 1970", Utc.ymd(1970, 10, 7).and_time(Utc::now().time()), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse .month_mdy(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "month_mdy/{}", input ) } assert!(parse.month_mdy("not-date-time").is_none()); } #[test] fn month_dmy_hms() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "12 Feb 2006, 19:17", Utc.ymd(2006, 2, 12).and_hms(19, 17, 0), ), ("12 Feb 2006 19:17", Utc.ymd(2006, 2, 12).and_hms(19, 17, 0)), ( "14 May 2019 19:11:40.164", Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.month_dmy_hms(input).unwrap().unwrap(), want, "month_dmy_hms/{}", input ) } assert!(parse.month_dmy_hms("not-date-time").is_none()); } #[test] fn month_dmy() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ("7 oct 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time())), ( "7 oct 1970", Utc.ymd(1970, 10, 7).and_time(Utc::now().time()), ), ( "03 February 2013", Utc.ymd(2013, 2, 3).and_time(Utc::now().time()), ), ( "1 July 2013", Utc.ymd(2013, 7, 1).and_time(Utc::now().time()), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse .month_dmy(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "month_dmy/{}", input ) } assert!(parse.month_dmy("not-date-time").is_none()); } #[test] fn slash_mdy_hms() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ("4/8/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)), ("04/08/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)), ("4/8/14 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)), ("04/2/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)), ("8/8/1965 12:00:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)), ( "8/8/1965 01:00:01 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 1), ), ("8/8/1965 01:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)), ("8/8/1965 1:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)), ("8/8/1965 12:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)), ("4/02/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)), ( "03/19/2012 10:11:59", Utc.ymd(2012, 3, 19).and_hms(10, 11, 59), ), ( "03/19/2012 10:11:59.3186369", Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.slash_mdy_hms(input).unwrap().unwrap(), want, "slash_mdy_hms/{}", input ) } assert!(parse.slash_mdy_hms("not-date-time").is_none()); } #[test] fn slash_mdy() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ( "3/31/2014", Utc.ymd(2014, 3, 31).and_time(Utc::now().time()), ), ( "03/31/2014", Utc.ymd(2014, 3, 31).and_time(Utc::now().time()), ), ("08/21/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())), ("8/1/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse .slash_mdy(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "slash_mdy/{}", input ) } assert!(parse.slash_mdy("not-date-time").is_none()); } #[test] fn slash_ymd_hms() { let parse = Parse::new(&Utc, None); let test_cases = vec![ ("2014/4/8 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)), ("2014/04/08 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)), ("2014/04/2 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)), ("2014/4/02 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)), ( "2012/03/19 10:11:59", Utc.ymd(2012, 3, 19).and_hms(10, 11, 59), ), ( "2012/03/19 10:11:59.3186369", Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.slash_ymd_hms(input).unwrap().unwrap(), want, "slash_ymd_hms/{}", input ) } assert!(parse.slash_ymd_hms("not-date-time").is_none()); } #[test] fn slash_ymd() { let parse = Parse::new(&Utc, Some(Utc::now().time())); let test_cases = vec![ ( "2014/3/31", Utc.ymd(2014, 3, 31).and_time(Utc::now().time()), ), ( "2014/03/31", Utc.ymd(2014, 3, 31).and_time(Utc::now().time()), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse .slash_ymd(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "slash_ymd/{}", input ) } assert!(parse.slash_ymd("not-date-time").is_none()); } #[test] fn dot_mdy_or_ymd() { let parse = Parse::new(&Utc, Some(Utc::now().time())); let test_cases = vec![ // mm.dd.yyyy ( "3.31.2014", Utc.ymd(2014, 3, 31).and_time(Utc::now().time()), ), ( "03.31.2014", Utc.ymd(2014, 3, 31).and_time(Utc::now().time()), ), ("08.21.71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())), // yyyy.mm.dd ( "2014.03.30", Utc.ymd(2014, 3, 30).and_time(Utc::now().time()), ), ( "2014.03", Utc.ymd(2014, 3, Utc::now().day()) .and_time(Utc::now().time()), ), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse .dot_mdy_or_ymd(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "dot_mdy_or_ymd/{}", input ) } assert!(parse.dot_mdy_or_ymd("not-date-time").is_none()); } #[test] fn mysql_log_timestamp() { let parse = Parse::new(&Utc, None); let test_cases = vec![ // yymmdd hh:mm:ss mysql log ("171113 14:14:20", Utc.ymd(2017, 11, 13).and_hms(14, 14, 20)), ]; for &(input, want) in test_cases.iter() { assert_eq!( parse.mysql_log_timestamp(input).unwrap().unwrap(), want, "mysql_log_timestamp/{}", input ) } assert!(parse.mysql_log_timestamp("not-date-time").is_none()); } #[test] fn chinese_ymd_hms() { let parse = Parse::new(&Utc, None); let test_cases = vec![( "2014年04月08日11时25分18秒", Utc.ymd(2014, 4, 8).and_hms(11, 25, 18), )]; for &(input, want) in test_cases.iter() { assert_eq!( parse.chinese_ymd_hms(input).unwrap().unwrap(), want, "chinese_ymd_hms/{}", input ) } assert!(parse.chinese_ymd_hms("not-date-time").is_none()); } #[test] fn chinese_ymd() { let parse = Parse::new(&Utc, Some(Utc::now().time())); let test_cases = vec![( "2014年04月08日", Utc.ymd(2014, 4, 8).and_time(Utc::now().time()), )]; for &(input, want) in test_cases.iter() { assert_eq!( parse .chinese_ymd(input) .unwrap() .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.unwrap().trunc_subsecs(0).with_second(0).unwrap(), "chinese_ymd/{}", input ) } assert!(parse.chinese_ymd("not-date-time").is_none()); } } dateparser-0.2.0/src/lib.rs000064400000000000000000000763311046102023000136720ustar 00000000000000#![allow(deprecated)] //! A rust library for parsing date strings in commonly used formats. Parsed date will be returned //! as `chrono`'s `DateTime`. //! //! # Quick Start //! //! ``` //! use chrono::prelude::*; //! use dateparser::parse; //! use std::error::Error; //! //! fn main() -> Result<(), Box> { //! assert_eq!( //! parse("6:15pm UTC")?, //! Utc::now().date().and_time( //! NaiveTime::from_hms(18, 15, 0), //! ).unwrap(), //! ); //! Ok(()) //! } //! ``` //! //! Use `str`'s `parse` method: //! //! ``` //! use chrono::prelude::*; //! use dateparser::DateTimeUtc; //! use std::error::Error; //! //! fn main() -> Result<(), Box> { //! assert_eq!( //! "2021-05-14 18:51 PDT".parse::()?.0, //! Utc.ymd(2021, 5, 15).and_hms(1, 51, 0), //! ); //! Ok(()) //! } //! ``` //! //! Parse using a custom timezone offset for a datetime string that doesn't come with a specific //! timezone: //! //! ``` //! use dateparser::parse_with_timezone; //! use chrono::offset::Utc; //! use std::error::Error; //! //! fn main() -> Result<(), Box> { //! let parsed_in_utc = parse_with_timezone("6:15pm", &Utc)?; //! assert_eq!( //! parsed_in_utc, //! Utc::now().date().and_hms(18, 15, 0), //! ); //! Ok(()) //! } //! ``` //! //! ## Accepted date formats //! //! ``` //! use dateparser::DateTimeUtc; //! //! let accepted = vec![ //! // unix timestamp //! "1511648546", //! "1620021848429", //! "1620024872717915000", //! // rfc3339 //! "2021-05-01T01:17:02.604456Z", //! "2017-11-25T22:34:50Z", //! // rfc2822 //! "Wed, 02 Jun 2021 06:31:39 GMT", //! // postgres timestamp yyyy-mm-dd hh:mm:ss z //! "2019-11-29 08:08-08", //! "2019-11-29 08:08:05-08", //! "2021-05-02 23:31:36.0741-07", //! "2021-05-02 23:31:39.12689-07", //! "2019-11-29 08:15:47.624504-08", //! "2017-07-19 03:21:51+00:00", //! // yyyy-mm-dd hh:mm:ss //! "2014-04-26 05:24:37 PM", //! "2021-04-30 21:14", //! "2021-04-30 21:14:10", //! "2021-04-30 21:14:10.052282", //! "2014-04-26 17:24:37.123", //! "2014-04-26 17:24:37.3186369", //! "2012-08-03 18:31:59.257000000", //! // yyyy-mm-dd hh:mm:ss z //! "2017-11-25 13:31:15 PST", //! "2017-11-25 13:31 PST", //! "2014-12-16 06:20:00 UTC", //! "2014-12-16 06:20:00 GMT", //! "2014-04-26 13:13:43 +0800", //! "2014-04-26 13:13:44 +09:00", //! "2012-08-03 18:31:59.257000000 +0000", //! "2015-09-30 18:48:56.35272715 UTC", //! // yyyy-mm-dd //! "2021-02-21", //! // yyyy-mm-dd z //! "2021-02-21 PST", //! "2021-02-21 UTC", //! "2020-07-20+08:00", //! // hh:mm:ss //! "01:06:06", //! "4:00pm", //! "6:00 AM", //! // hh:mm:ss z //! "01:06:06 PST", //! "4:00pm PST", //! "6:00 AM PST", //! "6:00pm UTC", //! // Mon dd hh:mm:ss //! "May 6 at 9:24 PM", //! "May 27 02:45:27", //! // Mon dd, yyyy, hh:mm:ss //! "May 8, 2009 5:57:51 PM", //! "September 17, 2012 10:09am", //! "September 17, 2012, 10:10:09", //! // Mon dd, yyyy hh:mm:ss z //! "May 02, 2021 15:51:31 UTC", //! "May 02, 2021 15:51 UTC", //! "May 26, 2021, 12:49 AM PDT", //! "September 17, 2012 at 10:09am PST", //! // yyyy-mon-dd //! "2021-Feb-21", //! // Mon dd, yyyy //! "May 25, 2021", //! "oct 7, 1970", //! "oct 7, 70", //! "oct. 7, 1970", //! "oct. 7, 70", //! "October 7, 1970", //! // dd Mon yyyy hh:mm:ss //! "12 Feb 2006, 19:17", //! "12 Feb 2006 19:17", //! "14 May 2019 19:11:40.164", //! // dd Mon yyyy //! "7 oct 70", //! "7 oct 1970", //! "03 February 2013", //! "1 July 2013", //! // mm/dd/yyyy hh:mm:ss //! "4/8/2014 22:05", //! "04/08/2014 22:05", //! "4/8/14 22:05", //! "04/2/2014 03:00:51", //! "8/8/1965 12:00:00 AM", //! "8/8/1965 01:00:01 PM", //! "8/8/1965 01:00 PM", //! "8/8/1965 1:00 PM", //! "8/8/1965 12:00 AM", //! "4/02/2014 03:00:51", //! "03/19/2012 10:11:59", //! "03/19/2012 10:11:59.3186369", //! // mm/dd/yyyy //! "3/31/2014", //! "03/31/2014", //! "08/21/71", //! "8/1/71", //! // yyyy/mm/dd hh:mm:ss //! "2014/4/8 22:05", //! "2014/04/08 22:05", //! "2014/04/2 03:00:51", //! "2014/4/02 03:00:51", //! "2012/03/19 10:11:59", //! "2012/03/19 10:11:59.3186369", //! // yyyy/mm/dd //! "2014/3/31", //! "2014/03/31", //! // mm.dd.yyyy //! "3.31.2014", //! "03.31.2014", //! "08.21.71", //! // yyyy.mm.dd //! "2014.03.30", //! "2014.03", //! // yymmdd hh:mm:ss mysql log //! "171113 14:14:20", //! // chinese yyyy mm dd hh mm ss //! "2014年04月08日11时25分18秒", //! // chinese yyyy mm dd //! "2014年04月08日", //! ]; //! //! for date_str in accepted { //! let result = date_str.parse::(); //! assert!(result.is_ok()) //! } //! ``` /// Datetime string parser /// /// ``` /// use chrono::prelude::*; /// use dateparser::datetime::Parse; /// use std::error::Error; /// /// fn main() -> Result<(), Box> { /// let parse_with_local = Parse::new(&Local, None); /// assert_eq!( /// parse_with_local.parse("2021-06-05 06:19 PM")?, /// Local.ymd(2021, 6, 5).and_hms(18, 19, 0).with_timezone(&Utc), /// ); /// /// let parse_with_utc = Parse::new(&Utc, None); /// assert_eq!( /// parse_with_utc.parse("2021-06-05 06:19 PM")?, /// Utc.ymd(2021, 6, 5).and_hms(18, 19, 0), /// ); /// /// Ok(()) /// } /// ``` pub mod datetime; /// Timezone offset string parser /// /// ``` /// use chrono::prelude::*; /// use dateparser::timezone::parse; /// use std::error::Error; /// /// fn main() -> Result<(), Box> { /// assert_eq!(parse("-0800")?, FixedOffset::west(8 * 3600)); /// assert_eq!(parse("+10:00")?, FixedOffset::east(10 * 3600)); /// assert_eq!(parse("PST")?, FixedOffset::west(8 * 3600)); /// assert_eq!(parse("PDT")?, FixedOffset::west(7 * 3600)); /// assert_eq!(parse("UTC")?, FixedOffset::west(0)); /// assert_eq!(parse("GMT")?, FixedOffset::west(0)); /// /// Ok(()) /// } /// ``` pub mod timezone; use crate::datetime::Parse; use anyhow::{Error, Result}; use chrono::prelude::*; /// DateTimeUtc is an alias for `chrono`'s `DateTime`. It implements `std::str::FromStr`'s /// `from_str` method, and it makes `str`'s `parse` method to understand the accepted date formats /// from this crate. /// /// ``` /// use dateparser::DateTimeUtc; /// /// // parsed is DateTimeUTC and parsed.0 is chrono's DateTime /// match "May 02, 2021 15:51:31 UTC".parse::() { /// Ok(parsed) => println!("PARSED into UTC datetime {:?}", parsed.0), /// Err(err) => println!("ERROR from parsing datetime string: {}", err) /// } /// ``` #[derive(Clone, Debug)] pub struct DateTimeUtc(pub DateTime); impl std::str::FromStr for DateTimeUtc { type Err = Error; fn from_str(s: &str) -> Result { parse(s).map(DateTimeUtc) } } /// This function tries to recognize the input datetime string with a list of accepted formats. /// When timezone is not provided, this function assumes it's a [`chrono::Local`] datetime. For /// custom timezone, use [`parse_with_timezone()`] instead.If all options are exhausted, /// [`parse()`] will return an error to let the caller know that no formats were matched. /// /// ``` /// use dateparser::parse; /// use chrono::offset::{Local, Utc}; /// use chrono_tz::US::Pacific; /// /// let parsed = parse("6:15pm").unwrap(); /// /// assert_eq!( /// parsed, /// Local::now().date().and_hms(18, 15, 0).with_timezone(&Utc), /// ); /// /// assert_eq!( /// parsed.with_timezone(&Pacific), /// Local::now().date().and_hms(18, 15, 0).with_timezone(&Utc).with_timezone(&Pacific), /// ); /// ``` pub fn parse(input: &str) -> Result> { Parse::new(&Local, None).parse(input) } /// Similar to [`parse()`], this function takes a datetime string and a custom [`chrono::TimeZone`], /// and tries to parse the datetime string. When timezone is not given in the string, this function /// will assume and parse the datetime by the custom timezone provided in this function's arguments. /// /// ``` /// use dateparser::parse_with_timezone; /// use chrono::offset::{Local, Utc}; /// use chrono_tz::US::Pacific; /// /// let parsed_in_local = parse_with_timezone("6:15pm", &Local).unwrap(); /// assert_eq!( /// parsed_in_local, /// Local::now().date().and_hms(18, 15, 0).with_timezone(&Utc), /// ); /// /// let parsed_in_utc = parse_with_timezone("6:15pm", &Utc).unwrap(); /// assert_eq!( /// parsed_in_utc, /// Utc::now().date().and_hms(18, 15, 0), /// ); /// /// let parsed_in_pacific = parse_with_timezone("6:15pm", &Pacific).unwrap(); /// assert_eq!( /// parsed_in_pacific, /// Utc::now().with_timezone(&Pacific).date().and_hms(18, 15, 0).with_timezone(&Utc), /// ); /// ``` pub fn parse_with_timezone(input: &str, tz: &Tz2) -> Result> { Parse::new(tz, None).parse(input) } /// Similar to [`parse()`] and [`parse_with_timezone()`], this function takes a datetime string, a /// custom [`chrono::TimeZone`] and a default naive time. In addition to assuming timezone when /// it's not given in datetime string, this function also use provided default naive time in parsed /// [`chrono::DateTime`]. /// /// ``` /// use dateparser::parse_with; /// use chrono::prelude::*; /// /// let utc_now = Utc::now().time().trunc_subsecs(0); /// let local_now = Local::now().time().trunc_subsecs(0); /// let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap(); /// let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap(); /// /// let parsed_with_local_now = parse_with("2021-10-09", &Local, local_now); /// let parsed_with_local_midnight = parse_with("2021-10-09", &Local, midnight_naive); /// let parsed_with_local_before_midnight = parse_with("2021-10-09", &Local, before_midnight_naive); /// let parsed_with_utc_now = parse_with("2021-10-09", &Utc, utc_now); /// let parsed_with_utc_midnight = parse_with("2021-10-09", &Utc, midnight_naive); /// /// assert_eq!( /// parsed_with_local_now.unwrap(), /// Local.ymd(2021, 10, 9).and_time(local_now).unwrap().with_timezone(&Utc), /// "parsed_with_local_now" /// ); /// assert_eq!( /// parsed_with_local_midnight.unwrap(), /// Local.ymd(2021, 10, 9).and_time(midnight_naive).unwrap().with_timezone(&Utc), /// "parsed_with_local_midnight" /// ); /// assert_eq!( /// parsed_with_local_before_midnight.unwrap(), /// Local.ymd(2021, 10, 9).and_time(before_midnight_naive).unwrap().with_timezone(&Utc), /// "parsed_with_local_before_midnight" /// ); /// assert_eq!( /// parsed_with_utc_now.unwrap(), /// Utc.ymd(2021, 10, 9).and_time(utc_now).unwrap(), /// "parsed_with_utc_now" /// ); /// assert_eq!( /// parsed_with_utc_midnight.unwrap(), /// Utc.ymd(2021, 10, 9).and_hms(0, 0, 0), /// "parsed_with_utc_midnight" /// ); /// ``` pub fn parse_with( input: &str, tz: &Tz2, default_time: NaiveTime, ) -> Result> { Parse::new(tz, Some(default_time)).parse(input) } #[cfg(test)] mod tests { use super::*; #[derive(Clone, Copy)] enum Trunc { Seconds, None, } #[test] fn parse_in_local() { let test_cases = vec![ ( "unix_timestamp", "1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26), Trunc::None, ), ( "rfc3339", "2017-11-25T22:34:50Z", Utc.ymd(2017, 11, 25).and_hms(22, 34, 50), Trunc::None, ), ( "rfc2822", "Wed, 02 Jun 2021 06:31:39 GMT", Utc.ymd(2021, 6, 2).and_hms(6, 31, 39), Trunc::None, ), ( "postgres_timestamp", "2019-11-29 08:08:05-08", Utc.ymd(2019, 11, 29).and_hms(16, 8, 5), Trunc::None, ), ( "ymd_hms", "2021-04-30 21:14:10", Local .ymd(2021, 4, 30) .and_hms(21, 14, 10) .with_timezone(&Utc), Trunc::None, ), ( "ymd_hms_z", "2017-11-25 13:31:15 PST", Utc.ymd(2017, 11, 25).and_hms(21, 31, 15), Trunc::None, ), ( "ymd", "2021-02-21", Local .ymd(2021, 2, 21) .and_time(Local::now().time()) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "ymd_z", "2021-02-21 PST", FixedOffset::west(8 * 3600) .ymd(2021, 2, 21) .and_time( Utc::now() .with_timezone(&FixedOffset::west(8 * 3600)) .time(), ) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "hms", "4:00pm", Local::now() .date() .and_time(NaiveTime::from_hms(16, 0, 0)) .unwrap() .with_timezone(&Utc), Trunc::None, ), ( "hms_z", "6:00 AM PST", Utc::now() .with_timezone(&FixedOffset::west(8 * 3600)) .date() .and_time(NaiveTime::from_hms(6, 0, 0)) .unwrap() .with_timezone(&Utc), Trunc::None, ), ( "month_ymd", "2021-Feb-21", Local .ymd(2021, 2, 21) .and_time(Local::now().time()) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "month_md_hms", "May 27 02:45:27", Local .ymd(Local::now().year(), 5, 27) .and_hms(2, 45, 27) .with_timezone(&Utc), Trunc::None, ), ( "month_mdy_hms", "May 8, 2009 5:57:51 PM", Local .ymd(2009, 5, 8) .and_hms(17, 57, 51) .with_timezone(&Utc), Trunc::None, ), ( "month_mdy_hms_z", "May 02, 2021 15:51 UTC", Utc.ymd(2021, 5, 2).and_hms(15, 51, 0), Trunc::None, ), ( "month_mdy", "May 25, 2021", Local .ymd(2021, 5, 25) .and_time(Local::now().time()) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "month_dmy_hms", "14 May 2019 19:11:40.164", Local .ymd(2019, 5, 14) .and_hms_milli(19, 11, 40, 164) .with_timezone(&Utc), Trunc::None, ), ( "month_dmy", "1 July 2013", Local .ymd(2013, 7, 1) .and_time(Local::now().time()) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "slash_mdy_hms", "03/19/2012 10:11:59", Local .ymd(2012, 3, 19) .and_hms(10, 11, 59) .with_timezone(&Utc), Trunc::None, ), ( "slash_mdy", "08/21/71", Local .ymd(1971, 8, 21) .and_time(Local::now().time()) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "slash_ymd_hms", "2012/03/19 10:11:59", Local .ymd(2012, 3, 19) .and_hms(10, 11, 59) .with_timezone(&Utc), Trunc::None, ), ( "slash_ymd", "2014/3/31", Local .ymd(2014, 3, 31) .and_time(Local::now().time()) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "dot_mdy_or_ymd", "2014.03.30", Local .ymd(2014, 3, 30) .and_time(Local::now().time()) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "mysql_log_timestamp", "171113 14:14:20", Local .ymd(2017, 11, 13) .and_hms(14, 14, 20) .with_timezone(&Utc), Trunc::None, ), ( "chinese_ymd_hms", "2014年04月08日11时25分18秒", Local .ymd(2014, 4, 8) .and_hms(11, 25, 18) .with_timezone(&Utc), Trunc::None, ), ( "chinese_ymd", "2014年04月08日", Local .ymd(2014, 4, 8) .and_time(Local::now().time()) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ]; for &(test, input, want, trunc) in test_cases.iter() { match trunc { Trunc::None => { assert_eq!( super::parse(input).unwrap(), want, "parse_in_local/{}/{}", test, input ) } Trunc::Seconds => assert_eq!( super::parse(input) .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.trunc_subsecs(0).with_second(0).unwrap(), "parse_in_local/{}/{}", test, input ), }; } } #[test] fn parse_with_timezone_in_utc() { let test_cases = vec![ ( "unix_timestamp", "1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26), Trunc::None, ), ( "rfc3339", "2017-11-25T22:34:50Z", Utc.ymd(2017, 11, 25).and_hms(22, 34, 50), Trunc::None, ), ( "rfc2822", "Wed, 02 Jun 2021 06:31:39 GMT", Utc.ymd(2021, 6, 2).and_hms(6, 31, 39), Trunc::None, ), ( "postgres_timestamp", "2019-11-29 08:08:05-08", Utc.ymd(2019, 11, 29).and_hms(16, 8, 5), Trunc::None, ), ( "ymd_hms", "2021-04-30 21:14:10", Utc.ymd(2021, 4, 30).and_hms(21, 14, 10), Trunc::None, ), ( "ymd_hms_z", "2017-11-25 13:31:15 PST", Utc.ymd(2017, 11, 25).and_hms(21, 31, 15), Trunc::None, ), ( "ymd", "2021-02-21", Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(), Trunc::Seconds, ), ( "ymd_z", "2021-02-21 PST", FixedOffset::west(8 * 3600) .ymd(2021, 2, 21) .and_time( Utc::now() .with_timezone(&FixedOffset::west(8 * 3600)) .time(), ) .unwrap() .with_timezone(&Utc), Trunc::Seconds, ), ( "hms", "4:00pm", Utc::now() .date() .and_time(NaiveTime::from_hms(16, 0, 0)) .unwrap(), Trunc::None, ), ( "hms_z", "6:00 AM PST", FixedOffset::west(8 * 3600) .from_local_date( &Utc::now() .with_timezone(&FixedOffset::west(8 * 3600)) .date() .naive_local(), ) .and_time(NaiveTime::from_hms(6, 0, 0)) .unwrap() .with_timezone(&Utc), Trunc::None, ), ( "month_ymd", "2021-Feb-21", Utc.ymd(2021, 2, 21).and_time(Utc::now().time()).unwrap(), Trunc::Seconds, ), ( "month_md_hms", "May 27 02:45:27", Utc.ymd(Utc::now().year(), 5, 27).and_hms(2, 45, 27), Trunc::None, ), ( "month_mdy_hms", "May 8, 2009 5:57:51 PM", Utc.ymd(2009, 5, 8).and_hms(17, 57, 51), Trunc::None, ), ( "month_mdy_hms_z", "May 02, 2021 15:51 UTC", Utc.ymd(2021, 5, 2).and_hms(15, 51, 0), Trunc::None, ), ( "month_mdy", "May 25, 2021", Utc.ymd(2021, 5, 25).and_time(Utc::now().time()).unwrap(), Trunc::Seconds, ), ( "month_dmy_hms", "14 May 2019 19:11:40.164", Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164), Trunc::None, ), ( "month_dmy", "1 July 2013", Utc.ymd(2013, 7, 1).and_time(Utc::now().time()).unwrap(), Trunc::Seconds, ), ( "slash_mdy_hms", "03/19/2012 10:11:59", Utc.ymd(2012, 3, 19).and_hms(10, 11, 59), Trunc::None, ), ( "slash_mdy", "08/21/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time()).unwrap(), Trunc::Seconds, ), ( "slash_ymd_hms", "2012/03/19 10:11:59", Utc.ymd(2012, 3, 19).and_hms(10, 11, 59), Trunc::None, ), ( "slash_ymd", "2014/3/31", Utc.ymd(2014, 3, 31).and_time(Utc::now().time()).unwrap(), Trunc::Seconds, ), ( "dot_mdy_or_ymd", "2014.03.30", Utc.ymd(2014, 3, 30).and_time(Utc::now().time()).unwrap(), Trunc::Seconds, ), ( "mysql_log_timestamp", "171113 14:14:20", Utc.ymd(2017, 11, 13).and_hms(14, 14, 20), Trunc::None, ), ( "chinese_ymd_hms", "2014年04月08日11时25分18秒", Utc.ymd(2014, 4, 8).and_hms(11, 25, 18), Trunc::None, ), ( "chinese_ymd", "2014年04月08日", Utc.ymd(2014, 4, 8).and_time(Utc::now().time()).unwrap(), Trunc::Seconds, ), ]; for &(test, input, want, trunc) in test_cases.iter() { match trunc { Trunc::None => { assert_eq!( super::parse_with_timezone(input, &Utc).unwrap(), want, "parse_with_timezone_in_utc/{}/{}", test, input ) } Trunc::Seconds => assert_eq!( super::parse_with_timezone(input, &Utc) .unwrap() .trunc_subsecs(0) .with_second(0) .unwrap(), want.trunc_subsecs(0).with_second(0).unwrap(), "parse_with_timezone_in_utc/{}/{}", test, input ), }; } } // test parse_with() with various timezones and times #[test] fn parse_with_edt() { // Eastern Daylight Time (EDT) is from (as of 2023) 2nd Sun in Mar to 1st Sun in Nov // It is UTC -4 let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap(); let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap(); let us_edt = &FixedOffset::west_opt(4 * 3600).unwrap(); let edt_test_cases = vec![ ("ymd", "2023-04-21"), ("ymd_z", "2023-04-21 EDT"), ("month_ymd", "2023-Apr-21"), ("month_mdy", "April 21, 2023"), ("month_dmy", "21 April 2023"), ("slash_mdy", "04/21/23"), ("slash_ymd", "2023/4/21"), ("dot_mdy_or_ymd", "2023.04.21"), ("chinese_ymd", "2023年04月21日"), ]; // test us_edt at midnight let us_edt_midnight_as_utc = Utc.ymd(2023, 4, 21).and_hms(4, 0, 0); for &(test, input) in edt_test_cases.iter() { assert_eq!( super::parse_with(input, us_edt, midnight_naive).unwrap(), us_edt_midnight_as_utc, "parse_with/{test}/{input}", ) } // test us_edt at 23:59:59 - UTC will be one day ahead let us_edt_before_midnight_as_utc = Utc.ymd(2023, 4, 22).and_hms(3, 59, 59); for &(test, input) in edt_test_cases.iter() { assert_eq!( super::parse_with(input, us_edt, before_midnight_naive).unwrap(), us_edt_before_midnight_as_utc, "parse_with/{test}/{input}", ) } } #[test] fn parse_with_est() { // Eastern Standard Time (EST) is from (as of 2023) 1st Sun in Nov to 2nd Sun in Mar // It is UTC -5 let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap(); let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap(); let us_est = &FixedOffset::west(5 * 3600); let est_test_cases = vec![ ("ymd", "2023-12-21"), ("ymd_z", "2023-12-21 EST"), ("month_ymd", "2023-Dec-21"), ("month_mdy", "December 21, 2023"), ("month_dmy", "21 December 2023"), ("slash_mdy", "12/21/23"), ("slash_ymd", "2023/12/21"), ("dot_mdy_or_ymd", "2023.12.21"), ("chinese_ymd", "2023年12月21日"), ]; // test us_est at midnight let us_est_midnight_as_utc = Utc.ymd(2023, 12, 21).and_hms(5, 0, 0); for &(test, input) in est_test_cases.iter() { assert_eq!( super::parse_with(input, us_est, midnight_naive).unwrap(), us_est_midnight_as_utc, "parse_with/{test}/{input}", ) } // test us_est at 23:59:59 - UTC will be one day ahead let us_est_before_midnight_as_utc = Utc.ymd(2023, 12, 22).and_hms(4, 59, 59); for &(test, input) in est_test_cases.iter() { assert_eq!( super::parse_with(input, us_est, before_midnight_naive).unwrap(), us_est_before_midnight_as_utc, "parse_with/{test}/{input}", ) } } #[test] fn parse_with_utc() { let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap(); let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap(); let utc_test_cases = vec![ ("ymd", "2023-12-21"), ("ymd_z", "2023-12-21 UTC"), ("month_ymd", "2023-Dec-21"), ("month_mdy", "December 21, 2023"), ("month_dmy", "21 December 2023"), ("slash_mdy", "12/21/23"), ("slash_ymd", "2023/12/21"), ("dot_mdy_or_ymd", "2023.12.21"), ("chinese_ymd", "2023年12月21日"), ]; // test utc at midnight let utc_midnight = Utc.ymd(2023, 12, 21).and_hms(0, 0, 0); for &(test, input) in utc_test_cases.iter() { assert_eq!( super::parse_with(input, &Utc, midnight_naive).unwrap(), utc_midnight, "parse_with/{test}/{input}", ) } // test utc at 23:59:59 let utc_before_midnight = Utc.ymd(2023, 12, 21).and_hms(23, 59, 59); for &(test, input) in utc_test_cases.iter() { assert_eq!( super::parse_with(input, &Utc, before_midnight_naive).unwrap(), utc_before_midnight, "parse_with/{test}/{input}", ) } } #[test] fn parse_with_local() { let midnight_naive = NaiveTime::from_hms_opt(0, 0, 0).unwrap(); let before_midnight_naive = NaiveTime::from_hms_opt(23, 59, 59).unwrap(); let local_test_cases = vec![ ("ymd", "2023-12-21"), ("month_ymd", "2023-Dec-21"), ("month_mdy", "December 21, 2023"), ("month_dmy", "21 December 2023"), ("slash_mdy", "12/21/23"), ("slash_ymd", "2023/12/21"), ("dot_mdy_or_ymd", "2023.12.21"), ("chinese_ymd", "2023年12月21日"), ]; // test local at midnight let local_midnight_as_utc = Local.ymd(2023, 12, 21).and_hms(0, 0, 0).with_timezone(&Utc); for &(test, input) in local_test_cases.iter() { assert_eq!( super::parse_with(input, &Local, midnight_naive).unwrap(), local_midnight_as_utc, "parse_with/{test}/{input}", ) } // test local at 23:59:59 let local_before_midnight_as_utc = Local .ymd(2023, 12, 21) .and_hms(23, 59, 59) .with_timezone(&Utc); for &(test, input) in local_test_cases.iter() { assert_eq!( super::parse_with(input, &Local, before_midnight_naive).unwrap(), local_before_midnight_as_utc, "parse_with/{test}/{input}", ) } } } dateparser-0.2.0/src/timezone.rs000064400000000000000000000111301046102023000147400ustar 00000000000000use anyhow::{anyhow, Result}; use chrono::offset::FixedOffset; /// Tries to parse `[-+]\d\d` continued by `\d\d`. Return FixedOffset if possible. /// It can parse RFC 2822 legacy timezones. If offset cannot be determined, -0000 will be returned. /// /// The additional `colon` may be used to parse a mandatory or optional `:` between hours and minutes, /// and should return a valid FixedOffset or `Err` when parsing fails. pub fn parse(s: &str) -> Result { let offset = if s.contains(':') { parse_offset_internal(s, colon_or_space, false)? } else { parse_offset_2822(s)? }; Ok(FixedOffset::east(offset)) } fn parse_offset_2822(s: &str) -> Result { // tries to parse legacy time zone names let upto = s .as_bytes() .iter() .position(|&c| !c.is_ascii_alphabetic()) .unwrap_or(s.len()); if upto > 0 { let name = &s[..upto]; let offset_hours = |o| Ok(o * 3600); if equals(name, "gmt") || equals(name, "ut") || equals(name, "utc") { offset_hours(0) } else if equals(name, "edt") { offset_hours(-4) } else if equals(name, "est") || equals(name, "cdt") { offset_hours(-5) } else if equals(name, "cst") || equals(name, "mdt") { offset_hours(-6) } else if equals(name, "mst") || equals(name, "pdt") { offset_hours(-7) } else if equals(name, "pst") { offset_hours(-8) } else { Ok(0) // recommended by RFC 2822: consume but treat it as -0000 } } else { let offset = parse_offset_internal(s, |s| Ok(s), false)?; Ok(offset) } } fn parse_offset_internal( mut s: &str, mut consume_colon: F, allow_missing_minutes: bool, ) -> Result where F: FnMut(&str) -> Result<&str>, { let err_out_of_range = "input is out of range"; let err_invalid = "input contains invalid characters"; let err_too_short = "premature end of input"; let digits = |s: &str| -> Result<(u8, u8)> { let b = s.as_bytes(); if b.len() < 2 { Err(anyhow!(err_too_short)) } else { Ok((b[0], b[1])) } }; let negative = match s.as_bytes().first() { Some(&b'+') => false, Some(&b'-') => true, Some(_) => return Err(anyhow!(err_invalid)), None => return Err(anyhow!(err_too_short)), }; s = &s[1..]; // hours (00--99) let hours = match digits(s)? { (h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')), _ => return Err(anyhow!(err_invalid)), }; s = &s[2..]; // colons (and possibly other separators) s = consume_colon(s)?; // minutes (00--59) // if the next two items are digits then we have to add minutes let minutes = if let Ok(ds) = digits(s) { match ds { (m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')), (b'6'..=b'9', b'0'..=b'9') => return Err(anyhow!(err_out_of_range)), _ => return Err(anyhow!(err_invalid)), } } else if allow_missing_minutes { 0 } else { return Err(anyhow!(err_too_short)); }; let seconds = hours * 3600 + minutes * 60; Ok(if negative { -seconds } else { seconds }) } /// Returns true when two slices are equal case-insensitively (in ASCII). /// Assumes that the `pattern` is already converted to lower case. fn equals(s: &str, pattern: &str) -> bool { let mut xs = s.as_bytes().iter().map(|&c| match c { b'A'..=b'Z' => c + 32, _ => c, }); let mut ys = pattern.as_bytes().iter().cloned(); loop { match (xs.next(), ys.next()) { (None, None) => return true, (None, _) | (_, None) => return false, (Some(x), Some(y)) if x != y => return false, _ => (), } } } /// Consumes any number (including zero) of colon or spaces. fn colon_or_space(s: &str) -> Result<&str> { Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } #[cfg(test)] mod tests { use super::*; #[test] fn parse() { let test_cases = vec![ ("-0800", FixedOffset::west(8 * 3600)), ("+10:00", FixedOffset::east(10 * 3600)), ("PST", FixedOffset::west(8 * 3600)), ("PDT", FixedOffset::west(7 * 3600)), ("UTC", FixedOffset::west(0)), ("GMT", FixedOffset::west(0)), ]; for &(input, want) in test_cases.iter() { assert_eq!(super::parse(input).unwrap(), want, "parse/{}", input) } } }