i3status_rs/blocks/
time.rs

1//! The current time.
2//!
3//! # Configuration
4//!
5//! Key        | Values | Default
6//! -----------|--------|--------
7//! `format`   | Format string. See [chrono docs](https://docs.rs/chrono/0.3.0/chrono/format/strftime/index.html#specifiers) for all options. | `" $icon $timestamp.datetime() "`
8//! `interval` | Update interval in seconds | `10`
9//! `timezone` | A timezone specifier (e.g. "Europe/Lisbon") | Local timezone
10//!
11//! Placeholder   | Value                                       | Type     | Unit
12//! --------------|---------------------------------------------|----------|-----
13//! `icon`        | A static icon                               | Icon     | -
14//! `timestamp`   | The current time                            | Datetime | -
15//!
16//! Action          | Default button
17//! ----------------|---------------
18//! `next_timezone` | Left
19//! `prev_timezone` | Right
20//!
21//! # Example
22//!
23//! ```toml
24//! [[block]]
25//! block = "time"
26//! interval = 60
27//! [block.format]
28//! full = " $icon $timestamp.datetime(f:'%a %Y-%m-%d %R %Z', l:fr_BE) "
29//! short = " $icon $timestamp.datetime(f:%R) "
30//! ```
31//!
32//! # Non Gregorian calendars
33//!
34//! You can use calendars other than the Gregorian calendar by adding the calendar specifier in the locale string. When using
35//! this feature you can't use chrono style format string, and you should use one of the options provided by
36//! the `icu4x` crate: `short`, `medium`, `long`, `full`.
37//!
38//! ** Only available using feature `icu_calendar`. **
39//!
40//! ## Example
41//!
42//! ```toml
43//! [[block]]
44//! block = "time"
45//! interval = 60
46//! format = "$timestamp.datetime(locale:'fa_IR-u-ca-persian', f:'full')"
47//! ```
48//!
49//! # Icons Used
50//! - `time`
51
52use chrono::{Timelike as _, Utc};
53use chrono_tz::Tz;
54
55use super::prelude::*;
56
57#[derive(Deserialize, Debug, SmartDefault)]
58#[serde(deny_unknown_fields, default)]
59pub struct Config {
60    pub format: FormatConfig,
61    #[default(10.into())]
62    pub interval: Seconds,
63    pub timezone: Option<Timezone>,
64}
65
66#[derive(Deserialize, Debug, Clone)]
67#[serde(untagged)]
68pub enum Timezone {
69    Timezone(Tz),
70    Timezones(Vec<Tz>),
71}
72
73pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
74    let mut actions = api.get_actions()?;
75    api.set_default_actions(&[
76        (MouseButton::Left, None, "next_timezone"),
77        (MouseButton::Right, None, "prev_timezone"),
78    ])?;
79
80    let format = config
81        .format
82        .with_default(" $icon $timestamp.datetime() ")?;
83
84    let timezones = match config.timezone.clone() {
85        Some(tzs) => match tzs {
86            Timezone::Timezone(tz) => vec![tz],
87            Timezone::Timezones(tzs) => tzs,
88        },
89        None => Vec::new(),
90    };
91
92    let prev_step_length = timezones.len().saturating_sub(2);
93
94    let mut timezone_iter = timezones.iter().cycle();
95
96    let mut timezone = timezone_iter.next();
97
98    let interval_seconds = config.interval.seconds().max(1);
99
100    let mut timer = tokio::time::interval_at(
101        tokio::time::Instant::now() + Duration::from_secs(interval_seconds),
102        Duration::from_secs(interval_seconds),
103    );
104    timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
105
106    loop {
107        let mut widget = Widget::new().with_format(format.clone());
108        let now = Utc::now();
109
110        widget.set_values(map! {
111            "icon" => Value::icon("time"),
112            "timestamp" => Value::datetime(now, timezone.copied())
113        });
114
115        api.set_widget(widget)?;
116
117        let phase = now.second() as u64 % interval_seconds;
118        if phase != 0 {
119            timer.reset_after(Duration::from_secs(interval_seconds - phase));
120        }
121
122        tokio::select! {
123            _ = timer.tick() => (),
124            _ = api.wait_for_update_request() => (),
125            Some(action) = actions.recv() => match action.as_ref() {
126                "next_timezone" => {
127                    timezone = timezone_iter.next();
128                },
129                "prev_timezone" => {
130                    timezone = timezone_iter.nth(prev_step_length);
131                },
132                _ => (),
133            }
134        }
135    }
136}