1use chrono_tz::Tz;
7use std::str::FromStr;
8use tracing::debug;
9
10#[derive(Debug, Clone)]
12pub struct TimezoneConfig {
13 pub tz: Tz,
15 pub is_utc: bool,
17}
18
19impl Default for TimezoneConfig {
20 fn default() -> Self {
21 let tz = get_local_timezone();
22 Self {
23 is_utc: tz == Tz::UTC,
24 tz,
25 }
26 }
27}
28
29impl TimezoneConfig {
30 pub fn from_cli(timezone_str: Option<&str>, use_utc: bool) -> crate::error::Result<Self> {
32 if use_utc {
33 return Ok(Self {
34 tz: Tz::UTC,
35 is_utc: true,
36 });
37 }
38
39 if let Some(tz_str) = timezone_str {
40 let tz = Tz::from_str(tz_str).map_err(|_| {
41 crate::error::CcstatError::InvalidTimezone(format!(
42 "'{}'. Use format like 'America/New_York', 'Asia/Tokyo', or 'UTC'",
43 tz_str
44 ))
45 })?;
46 Ok(Self {
47 tz,
48 is_utc: tz == Tz::UTC,
49 })
50 } else {
51 Ok(Self::default())
52 }
53 }
54
55 pub fn display_name(&self) -> &str {
57 if self.is_utc { "UTC" } else { self.tz.name() }
58 }
59}
60
61pub fn get_local_timezone() -> Tz {
66 #[allow(clippy::collapsible_if)]
69 if let Ok(tz_str) = std::env::var("TZ") {
70 if let Ok(tz) = Tz::from_str(&tz_str) {
71 debug!("Using timezone from TZ environment variable: {}", tz_str);
72 return tz;
73 }
74 }
75
76 match iana_time_zone::get_timezone() {
78 Ok(tz_str) => match Tz::from_str(&tz_str) {
79 Ok(tz) => {
80 debug!("Using system timezone from iana-time-zone: {}", tz_str);
81 tz
82 }
83 Err(_) => {
84 debug!(
85 "Could not parse timezone from iana-time-zone: '{}', falling back to UTC",
86 tz_str
87 );
88 Tz::UTC
89 }
90 },
91 Err(e) => {
92 debug!(
93 "Could not detect local timezone via iana-time-zone: {:?}, falling back to UTC",
94 e
95 );
96 Tz::UTC
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_timezone_config_utc() {
107 let config = TimezoneConfig::from_cli(None, true).unwrap();
108 assert!(config.is_utc);
109 assert_eq!(config.tz, Tz::UTC);
110 assert_eq!(config.display_name(), "UTC");
111 }
112
113 #[test]
114 fn test_timezone_config_explicit() {
115 let config = TimezoneConfig::from_cli(Some("America/New_York"), false).unwrap();
116 assert!(!config.is_utc);
117 assert_eq!(config.tz.name(), "America/New_York");
118 }
119
120 #[test]
121 fn test_timezone_config_invalid() {
122 let result = TimezoneConfig::from_cli(Some("Invalid/Timezone"), false);
123 assert!(result.is_err());
124 }
125
126 #[test]
127 fn test_timezone_config_utc_via_timezone_flag() {
128 let config = TimezoneConfig::from_cli(Some("UTC"), false).unwrap();
130 assert!(config.is_utc);
131 assert_eq!(config.tz, Tz::UTC);
132 assert_eq!(config.display_name(), "UTC");
133 }
134}