timeago-0.4.1/.cargo_vcs_info.json0000644000000001360000000000100124630ustar { "git": { "sha1": "e9fb6f02179f86190815eff22d422dc286ca3a07" }, "path_in_vcs": "" }timeago-0.4.1/.gitignore000064400000000000000000000002021046102023000132350ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target # These are backup files generated by rustfmt **/*.rs.bk timeago-0.4.1/.travis.yml000064400000000000000000000002051046102023000133610ustar 00000000000000language: rust rust: - 1.24.1 script: - cargo build --all-features --verbose --all - cargo test --all-features --verbose --all timeago-0.4.1/Cargo.lock0000644000000126470000000000100104500ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "chrono" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.87 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "isolang" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "phf 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libc" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "num-integer" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "phf" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "phf_shared 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "phf_shared" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "siphasher 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "siphasher" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "time" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.87 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "timeago" version = "0.4.1" dependencies = [ "chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", "isolang 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" "checksum chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" "checksum isolang 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b64fd6448ee8a45ce6e4365c58e4fa7d8740cba2ed70db3e9ab4879ebd93eaaa" "checksum libc 0.2.87 (registry+https://github.com/rust-lang/crates.io-index)" = "265d751d31d6780a3f956bb5b8022feba2d94eeee5a84ba64f4212eedca42213" "checksum num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" "checksum num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" "checksum phf 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" "checksum phf_shared 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" "checksum siphasher 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" "checksum time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" "checksum wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" "checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" timeago-0.4.1/Cargo.toml0000644000000021360000000000100104630ustar # 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] name = "timeago" version = "0.4.1" authors = ["Vitaly _Vi Shukela "] 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/Apache-2.0" repository = "https://github.com/vi/timeago" [package.metadata.docs.rs] features = [ "translations", "isolang", "chrono", ] [dependencies.chrono] version = "0.4" optional = true [dependencies.isolang] version = "2" optional = true [features] default = [ "translations", "isolang", "chrono", ] translations = [] timeago-0.4.1/Cargo.toml.orig000064400000000000000000000011331046102023000141400ustar 00000000000000[package] name = "timeago" version = "0.4.1" authors = ["Vitaly _Vi Shukela "] license = "MIT/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.4.1/LICENSE000064400000000000000000000020571046102023000122640ustar 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.4.1/README.md000064400000000000000000000046051046102023000125370ustar 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, it supports 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) * Belarusian (unchecked) * Polish (unchecked) * Spanish (contributed) * Chinese (contributed) * Romanian (contributed) * Swedish (contributed) * Turkish (contributed) * Japanese (contributed) * Danish (contributed) * Italian (contributed) * Ukrainian (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.4.1/src/languages/belarusian.rs000064400000000000000000000110121046102023000164760ustar 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 { if (x % 100) >= 11 && (x % 100) <= 20 { self.genitive_plural(tu) } else if x % 10 == 1 { self.accusative(tu) } else if x % 10 >= 2 && x % 10 <= 4 { self.genitive(tu) } else if x % 10 >= 5 || x % 10 == 0 { self.genitive_plural(tu) } else { 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.4.1/src/languages/chinese.rs000064400000000000000000000015221046102023000157740ustar 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.4.1/src/languages/danish.rs000064400000000000000000000026541046102023000156330ustar 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.4.1/src/languages/english.rs000064400000000000000000000027551046102023000160200ustar 00000000000000#![cfg_attr(rustfmt, rustfmt_skip)] use 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.4.1/src/languages/french.rs000064400000000000000000000032731046102023000156300ustar 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.4.1/src/languages/german.rs000064400000000000000000000032631046102023000156330ustar 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.4.1/src/languages/italian.rs000064400000000000000000000033131046102023000157770ustar 00000000000000#![cfg_attr(rustfmt, rustfmt_skip)] use super::super::{Language, TimeUnit}; /// Default language for timeago #[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.4.1/src/languages/japanese.rs000064400000000000000000000014711046102023000161470ustar 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.4.1/src/languages/mod.rs000064400000000000000000000046001046102023000151350ustar 00000000000000#![allow(missing_docs)] #![cfg_attr(rustfmt, rustfmt_skip)] #[cfg(feature = "isolang")] extern crate isolang; pub mod belarusian; pub mod chinese; pub mod english; pub mod german; pub mod japanese; pub mod polish; pub mod portuguese; pub mod romanian; pub mod russian; pub mod swedish; pub mod turkish; pub mod french; pub mod spanish; pub mod danish; pub mod italian; 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), _ => return None, }) } timeago-0.4.1/src/languages/polish.rs000064400000000000000000000103311046102023000156520ustar 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"; } if (x % 100) >= 11 && (x % 100) <= 20 { self.genitive_plural(tu) } else if x % 10 == 1 { self.accusative(tu) } else if x % 10 >= 2 && x % 10 <= 4 { self.genitive(tu) } else if x % 10 >= 5 || x % 10 == 0 { self.genitive_plural(tu) } else { 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.4.1/src/languages/portuguese.rs000064400000000000000000000030111046102023000165530ustar 00000000000000#![cfg_attr(rustfmt, rustfmt_skip)] use 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.4.1/src/languages/romanian.rs000064400000000000000000000032631046102023000161660ustar 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.4.1/src/languages/russian.rs000064400000000000000000000107661046102023000160540ustar 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 { //if (11..=20).contains(x) { if (x % 100) >= 11 && (x % 100) <= 20 { self.genitive_plural(tu) } else if x % 10 == 1 { self.accusative(tu) } else if x % 10 >= 2 && x % 10 <= 4 { self.genitive(tu) } else if x % 10 >= 5 || x % 10 == 0 { self.genitive_plural(tu) } else { 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.4.1/src/languages/spanish.rs000064400000000000000000000032641046102023000160300ustar 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.4.1/src/languages/swedish.rs000064400000000000000000000026641046102023000160340ustar 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.4.1/src/languages/turkish.rs000064400000000000000000000043121046102023000160470ustar 00000000000000use super::super::{Language, TimeUnit}; /// Default language for timeago #[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.4.1/src/languages/ukrainian.rs000064400000000000000000000107451046102023000163460ustar 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 { if (x % 100) >= 11 && (x % 100) <= 20 { self.genitive_plural(tu) } else if x % 10 == 1 { self.accusative(tu) } else if x % 10 >= 2 && x % 10 <= 4 { self.genitive(tu) } else if x % 10 >= 5 || x % 10 == 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.4.1/src/lib.rs000064400000000000000000000627211046102023000131660ustar 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 } /// Make a dynamic copy of this language fn clone_boxed(&self) -> BoxedLanguage; } #[cfg_attr(rustfmt, rustfmt_skip)] 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 place_ago_before(&self) -> bool { (**self).place_ago_before() } fn get_word(&self, tu: TimeUnit, x: u64) -> &'static str { (**self).get_word(tu, x) } } /// 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. #[cfg_attr(rustfmt, rustfmt_skip)] 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. #[cfg_attr(rustfmt, rustfmt_skip)] 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(std::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 == "" { 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.get_word(self.min_unit, 0)); } } let ago = self.ago.unwrap_or_else(|| self.lang.ago()); if ago == "" { ret } else if !self.lang.place_ago_before() { format!("{} {}", ret, ago) } else { format!("{} {}", ago, ret) } } 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); if recurse_result == "" { format!("{} {}", x, self.lang.get_word(dtu, x)) } else { format!("{} {} {}", x, self.lang.get_word(dtu, x), recurse_result) } } } #[cfg_attr(rustfmt, rustfmt_skip)] 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 % 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 x > 0 && x < 60 => format!("{:02}sec", x), x if x >= 60 && x < 60 * 60 => format!("{:02}min", x / 60), x if x >= 60 * 60 && x < 60 * 60 * 24 => format!("{:02}hou", x / 60 / 60), x if x >= 60 * 60 * 24 && x < S_IN_MNTH => format!("{:02}day", x / 60 / 60 / 24), x if x >= S_IN_MNTH && x < 12 * S_IN_MNTH => format!("{:02}Mon", x / S_IN_MNTH), x if x >= 12 * S_IN_MNTH && x <= 99 * 12 * S_IN_MNTH => { 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.4.1/src/main.rs000064400000000000000000000020421046102023000133320ustar 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))); } }