Skip to content

Commit ebef287

Browse files
Formattable datetime strings with strftime
Closes: #1096, #779, pop-os/cosmic-epoch#2255 The format follows standard strftime specifiers. Chrono's docs has a page listing them with examples: https://docs.rs/chrono/latest/chrono/format/strftime/index.html The strftime formatter overrides ICU if enabled because ICU formats time in a locale appropriate manner. Strftime, by its nature, is an override.
1 parent 351f138 commit ebef287

File tree

2 files changed

+99
-57
lines changed

2 files changed

+99
-57
lines changed

cosmic-applet-time/src/window.rs

Lines changed: 82 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -159,45 +159,61 @@ impl Window {
159159
}
160160

161161
fn vertical_layout(&self) -> Element<'_, Message> {
162-
let mut elements = Vec::new();
163-
let date = self.now.naive_local();
164-
let datetime = self.create_datetime(&date);
165-
let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone());
166-
prefs.hour_cycle = Some(if self.config.military_time {
167-
HourCycle::H23
162+
let elements: Vec<Element<'_, Message>> = if let Some(formatted) = self
163+
.config
164+
.format_strftime
165+
.as_deref()
166+
.map(|format| self.now.format(format).to_string())
167+
{
168+
// strftime formatter may override locale specific elements so it stands alone rather
169+
// than using ICU to determine a format.
170+
formatted
171+
.split_whitespace()
172+
.map(|piece| self.core.applet.text(piece.to_owned()).into())
173+
.collect()
168174
} else {
169-
HourCycle::H12
170-
});
175+
let mut elements = Vec::new();
176+
let date = self.now.naive_local();
177+
let datetime = self.create_datetime(&date);
178+
let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone());
179+
prefs.hour_cycle = Some(if self.config.military_time {
180+
HourCycle::H23
181+
} else {
182+
HourCycle::H12
183+
});
184+
185+
if self.config.show_date_in_top_panel {
186+
let formatted_date = DateTimeFormatter::try_new(prefs, fieldsets::MD::medium())
187+
.unwrap()
188+
.format(&datetime)
189+
.to_string();
171190

172-
if self.config.show_date_in_top_panel {
173-
let formatted_date = DateTimeFormatter::try_new(prefs, fieldsets::MD::medium())
191+
for p in formatted_date.split_whitespace() {
192+
elements.push(self.core.applet.text(p.to_owned()).into());
193+
}
194+
elements.push(
195+
horizontal_rule(2)
196+
.width(self.core.applet.suggested_size(true).0)
197+
.into(),
198+
);
199+
}
200+
let mut fs = fieldsets::T::medium();
201+
if !self.config.show_seconds {
202+
fs = fs.with_time_precision(TimePrecision::Minute);
203+
}
204+
let formatted_time = DateTimeFormatter::try_new(prefs, fs)
174205
.unwrap()
175206
.format(&datetime)
176207
.to_string();
177208

178-
for p in formatted_date.split_whitespace() {
209+
// todo: split using formatToParts when it is implemented
210+
// https://github.com/unicode-org/icu4x/issues/4936#issuecomment-2128812667
211+
for p in formatted_time.split_whitespace().flat_map(|s| s.split(':')) {
179212
elements.push(self.core.applet.text(p.to_owned()).into());
180213
}
181-
elements.push(
182-
horizontal_rule(2)
183-
.width(self.core.applet.suggested_size(true).0)
184-
.into(),
185-
);
186-
}
187-
let mut fs = fieldsets::T::medium();
188-
if !self.config.show_seconds {
189-
fs = fs.with_time_precision(TimePrecision::Minute);
190-
}
191-
let formatted_time = DateTimeFormatter::try_new(prefs, fs)
192-
.unwrap()
193-
.format(&datetime)
194-
.to_string();
195-
196-
// todo: split using formatToParts when it is implemented
197-
// https://github.com/unicode-org/icu4x/issues/4936#issuecomment-2128812667
198-
for p in formatted_time.split_whitespace().flat_map(|s| s.split(':')) {
199-
elements.push(self.core.applet.text(p.to_owned()).into());
200-
}
214+
215+
elements
216+
};
201217

