diff --git a/Cargo.lock b/Cargo.lock index 4477b5f..95bd611 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -901,6 +901,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" +[[package]] +name = "calendrical_calculations" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97f73e95d668625c9b28a3072e6326773785a0cf807de9f3d632778438f3d38" +dependencies = [ + "core_maths", + "displaydoc", +] + [[package]] name = "calloop" version = "0.13.0" @@ -1413,6 +1423,15 @@ dependencies = [ "libc", ] +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + [[package]] name = "cosmic-client-toolkit" version = "0.1.0" @@ -1476,6 +1495,7 @@ dependencies = [ "glob", "i18n-embed", "i18n-embed-fl", + "icu", "icu_collator", "icu_provider", "ignore", @@ -2223,6 +2243,17 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "fixed_decimal" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8" +dependencies = [ + "displaydoc", + "smallvec", + "writeable", +] + [[package]] name = "flate2" version = "1.1.0" @@ -3250,6 +3281,75 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "icu" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502" +dependencies = [ + "icu_calendar", + "icu_casemap", + "icu_collator", + "icu_collections", + "icu_datetime", + "icu_decimal", + "icu_experimental", + "icu_list", + "icu_locid", + "icu_locid_transform", + "icu_normalizer", + "icu_plurals", + "icu_properties", + "icu_provider", + "icu_segmenter", + "icu_timezone", +] + +[[package]] +name = "icu_calendar" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff" +dependencies = [ + "calendrical_calculations", + "displaydoc", + "icu_calendar_data", + "icu_locid", + "icu_locid_transform", + "icu_provider", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_calendar_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "820499e77e852162190608b4f444e7b4552619150eafc39a9e39333d9efae9e1" + +[[package]] +name = "icu_casemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f" +dependencies = [ + "displaydoc", + "icu_casemap_data", + "icu_collections", + "icu_locid", + "icu_properties", + "icu_provider", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_casemap_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd9f6276270c85a5cd54611adbbf94e993ec464a2a86a452a6c565b7ded5d9" + [[package]] name = "icu_collator" version = "1.5.0" @@ -3287,6 +3387,112 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_datetime" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780" +dependencies = [ + "displaydoc", + "either", + "fixed_decimal", + "icu_calendar", + "icu_datetime_data", + "icu_decimal", + "icu_locid", + "icu_locid_transform", + "icu_plurals", + "icu_provider", + "icu_timezone", + "litemap", + "smallvec", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_datetime_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef5f04076123cab1b7a926a7083db27fe0d7a0e575adb984854aae3f3a6507d" + +[[package]] +name = "icu_decimal" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_decimal_data", + "icu_locid_transform", + "icu_provider", + "writeable", +] + +[[package]] +name = "icu_decimal_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c95dd97f5ccf6d837a9c115496ec7d36646fa86ca18e7f1412115b4c820ae2" + +[[package]] +name = "icu_experimental" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "844ad7b682a165c758065d694bc4d74ac67f176da1c499a04d85d492c0f193b7" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_collections", + "icu_decimal", + "icu_experimental_data", + "icu_locid", + "icu_locid_transform", + "icu_normalizer", + "icu_pattern", + "icu_plurals", + "icu_properties", + "icu_provider", + "litemap", + "num-bigint", + "num-rational", + "num-traits", + "smallvec", + "tinystr", + "writeable", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_experimental_data" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121df92eafb8f5286d4e8ff401c1e7db8384377f806db3f8db77b91e5b7bd4dd" + +[[package]] +name = "icu_list" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfeda1d7775b6548edd4e8b7562304a559a91ed56ab56e18961a053f367c365" +dependencies = [ + "displaydoc", + "icu_list_data", + "icu_locid_transform", + "icu_provider", + "regex-automata 0.2.0", + "writeable", +] + +[[package]] +name = "icu_list_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b1a7fbdbf3958f1be8354cb59ac73f165b7b7082d447ff2090355c9a069120" + [[package]] name = "icu_locid" version = "1.5.0" @@ -3344,6 +3550,39 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +[[package]] +name = "icu_pattern" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f36aafd098d6717de34e668a8120822275c1fba22b936e757b7de8a2fd7e4" +dependencies = [ + "displaydoc", + "either", + "writeable", + "yoke", + "zerofrom", +] + +[[package]] +name = "icu_plurals" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_locid_transform", + "icu_plurals_data", + "icu_provider", + "zerovec", +] + +[[package]] +name = "icu_plurals_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a483403238cb7d6a876a77a5f8191780336d80fe7b8b00bfdeb20be6abbfd112" + [[package]] name = "icu_properties" version = "1.5.1" @@ -3393,6 +3632,49 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "icu_segmenter" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" +dependencies = [ + "core_maths", + "displaydoc", + "icu_collections", + "icu_locid", + "icu_provider", + "icu_segmenter_data", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e52775179941363cc594e49ce99284d13d6948928d8e72c755f55e98caa1eb" + +[[package]] +name = "icu_timezone" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91ba6a585939a020c787235daa8aee856d9bceebd6355e283c0c310bc6de96" +dependencies = [ + "displaydoc", + "icu_calendar", + "icu_provider", + "icu_timezone_data", + "tinystr", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_timezone_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adcf7b613a268af025bc2a2532b4b9ee294e6051c5c0832d8bff20ac0232e68" + [[package]] name = "ident_case" version = "1.0.1" @@ -5556,6 +5838,15 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782" +dependencies = [ + "memchr", +] + [[package]] name = "regex-automata" version = "0.4.9" @@ -7839,6 +8130,9 @@ name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +dependencies = [ + "either", +] [[package]] name = "x11-dl" @@ -8280,6 +8574,17 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "zerotrie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index 699d15d..3606447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,11 @@ vergen = { version = "8", features = ["git", "gitcl"] } [dependencies] chrono = { version = "0.4", features = ["unstable-locales"] } +icu = { version = "1.5.0", features = [ + "experimental", + "compiled_data", + "icu_datetime_experimental", +] } # Completion-based IO runtime to enable io_uring / IOCP file IO support. compio = { version = "0.14.0", features = ["io", "macros", "runtime"] } cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true } diff --git a/src/app.rs b/src/app.rs index a76ee8f..f29db82 100644 --- a/src/app.rs +++ b/src/app.rs @@ -68,7 +68,10 @@ use wayland_client::{protocol::wl_output::WlOutput, Proxy}; use crate::{ clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, - config::{AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig, TypeToSearch}, + config::{ + AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig, TimeConfig, TypeToSearch, + TIME_CONFIG_ID, + }, dialog::{Dialog, DialogKind, DialogMessage, DialogResult}, fl, home_dir, key_bind::key_binds, @@ -371,6 +374,7 @@ pub enum Message { Option>, ), TabView(Option, tab::View), + TimeConfigChange(TimeConfig), ToggleContextPage(ContextPage), ToggleFoldersFirst, Undo(usize), @@ -3499,6 +3503,10 @@ impl Application for App { config.view = view; return self.update(Message::TabConfig(config)); } + Message::TimeConfigChange(time_config) => { + self.config.tab.military_time = time_config.military_time; + return self.update_config(); + } Message::ToggleContextPage(context_page) => { //TODO: ensure context menus are closed if self.context_page == context_page @@ -5063,6 +5071,7 @@ impl Application for App { struct ThemeSubscription; struct WatcherSubscription; struct TrashWatcherSubscription; + struct TimeSubscription; let mut subscriptions = vec![ event::listen_with(|event, status, window_id| match event { @@ -5127,6 +5136,21 @@ impl Application for App { } Message::SystemThemeModeChange(update.config) }), + cosmic_config::config_subscription::<_, TimeConfig>( + TypeId::of::(), + TIME_CONFIG_ID.into(), + 1, + ) + .map(|update| { + if !update.errors.is_empty() { + log::info!( + "errors loading time config {:?}: {:?}", + update.keys, + update.errors + ); + } + Message::TimeConfigChange(update.config) + }), Subscription::run_with_id( TypeId::of::(), stream::channel(100, |mut output| async move { diff --git a/src/config.rs b/src/config.rs index 959c35a..b3c467a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ use std::{any::TypeId, num::NonZeroU16, path::PathBuf}; use cosmic::{ - cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, ConfigGet, CosmicConfigEntry}, + cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry}, iced::Subscription, theme, Application, }; @@ -205,7 +205,7 @@ pub struct TabConfig { pub show_hidden: bool, /// Icon zoom pub icon_sizes: IconSizes, - #[serde(skip, default = "military_time_enabled")] + #[serde(skip)] /// 24 hour clock; this is neither serialized nor deserialized because we use the user's global /// preference rather than save it pub military_time: bool, @@ -220,27 +220,12 @@ impl Default for TabConfig { folders_first: true, show_hidden: false, icon_sizes: IconSizes::default(), - military_time: military_time_enabled(), + military_time: false, single_click: false, } } } -/// Return whether the user enabled military time via the Time applet. -fn military_time_enabled() -> bool { - // Borrowed from COSMIC Greeter - match cosmic_config::Config::new("com.system76.CosmicAppletTime", 1) { - Ok(config_handler) => config_handler.get("military_time").unwrap_or_default(), - Err(err) => { - log::error!( - "failed to create CosmicAppletTime config handler: {:?}", - err - ); - false - } - } -} - #[derive(Clone, Copy, Debug, Eq, PartialEq, CosmicConfigEntry, Deserialize, Serialize)] #[serde(default)] pub struct IconSizes { @@ -270,3 +255,11 @@ impl IconSizes { percent!(self.grid, ICON_SIZE_GRID) as _ } } + +pub const TIME_CONFIG_ID: &str = "com.system76.CosmicAppletTime"; + +#[derive(Debug, Default, Clone, CosmicConfigEntry, PartialEq, Eq)] +#[version = 1] +pub struct TimeConfig { + pub military_time: bool, +} diff --git a/src/dialog.rs b/src/dialog.rs index fe718be..e226dea 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -36,7 +36,7 @@ use std::{ use crate::{ app::{Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind}, - config::{Config, Favorite, IconSizes, TabConfig}, + config::{Config, Favorite, IconSizes, TabConfig, TimeConfig, TIME_CONFIG_ID}, fl, home_dir, key_bind::key_binds, localize::LANGUAGE_SORTER, @@ -399,6 +399,7 @@ enum Message { TabMessage(tab::Message), TabRescan(Location, Option, Vec), TabView(tab::View), + TimeConfigChange(TimeConfig), ToggleFoldersFirst, ZoomDefault, ZoomIn, @@ -686,7 +687,10 @@ impl App { fn update_config(&mut self) -> Task { self.update_nav_model(); - Task::none() + + self.update(Message::TabMessage(tab::Message::Config( + self.flags.config.tab, + ))) } fn activate_nav_model_location(&mut self, location: &Location) { @@ -1658,6 +1662,10 @@ impl Application for App { Message::TabView(view) => { self.tab.config.view = view; } + Message::TimeConfigChange(time_config) => { + self.flags.config.tab.military_time = time_config.military_time; + return self.update_config(); + } Message::ToggleFoldersFirst => { self.tab.config.folders_first = !self.tab.config.folders_first; } @@ -1741,6 +1749,7 @@ impl Application for App { fn subscription(&self) -> Subscription { struct WatcherSubscription; + struct TimeSubscription; let mut subscriptions = vec![ event::listen_with(|event, status, _window_id| match event { Event::Keyboard(KeyEvent::KeyPressed { key, modifiers, .. }) => match status { @@ -1765,6 +1774,21 @@ impl Application for App { } Message::Config(update.config) }), + cosmic_config::config_subscription::<_, TimeConfig>( + TypeId::of::(), + TIME_CONFIG_ID.into(), + 1, + ) + .map(|update| { + if !update.errors.is_empty() { + log::info!( + "errors loading time config {:?}: {:?}", + update.keys, + update.errors + ); + } + Message::TimeConfigChange(update.config) + }), Subscription::run_with_id( TypeId::of::(), stream::channel(100, |mut output| async move { diff --git a/src/localize.rs b/src/localize.rs index 6102ff3..86cced1 100644 --- a/src/localize.rs +++ b/src/localize.rs @@ -6,6 +6,7 @@ use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, DefaultLocalizer, LanguageLoader, Localizer, }; +use icu::locid::Locale; use icu_collator::{Collator, CollatorOptions, Numeric}; use icu_provider::DataLocale; use once_cell::sync::Lazy; @@ -40,18 +41,29 @@ pub static LANGUAGE_SORTER: Lazy = Lazy::new(|| { .expect("Creating a collator from the system's current language, the fallback language, or American English should succeed") }); -pub static LANGUAGE_CHRONO: Lazy = Lazy::new(|| { - std::env::var("LC_TIME") - .or_else(|_| std::env::var("LANG")) - .ok() - .and_then(|locale_full| { - // Split LANG because it may be set to a locale such as en_US.UTF8 - locale_full - .split('.') - .next() - .and_then(|locale| chrono::Locale::from_str(locale).ok()) - }) - .unwrap_or(chrono::Locale::en_US) +pub static LOCALE: Lazy = Lazy::new(|| { + fn get_local() -> Result> { + let locale = std::env::var("LC_TIME").or_else(|_| std::env::var("LANG"))?; + + let locale = locale + .split('.') + .next() + .ok_or(format!("Can't split the locale {locale}"))?; + + let locale = Locale::from_str(locale).map_err(|e| format!("{e:?}"))?; + + Ok(locale) + } + + match get_local() { + Ok(locale) => locale, + + Err(e) => { + log::error!("can't get locale {e}"); + + Locale::default() + } + } }); #[macro_export] diff --git a/src/tab.rs b/src/tab.rs index 68a0e3a..b6436da 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -37,8 +37,12 @@ use cosmic::{ Element, }; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Datelike, Timelike, Utc}; use i18n_embed::LanguageLoader; +use icu::datetime::{ + options::{components, preferences}, + DateTimeFormatter, DateTimeFormatterOptions, +}; use mime_guess::{mime, Mime}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -64,7 +68,7 @@ use crate::{ config::{DesktopConfig, IconSizes, TabConfig, ICON_SCALE_MAX, ICON_SIZE_GRID}, dialog::DialogKind, fl, - localize::{LANGUAGE_CHRONO, LANGUAGE_SORTER}, + localize::{LANGUAGE_SORTER, LOCALE}, menu, mime_app, mime_icon::{mime_for_path, mime_icon}, mounter::MOUNTERS, @@ -85,11 +89,6 @@ const THUMBNAIL_SIZE: u32 = (ICON_SIZE_GRID as u32) * (ICON_SCALE_MAX as u32); const DRAG_SCROLL_DISTANCE: f32 = 15.0; -//TODO: adjust for locales? -const DATE_TIME_FORMAT: &str = "%b %-d, %-Y, %-I:%M %p"; -const DATE_TIME_FORMAT_MILITARY: &str = "%b %-d, %-Y, %-H:%M %p"; -const TIME_FORMAT: &str = "%-I:%M %p"; -const TIME_FORMAT_MILITARY: &str = "%-H:%M %p"; static SPECIAL_DIRS: Lazy> = Lazy::new(|| { let mut special_dirs = HashMap::new(); if let Some(dir) = dirs::document_dir() { @@ -382,13 +381,50 @@ fn format_permissions(metadata: &Metadata, owner: PermissionOwner) -> String { } } -struct FormatTime { - pub time: SystemTime, - pub military_time: bool, +fn date_time_formatter(military_time: bool) -> DateTimeFormatter { + let mut bag = components::Bag::empty(); + bag.day = Some(components::Day::NumericDayOfMonth); + bag.month = Some(components::Month::Short); + bag.year = Some(components::Year::Numeric); + bag = bag.merge(time_bag(military_time)); + let options = DateTimeFormatterOptions::Components(bag); + + DateTimeFormatter::try_new_experimental(&LOCALE.as_ref().into(), options) + .expect("failed to create DateTimeFormatter") } -impl FormatTime { - fn from_secs(secs: i64, military_time: bool) -> Option { +fn time_formatter(military_time: bool) -> DateTimeFormatter { + let options = DateTimeFormatterOptions::Components(time_bag(military_time)); + + DateTimeFormatter::try_new_experimental(&LOCALE.as_ref().into(), options) + .expect("failed to create DateTimeFormatter") +} + +fn time_bag(military_time: bool) -> components::Bag { + let mut bag = components::Bag::empty(); + bag.hour = Some(components::Numeric::Numeric); + bag.minute = Some(components::Numeric::Numeric); + let hour_cyle = if military_time { + preferences::HourCycle::H23 + } else { + preferences::HourCycle::H12 + }; + bag.preferences = Some(preferences::Bag::from_hour_cycle(hour_cyle)); + bag +} + +struct FormatTime<'a> { + pub time: SystemTime, + pub date_time_formatter: &'a DateTimeFormatter, + pub time_formatter: &'a DateTimeFormatter, +} + +impl<'a> FormatTime<'a> { + fn from_secs( + secs: i64, + date_time_formatter: &'a DateTimeFormatter, + time_formatter: &'a DateTimeFormatter, + ) -> Option { // This looks convoluted because we need to ensure the units match up let secs: u64 = secs.try_into().ok()?; let now = SystemTime::now(); @@ -400,48 +436,57 @@ impl FormatTime { .map(Duration::from_secs)?; now.checked_sub(filetime_diff).map(|time| Self { time, - military_time, + date_time_formatter, + time_formatter, }) } } -impl Display for FormatTime { +impl Display for FormatTime<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let date_time = chrono::DateTime::::from(self.time); + let datetime = chrono::DateTime::::from(self.time); let now = chrono::Local::now(); - if date_time.date_naive() == now.date_naive() { + let icu_datetime = icu::calendar::DateTime::try_new_iso_datetime( + datetime.year(), + datetime.month() as u8, + datetime.day() as u8, + datetime.hour() as u8, + datetime.minute() as u8, + datetime.second() as u8, + ) + .expect("failed to construct DateTime") + .to_any(); + + if datetime.date_naive() == now.date_naive() { write!( f, "{}, {}", fl!("today"), - date_time.format_localized( - if self.military_time { - TIME_FORMAT_MILITARY - } else { - TIME_FORMAT - }, - *LANGUAGE_CHRONO - ) + self.time_formatter + .format(&icu_datetime) + .map_err(|_| fmt::Error)? ) } else { - date_time - .format_localized( - if self.military_time { - DATE_TIME_FORMAT_MILITARY - } else { - DATE_TIME_FORMAT - }, - *LANGUAGE_CHRONO, - ) - .fmt(f) + write!( + f, + "{}", + self.date_time_formatter + .format(&icu_datetime) + .map_err(|_| fmt::Error)? + ) } } } -const fn format_time(time: SystemTime, military_time: bool) -> FormatTime { +const fn format_time<'a>( + time: SystemTime, + date_time_formatter: &'a DateTimeFormatter, + time_formatter: &'a DateTimeFormatter, +) -> FormatTime<'a> { FormatTime { time, - military_time, + date_time_formatter, + time_formatter, } } @@ -1593,24 +1638,30 @@ impl Item { ))); } + let date_time_formatter = date_time_formatter(military_time); + let time_formatter = time_formatter(military_time); + if let Ok(time) = metadata.created() { details = details.push(widget::text::body(fl!( "item-created", - created = format_time(time, military_time).to_string() + created = + format_time(time, &date_time_formatter, &time_formatter).to_string() ))); } if let Ok(time) = metadata.modified() { details = details.push(widget::text::body(fl!( "item-modified", - modified = format_time(time, military_time).to_string() + modified = + format_time(time, &date_time_formatter, &time_formatter).to_string() ))); } if let Ok(time) = metadata.accessed() { details = details.push(widget::text::body(fl!( "item-accessed", - accessed = format_time(time, military_time).to_string() + accessed = + format_time(time, &date_time_formatter, &time_formatter).to_string() ))); } @@ -1703,9 +1754,12 @@ impl Item { ))); } if let Ok(time) = metadata.modified() { + let date_time_formatter = date_time_formatter(military_time); + let time_formatter = time_formatter(military_time); + column = column.push(widget::text::body(format!( "Last modified: {}", - format_time(time, military_time) + format_time(time, &date_time_formatter, &time_formatter) ))); } } @@ -1827,6 +1881,8 @@ pub struct Tab { last_scroll_position: Option, last_scroll_offset: Option, scroll_bounds_opt: Option, + date_time_formatter: DateTimeFormatter, + time_formatter: DateTimeFormatter, } async fn calculate_dir_size(path: &Path, controller: Controller) -> Result { @@ -1921,6 +1977,8 @@ impl Tab { last_scroll_position: None, last_scroll_offset: None, scroll_bounds_opt: None, + date_time_formatter: date_time_formatter(config.military_time), + time_formatter: time_formatter(config.military_time), } } @@ -2523,9 +2581,14 @@ impl Tab { // View is preserved for existing tabs let view = self.config.view; let show_hidden = self.config.show_hidden; + let military_time_changed = self.config.military_time != config.military_time; self.config = config; self.config.view = view; self.config.show_hidden = show_hidden; + if military_time_changed { + self.date_time_formatter = date_time_formatter(self.config.military_time); + self.time_formatter = time_formatter(self.config.military_time); + } } Message::ContextAction(action) => { // Close context menu @@ -4386,17 +4449,18 @@ impl Tab { y += 1; } - let military_time = self.config.military_time; let modified_text = match &item.metadata { ItemMetadata::Path { metadata, .. } => match metadata.modified() { - Ok(time) => format_time(time, military_time).to_string(), + Ok(time) => self.format_time(time).to_string(), Err(_) => String::new(), }, - ItemMetadata::Trash { entry, .. } => { - FormatTime::from_secs(entry.time_deleted, military_time) - .map(|t| t.to_string()) - .unwrap_or_default() - } + ItemMetadata::Trash { entry, .. } => FormatTime::from_secs( + entry.time_deleted, + &self.date_time_formatter, + &self.time_formatter, + ) + .map(|t| t.to_string()) + .unwrap_or_default(), _ => String::new(), }; @@ -5102,6 +5166,10 @@ impl Tab { Subscription::batch(subscriptions) } + + fn format_time<'a>(&'a self, time: SystemTime) -> FormatTime<'a> { + format_time(time, &self.date_time_formatter, &self.time_formatter) + } } pub fn respond_to_scroll_direction(delta: ScrollDelta, modifiers: Modifiers) -> Option {