Skip to main content

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//! If you set `precision` to `hours`/`hour`/`h`, `minutes`/`minute`/`m`, or `seconds`/`second`/`s` then then the datetime will be formatted accordingly, otherwise only the date will be displayed.
38//!
39//! ** Only available using feature `icu_calendar`. **
40//!
41//! ## Example
42//!
43//! ```toml
44//! [[block]]
45//! block = "time"
46//! interval = 60
47//! format = "$timestamp.datetime(locale:'fa_IR-u-ca-persian', f:'full', precision: minutes)"
48//! ```
49//!
50//! # Icons Used
51//! - `time`
52
53use chrono::{Timelike as _, Utc};
54use chrono_tz::Tz;
55
56use super::prelude::*;
57
58#[derive(Deserialize, Debug, SmartDefault)]
59#[serde(deny_unknown_fields, default)]
60pub struct Config {
61    pub format: FormatConfig,
62    #[default(10.into())]
63    pub interval: Seconds,
64    pub timezone: Option<Timezone>,
65}
66
67#[derive(Deserialize, Debug, Clone)]
68#[serde(untagged)]
69pub enum Timezone {
70    Timezone(Tz),
71    Timezones(Vec<Tz>),
72}
73
74pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
75    let mut actions = api.get_actions()?;
76    api.set_default_actions(&[
77        (MouseButton::Left, None, "next_timezone"),
78        (MouseButton::Right, None, "prev_timezone"),
79    ])?;
80
81    let format = config
82        .format
83        .with_default(" $icon $timestamp.datetime() ")?;
84
85    let timezones = match config.timezone.clone() {
86        Some(tzs) => match tzs {
87            Timezone::Timezone(tz) => vec![tz],
88            Timezone::Timezones(tzs) => tzs,
89        },
90        None => Vec::new(),
91    };
92
93    let prev_step_length = timezones.len().saturating_sub(2);
94
95    let mut timezone_iter = timezones.iter().cycle();
96
97    let mut timezone = timezone_iter.next();
98
99    let interval_seconds = config.interval.seconds().max(1);
100
101    let mut timer = tokio::time::interval_at(
102        tokio::time::Instant::now() + Duration::from_secs(interval_seconds),
103        Duration::from_secs(interval_seconds),
104    );
105    timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
106
107    loop {
108        let mut widget = Widget::new().with_format(format.clone());
109        let now = Utc::now();
110
111        widget.set_values(map! {
112            "icon" => Value::icon("time"),
113            "timestamp" => Value::datetime(now, timezone.copied())
114        });
115
116        api.set_widget(widget)?;
117
118        let phase = now.second() as u64 % interval_seconds;
119        if phase != 0 {
120            timer.reset_after(Duration::from_secs(interval_seconds - phase));
121        }
122
123        tokio::select! {
124            _ = timer.tick() => (),
125            _ = api.wait_for_update_request() => (),
126            Some(action) = actions.recv() => match action.as_ref() {
127                "next_timezone" => {
128                    timezone = timezone_iter.next();
129                },
130                "prev_timezone" => {
131                    timezone = timezone_iter.nth(prev_step_length);
132                },
133                _ => (),
134            }
135        }
136    }
137}