202218
let date_time_col = Column::with_children(elements)
203219
.align_x(Alignment::Center)
@@ -216,26 +232,44 @@ impl Window {
216232
}
217233

218234
fn horizontal_layout(&self) -> Element<'_, Message> {
219-
let datetime = self.create_datetime(&self.now);
220-
let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone());
221-
prefs.hour_cycle = Some(if self.config.military_time {
222-
HourCycle::H23
235+
let formatted_date = if let Some(formatted) = self
236+
.config
237+
.format_strftime
238+
.as_deref()
239+
.map(|format| self.now.format(format).to_string())
240+
{
241+
formatted
223242
} else {
224-
HourCycle::H12
225-
});
226-
227-
let formatted_date = if self.config.show_date_in_top_panel {
228-
if self.config.show_weekday {
229-
let mut fs = fieldsets::MDET::long();
230-
if !self.config.show_seconds {
231-
fs = fs.with_time_precision(TimePrecision::Minute);
243+
let datetime = self.create_datetime(&self.now);
244+
let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone());
245+
prefs.hour_cycle = Some(if self.config.military_time {
246+
HourCycle::H23
247+
} else {
248+
HourCycle::H12
249+
});
250+
251+
if self.config.show_date_in_top_panel {
252+
if self.config.show_weekday {
253+
let mut fs = fieldsets::MDET::long();
254+
if !self.config.show_seconds {
255+
fs = fs.with_time_precision(TimePrecision::Minute);
256+
}
257+
DateTimeFormatter::try_new(prefs, fs)
258+
.unwrap()
259+
.format(&datetime)
260+
.to_string()
261+
} else {
262+
let mut fs = fieldsets::MDT::long();
263+
if !self.config.show_seconds {
264+
fs = fs.with_time_precision(TimePrecision::Minute);
265+
}
266+
DateTimeFormatter::try_new(prefs, fs)
267+
.unwrap()
268+
.format(&datetime)
269+
.to_string()
232270
}
233-
DateTimeFormatter::try_new(prefs, fs)
234-
.unwrap()
235-
.format(&datetime)
236-
.to_string()
237271
} else {
238-
let mut fs = fieldsets::MDT::long();
272+
let mut fs = fieldsets::T::medium();
239273
if !self.config.show_seconds {
240274
fs = fs.with_time_precision(TimePrecision::Minute);
241275
}
@@ -244,15 +278,6 @@ impl Window {
244278
.format(&datetime)
245279
.to_string()
246280
}
247-
} else {
248-
let mut fs = fieldsets::T::medium();
249-
if !self.config.show_seconds {
250-
fs = fs.with_time_precision(TimePrecision::Minute);
251-
}
252-
DateTimeFormatter::try_new(prefs, fs)
253-
.unwrap()
254-
.format(&datetime)
255-
.to_string()
256281
};
257282

258283
Element::from(

cosmic-applets-config/src/time.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ pub struct TimeAppletConfig {
1111
pub first_day_of_week: u8,
1212
pub show_date_in_top_panel: bool,
1313
pub show_weekday: bool,
14+
#[serde(default, deserialize_with = "strftime_opt_de")]
15+
pub format_strftime: Option<String>,
1416
}
1517

1618
impl Default for TimeAppletConfig {
@@ -21,6 +23,21 @@ impl Default for TimeAppletConfig {
2123
first_day_of_week: 6,
2224
show_date_in_top_panel: true,
2325
show_weekday: false,
26+
format_strftime: None,
2427
}
2528
}
2629
}
30+
31+
/// Deserialize optional String but only if it is non-empty.
32+
fn strftime_opt_de<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
33+
where
34+
D: serde::Deserializer<'de>,
35+
{
36+
serde::Deserialize::deserialize(deserializer).map(|strftime: Option<String>| {
37+
if strftime.as_deref().is_none_or(str::is_empty) {
38+
None
39+
} else {
40+
strftime
41+
}
42+
})
43+
}

0 commit comments

Comments
 (0)