timeago-0.6.0/.cargo_vcs_info.json0000644000000001361046102023000125060ustar { "git": { "sha1": "c797cfb24f1335366013fd278e7260ef1f79614f" }, "path_in_vcs": "" }timeago-0.6.0/.gitignore000064400000000000000000000002021046102023000132360ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target # These are backup files generated by rustfmt **/*.rs.bk timeago-0.6.0/.travis.yml000064400000000000000000000002051046102023000133620ustar 00000000000000language: rust rust: - 1.24.1 script: - cargo build --all-features --verbose --all - cargo test --all-features --verbose --all timeago-0.6.0/CHANGELOG.md000064400000000000000000000022151046102023000130650ustar 00000000000000## v0.6.0 (2026-02-17) * Add Basque and Korean * MSRV bump and some unverified revamps ## v0.5.0 (2025-07-12) * add Thai ## v0.4.2 (2023-09-18) * Only repo metadata changes ## v0.4.1 (2023-01-26) * Ukrainian ## v0.4.0 (2022-12-10) * Update isolang dependency. ## v0.3.1 (2022-02-24) * Added Italian ## v0.3.0 (2021-03-03) * Clonable Formatter and boxed language improvements ## v0.2.2 (2021-03-03) * Add Portuguese and Danish ## v0.2.1 (2020-04-17) * Fix errors in french translation ## v0.2.0 (2019-04-08) * Allow calling place_ago_before() from inside a Box * add Spanish language * Update isoland dependency, bump MSRV ## v0.1.5 (2018-04-09) * Make all cargo features default * Added French. ## v0.1.4 (2018-04-05) * add japanese translation ## v0.1.3 (2018-03-28) * A last-minute minor fix ## v0.1.2 (2018-03-28) * add chinese * Added swedish * modify get_word for chinese mod * Chinese: Fix minor warning * Swedish: Remove misleading comment * Added Romanian. * Added Turkish language. * Add Chrono mode (for future) ## 0.1.1 (2018-03-26) * Minor doc fix ## v0.1.0 (2018-03-26) Changelog start. timeago-0.6.0/Cargo.lock0000644000000206241046102023000104650ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[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 = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "cc" version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "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 = "isolang" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe50d48c77760c55188549098b9a7f6e37ae980c586a24693d6b01c3b2010c3c" dependencies = [ "phf", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_shared", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rustversion" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "timeago" version = "0.6.0" dependencies = [ "chrono", "isolang", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] timeago-0.6.0/Cargo.toml0000644000000025151046102023000105070ustar # 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" rust-version = "1.63" name = "timeago" version = "0.6.0" authors = ["Vitaly _Vi Shukela "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Given a Duration, lossily format it like in 'N days ago'. Parsing it back to Duration is not supported yet." documentation = "https://docs.rs/timeago/" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/vi/timeago" [package.metadata.docs.rs] features = [ "translations", "isolang", "chrono", ] [features] default = [ "translations", "isolang", "chrono", ] translations = [] [lib] name = "timeago" path = "src/lib.rs" [[bin]] name = "timeago" path = "src/main.rs" [dependencies.chrono] version = "0.4" optional = true [dependencies.isolang] version = "2" optional = true timeago-0.6.0/Cargo.toml.orig000064400000000000000000000012051046102023000141410ustar 00000000000000[package] name = "timeago" version = "0.6.0" edition = "2021" rust-version = "1.63" authors = ["Vitaly _Vi Shukela "] license = "MIT OR Apache-2.0" description = "Given a Duration, lossily format it like in 'N days ago'. Parsing it back to Duration is not supported yet." repository = "https://github.com/vi/timeago" documentation = "https://docs.rs/timeago/" readme = "README.md" [dependencies] isolang={version="2", optional=true} chrono={version="0.4", optional=true} [features] default = ["translations", "isolang", "chrono"] translations=[] [package.metadata.docs.rs] features = [ "translations", "isolang", "chrono" ] timeago-0.6.0/LICENSE-APACHE000064400000000000000000000227731046102023000132130ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS timeago-0.6.0/LICENSE-MIT000064400000000000000000000020571046102023000127140ustar 00000000000000MIT License Copyright (c) 2017 Vitaly Shukela Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. timeago-0.6.0/README.md000064400000000000000000000047431046102023000125430ustar 00000000000000# timeago In Rust, format Duration into string like "1 hour ago" or "01hou". Currently it does not [take the calendar into account](https://github.com/vi/timeago/issues/12) and assumes each month is about 30.4 days long. Parsing such string back to a `Duration` is out of scope for this crate. Maybe see the [`chrono-english`](https://docs.rs/chrono-english) crate instead. With `isolang` feature off, version `0.5.0` of the crate supported Rust from version 1.24. ## API [Documentation link](https://docs.rs/timeago/) Simplified API excerpt (pseudocode): ```rust pub struct Formatter{...} impl Formatter { pub fn new() -> Formatter; pub fn with_language(l: Language) -> Self; pub fn num_items(&mut self, x: usize) -> &mut Self; pub fn max_unit(&mut self, x: TimeUnit) -> &mut Self; pub fn min_unit(&mut self, x: TimeUnit) -> &mut Self; pub fn too_low(&mut self, x: &'static str) -> &mut Self; pub fn too_high(&mut self, x: &'static str) -> &mut Self; pub fn max_duration(&mut self, x: Duration) -> &mut Self; pub fn ago(&mut self, x: &'static str) -> &mut Self; pub fn convert(&self, d: Duration) -> String; pub fn convert_chrono(&self, from: chrono::DateTime, to: chrono::DateTime) -> String; } pub fn from_isolang(x : isolang::Language) -> Option>; pub fn format_5chars(d: Duration) -> String; ``` A `Language` can be constructed from [isolang::Language](https://docs.rs/isolang/1/isolang/enum.Language.html). ## Translations * English * Russian * French * Portuguese (contributed) * German (unchecked) * Basque (contributed) * Belarusian (unchecked) * Polish (unchecked) * Spanish (contributed) * Chinese (contributed) * Romanian (contributed) * Swedish (contributed) * Turkish (contributed) * Japanese (contributed) * Danish (contributed) * Italian (contributed) * Ukrainian (contributed) * Thai (contributed) * Korean (contributed) If you checked some language and certify that it's allright, submit a pull request that removes "(unchecked)" or "(contributed)" in the list above. ## Tool There is a helper command line tool that allows easier experimenting when adding a new translation: ``` $ cargo run --features isolang en Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/timeago en` 60 1 minute ago 7200 2 hours ago ``` # See also * [chrono-humanize](https://docs.rs/crate/chrono-humanize) * compound_duration - split `Duration` into weeks/days/minues/etc. parts timeago-0.6.0/src/languages/basque.rs000064400000000000000000000023761046102023000156470ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Basque; impl Language for Basque { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "oraintxe bertan" } fn too_high(&self) -> &'static str { "zaharregi" } fn ago(&self) -> &'static str { "orain dela" } fn place_ago_before(&self) -> bool { true } fn place_unit_before(&self, x: u64) -> bool { x == 1 } fn get_word(&self, tu: TimeUnit, _: u64) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "nanosegundo", Microseconds => "mikrosegundo", Milliseconds => "milisegundo", Seconds => "segundo", Minutes => "minutu", Hours => "ordu", Days => "egun", Weeks => "aste", Months => "hile", Years => "urte", } } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Basque); assert_eq!(f.convert(Duration::from_secs(60)), "orain dela minutu 1"); assert_eq!(f.convert(Duration::from_secs(120)), "orain dela 2 minutu"); } timeago-0.6.0/src/languages/belarusian.rs000064400000000000000000000115351046102023000165110ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Belarusian; impl Belarusian { fn accusative(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "нанасэкунду", Microseconds => "мікрасэкунду", Milliseconds => "мілісэкунду", Seconds => "сэкунду", Minutes => "хвіліну", Hours => "гадзіну", Days => "дзень", Weeks => "тыдзень", Months => "месяц", Years => "год", } } fn genitive(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "нанасэкунды", Microseconds => "мікрасэкунды", Milliseconds => "мілісэкунды", Seconds => "сэкунды", Minutes => "хвіліны", Hours => "гадзіны", Days => "дні", Weeks => "тыдні", Months => "месяца", Years => "гады", } } fn genitive_plural(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "нанасэкундаў", Microseconds => "мікрасэкундаў", Milliseconds => "мілісэкундаў", Seconds => "сэкундаў", Minutes => "хвілін", Hours => "галзін", Days => "дней", Weeks => "тыдняў", Months => "месяцаў", Years => "гадоў", } } } impl Language for Belarusian { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "зараз" } fn too_high(&self) -> &'static str { "даўно" } fn ago(&self) -> &'static str { "таму" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { let last_two = x % 100; let last = x % 10; if (11..=20).contains(&last_two) { self.genitive_plural(tu) } else { match last { 1 => self.accusative(tu), 2..=4 => self.genitive(tu), 0 | 5..=9 => self.genitive_plural(tu), _ => unreachable!(), } } } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Belarusian); assert_eq!(f.convert(Duration::from_secs(60)), "1 хвіліну таму"); assert_eq!(f.convert(Duration::from_secs(2)), "2 сэкунды таму"); assert_eq!(f.convert(Duration::from_secs(5)), "5 сэкундаў таму"); assert_eq!(f.convert(Duration::from_secs(12)), "12 сэкундаў таму"); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 12 * 366)), "6 месяцаў таму" ); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 24 * 366)), "1 год таму" ); assert_eq!( f.convert(Duration::from_secs(2 * 3600 * 24 * 366)), "2 гады таму" ); assert_eq!( f.convert(Duration::from_secs(4 * 3600 * 24 * 366)), "4 гады таму" ); assert_eq!( f.convert(Duration::from_secs(5 * 3600 * 24 * 366)), "5 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(10 * 3600 * 24 * 366)), "10 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(11 * 3600 * 24 * 366)), "11 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(14 * 3600 * 24 * 366)), "14 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(15 * 3600 * 24 * 366)), "15 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(19 * 3600 * 24 * 366)), "19 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(20 * 3600 * 24 * 366)), "20 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(21 * 3600 * 24 * 366)), "21 год таму" ); assert_eq!( f.convert(Duration::from_secs(32 * 3600 * 24 * 366)), "32 гады таму" ); assert_eq!( f.convert(Duration::from_secs(99 * 3600 * 24 * 366)), "99 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(100 * 3600 * 24 * 366)), "100 гадоў таму" ); assert_eq!( f.convert(Duration::from_secs(101 * 3600 * 24 * 366)), "101 год таму" ); assert_eq!( f.convert(Duration::from_secs(111 * 3600 * 24 * 366)), "111 гадоў таму" ); } timeago-0.6.0/src/languages/chinese.rs000064400000000000000000000014731046102023000160020ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Chinese; impl Language for Chinese { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "刚刚" } fn too_high(&self) -> &'static str { "大于" } fn ago(&self) -> &'static str { "之前" } fn get_word(&self, tu: TimeUnit, _x: u64) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "纳秒", Microseconds => "微秒", Milliseconds => "毫秒", Seconds => "秒", Minutes => "分", Hours => "小时", Days => "天", Weeks => "周", Months => "月", Years => "年", } } } timeago-0.6.0/src/languages/danish.rs000064400000000000000000000025231046102023000156270ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Danish; impl Language for Danish { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "nu" } fn too_high(&self) -> &'static str { "gammel" } fn ago(&self) -> &'static str { "siden" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "nanosekund", Microseconds => "mikrosekund", Milliseconds => "millisekund", Seconds => "sekund", Minutes => "minut", Hours => "time", Days => "dag", Weeks => "uge", Months => "måned", Years => "år", } } else { match tu { Nanoseconds => "nanosekunder", Microseconds => "mikrosekunder", Milliseconds => "millisekunder", Seconds => "sekunder", Minutes => "minutter", Hours => "timer", Days => "dage", Weeks => "uger", Months => "måneder", Years => "år", } } } } timeago-0.6.0/src/languages/english.rs000064400000000000000000000025601046102023000160130ustar 00000000000000use super::super::{Language, TimeUnit}; /// Default language for timeago #[derive(Default)] pub struct English; impl Language for English { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "now" } fn too_high(&self) -> &'static str { "old" } fn ago(&self) -> &'static str { "ago" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "nanosecond", Microseconds => "microsecond", Milliseconds => "millisecond", Seconds => "second", Minutes => "minute", Hours => "hour", Days => "day", Weeks => "week", Months => "month", Years => "year", } } else { match tu { Nanoseconds => "nanoseconds", Microseconds => "microseconds", Milliseconds => "milliseconds", Seconds => "seconds", Minutes => "minutes", Hours => "hours", Days => "days", Weeks => "weeks", Months => "months", Years => "years", } } } } timeago-0.6.0/src/languages/french.rs000064400000000000000000000031561046102023000156310ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct French; impl Language for French { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "maintenant" } fn too_high(&self) -> &'static str { "ancien" } fn ago(&self) -> &'static str { "il y a" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "nanoseconde", Microseconds => "microseconde", Milliseconds => "milliseconde", Seconds => "seconde", Minutes => "minute", Hours => "heure", Days => "jour", Weeks => "semaine", Months => "mois", Years => "année", } } else { match tu { Nanoseconds => "nanosecondes", Microseconds => "microsecondes", Milliseconds => "milisecondes", Seconds => "secondes", Minutes => "minutes", Hours => "heures", Days => "jours", Weeks => "semaines", Months => "mois", Years => "ans", } } } fn place_ago_before(&self) -> bool { true } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(French); assert_eq!(f.convert(Duration::from_secs(60)), "il y a 1 minute"); } timeago-0.6.0/src/languages/german.rs000064400000000000000000000031461046102023000156340ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct German; impl Language for German { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "jetzt" } fn too_high(&self) -> &'static str { "zu alt" } fn ago(&self) -> &'static str { "vor" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "Nanosekunde", Microseconds => "Mikrosekunde", Milliseconds => "Millisekunde", Seconds => "Sekunde", Minutes => "Minute", Hours => "Stunde", Days => "Tag", Weeks => "Woche", Months => "Monat", Years => "Jahr", } } else { match tu { Nanoseconds => "Nanosekunden", Microseconds => "Mikrosekunden", Milliseconds => "Millisekunden", Seconds => "Sekunden", Minutes => "Minuten", Hours => "Stunden", Days => "Tagen", Weeks => "Wochen", Months => "Monaten", Years => "Jahren", } } } fn place_ago_before(&self) -> bool { true } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(German); assert_eq!(f.convert(Duration::from_secs(60)), "vor 1 Minute"); } timeago-0.6.0/src/languages/italian.rs000064400000000000000000000030551046102023000160030ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Italian; impl Language for Italian { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "adesso" } fn too_high(&self) -> &'static str { "troppo vecchio" } fn ago(&self) -> &'static str { "fa" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "nanosecondo", Microseconds => "microsecondo", Milliseconds => "millisecondo", Seconds => "secondo", Minutes => "minuto", Hours => "ora", Days => "giorno", Weeks => "settimana", Months => "mese", Years => "anno", } } else { match tu { Nanoseconds => "nanosecondi", Microseconds => "microsecondi", Milliseconds => "millisecondi", Seconds => "secondi", Minutes => "minuti", Hours => "ore", Days => "giorni", Weeks => "settimane", Months => "mesi", Years => "anni", } } } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Italian); assert_eq!(f.convert(Duration::from_secs(60)), "1 minuto fa"); } timeago-0.6.0/src/languages/japanese.rs000064400000000000000000000015061046102023000161470ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Japanese; impl Language for Japanese { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "今" } fn too_high(&self) -> &'static str { "後" } fn ago(&self) -> &'static str { "前" } fn get_word(&self, tu: TimeUnit, _x: u64) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "ナノ秒", Microseconds => "マイクロ秒", Milliseconds => "ミリ秒", Seconds => "秒", Minutes => "分", Hours => "時間", Days => "日", Weeks => "週間", Months => "月", Years => "年", } } } timeago-0.6.0/src/languages/korean.rs000064400000000000000000000071061046102023000156420ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Korean; impl Language for Korean { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "방금" } fn too_high(&self) -> &'static str { // https://stdict.korean.go.kr/search/searchView.do?word_no=241146&searchKeywordTo=3 "오래전" } fn ago(&self) -> &'static str { "전" } fn get_word(&self, tu: TimeUnit, _x: u64) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "나노초", Microseconds => "마이크로초", Milliseconds => "밀리초", Seconds => "초", Minutes => "분", Hours => "시간", Days => "일", Weeks => "주", Months => "개월", Years => "년", } } fn between_value_and_word(&self) -> &str { // https://www.korean.go.kr/kornorms/regltn/regltnView.do?regltn_code=0001®ltn_no=263 "" } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; fn test_with_formatter(mut f: Formatter) { f.min_unit(TimeUnit::Seconds); assert_eq!(f.convert(Duration::from_secs(0)), "방금"); assert_eq!(f.convert(Duration::from_nanos(42)), "방금"); assert_eq!(f.convert(Duration::from_micros(42)), "방금"); assert_eq!(f.convert(Duration::from_millis(42)), "방금"); assert_eq!(f.convert(Duration::from_secs(42)), "42초 전"); assert_eq!(f.convert(Duration::from_mins(42)), "42분 전"); assert_eq!(f.convert(Duration::from_hours(2)), "2시간 전"); assert_eq!(f.convert(Duration::from_hours(23)), "23시간 전"); assert_eq!(f.convert(Duration::from_hours(24)), "1일 전"); assert_eq!(f.convert(Duration::from_hours(24 + 1)), "1일 전"); assert_eq!(f.convert(Duration::from_hours(2 * 24 - 1)), "1일 전"); assert_eq!(f.convert(Duration::from_hours(2 * 24)), "2일 전"); assert_eq!(f.convert(Duration::from_hours(2 * 24 + 1)), "2일 전"); assert_eq!(f.convert(Duration::from_hours(3 * 24 - 1)), "2일 전"); assert_eq!(f.convert(Duration::from_hours(3 * 24)), "3일 전"); assert_eq!(f.convert(Duration::from_hours(3 * 24 + 1)), "3일 전"); assert_eq!(f.convert(Duration::from_hours(42 * 24)), "1개월 전"); assert_eq!(f.convert(Duration::from_hours(364 * 24)), "11개월 전"); assert_eq!(f.convert(Duration::from_hours(365 * 24)), "11개월 전"); assert_eq!(f.convert(Duration::from_hours(366 * 24)), "1년 전"); assert_eq!(f.convert(Duration::from_hours(42 * 366 * 24)), "42년 전"); f.min_unit(TimeUnit::Nanoseconds); assert_eq!(f.convert(Duration::from_nanos(42)), "42나노초 전"); assert_eq!(f.convert(Duration::from_micros(42)), "42마이크로초 전"); assert_eq!(f.convert(Duration::from_millis(42)), "42밀리초 전"); f.max_unit(TimeUnit::Months); assert_eq!(f.convert(Duration::from_hours(365 * 24)), "11개월 전"); assert_eq!(f.convert(Duration::from_hours(366 * 24)), "12개월 전"); f.max_duration(Duration::from_hours(365 * 24)); assert_eq!(f.convert(Duration::from_hours(365 * 24)), "11개월 전"); assert_eq!(f.convert(Duration::from_hours(366 * 24)), "오래전"); } test_with_formatter(Formatter::with_language(Korean)); test_with_formatter(Formatter::with_language(Korean.clone_boxed())); } timeago-0.6.0/src/languages/mod.rs000064400000000000000000000047301046102023000151420ustar 00000000000000#![allow(missing_docs)] #[cfg(feature = "isolang")] extern crate isolang; pub mod basque; pub mod belarusian; pub mod chinese; pub mod danish; pub mod english; pub mod french; pub mod german; pub mod italian; pub mod japanese; pub mod korean; pub mod polish; pub mod portuguese; pub mod romanian; pub mod russian; pub mod spanish; pub mod swedish; pub mod thai; pub mod turkish; pub mod ukrainian; /// Helper function to make a language dynamically dispatched pub fn boxup(x: L) -> super::BoxedLanguage { Box::new(x) as super::BoxedLanguage } /// A public use for a public dependency. #[cfg(feature = "isolang")] pub use self::isolang::Language as IsolangLanguage; /// Requires `isolang` Cargo feature /// /// Try converting a isolang's language into our dynamically dispatched language /// ``` /// extern crate isolang; /// extern crate timeago; /// let il = isolang::Language::from_639_1("ru").unwrap(); /// let l = timeago::from_isolang(il).unwrap(); /// let f = timeago::Formatter::with_language(l); /// let d = std::time::Duration::from_secs(3600); /// assert_eq!(f.convert(d), "1 час назад"); /// ``` #[cfg(feature = "isolang")] pub fn from_isolang(x: isolang::Language) -> Option { Some(match x { x if x.to_name() == "English" => boxup(english::English), x if x.to_name() == "Chinese" => boxup(chinese::Chinese), x if x.to_name() == "Japanese" => boxup(japanese::Japanese), x if x.to_name() == "Russian" => boxup(russian::Russian), x if x.to_name() == "German" => boxup(german::German), x if x.to_name() == "Belarusian" => boxup(belarusian::Belarusian), x if x.to_name() == "Polish" => boxup(polish::Polish), x if x.to_name() == "Swedish" => boxup(swedish::Swedish), x if x.to_name() == "Romanian" => boxup(romanian::Romanian), x if x.to_name() == "Turkish" => boxup(turkish::Turkish), x if x.to_name() == "French" => boxup(french::French), x if x.to_name() == "Spanish" => boxup(spanish::Spanish), x if x.to_name() == "Danish" => boxup(danish::Danish), x if x.to_name() == "Portuguese" => boxup(portuguese::Portuguese), x if x.to_name() == "Italian" => boxup(italian::Italian), x if x.to_name() == "Ukrainian" => boxup(ukrainian::Ukrainian), x if x.to_name() == "Thai" => boxup(thai::Thai), x if x.to_name() == "Korean" => boxup(korean::Korean), _ => return None, }) } timeago-0.6.0/src/languages/polish.rs000064400000000000000000000111131046102023000156520ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Polish; impl Polish { fn accusative(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "nanosekundę", Microseconds => "mikrosekundę", Milliseconds => "milisekundę", Seconds => "sekundę", Minutes => "minutę", Hours => "godzinę", Days => "dzień", Weeks => "tydzień", Months => "miesiąc", Years => "lat", } } fn genitive(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "nanosekundy", Microseconds => "mikrosekundy", Milliseconds => "milisekundy", Seconds => "sekundy", Minutes => "minuty", Hours => "godziny", Days => "dni", Weeks => "tygodnie", Months => "miesiące", Years => "lata", } } fn genitive_plural(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "nanosekund", Microseconds => "mikrosekund", Milliseconds => "milisekund", Seconds => "sekund", Minutes => "minut", Hours => "godzin", Days => "dni", Weeks => "tygodni", Months => "miesięcy", Years => "lat", } } } impl Language for Polish { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "teraz" } fn too_high(&self) -> &'static str { "dawno" } fn ago(&self) -> &'static str { "temu" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { if tu == TimeUnit::Years && x == 1 { return "rok"; } let last_two = x % 100; let last = x % 10; if (11..=20).contains(&last_two) { self.genitive_plural(tu) } else { match last { 1 => self.accusative(tu), 2..=4 => self.genitive(tu), 0 | 5..=9 => self.genitive_plural(tu), _ => unreachable!(), } } } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Polish); assert_eq!(f.convert(Duration::from_secs(60)), "1 minutę temu"); assert_eq!(f.convert(Duration::from_secs(2)), "2 sekundy temu"); assert_eq!(f.convert(Duration::from_secs(5)), "5 sekund temu"); assert_eq!(f.convert(Duration::from_secs(12)), "12 sekund temu"); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 12 * 366)), "6 miesięcy temu" ); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 24 * 366)), "1 rok temu" ); assert_eq!( f.convert(Duration::from_secs(2 * 3600 * 24 * 366)), "2 lata temu" ); assert_eq!( f.convert(Duration::from_secs(4 * 3600 * 24 * 366)), "4 lata temu" ); assert_eq!( f.convert(Duration::from_secs(5 * 3600 * 24 * 366)), "5 lat temu" ); assert_eq!( f.convert(Duration::from_secs(10 * 3600 * 24 * 366)), "10 lat temu" ); assert_eq!( f.convert(Duration::from_secs(11 * 3600 * 24 * 366)), "11 lat temu" ); assert_eq!( f.convert(Duration::from_secs(14 * 3600 * 24 * 366)), "14 lat temu" ); assert_eq!( f.convert(Duration::from_secs(15 * 3600 * 24 * 366)), "15 lat temu" ); assert_eq!( f.convert(Duration::from_secs(19 * 3600 * 24 * 366)), "19 lat temu" ); assert_eq!( f.convert(Duration::from_secs(20 * 3600 * 24 * 366)), "20 lat temu" ); assert_eq!( f.convert(Duration::from_secs(21 * 3600 * 24 * 366)), "21 lat temu" ); assert_eq!( f.convert(Duration::from_secs(32 * 3600 * 24 * 366)), "32 lata temu" ); assert_eq!( f.convert(Duration::from_secs(99 * 3600 * 24 * 366)), "99 lat temu" ); assert_eq!( f.convert(Duration::from_secs(100 * 3600 * 24 * 366)), "100 lat temu" ); assert_eq!( f.convert(Duration::from_secs(101 * 3600 * 24 * 366)), "101 lat temu" ); assert_eq!( f.convert(Duration::from_secs(104 * 3600 * 24 * 366)), "104 lata temu" ); assert_eq!( f.convert(Duration::from_secs(111 * 3600 * 24 * 366)), "111 lat temu" ); } timeago-0.6.0/src/languages/portuguese.rs000064400000000000000000000026231046102023000165640ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Portuguese; impl Language for Portuguese { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "agora" } fn too_high(&self) -> &'static str { "antigo" } fn ago(&self) -> &'static str { "atrás" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "nanosegundo", Microseconds => "microsegundo", Milliseconds => "milisegundo", Seconds => "segundo", Minutes => "minuto", Hours => "hora", Days => "dia", Weeks => "semana", Months => "mês", Years => "ano", } } else { match tu { Nanoseconds => "nanosegundos", Microseconds => "microsegundos", Milliseconds => "milisegundos", Seconds => "segundos", Minutes => "minutos", Hours => "horas", Days => "dias", Weeks => "semanas", Months => "meses", Years => "anos", } } } } timeago-0.6.0/src/languages/romanian.rs000064400000000000000000000031461046102023000161670ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Romanian; impl Language for Romanian { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "acum" } fn too_high(&self) -> &'static str { "demult" } fn ago(&self) -> &'static str { "acum" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "nanosecundă", Microseconds => "microsecundă", Milliseconds => "milisecundă", Seconds => "secundă", Minutes => "minut", Hours => "oră", Days => "zi", Weeks => "săptămână", Months => "lună", Years => "an", } } else { match tu { Nanoseconds => "nanosecunde", Microseconds => "microsecunde", Milliseconds => "milisecunde", Seconds => "secunde", Minutes => "minute", Hours => "ore", Days => "zile", Weeks => "săptămâni", Months => "luni", Years => "ani", } } } fn place_ago_before(&self) -> bool { true } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Romanian); assert_eq!(f.convert(Duration::from_secs(60)), "acum 1 minut"); } timeago-0.6.0/src/languages/russian.rs000064400000000000000000000114461046102023000160510ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Russian; impl Russian { fn accusative(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "наносекунду", Microseconds => "микросекунду", Milliseconds => "миллисекунду", Seconds => "секунду", Minutes => "минуту", Hours => "час", Days => "день", Weeks => "неделю", Months => "месяц", Years => "год", } } fn genitive(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "наносекунды", Microseconds => "микросекунды", Milliseconds => "миллисекунды", Seconds => "секунды", Minutes => "минуты", Hours => "часа", Days => "дня", Weeks => "недели", Months => "месяца", Years => "года", } } fn genitive_plural(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "наносекунд", Microseconds => "микросекунд", Milliseconds => "миллисекунд", Seconds => "секунд", Minutes => "минут", Hours => "часов", Days => "дней", Weeks => "недель", Months => "месяцев", Years => "лет", } } } impl Language for Russian { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "сейчас" } fn too_high(&self) -> &'static str { "давно" } fn ago(&self) -> &'static str { "назад" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { let last_two = x % 100; let last = x % 10; if (11..=20).contains(&last_two) { self.genitive_plural(tu) } else { match last { 1 => self.accusative(tu), 2..=4 => self.genitive(tu), 0 | 5..=9 => self.genitive_plural(tu), _ => unreachable!(), } } } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Russian); assert_eq!(f.convert(Duration::from_secs(60)), "1 минуту назад"); assert_eq!(f.convert(Duration::from_secs(2)), "2 секунды назад"); assert_eq!(f.convert(Duration::from_secs(5)), "5 секунд назад"); assert_eq!(f.convert(Duration::from_secs(12)), "12 секунд назад"); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 12 * 366)), "6 месяцев назад" ); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 24 * 366)), "1 год назад" ); assert_eq!( f.convert(Duration::from_secs(2 * 3600 * 24 * 366)), "2 года назад" ); assert_eq!( f.convert(Duration::from_secs(4 * 3600 * 24 * 366)), "4 года назад" ); assert_eq!( f.convert(Duration::from_secs(5 * 3600 * 24 * 366)), "5 лет назад" ); assert_eq!( f.convert(Duration::from_secs(10 * 3600 * 24 * 366)), "10 лет назад" ); assert_eq!( f.convert(Duration::from_secs(11 * 3600 * 24 * 366)), "11 лет назад" ); assert_eq!( f.convert(Duration::from_secs(14 * 3600 * 24 * 366)), "14 лет назад" ); assert_eq!( f.convert(Duration::from_secs(15 * 3600 * 24 * 366)), "15 лет назад" ); assert_eq!( f.convert(Duration::from_secs(19 * 3600 * 24 * 366)), "19 лет назад" ); assert_eq!( f.convert(Duration::from_secs(20 * 3600 * 24 * 366)), "20 лет назад" ); assert_eq!( f.convert(Duration::from_secs(21 * 3600 * 24 * 366)), "21 год назад" ); assert_eq!( f.convert(Duration::from_secs(32 * 3600 * 24 * 366)), "32 года назад" ); assert_eq!( f.convert(Duration::from_secs(99 * 3600 * 24 * 366)), "99 лет назад" ); assert_eq!( f.convert(Duration::from_secs(100 * 3600 * 24 * 366)), "100 лет назад" ); assert_eq!( f.convert(Duration::from_secs(101 * 3600 * 24 * 366)), "101 год назад" ); assert_eq!( f.convert(Duration::from_secs(111 * 3600 * 24 * 366)), "111 лет назад" ); } timeago-0.6.0/src/languages/spanish.rs000064400000000000000000000031471046102023000160310ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Spanish; impl Language for Spanish { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "ahora" } fn too_high(&self) -> &'static str { "hace mucho" } fn ago(&self) -> &'static str { "hace" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "nanosegundo", Microseconds => "microsegundo", Milliseconds => "milisegundo", Seconds => "segundo", Minutes => "minuto", Hours => "hora", Days => "día", Weeks => "semana", Months => "mes", Years => "año", } } else { match tu { Nanoseconds => "nanosegundos", Microseconds => "microsegundos", Milliseconds => "milisegundos", Seconds => "segundos", Minutes => "minutos", Hours => "horas", Days => "días", Weeks => "semanas", Months => "meses", Years => "años", } } } fn place_ago_before(&self) -> bool { true } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Spanish); assert_eq!(f.convert(Duration::from_secs(60)), "hace 1 minuto"); } timeago-0.6.0/src/languages/swedish.rs000064400000000000000000000025331046102023000160300ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Swedish; impl Language for Swedish { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "nu" } fn too_high(&self) -> &'static str { "gammal" } fn ago(&self) -> &'static str { "sedan" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { use TimeUnit::*; if x == 1 { match tu { Nanoseconds => "nanosekund", Microseconds => "mikrosekund", Milliseconds => "millisekund", Seconds => "sekund", Minutes => "minut", Hours => "timme", Days => "dag", Weeks => "vecka", Months => "månad", Years => "år", } } else { match tu { Nanoseconds => "nanosekunder", Microseconds => "mikrosekunder", Milliseconds => "millisekunder", Seconds => "sekunder", Minutes => "minuter", Hours => "timmar", Days => "dagar", Weeks => "veckor", Months => "månader", Years => "år", } } } } timeago-0.6.0/src/languages/thai.rs000064400000000000000000000021011046102023000152760ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Thai; impl Language for Thai { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "ตอนนี้" } fn too_high(&self) -> &'static str { "นานมาแล้ว" } fn ago(&self) -> &'static str { "ที่แล้ว" } fn override_space_near_ago(&self) -> &str { "" } fn get_word(&self, tu: TimeUnit, _: u64) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "นาโนวินาที", Microseconds => "ไมโครวินาที", Milliseconds => "มิลลิวินาที", Seconds => "วินาที", Minutes => "นาที", Hours => "ชั่วโมง", Days => "วัน", Weeks => "สัปดาห์", Months => "เดือน", Years => "ปี", } } } timeago-0.6.0/src/languages/turkish.rs000064400000000000000000000046671046102023000160650ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Turkish; impl Language for Turkish { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "şimdi" } fn too_high(&self) -> &'static str { "eski" } fn ago(&self) -> &'static str { "önce" } fn get_word(&self, tu: TimeUnit, _x: u64) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "nanosaniye", Microseconds => "mikrosaniye", Milliseconds => "milisaniye", Seconds => "saniye", Minutes => "dakika", Hours => "saat", Days => "gün", Weeks => "hafta", Months => "ay", Years => "yıl", } } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Turkish); assert_eq!(f.convert(Duration::from_secs(60)), "1 dakika önce"); assert_eq!(f.convert(Duration::from_secs(2)), "2 saniye önce"); assert_eq!(f.convert(Duration::from_secs(5)), "5 saniye önce"); assert_eq!(f.convert(Duration::from_secs(12)), "12 saniye önce"); assert_eq!(f.convert(Duration::from_secs(1 * 60 * 60)), "1 saat önce"); assert_eq!(f.convert(Duration::from_secs(2 * 60 * 60)), "2 saat önce"); assert_eq!( f.convert(Duration::from_secs(1 * 24 * 60 * 60)), "1 gün önce" ); assert_eq!( f.convert(Duration::from_secs(2 * 24 * 60 * 60)), "2 gün önce" ); assert_eq!( f.convert(Duration::from_secs(1 * 7 * 24 * 60 * 60)), "1 hafta önce" ); assert_eq!( f.convert(Duration::from_secs(2 * 7 * 24 * 60 * 60)), "2 hafta önce" ); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 12 * 366)), "6 ay önce" ); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 24 * 366)), "1 yıl önce" ); assert_eq!( f.convert(Duration::from_secs(2 * 3600 * 24 * 366)), "2 yıl önce" ); assert_eq!( f.convert(Duration::from_secs(100 * 3600 * 24 * 366)), "100 yıl önce" ); assert_eq!( f.convert(Duration::from_secs(101 * 3600 * 24 * 366)), "101 yıl önce" ); assert_eq!( f.convert(Duration::from_secs(111 * 3600 * 24 * 366)), "111 yıl önce" ); } timeago-0.6.0/src/languages/ukrainian.rs000064400000000000000000000115421046102023000163430ustar 00000000000000use super::super::{Language, TimeUnit}; #[derive(Default)] pub struct Ukrainian; impl Ukrainian { fn accusative(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "наносекунду", Microseconds => "мікросекунду", Milliseconds => "мілісекунду", Seconds => "секунду", Minutes => "хвилину", Hours => "годину", Days => "день", Weeks => "тиждень", Months => "місяць", Years => "рік", } } fn genitive(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "наносекунди", Microseconds => "мікросекунди", Milliseconds => "мілісекунди", Seconds => "секунди", Minutes => "хвилини", Hours => "години", Days => "дня", Weeks => "тижня", Months => "місяця", Years => "роки", } } fn genitive_plural(&self, tu: TimeUnit) -> &'static str { use TimeUnit::*; match tu { Nanoseconds => "наносекунд", Microseconds => "мікросекунд", Milliseconds => "мілісекунд", Seconds => "секунд", Minutes => "хвилин", Hours => "годин", Days => "днів", Weeks => "тижнів", Months => "місяців", Years => "років", } } } impl Language for Ukrainian { fn clone_boxed(&self) -> super::super::BoxedLanguage { Box::new(Self {}) } fn too_low(&self) -> &'static str { "зараз" } fn too_high(&self) -> &'static str { "давно" } fn ago(&self) -> &'static str { "тому" } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { let last_two = x % 100; let last = x % 10; if (11..=20).contains(&last_two) { self.genitive_plural(tu) } else if last == 1 { self.accusative(tu) } else if (2..=4).contains(&last) { self.genitive(tu) } else if (5..=9).contains(&last) || last == 0 { self.genitive_plural(tu) } else { unreachable!() } } } #[test] fn test() { use super::super::Formatter; use std::time::Duration; let f = Formatter::with_language(Ukrainian); assert_eq!(f.convert(Duration::from_secs(60)), "1 хвилину тому"); assert_eq!(f.convert(Duration::from_secs(2)), "2 секунди тому"); assert_eq!(f.convert(Duration::from_secs(5)), "5 секунд тому"); assert_eq!(f.convert(Duration::from_secs(12)), "12 секунд тому"); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 12 * 366)), "6 місяців тому" ); assert_eq!( f.convert(Duration::from_secs(1 * 3600 * 24 * 366)), "1 рік тому" ); assert_eq!( f.convert(Duration::from_secs(2 * 3600 * 24 * 366)), "2 роки тому" ); assert_eq!( f.convert(Duration::from_secs(4 * 3600 * 24 * 366)), "4 роки тому" ); assert_eq!( f.convert(Duration::from_secs(5 * 3600 * 24 * 366)), "5 років тому" ); assert_eq!( f.convert(Duration::from_secs(10 * 3600 * 24 * 366)), "10 років тому" ); assert_eq!( f.convert(Duration::from_secs(11 * 3600 * 24 * 366)), "11 років тому" ); assert_eq!( f.convert(Duration::from_secs(14 * 3600 * 24 * 366)), "14 років тому" ); assert_eq!( f.convert(Duration::from_secs(15 * 3600 * 24 * 366)), "15 років тому" ); assert_eq!( f.convert(Duration::from_secs(19 * 3600 * 24 * 366)), "19 років тому" ); assert_eq!( f.convert(Duration::from_secs(20 * 3600 * 24 * 366)), "20 років тому" ); assert_eq!( f.convert(Duration::from_secs(21 * 3600 * 24 * 366)), "21 рік тому" ); assert_eq!( f.convert(Duration::from_secs(32 * 3600 * 24 * 366)), "32 роки тому" ); assert_eq!( f.convert(Duration::from_secs(99 * 3600 * 24 * 366)), "99 років тому" ); assert_eq!( f.convert(Duration::from_secs(100 * 3600 * 24 * 366)), "100 років тому" ); assert_eq!( f.convert(Duration::from_secs(101 * 3600 * 24 * 366)), "101 рік тому" ); assert_eq!( f.convert(Duration::from_secs(111 * 3600 * 24 * 366)), "111 років тому" ); } timeago-0.6.0/src/lib.rs000064400000000000000000000645121046102023000131670ustar 00000000000000#![deny(missing_docs)] //! Given a Duration, lossily format it like in 'N days ago'. //! //! Parsing it back to Duration is not supported yet (See [`chrono-english`] crate). //! //! Multiple languages are supported though `Language` trait. //! Enable `isolang` feature to gain support of getting Language impl from //! `lsolang::Language`. //! //! You can configure minimum and maximum time units, as well as "precision" of //! how many items to emit. //! //! Fractional results like "1.5 days ago" are not supported. //! //! There is a special simplified version to get compact 5-character representation: `format_5chars`. //! //! The main item of timeago is [`Formatter`]. //! //! [`chrono-english`]:https://docs.rs/chrono-english //! [`Formatter`]:struct.Formatter.html use std::time::Duration; #[cfg(feature = "chrono")] extern crate chrono; /// Interface for connecting natural languages to use for the formatting /// See "language" module documentation for details. #[allow(missing_docs)] pub trait Language { /// What to emit by default if value is too high fn too_low(&self) -> &'static str; /// What to emit by default if value is too low fn too_high(&self) -> &'static str; /// Chunk of text to put at the end by default fn ago(&self) -> &'static str; /// Get word representing the given time unit, for using with `x` number fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str; /// For German and such fn place_ago_before(&self) -> bool { false } /// For Thai and such fn override_space_near_ago(&self) -> &str { " " } /// For basque and such fn place_unit_before(&self, _: u64) -> bool { false } fn between_chunks(&self) -> &str { " " } fn between_value_and_word(&self) -> &str { " " } /// Make a dynamic copy of this language fn clone_boxed(&self) -> BoxedLanguage; } impl Language for BoxedLanguage { fn clone_boxed(&self) -> BoxedLanguage { (**self).clone_boxed() } fn too_low(&self) -> &'static str { (**self).too_low() } fn too_high(&self) -> &'static str { (**self).too_high() } fn ago(&self) -> &'static str { (**self).ago() } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { (**self).get_word(tu, x) } fn place_ago_before(&self) -> bool { (**self).place_ago_before() } fn override_space_near_ago(&self) -> &str { (**self).override_space_near_ago() } fn place_unit_before(&self, x: u64) -> bool { (**self).place_unit_before(x) } fn between_chunks(&self) -> &str { (**self).between_chunks() } fn between_value_and_word(&self) -> &str { (**self).between_value_and_word() } } /// Dynamic version of the `Language` trait pub type BoxedLanguage = Box; /// A collection of natural languages supported out-of-the-box for the formatting. /// /// You can implement a language yourself by deriving /// the `Language` trait (pull requests are welcome). /// /// The list of languages is also tracked in `README.md`. /// If you spot an error, submit a fix or point it out on [Github issues](https://github.com/vi/timeago/issues/new). If on the other hand you have checked a language and assert that it is done properly, [submit a pull request against `README.md` of this project][er]. /// /// You can also choose the language at runtime using the `isolang` cargo feature and [`from_isolang`] function. /// /// Requires `translations` Cargo feature. /// /// [`from_isolang`]:fn.from_isolang.html /// [er]:https://github.com/vi/timeago/edit/master/README.md #[cfg(feature = "translations")] pub mod languages; #[cfg(all(feature = "isolang", feature = "translations"))] pub use languages::from_isolang; #[cfg(not(feature = "translations"))] /// Non-english modes are currently disabled by omission of "translations" cargo feature. pub mod languages { /// Non-english modes are currently disabled by omission of "translations" cargo feature. pub mod english; } pub use languages::english::English; /// Various units of time to specify as maximum or minimum. /// Note that calculations are approximate, not calendar-based. #[allow(missing_docs)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum TimeUnit { Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours, Days, Weeks, Months, Years, } impl TimeUnit { /// Get `std::time::Duration` corresponding to minimum duration that is representable by this time unit. pub fn min_duration(&self) -> Duration { use TimeUnit::*; match *self { Nanoseconds => Duration::new(0, 1), Microseconds => Duration::new(0, 1000), Milliseconds => Duration::new(0, 1_000_000), Seconds => Duration::new(1, 0), Minutes => Duration::new(60, 0), Hours => Duration::new(60 * 60, 0), Days => Duration::new(24 * 60 * 60, 0), Weeks => Duration::new(7 * 24 * 60 * 60, 0), Months => Duration::new(S_IN_MNTH, 0), Years => Duration::new(S_IN_MNTH * 12, 0), } } /// "Upgrade" minutes to hours, hours to days and so on. pub fn bigger_unit(&self) -> Option { use TimeUnit::*; match *self { Nanoseconds => Some(Microseconds), Microseconds => Some(Milliseconds), Milliseconds => Some(Seconds), Seconds => Some(Minutes), Minutes => Some(Hours), Hours => Some(Days), Days => Some(Weeks), Weeks => Some(Months), Months => Some(Years), Years => None, } } /// "Downgrade" weeks to days, seconds to milliseconds and so on. pub fn smaller_unit(&self) -> Option { use TimeUnit::*; match *self { Nanoseconds => None, Microseconds => Some(Nanoseconds), Milliseconds => Some(Microseconds), Seconds => Some(Milliseconds), Minutes => Some(Seconds), Hours => Some(Minutes), Days => Some(Hours), Weeks => Some(Days), Months => Some(Weeks), Years => Some(Months), } } } /// Main formatter struct. Build it with new() and maybe modify some options, then use convert. /// ``` /// let f = timeago::Formatter::new(); /// let d = std::time::Duration::from_secs(3600); /// assert_eq!(f.convert(d), "1 hour ago"); /// ``` pub struct Formatter { lang: L, num_items: usize, min_unit: TimeUnit, max_unit: TimeUnit, too_low: Option<&'static str>, too_high: Option<&'static str>, ago: Option<&'static str>, max_duration: Duration, } impl Default for Formatter { fn default() -> Self { Self::new() } } impl Formatter { /// Constructor for some default formatting in English /// /// It emits one chunk, limits to seconds and has no maximum duration. pub fn new() -> Formatter { Formatter::with_language(English) } } impl Clone for Formatter { fn clone(&self) -> Formatter { Formatter { lang: self.lang.clone_boxed(), num_items: self.num_items, min_unit: self.min_unit, max_unit: self.max_unit, too_low: self.too_low, too_high: self.too_high, ago: self.ago, max_duration: self.max_duration, } } } impl Formatter { /// Constructor for some default formatting with specified language instance /// /// It emits one item (chunk), limits to seconds and has no maximum duration. pub fn with_language(l: L) -> Self { Formatter { lang: l, num_items: 1, min_unit: TimeUnit::Seconds, max_unit: TimeUnit::Years, too_low: None, too_high: None, ago: None, max_duration: Duration::new(u64::MAX, 999_999_999), } } /// Set number of time unit items to emit (for example, 1 item is for "1 year"; 3 items is for "1 year 3 months 17 days"). Zero chunks like "0 minutes" are not emitted, expect of at the end if `too_low` is `"0"`. /// Default is 1. /// ``` /// let mut f = timeago::Formatter::new(); /// f.num_items(1); /// let d = std::time::Duration::from_secs(3600+60+3); /// assert_eq!(f.convert(d), "1 hour ago"); /// f.num_items(2); /// assert_eq!(f.convert(d), "1 hour 1 minute ago"); /// f.num_items(3); /// assert_eq!(f.convert(d), "1 hour 1 minute 3 seconds ago"); /// f.num_items(4); /// assert_eq!(f.convert(d), "1 hour 1 minute 3 seconds ago"); /// ``` pub fn num_items(&mut self, x: usize) -> &mut Self { assert!(x > 0); self.num_items = x; self } /// Set maximum used unit. Not to be confused with `max_duration`. /// Should not affect appearance of "old" or other `too_high` values. /// ``` /// let mut f = timeago::Formatter::new(); /// f.max_unit(timeago::TimeUnit::Hours); /// let d = std::time::Duration::from_secs(60); /// assert_eq!(f.convert(d), "1 minute ago"); /// let d = std::time::Duration::from_secs(3600); /// assert_eq!(f.convert(d), "1 hour ago"); /// let d = std::time::Duration::from_secs(24*3600); /// assert_eq!(f.convert(d), "24 hours ago"); /// let d = std::time::Duration::from_secs(30*24*3600); /// assert_eq!(f.convert(d), "720 hours ago"); /// ``` pub fn max_unit(&mut self, x: TimeUnit) -> &mut Self { self.max_unit = x; self } /// Set minimum used unit. Durations below minimally representable by that unit emit `too_low` value like "now", or like "0 days" instead of normal output. /// When `num_items` > 1, it also acts as precision limiter. /// ``` /// let mut f = timeago::Formatter::new(); /// f.min_unit(timeago::TimeUnit::Minutes); /// let d = std::time::Duration::from_secs(30); /// assert_eq!(f.convert(d), "now"); /// let d = std::time::Duration::from_secs(90); /// assert_eq!(f.convert(d), "1 minute ago"); /// ``` /// ``` /// let mut f = timeago::Formatter::new(); /// f.num_items(99); /// let d = std::time::Duration::new(1*3600*24 + 2*3600 + 3*60 + 4, 500_000_000); /// assert_eq!(f.convert(d), "1 day 2 hours 3 minutes 4 seconds ago"); /// f.min_unit(timeago::TimeUnit::Hours); /// assert_eq!(f.convert(d), "1 day 2 hours ago"); /// f.min_unit(timeago::TimeUnit::Microseconds); /// assert_eq!(f.convert(d), "1 day 2 hours 3 minutes 4 seconds 500 milliseconds ago"); /// f.min_unit(timeago::TimeUnit::Months); /// assert_eq!(f.convert(d), "now"); /// ``` pub fn min_unit(&mut self, x: TimeUnit) -> &mut Self { self.min_unit = x; self } /// Override what is used instead of "now" for too short durations (not representable with the time unit configures as `min_unit`). /// Setting this to special value `"0"` causes emitting output like "0 days", depending on `min_unit` property. /// Note that `Language`'s `too_low` is not used in this case, except of for `"0"`. /// ``` /// let mut f = timeago::Formatter::new(); /// f.min_unit(timeago::TimeUnit::Months) /// .too_low("this month"); /// let d = std::time::Duration::from_secs(24*3600); /// assert_eq!(f.convert(d), "this month"); /// ``` /// ``` /// let mut f = timeago::Formatter::new(); /// f.min_unit(timeago::TimeUnit::Minutes); /// let d = std::time::Duration::from_secs(30); /// assert_eq!(f.convert(d), "now"); /// f.too_low("-"); /// assert_eq!(f.convert(d), "-"); /// f.too_low(""); /// assert_eq!(f.convert(d), ""); /// f.too_low("0"); /// assert_eq!(f.convert(d), "0 minutes ago"); /// ``` pub fn too_low(&mut self, x: &'static str) -> &mut Self { self.too_low = Some(x); self } /// Override what is used instead of "old" for too high units. /// Note that `Language`'s `too_high` is not used in this case. /// ``` /// let mut f = timeago::Formatter::new(); /// f.max_duration(std::time::Duration::from_secs(3600*24*30)); /// f.too_high("ancient"); /// let d = std::time::Duration::from_secs(1000_000_000_000); /// assert_eq!(f.convert(d), "ancient"); /// ``` pub fn too_high(&mut self, x: &'static str) -> &mut Self { self.too_high = Some(x); self } /// Maximum duration before it start giving "old" (or other `too_high` value) /// ``` /// let mut f = timeago::Formatter::new(); /// f.max_duration(std::time::Duration::new(3600*24*30, 0)); /// let d = std::time::Duration::from_secs(1000_000_000); /// assert_eq!(f.convert(d), "old"); /// ``` pub fn max_duration(&mut self, x: Duration) -> &mut Self { self.max_duration = x; self } /// Override what is used instead of "ago". /// Empty string literal `""` is a bit special in the space handling. /// ``` /// let mut f = timeago::Formatter::new(); /// let d = std::time::Duration::from_secs(60); /// assert_eq!(f.convert(d), "1 minute ago"); /// f.ago("later"); /// assert_eq!(f.convert(d), "1 minute later"); /// f.ago(""); /// assert_eq!(f.convert(d), "1 minute"); /// ``` pub fn ago(&mut self, x: &'static str) -> &mut Self { self.ago = Some(x); self } /// Format the timespan between `from` and `to` as a string like "15 days ago". /// /// Requires `chrono` Cargo feature. /// /// `from` should come before `to`, otherwise `"???"` will be returned. /// /// Currently it doesn't actually take the calendar into account and just converts datetimes /// into a plain old `std::time::Duration`, but in future here may be a proper implementation. /// /// ``` /// extern crate chrono; /// extern crate timeago; /// let mut f = timeago::Formatter::new(); /// f.num_items(2); /// let from = chrono::DateTime::parse_from_rfc3339("2013-12-19T15:00:00+03:00").unwrap(); /// let to = chrono::DateTime::parse_from_rfc3339("2013-12-23T17:00:00+03:00").unwrap(); /// assert_eq!(f.convert_chrono(from, to), "4 days 2 hours ago"); /// ``` #[cfg(feature = "chrono")] pub fn convert_chrono( &self, from: chrono::DateTime, to: chrono::DateTime, ) -> String where Tz1: chrono::TimeZone, Tz2: chrono::TimeZone, { let q = to.signed_duration_since(from); if let Ok(dur) = q.to_std() { self.convert(dur) } else { "???".to_owned() } } /// Convert specified [`Duration`] to a String representing /// approximation of specified timespan as a string like /// "5 days ago", with specified by other methods settings. /// See module-level doc for more info. /// ``` /// let f = timeago::Formatter::new(); /// let d = std::time::Duration::from_secs(3600*24); /// assert_eq!(f.convert(d), "1 day ago"); /// ``` /// /// [`Duration`]:https://doc.rust-lang.org/std/time/struct.Duration.html pub fn convert(&self, d: Duration) -> String { if d > self.max_duration { return self .too_high .unwrap_or_else(|| self.lang.too_high()) .to_owned(); } let mut ret = self.convert_impl(d, self.num_items); if ret.is_empty() { let now = self.too_low.unwrap_or_else(|| self.lang.too_low()); if now != "0" { return now.to_owned(); } else { ret = format!( "0{}{}", self.lang.between_value_and_word(), self.lang.get_word(self.min_unit, 0) ); } } let ago = self.ago.unwrap_or_else(|| self.lang.ago()); if ago.is_empty() { ret } else if self.lang.place_ago_before() { format!("{}{}{}", ago, self.lang.override_space_near_ago(), ret) } else { format!("{}{}{}", ret, self.lang.override_space_near_ago(), ago) } } fn convert_impl(&self, d: Duration, items_left: usize) -> String { if items_left == 0 { return "".to_owned(); } let mut dtu = dominant_time_unit(d); while dtu > self.max_unit { dtu = dtu.smaller_unit().unwrap(); } while dtu < self.min_unit { dtu = dtu.bigger_unit().unwrap(); } let (x, rem) = split_up(d, dtu); if x == 0 { return "".to_owned(); } let recurse_result = self.convert_impl(rem, items_left - 1); let word = self.lang.get_word(dtu, x); let between = self.lang.between_value_and_word(); let between_chunk = self.lang.between_chunks(); match (self.lang.place_unit_before(x), recurse_result.is_empty()) { (true, true) => format!("{word}{between}{x}"), (true, false) => format!("{word}{between}{x}{between_chunk}{recurse_result}"), (false, true) => format!("{x}{between}{word}"), (false, false) => format!("{x}{between}{word}{between_chunk}{recurse_result}"), } } } fn dominant_time_unit(d: Duration) -> TimeUnit { use TimeUnit::*; match d { x if x < Microseconds.min_duration() => Nanoseconds, x if x < Milliseconds.min_duration() => Microseconds, x if x < Seconds.min_duration() => Milliseconds, x if x < Minutes.min_duration() => Seconds, x if x < Hours.min_duration() => Minutes, x if x < Days.min_duration() => Hours, x if x < Weeks.min_duration() => Days, x if x < Months.min_duration() => Weeks, x if x < Years.min_duration() => Months, _ => Years, } } fn divmod64(a: u64, b: u64) -> (u64, u64) { (a / b, a % b) } fn divmod32(a: u32, b: u32) -> (u32, u32) { (a / b, a % b) } fn split_up(d: Duration, tu: TimeUnit) -> (u64, Duration) { let s = d.as_secs(); let n = d.subsec_nanos(); let tud = tu.min_duration(); let tus = tud.as_secs(); let tun = tud.subsec_nanos(); if tus != 0 { assert!(tun == 0); if s == 0 { (0, d) } else { let (c, s2) = divmod64(s, tus); (c, Duration::new(s2, n)) } } else { // subsecond timeunit assert!(tus == 0); if s == 0 { let (c, n2) = divmod32(n, tun); (c.into(), Duration::new(0, n2)) } else { assert!(1_000_000_000_u32 % tun == 0); let tuninv = 1_000_000_000 / (u64::from(tun)); let pieces = s.saturating_mul(tuninv).saturating_add(u64::from(n / tun)); let subtract_s = pieces / tuninv; let subtract_ns = ((pieces % tuninv) as u32) * tun; let (mut s, mut n) = (s, n); if subtract_ns > n { s -= 1; n += 1_000_000_000; } let remain_s = s - subtract_s; let remain_ns = n - subtract_ns; (pieces, Duration::new(remain_s, remain_ns)) } } } #[cfg(test)] mod tests_split_up { use super::*; fn ds(secs: u64) -> Duration { Duration::from_secs(secs) } fn dn(secs: u64, nanos: u32) -> Duration { Duration::new(secs, nanos) } #[test] fn dominant_time_unit_test() { use TimeUnit::*; assert_eq!(dominant_time_unit(ds(3)), Seconds); assert_eq!(dominant_time_unit(ds(60)), Minutes); assert_eq!(dominant_time_unit(dn(0, 250_000_000)), Milliseconds); } #[test] fn split_up_test_sane() { use TimeUnit::*; assert_eq!(split_up(ds(120), Minutes), (2, ds(0))); assert_eq!(split_up(ds(119), Minutes), (1, ds(59))); assert_eq!(split_up(ds(60), Minutes), (1, ds(0))); assert_eq!(split_up(ds(1), Minutes), (0, ds(1))); assert_eq!(split_up(ds(0), Minutes), (0, ds(0))); assert_eq!(split_up(ds(3600), Minutes), (60, ds(0))); assert_eq!(split_up(ds(3600), Hours), (1, ds(0))); assert_eq!(split_up(ds(3600), Seconds), (3600, ds(0))); assert_eq!(split_up(ds(3600), Milliseconds), (3600_000, ds(0))); assert_eq!(split_up(ds(100000000), Years), (3, ds(5391892))); assert_eq!(split_up(ds(100000000), Months), (38, ds(135886))); assert_eq!(split_up(ds(100000000), Days), (1157, ds(35200))); assert_eq!(split_up(ds(3600), Microseconds), (3600_000_000, ds(0))); } #[test] fn split_up_test_tricky() { use TimeUnit::*; assert_eq!(split_up(ds(3600), Nanoseconds), (3600_000_000_000, ds(0))); assert_eq!( split_up(ds(3600_000), Nanoseconds), (3600_000_000_000_000, ds(0)) ); assert_eq!( split_up(ds(3600_000_000), Nanoseconds), (3600_000_000_000_000_000, ds(0)) ); assert_eq!( split_up(ds(3600_000_000_000), Nanoseconds), (std::u64::MAX, dn(3581_553_255_926, 290448385)) ); assert_eq!( split_up(ds(3600_000_000_000), Microseconds), (3600_000_000_000_000_000, ds(0)) ); assert_eq!( split_up(ds(3600_000_000_000_000), Microseconds), (std::u64::MAX, dn(3581_553_255_926_290, 448385000)) ); assert_eq!( split_up(ds(3600_000_000_000_000), Milliseconds), (3600_000_000_000_000_000, ds(0)) ); assert_eq!( split_up(ds(3600_000_000_000_000_000), Milliseconds), (std::u64::MAX, dn(3581_553_255_926_290_448, 385000000)) ); } } /// A simplified formatter, resulting in short strings like "02Yea" or " now " or "07min". /// Designed to always give 5-character strings. pub fn format_5chars(d: Duration) -> String { let s = d.as_secs(); match s { 0 => " now ".into(), x if (1..60).contains(&x) => format!("{x:02}sec"), x if (60..3600).contains(&x) => format!("{:02}min", x / 60), x if (3600..86400).contains(&x) => format!("{:02}hou", x / 3600), x if (86400..S_IN_MNTH).contains(&x) => format!("{:02}day", x / 86400), x if (S_IN_MNTH..(12 * S_IN_MNTH)).contains(&x) => format!("{:02}Mon", x / S_IN_MNTH), x if ((12 * S_IN_MNTH)..=(99 * 12 * S_IN_MNTH)).contains(&x) => { format!("{:02}Yea", x / (12 * S_IN_MNTH)) } _ => " OLD ".into(), } } /// Simple formatting style for deprecated `format`. #[deprecated(since = "0.1.0", note = "Use Formatter or format_5chars")] #[derive(Copy, Clone)] pub enum Style { /// Long format, like "2 years ago" LONG, /// Human format, like LONG but makes less than 1 second as `just now` HUMAN, /// Short format, like "02Yea". Should be exactly 5 characters. SHORT, } const S_IN_MNTH: u64 = 2_628_003; // 2628002,88 seconds according to Google /// Do the formatting. See `Style`'s docstring for formatting options. /// If you need just simple mode without bloated featureful implementation, /// use version 0.0.2 of this crate /// /// ``` /// extern crate timeago; /// assert_eq!(timeago::format(std::time::Duration::new(3600, 0), timeago::Style::LONG), "1 hour ago"); /// ``` #[deprecated(since = "0.1.0", note = "Use Formatter or format_5chars")] #[allow(deprecated)] pub fn format(d: Duration, style: Style) -> String { match style { Style::LONG => Formatter::new().min_unit(TimeUnit::Nanoseconds).convert(d), Style::HUMAN => { let ret = Formatter::new().convert(d); if ret == "now" { "just now".to_owned() } else { ret } } Style::SHORT => format_5chars(d), } } #[cfg(test)] mod tests { #[allow(deprecated)] use super::{format, Style}; use std::time::Duration; fn dns(secs: u64) -> Duration { Duration::from_secs(secs) } fn dn(secs: u64, nanos: u32) -> Duration { Duration::new(secs, nanos) } #[allow(deprecated)] fn fmtl(d: Duration) -> String { format(d, Style::LONG) } #[allow(deprecated)] fn fmth(d: Duration) -> String { format(d, Style::HUMAN) } #[allow(deprecated)] fn fmts(d: Duration) -> String { format(d, Style::SHORT) } #[test] fn test_long() { assert_eq!(fmtl(dns(0)), "now"); assert_eq!(fmtl(dn(0, 500_000_000)), "500 milliseconds ago"); assert_eq!(fmtl(dns(1)), "1 second ago"); assert_eq!(fmtl(dn(1, 500_000_000)), "1 second ago"); assert_eq!(fmtl(dns(59)), "59 seconds ago"); assert_eq!(fmtl(dns(60)), "1 minute ago"); assert_eq!(fmtl(dns(65)), "1 minute ago"); assert_eq!(fmtl(dns(119)), "1 minute ago"); assert_eq!(fmtl(dns(120)), "2 minutes ago"); assert_eq!(fmtl(dns(3599)), "59 minutes ago"); assert_eq!(fmtl(dns(3600)), "1 hour ago"); assert_eq!(fmtl(dns(1000_000)), "1 week ago"); assert_eq!(fmtl(dns(1000_000_000)), "31 years ago"); } #[test] fn test_human() { assert_eq!(fmth(dns(0)), "just now"); assert_eq!(fmth(dn(0, 500_000_000)), "just now"); assert_eq!(fmth(dns(1)), "1 second ago"); assert_eq!(fmth(dn(1, 500_000_000)), "1 second ago"); assert_eq!(fmth(dns(59)), "59 seconds ago"); assert_eq!(fmth(dns(60)), "1 minute ago"); assert_eq!(fmth(dns(65)), "1 minute ago"); assert_eq!(fmth(dns(119)), "1 minute ago"); assert_eq!(fmth(dns(120)), "2 minutes ago"); assert_eq!(fmth(dns(3599)), "59 minutes ago"); assert_eq!(fmth(dns(3600)), "1 hour ago"); assert_eq!(fmth(dns(1000_000)), "1 week ago"); assert_eq!(fmth(dns(1000_000_000)), "31 years ago"); } #[test] fn test_short() { assert_eq!(fmts(dns(0)), " now "); assert_eq!(fmts(dn(0, 500_000_000)), " now "); assert_eq!(fmts(dns(1)), "01sec"); assert_eq!(fmts(dn(1, 500_000_000)), "01sec"); assert_eq!(fmts(dns(59)), "59sec"); assert_eq!(fmts(dns(60)), "01min"); assert_eq!(fmts(dns(65)), "01min"); assert_eq!(fmts(dns(119)), "01min"); assert_eq!(fmts(dns(120)), "02min"); assert_eq!(fmts(dns(3599)), "59min"); assert_eq!(fmts(dns(3600)), "01hou"); assert_eq!(fmts(dns(1000_000)), "11day"); assert_eq!(fmts(dns(1000_000_000)), "31Yea"); } } timeago-0.6.0/src/main.rs000064400000000000000000000020421046102023000133330ustar 00000000000000#[cfg(all(feature = "isolang", feature = "translations"))] extern crate isolang; extern crate timeago; use std::io::BufRead; fn main() { let ls = std::env::args().nth(1).expect( "Usage: timeago Then feed unsigned numbers (seconds) into it. ", ); let l; #[cfg(all(feature = "isolang", feature = "translations"))] { l = timeago::from_isolang(isolang::Language::from_639_1(&ls).unwrap()).unwrap(); } #[cfg(any(not(feature = "isolang"), not(feature = "translations")))] { if ls != "en" { eprintln!("Enable both `isolang` and `translations` Cargo features for any languages apart from `en`"); return; } l = timeago::English; } let mut f = timeago::Formatter::with_language(l); f.num_items(3); let si1 = std::io::stdin(); let si = si1.lock(); for line in si.lines() { let sec: u64 = line.unwrap().parse().unwrap(); println!("{}", f.convert(std::time::Duration::from_secs(sec))); } }