fix(time): Update applet timezone on change

Closes: #582

The chrono crate caches the local timezone but doesn't update it. This
makes sense because it'd be inefficient to constantly evaluate the local
timezone.

Instead of using the local timezone, this patch changes the applet to
use a fixed offset internally which is updated if the external timezone
changes.
This commit is contained in:
Josh Megnauth 2024-08-17 00:40:10 -04:00 committed by Michael Murphy
parent f43705d31f
commit 323e8a55b2
3 changed files with 139 additions and 5 deletions

52
Cargo.lock generated
View file

@ -706,6 +706,28 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "chrono-tz"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
dependencies = [
"chrono",
"chrono-tz-build",
"phf",
]
[[package]]
name = "chrono-tz-build"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
dependencies = [
"parse-zoneinfo",
"phf",
"phf_codegen",
]
[[package]]
name = "clipboard-win"
version = "5.4.0"
@ -1141,16 +1163,19 @@ name = "cosmic-applet-time"
version = "0.1.0"
dependencies = [
"chrono",
"chrono-tz",
"i18n-embed 0.14.1",
"i18n-embed-fl 0.8.0",
"icu",
"libcosmic",
"once_cell",
"rust-embed 8.5.0",
"timedate-zbus",
"tokio",
"tracing",
"tracing-log",
"tracing-subscriber",
"zbus 4.3.1",
]
[[package]]
@ -4436,6 +4461,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "parse-zoneinfo"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
dependencies = [
"regex",
]
[[package]]
name = "paste"
version = "1.0.15"
@ -4458,6 +4492,16 @@ dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
@ -5705,6 +5749,14 @@ dependencies = [
"time-core",
]
[[package]]
name = "timedate-zbus"
version = "0.1.0"
source = "git+https://github.com/pop-os/dbus-settings-bindings#cd21ddcb1b5cbfc80ab84b34d3c8b1ff3d81179a"
dependencies = [
"zbus 4.3.1",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"

View file

@ -6,6 +6,7 @@ license = "GPL-3.0"
[dependencies]
chrono = { version = "0.4.35", features = ["clock"] }
chrono-tz = "0.9"
i18n-embed-fl.workspace = true
i18n-embed.workspace = true
libcosmic.workspace = true
@ -15,4 +16,6 @@ tokio = { version = "1.36.0", features = ["time"] }
tracing-log.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
icu = { version = "1.4.0", features = ["experimental", "compiled_data", "icu_datetime_experimental"]}
icu = { version = "1.4.0", features = ["experimental", "compiled_data", "icu_datetime_experimental"]}
zbus.workspace = true
timedate-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings" }

View file

@ -9,12 +9,14 @@ use cosmic::{
applet::{cosmic_panel_config::PanelAnchor, menu_button, padded_control},
cctk::sctk::reexports::calloop,
iced::{
futures::{FutureExt, TryFutureExt},
subscription,
wayland::popup::{destroy_popup, get_popup},
widget::{column, row, text, vertical_space},
window, Alignment, Length, Rectangle, Subscription,
},
iced_core::alignment::{Horizontal, Vertical},
iced_runtime::command,
iced_style::application,
iced_widget::{horizontal_rule, Column},
widget::{
@ -23,6 +25,8 @@ use cosmic::{
},
Command, Element, Theme,
};
use timedate_zbus::TimeDateProxy;
use zbus::Connection;
use icu::{
calendar::DateTime,
@ -47,17 +51,20 @@ use cosmic::applet::token::subscription::{
pub struct Window {
core: cosmic::app::Core,
popup: Option<window::Id>,
now: chrono::DateTime<chrono::Local>,
now: chrono::DateTime<chrono::FixedOffset>,
timezone: Option<chrono_tz::Tz>,
date_selected: chrono::NaiveDate,
rectangle_tracker: Option<RectangleTracker<u32>>,
rectangle: Rectangle,
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
config: TimeAppletConfig,
locale: Locale,
conn: Option<Connection>,
}
#[derive(Debug, Clone)]
pub enum Message {
Init(Option<Connection>),
TogglePopup,
CloseRequested(window::Id),
Tick,
@ -68,6 +75,7 @@ pub enum Message {
OpenDateTimeSettings,
Token(TokenUpdate),
ConfigChanged(TimeAppletConfig),
TimezoneUpdate(Option<String>),
}
impl Window {
@ -126,21 +134,28 @@ impl cosmic::Application for Window {
}
};
let now: chrono::prelude::DateTime<chrono::prelude::Local> = chrono::Local::now();
// Chrono evaluates the local timezone once whereby it's stored in a thread local
// variable but never updated
// Instead of using the local timezone, we will store an offset that is updated if the
// timezone is ever externally changed
let now: chrono::DateTime<chrono::FixedOffset> = chrono::Local::now().fixed_offset();
(
Self {
core,
popup: None,
now,
timezone: None,
date_selected: chrono::NaiveDate::from(now.naive_local()),
rectangle_tracker: None,
rectangle: Rectangle::default(),
token_tx: None,
config: TimeAppletConfig::default(),
locale,
conn: None,
},
Command::none(),
Command::single(command::Action::Future(Box::pin(Connection::system())))
.map(|res| Message::Init(res.ok()).into()),
)
}
@ -170,10 +185,57 @@ impl cosmic::Application for Window {
})
}
let conn = self.conn.clone();
fn timezone_subscription(conn: Option<Connection>) -> Subscription<Message> {
use cosmic::iced_futures::futures::StreamExt;
let Some(conn) = conn else {
return Subscription::none();
};
// Update applet's timezone if the system's timezone changes
subscription::unfold("timezone-sub", (), move |_| {
let conn = conn.clone();
async move {
TimeDateProxy::new(&conn)
.inspect_err(|e| {
tracing::error!("Failed to connect to timedate endpoint: {e:?}")
})
.then(|proxy| async move {
let Ok(proxy) = proxy else {
return (Message::TimezoneUpdate(None), ());
};
proxy
.receive_timezone_changed()
.then(|stream| stream.into_future())
.then(|(prop_opt, _)| async move {
if let Some(property) = prop_opt {
property
.get()
.await
.inspect_err(|e| {
tracing::error!(
"Failed to receive time zone update: {e:?}"
)
})
.ok()
.map(|tz| (Message::TimezoneUpdate(Some(tz)), ()))
.unwrap_or((Message::TimezoneUpdate(None), ()))
} else {
(Message::TimezoneUpdate(None), ())
}
})
.await
})
.await
}
})
}
Subscription::batch(vec![
rectangle_tracker_subscription(0).map(|e| Message::Rectangle(e.1)),
time_subscription().map(|_| Message::Tick),
activation_token_subscription(0).map(Message::Token),
timezone_subscription(conn),
self.core.watch_config(Self::APP_ID).map(|u| {
for err in u.errors {
tracing::error!(?err, "Error watching config");
@ -188,6 +250,10 @@ impl cosmic::Application for Window {
message: Self::Message,
) -> cosmic::iced::Command<app::Message<Self::Message>> {
match message {
Message::Init(conn) => {
self.conn = conn;
Command::none()
}
Message::TogglePopup => {
if let Some(p) = self.popup.take() {
destroy_popup(p)
@ -220,7 +286,10 @@ impl cosmic::Application for Window {
}
}
Message::Tick => {
self.now = chrono::Local::now();
self.now = self
.timezone
.map(|tz| chrono::Local::now().with_timezone(&tz).fixed_offset())
.unwrap_or_else(|| chrono::Local::now().into());
Command::none()
}
Message::Rectangle(u) => {
@ -306,6 +375,16 @@ impl cosmic::Application for Window {
self.config = c;
Command::none()
}
Message::TimezoneUpdate(timezone) => {
if let Some(timezone) =
timezone.and_then(|timezone| timezone.parse::<chrono_tz::Tz>().ok())
{
self.now = chrono::Local::now().with_timezone(&timezone).fixed_offset();
self.timezone = Some(timezone);
}
self.update(Message::Tick)
}
}
}