flowsurface_data/config/
timezone.rs1use chrono::DateTime;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Default)]
6pub enum UserTimezone {
7 #[default]
8 Utc,
9 Local,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum TimeLabelKind<'a> {
16 Axis { timeframe: exchange::Timeframe },
19 Crosshair { show_millis: bool },
23 Custom(&'a str),
25}
26
27impl UserTimezone {
28 pub fn to_user_datetime(
29 &self,
30 datetime: DateTime<chrono::Utc>,
31 ) -> DateTime<chrono::FixedOffset> {
32 self.with_user_timezone(datetime, |time_with_zone| time_with_zone)
33 }
34
35 pub fn format_with_kind(&self, timestamp_ms: i64, kind: TimeLabelKind<'_>) -> Option<String> {
37 DateTime::from_timestamp_millis(timestamp_ms).map(|datetime| {
38 self.with_user_timezone(datetime, |time_with_zone| match kind {
39 TimeLabelKind::Axis { timeframe } => {
40 Self::format_by_timeframe(&time_with_zone, timeframe)
41 }
42 TimeLabelKind::Crosshair { show_millis } => {
43 if show_millis {
44 time_with_zone.format("%H:%M:%S.%3f").to_string()
45 } else {
46 time_with_zone.format("%a %b %-d %H:%M").to_string()
47 }
48 }
49 TimeLabelKind::Custom(fmt) => time_with_zone.format(fmt).to_string(),
50 })
51 })
52 }
53
54 fn with_user_timezone<T>(
57 &self,
58 datetime: DateTime<chrono::Utc>,
59 formatter: impl FnOnce(DateTime<chrono::FixedOffset>) -> T,
60 ) -> T {
61 let time_with_zone = match self {
62 UserTimezone::Local => datetime.with_timezone(&chrono::Local).fixed_offset(),
63 UserTimezone::Utc => datetime.fixed_offset(),
64 };
65
66 formatter(time_with_zone)
67 }
68
69 fn format_by_timeframe(
75 datetime: &DateTime<chrono::FixedOffset>,
76 timeframe: exchange::Timeframe,
77 ) -> String {
78 let interval = timeframe.to_milliseconds();
79
80 if interval < 10_000 {
81 datetime.format("%M:%S").to_string()
82 } else if datetime.format("%H:%M").to_string() == "00:00" {
83 datetime.format("%-d").to_string()
84 } else {
85 datetime.format("%H:%M").to_string()
86 }
87 }
88}
89
90impl fmt::Display for UserTimezone {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 UserTimezone::Utc => write!(f, "UTC"),
94 UserTimezone::Local => {
95 let local_offset = chrono::Local::now().offset().local_minus_utc();
96 let hours = local_offset / 3600;
97 let minutes = (local_offset % 3600) / 60;
98 write!(f, "Local (UTC {hours:+03}:{minutes:02})")
99 }
100 }
101 }
102}
103
104impl<'de> Deserialize<'de> for UserTimezone {
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: serde::Deserializer<'de>,
108 {
109 let timezone_str = String::deserialize(deserializer)?;
110 match timezone_str.to_lowercase().as_str() {
111 "utc" => Ok(UserTimezone::Utc),
112 "local" => Ok(UserTimezone::Local),
113 _ => Err(serde::de::Error::custom("Invalid UserTimezone")),
114 }
115 }
116}
117
118impl Serialize for UserTimezone {
119 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120 where
121 S: serde::Serializer,
122 {
123 match self {
124 UserTimezone::Utc => serializer.serialize_str("UTC"),
125 UserTimezone::Local => serializer.serialize_str("Local"),
126 }
127 }
128}