Skip to main content

i3status_rs/blocks/
time.rs

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