dateparser-0.2.1/.cargo_vcs_info.json0000644000000001500000000000100131620ustar { "git": { "sha1": "4b652ea390f6e33b402d7ac834e23f05f845eed1" }, "path_in_vcs": "dateparser" }dateparser-0.2.1/Cargo.lock0000644000000530730000000000100111510ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[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 = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bumpalo" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[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.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" dependencies = [ "libc", ] [[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.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-targets", ] [[package]] name = "chrono-tz" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e23185c0e21df6ed832a12e2bda87c7d1def6842881fb634a8511ced741b0d76" dependencies = [ "chrono", "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" dependencies = [ "parse-zoneinfo", "phf", "phf_codegen", ] [[package]] name = "ciborium" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[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.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "dateparser" version = "0.2.1" dependencies = [ "anyhow", "chrono", "chrono-tz", "criterion", "lazy_static", "regex", ] [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "errno" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" dependencies = [ "libc", "windows-sys", ] [[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.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "iana-time-zone" version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", "windows-sys", ] [[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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" 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.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_shared" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] [[package]] name = "plotters" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "proc-macro2" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 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.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "regex" version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustix" version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[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.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "syn" version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[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 = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "walkdir" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "web-sys" version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" 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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 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-core" version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" dateparser-0.2.1/Cargo.toml0000644000000023010000000000100111600ustar # 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.1" 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" [[bench]] name = "parse" harness = false [dependencies.anyhow] version = "1.0.75" [dependencies.chrono] version = "0.4.31" [dependencies.lazy_static] version = "1.4.0" [dependencies.regex] version = "1.10.2" [dev-dependencies.chrono-tz] version = "0.8.4" [dev-dependencies.criterion] version = "0.5.1" features = ["html_reports"] dateparser-0.2.1/Cargo.toml.orig000064400000000000000000000011621046102023000146450ustar 00000000000000[package] name = "dateparser" version = "0.2.1" 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.75" chrono = "0.4.31" lazy_static = "1.4.0" regex = "1.10.2" [dev-dependencies] chrono-tz = "0.8.4" criterion = { version = "0.5.1", features = ["html_reports"] } [[bench]] name = "parse" harness = false dateparser-0.2.1/README.md000064400000000000000000000130671046102023000132440ustar 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.1" ``` 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.1" ``` ```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.1/benches/parse.rs000064400000000000000000000043341046102023000150510ustar 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.1/examples/convert_to_pacific.rs000064400000000000000000000004131046102023000200000ustar 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.1/examples/parse.rs000064400000000000000000000002501046102023000152510ustar 00000000000000use dateparser::parse; use std::error::Error; fn main() -> Result<(), Box> { let parsed = parse("6:15pm")?; println!("{:#?}", parsed); Ok(()) } dateparser-0.2.1/examples/parse_with.rs000064400000000000000000000010061046102023000163040ustar 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.1/examples/parse_with_timezone.rs000064400000000000000000000007631046102023000202270ustar 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.1/examples/str_parse_method.rs000064400000000000000000000003161046102023000175040ustar 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.1/src/datetime.rs000064400000000000000000001513621046102023000147170ustar 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 = [ ("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 = [ ( "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 = [ ( "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 = [ ( "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 = [( "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 = [ ( "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 = [ ( "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 = [ ( "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 = [( "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 = [ ( "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 = [ ( "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 = [ ( "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 = [ ( "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 = [ ( "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 = [ ("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 = [ ( "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 = [ ("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 = [ ( "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 = [ // 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 = [ // 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 = [( "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 = [( "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.1/src/lib.rs000064400000000000000000000763311046102023000136730ustar 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.1/src/timezone.rs000064400000000000000000000111241046102023000147440ustar 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 = [ ("-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) } } }