diff --git a/Cargo.lock b/Cargo.lock index 223dac78dd..6451874335 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,6 +312,16 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "calendrical_calculations" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dfe3bc6a50b4667fafdb6d9cf26731c5418c457e317d8166c972014facf9a5d" +dependencies = [ + "core_maths", + "displaydoc", +] + [[package]] name = "calibright" version = "0.1.4" @@ -475,6 +485,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "core_maths" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" +dependencies = [ + "libm", +] + [[package]] name = "cpufeatures" version = "0.2.9" @@ -630,6 +649,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "either" version = "1.9.0" @@ -739,6 +769,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "fixed_decimal" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5287d527037d0f35c8801880361eb38bb9bce194805350052c2a79538388faeb" +dependencies = [ + "displaydoc", + "smallvec", + "writeable", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1067,6 +1108,9 @@ dependencies = [ "futures", "glob", "hyper", + "icu_calendar", + "icu_datetime", + "icu_locid", "inotify 0.10.2", "libc", "libpulse-binding", @@ -1121,6 +1165,183 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_calendar" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b520c5675775e3838447c33fc55bf558148c6824ef0d20ff7a9e0df7345a281c" +dependencies = [ + "calendrical_calculations", + "displaydoc", + "icu_calendar_data", + "icu_locid", + "icu_locid_transform", + "icu_provider", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_calendar_data" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d8d1a514ca7e6dc547be930f2fd661d578909c07cf1c1adade81c3f7a78840" + +[[package]] +name = "icu_datetime" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5bf2e6dd961b59ee5935070220915db6cf0ab5137de362964f800c2b7d14fa" +dependencies = [ + "displaydoc", + "either", + "fixed_decimal", + "icu_calendar", + "icu_datetime_data", + "icu_decimal", + "icu_locid", + "icu_locid_transform", + "icu_plurals", + "icu_provider", + "icu_timezone", + "smallvec", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_datetime_data" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced82224d980ffebafebf443a85c062ac6e801a24415324d0f25962b088f55f4" + +[[package]] +name = "icu_decimal" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1986a0b7df834aaddb911b4593c990950ac5606fc83ce9aad4311be80f51e81a" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_decimal_data", + "icu_locid", + "icu_locid_transform", + "icu_provider", + "writeable", +] + +[[package]] +name = "icu_decimal_data" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20116c22b56b74384904ecd5e061fa7ece6e3eb26a48c524fc490ec8f46d26a2" + +[[package]] +name = "icu_locid" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f284eb342dc49d3e9d9f3b188489d76b5d22dfb1d1a5e0d1941811253bac625c" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6551daf80882d8e68eee186cc19e132d8bde1b1f059a79b93384a5ca0e8fc5e7" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a741eba5431f75eb2f1f9022d3cffabcadda6771e54fb4e77c8ba8653e4da44" + +[[package]] +name = "icu_plurals" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20556516b8be2b2f5dc3d6b23884b65c5c59ed8be0b44c419e4808c9b0792fce" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_locid", + "icu_locid_transform", + "icu_plurals_data", + "icu_provider", + "zerovec", +] + +[[package]] +name = "icu_plurals_data" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc552215224997aaaa4e05d95981386d3c52042acebfcc732137d5d9be96a21" + +[[package]] +name = "icu_provider" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68acdef80034b5e35d8524e9817479d389a4f9774f3f0cbe1bf3884d80fd5934" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2060258edfcfe32ca7058849bf0f146cb5c59aadbedf480333c0d0002f97bc99" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "icu_timezone" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e6401cd210ccda98b2e7fc707831b29c6efe319efbbec460f957b6f331f626" +dependencies = [ + "displaydoc", + "icu_calendar", + "icu_locid", + "icu_provider", + "icu_timezone_data", + "tinystr", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_timezone_data" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7e214a653bac59b768c42f82d252f13af95e8a9cb07b6108b8bc723c561b43" + [[package]] name = "ident_case" version = "1.0.1" @@ -1285,6 +1506,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libpulse-binding" version = "2.28.1" @@ -1330,6 +1557,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +[[package]] +name = "litemap" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a1a2647d5b7134127971a6de0d533c49de2159167e7f259c427195f87168a1" + [[package]] name = "log" version = "0.4.20" @@ -2189,6 +2422,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + [[package]] name = "smart-default" version = "0.7.1" @@ -2220,6 +2459,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2285,6 +2530,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "unicode-xid", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -2355,6 +2612,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0e245e80bdc9b4e5356fc45a72184abbc3861992603f515270e9340f5a219" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2570,6 +2837,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "url" version = "2.4.1" @@ -2931,6 +3204,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "writeable" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0af0c3d13faebf8dda0b5256fa7096a2d5ccb662f7b9f54a40fe201077ab1c2" + [[package]] name = "xdg-home" version = "1.0.0" @@ -2952,6 +3231,30 @@ dependencies = [ "pandoc", ] +[[package]] +name = "yoke" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e38c508604d6bbbd292dadb3c02559aa7fff6b654a078a36217cad871636e4" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5e19fb6ed40002bab5403ffa37e53e0e56f914a4450c8765f533018db1db35f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "synstructure", +] + [[package]] name = "zbus" version = "3.14.1" @@ -3013,6 +3316,60 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerofrom" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655b0814c5c0b19ade497851070c640773304939a6c0fd5f5fb43da0696d05b7" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a647510471d372f2e6c2e6b7219e44d8c574d24fdc11c610a61455782f18c3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9685bb4deb98dab812e87c296a9631fc00d7ca4bc5c2c5f304f375bbed711a8a" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1194130c5b155bf8ae50ab16c86ab758cd695cf9ad176d2f870b744cbdbb572e" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acabf549809064225ff8878baedc4ce3732ac3b07e7c7ce6e5c2ccdbc485c324" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "zvariant" version = "3.15.0" diff --git a/Cargo.toml b/Cargo.toml index f1ce11552d..0b7a1d7184 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,9 @@ env_logger = "0.10" futures = { version = "0.3", default-features = false } glob = { version = "0.3.1", optional = true } hyper = "0.14" +icu_datetime = "1.3.0" +icu_calendar = "1.3.0" +icu_locid = "1.3.0" inotify = "0.10" libc = "0.2" libpulse-binding = { version = "2.0", default-features = false, optional = true } diff --git a/cspell.yaml b/cspell.yaml index ce603512c0..09d3eb952f 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -79,6 +79,7 @@ words: - kmon - libc - liquidctl + - locid - macchiato - maildir - mebi diff --git a/src/blocks/time.rs b/src/blocks/time.rs index 3455174a90..f406da2bcc 100644 --- a/src/blocks/time.rs +++ b/src/blocks/time.rs @@ -29,6 +29,21 @@ //! short = " $icon $timestamp.datetime(f:%R) " //! ``` //! +//! # Non Gregorian calendars +//! +//! You can use calendars other than the Gregorian calendar by adding the calendar specifier in the locale string. When using +//! this feature you can't use chrono style format string, and you should use one of the options provided by +//! the `icu4x` crate: `short`, `medium`, `long`, `full`. +//! +//! ## Example +//! +//! ```toml +//! [[block]] +//! block = "time" +//! interval = 60 +//! format = "$timestamp.datetime(locale:'fa_IR-u-ca-persian', f:'full')" +//! ``` +//! //! # Icons Used //! - `time` diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs index 4d5b751fa4..ee9e34d37b 100644 --- a/src/formatting/formatter.rs +++ b/src/formatting/formatter.rs @@ -1,10 +1,11 @@ use chrono::format::{Item, StrftimeItems}; -use chrono::{Local, Locale}; +use chrono::{DateTime, Datelike, Local, Locale, TimeZone}; use once_cell::sync::Lazy; use unicode_segmentation::UnicodeSegmentation; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::iter::repeat; +use std::str::FromStr; use std::time::{Duration, Instant}; use super::parse::Arg; @@ -53,7 +54,7 @@ pub const DEFAULT_NUMBER_FORMATTER: EngFormatter = EngFormatter(EngFixConfig { }); pub static DEFAULT_DATETIME_FORMATTER: Lazy = - Lazy::new(|| DatetimeFormatter::new(DEFAULT_DATETIME_FORMAT, None).unwrap()); + Lazy::new(|| DatetimeFormatter::new(Some(DEFAULT_DATETIME_FORMAT), None).unwrap()); pub const DEFAULT_FLAG_FORMATTER: FlagFormatter = FlagFormatter; @@ -177,10 +178,7 @@ pub fn new_formatter(name: &str, args: &[Arg]) -> Result> { } } - Ok(Box::new(DatetimeFormatter::new( - format.unwrap_or(DEFAULT_DATETIME_FORMAT), - locale, - )?)) + Ok(Box::new(DatetimeFormatter::new(format, locale)?)) } _ => Err(Error::new(format!("Unknown formatter: '{name}'"))), } @@ -498,9 +496,15 @@ impl Formatter for FixFormatter { } #[derive(Debug)] -pub struct DatetimeFormatter { - items: Vec>, - locale: Option, +pub enum DatetimeFormatter { + Chrono { + items: Vec>, + locale: Option, + }, + Icu { + length: icu_datetime::options::length::Date, + locale: icu_locid::Locale, + }, } fn make_static_item(item: Item<'_>) -> Item<'static> { @@ -516,16 +520,38 @@ fn make_static_item(item: Item<'_>) -> Item<'static> { } impl DatetimeFormatter { - fn new(format: &str, locale: Option<&str>) -> Result { + fn new(format: Option<&str>, locale: Option<&str>) -> Result { let (items, locale) = match locale { Some(locale) => { - let locale = locale.try_into().ok().error("invalid locale")?; - (StrftimeItems::new_with_locale(format, locale), Some(locale)) + let Ok(locale) = locale.try_into() else { + // try with icu4x + let locale = icu_locid::Locale::from_str(locale) + .ok() + .error("invalid locale")?; + let length = match format { + Some("full") => icu_datetime::options::length::Date::Full, + None | Some("long") => icu_datetime::options::length::Date::Long, + Some("medium") => icu_datetime::options::length::Date::Medium, + Some("short") => icu_datetime::options::length::Date::Short, + _ => return Err(Error::new("Unknown format option for icu based locale")), + }; + return Ok(Self::Icu { locale, length }); + }; + ( + StrftimeItems::new_with_locale( + format.unwrap_or(DEFAULT_DATETIME_FORMAT), + locale, + ), + Some(locale), + ) } - None => (StrftimeItems::new(format), None), + None => ( + StrftimeItems::new(format.unwrap_or(DEFAULT_DATETIME_FORMAT)), + None, + ), }; - Ok(Self { + Ok(Self::Chrono { items: items.map(make_static_item).collect(), locale, }) @@ -534,26 +560,44 @@ impl DatetimeFormatter { impl Formatter for DatetimeFormatter { fn format(&self, val: &Value, _config: &SharedConfig) -> Result { + fn for_generic_datetime( + this: &DatetimeFormatter, + datetime: DateTime, + ) -> Result + where + T: TimeZone, + T::Offset: Display, + { + Ok(match this { + DatetimeFormatter::Chrono { items, locale } => match *locale { + Some(locale) => datetime.format_localized_with_items(items.iter(), locale), + None => datetime.format_with_items(items.iter()), + } + .to_string(), + DatetimeFormatter::Icu { locale, length } => { + let date = icu_calendar::Date::try_new_iso_date( + datetime.year_ce().1 as i32, + datetime.month() as u8, + datetime.day() as u8, + ) + .ok() + .error("Current date should be a valid date")?; + let date = date.to_any(); + let dft = + icu_datetime::DateFormatter::try_new_with_length(&locale.into(), *length) + .ok() + .error("locale should be present in compiled data")?; + dft.format_to_string(&date) + .ok() + .error("formatting date using icu failed")? + } + }) + } match val { - Value::Datetime(datetime, timezone) => Ok(match self.locale { - Some(locale) => match timezone { - Some(tz) => datetime - .with_timezone(tz) - .format_localized_with_items(self.items.iter(), locale), - None => datetime - .with_timezone(&Local) - .format_localized_with_items(self.items.iter(), locale), - }, - None => match timezone { - Some(tz) => datetime - .with_timezone(tz) - .format_with_items(self.items.iter()), - None => datetime - .with_timezone(&Local) - .format_with_items(self.items.iter()), - }, - } - .to_string()), + Value::Datetime(datetime, timezone) => match timezone { + Some(tz) => for_generic_datetime(self, datetime.with_timezone(tz)), + None => for_generic_datetime(self, datetime.with_timezone(&Local)), + }, other => Err(FormatError::IncompatibleFormatter { ty: other.type_name(), fmt: "datetime",