feat(time): formattable datetime strings with strftime

This commit is contained in:
Joshua Megnauth 2025-11-07 13:42:36 -05:00 committed by GitHub
parent ba1e379a75
commit bd98de8228
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 96 additions and 58 deletions

View file

@ -43,6 +43,11 @@ use icu::{
static AUTOSIZE_MAIN_ID: LazyLock<Id> = LazyLock::new(|| Id::new("autosize-main"));
// Specifiers for strftime that indicate seconds. Subsecond precision isn't supported by the applet
// so those specifiers aren't listed here. This list is non-exhaustive, and it's possible that %X
// and other specifiers have to be added depending on locales.
const STRFTIME_SECONDS: &[char] = &['S', 'T', '+', 's'];
fn get_system_locale() -> Locale {
for var in ["LC_TIME", "LC_ALL", "LANG"] {
if let Ok(locale_str) = std::env::var(var) {
@ -159,45 +164,58 @@ impl Window {
}
fn vertical_layout(&self) -> Element<'_, Message> {
let mut elements = Vec::new();
let date = self.now.naive_local();
let datetime = self.create_datetime(&date);
let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone());
prefs.hour_cycle = Some(if self.config.military_time {
HourCycle::H23
let elements: Vec<Element<'_, Message>> = if !self.config.format_strftime.is_empty() {
// strftime formatter may override locale specific elements so it stands alone rather
// than using ICU to determine a format.
self.now
.format(&self.config.format_strftime)
.to_string()
.split_whitespace()
.map(|piece| self.core.applet.text(piece.to_owned()).into())
.collect()
} else {
HourCycle::H12
});
let mut elements = Vec::new();
let date = self.now.naive_local();
let datetime = self.create_datetime(&date);
let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone());
prefs.hour_cycle = Some(if self.config.military_time {
HourCycle::H23
} else {
HourCycle::H12
});
if self.config.show_date_in_top_panel {
let formatted_date = DateTimeFormatter::try_new(prefs, fieldsets::MD::medium())
if self.config.show_date_in_top_panel {
let formatted_date = DateTimeFormatter::try_new(prefs, fieldsets::MD::medium())
.unwrap()
.format(&datetime)
.to_string();
for p in formatted_date.split_whitespace() {
elements.push(self.core.applet.text(p.to_owned()).into());
}
elements.push(
horizontal_rule(2)
.width(self.core.applet.suggested_size(true).0)
.into(),
);
}
let mut fs = fieldsets::T::medium();
if !self.config.show_seconds {
fs = fs.with_time_precision(TimePrecision::Minute);
}
let formatted_time = DateTimeFormatter::try_new(prefs, fs)
.unwrap()
.format(&datetime)
.to_string();
for p in formatted_date.split_whitespace() {
// todo: split using formatToParts when it is implemented
// https://github.com/unicode-org/icu4x/issues/4936#issuecomment-2128812667
for p in formatted_time.split_whitespace().flat_map(|s| s.split(':')) {
elements.push(self.core.applet.text(p.to_owned()).into());
}
elements.push(
horizontal_rule(2)
.width(self.core.applet.suggested_size(true).0)
.into(),
);
}
let mut fs = fieldsets::T::medium();
if !self.config.show_seconds {
fs = fs.with_time_precision(TimePrecision::Minute);
}
let formatted_time = DateTimeFormatter::try_new(prefs, fs)
.unwrap()
.format(&datetime)
.to_string();
// todo: split using formatToParts when it is implemented
// https://github.com/unicode-org/icu4x/issues/4936#issuecomment-2128812667
for p in formatted_time.split_whitespace().flat_map(|s| s.split(':')) {
elements.push(self.core.applet.text(p.to_owned()).into());
}
elements
};
let date_time_col = Column::with_children(elements)
.align_x(Alignment::Center)
@ -216,26 +234,39 @@ impl Window {
}
fn horizontal_layout(&self) -> Element<'_, Message> {
let datetime = self.create_datetime(&self.now);
let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone());
prefs.hour_cycle = Some(if self.config.military_time {
HourCycle::H23
let formatted_date = if !self.config.format_strftime.is_empty() {
self.now.format(&self.config.format_strftime).to_string()
} else {
HourCycle::H12
});
let formatted_date = if self.config.show_date_in_top_panel {
if self.config.show_weekday {
let mut fs = fieldsets::MDET::long();
if !self.config.show_seconds {
fs = fs.with_time_precision(TimePrecision::Minute);
}
DateTimeFormatter::try_new(prefs, fs)
.unwrap()
.format(&datetime)
.to_string()
let datetime = self.create_datetime(&self.now);
let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone());
prefs.hour_cycle = Some(if self.config.military_time {
HourCycle::H23
} else {
let mut fs = fieldsets::MDT::long();
HourCycle::H12
});
if self.config.show_date_in_top_panel {
if self.config.show_weekday {
let mut fs = fieldsets::MDET::long();
if !self.config.show_seconds {
fs = fs.with_time_precision(TimePrecision::Minute);
}
DateTimeFormatter::try_new(prefs, fs)
.unwrap()
.format(&datetime)
.to_string()
} else {
let mut fs = fieldsets::MDT::long();
if !self.config.show_seconds {
fs = fs.with_time_precision(TimePrecision::Minute);
}
DateTimeFormatter::try_new(prefs, fs)
.unwrap()
.format(&datetime)
.to_string()
}
} else {
let mut fs = fieldsets::T::medium();
if !self.config.show_seconds {
fs = fs.with_time_precision(TimePrecision::Minute);
}
@ -244,15 +275,6 @@ impl Window {
.format(&datetime)
.to_string()
}
} else {
let mut fs = fieldsets::T::medium();
if !self.config.show_seconds {
fs = fs.with_time_precision(TimePrecision::Minute);
}
DateTimeFormatter::try_new(prefs, fs)
.unwrap()
.format(&datetime)
.to_string()
};
Element::from(
@ -587,7 +609,20 @@ impl cosmic::Application for Window {
Message::ConfigChanged(c) => {
// Don't interrupt the tick subscription unless necessary
self.show_seconds_tx.send_if_modified(|show_seconds| {
if *show_seconds == c.show_seconds {
if !c.format_strftime.is_empty() {
if c.format_strftime.split('%').any(|s| {
STRFTIME_SECONDS.contains(&s.chars().next().unwrap_or_default())
}) && !*show_seconds
{
// The strftime formatter contains a seconds specifier. Force enable
// ticking per seconds internally regardless of the user setting.
// This does not change the user's setting. It's invisible to the user.
*show_seconds = true;
true
} else {
false
}
} else if *show_seconds == c.show_seconds {
false
} else {
*show_seconds = c.show_seconds;