flowsurface_data/config/
timezone.rs

1use std::fmt;
2
3use chrono::DateTime;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Default)]
7pub enum UserTimezone {
8    #[default]
9    Utc,
10    Local,
11}
12
13impl UserTimezone {
14    /// Converts UTC timestamp to the appropriate timezone and formats it according to timeframe
15    pub fn format_timestamp(&self, timestamp: i64, timeframe: exchange::Timeframe) -> String {
16        if let Some(datetime) = DateTime::from_timestamp(timestamp, 0) {
17            match self {
18                UserTimezone::Local => {
19                    let time_with_zone = datetime.with_timezone(&chrono::Local);
20                    Self::format_by_timeframe(&time_with_zone, timeframe)
21                }
22                UserTimezone::Utc => {
23                    let time_with_zone = datetime.with_timezone(&chrono::Utc);
24                    Self::format_by_timeframe(&time_with_zone, timeframe)
25                }
26            }
27        } else {
28            String::new()
29        }
30    }
31
32    /// Formats a `DateTime` with appropriate format based on timeframe
33    fn format_by_timeframe<Tz: chrono::TimeZone>(
34        datetime: &DateTime<Tz>,
35        timeframe: exchange::Timeframe,
36    ) -> String
37    where
38        Tz::Offset: std::fmt::Display,
39    {
40        let interval = timeframe.to_milliseconds();
41
42        if interval < 10000 {
43            datetime.format("%M:%S").to_string()
44        } else if datetime.format("%H:%M").to_string() == "00:00" {
45            datetime.format("%-d").to_string()
46        } else {
47            datetime.format("%H:%M").to_string()
48        }
49    }
50
51    /// Formats a `DateTime` with detailed format for crosshair display
52    pub fn format_crosshair_timestamp(&self, timestamp_millis: i64, interval: u64) -> String {
53        if let Some(datetime) = DateTime::from_timestamp_millis(timestamp_millis) {
54            if interval < 10000 {
55                return datetime.format("%M:%S.%3f").to_string();
56            }
57
58            match self {
59                UserTimezone::Local => datetime
60                    .with_timezone(&chrono::Local)
61                    .format("%a %b %-d %H:%M")
62                    .to_string(),
63                UserTimezone::Utc => datetime
64                    .with_timezone(&chrono::Utc)
65                    .format("%a %b %-d %H:%M")
66                    .to_string(),
67            }
68        } else {
69            String::new()
70        }
71    }
72}
73
74impl fmt::Display for UserTimezone {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        match self {
77            UserTimezone::Utc => write!(f, "UTC"),
78            UserTimezone::Local => {
79                let local_offset = chrono::Local::now().offset().local_minus_utc();
80                let hours = local_offset / 3600;
81                let minutes = (local_offset % 3600) / 60;
82                write!(f, "Local (UTC {hours:+03}:{minutes:02})")
83            }
84        }
85    }
86}
87
88impl<'de> Deserialize<'de> for UserTimezone {
89    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90    where
91        D: serde::Deserializer<'de>,
92    {
93        let timezone_str = String::deserialize(deserializer)?;
94        match timezone_str.to_lowercase().as_str() {
95            "utc" => Ok(UserTimezone::Utc),
96            "local" => Ok(UserTimezone::Local),
97            _ => Err(serde::de::Error::custom("Invalid UserTimezone")),
98        }
99    }
100}
101
102impl Serialize for UserTimezone {
103    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
104    where
105        S: serde::Serializer,
106    {
107        match self {
108            UserTimezone::Utc => serializer.serialize_str("UTC"),
109            UserTimezone::Local => serializer.serialize_str("Local"),
110        }
111    }
112}