timezone_converter/
lib.rs

1//! # Timezone Converter
2//! 
3//! A Rust library for converting times between different timezones and getting timezone information.
4//! This library provides functionality to:
5//! 
6//! - Convert times between any two timezones
7//! - Get current time in different timezones
8//! - Get timezone information including offset and DST status
9//! - Calculate time differences between timezones
10//! 
11//! ## Example
12//! ```rust
13//! use timezone_converter::TimeZoneConverter;
14//! 
15//! let converter = TimeZoneConverter::new("America/New_York", "Europe/London").unwrap();
16//! let current_time = converter.get_current_time_source().unwrap();
17//! let converted_time = converter.convert(current_time).unwrap();
18//! ```
19
20use chrono::{DateTime, TimeZone as ChronoTimeZone, Utc, Duration, Offset};
21use chrono_tz::{OffsetName, Tz};
22
23/// A struct that handles timezone conversions between a source and target timezone
24#[derive(Debug)]
25pub struct TimeZoneConverter {
26    /// The source timezone to convert from
27    source_tz: Tz,
28    /// The target timezone to convert to
29    target_tz: Tz,
30}
31
32/// Represents detailed information about a timezone
33#[derive(Debug)]
34pub struct TimeZoneInfo {
35    /// The name of the timezone (e.g., "America/New_York")
36    name: String,
37    /// The offset from UTC
38    offset: Duration,
39    /// Whether Daylight Saving Time is currently in effect
40    is_dst: bool,
41}
42
43/// Possible errors that can occur during timezone operations
44#[derive(Debug)]
45pub enum Errors {
46    /// Error when an invalid timezone identifier is provided
47    InvalidTimeZone(String),
48    /// Error when parsing datetime strings
49    ParseError(String),
50    /// Error during timezone conversion
51    ConversionError(String),
52}
53
54impl TimeZoneConverter {
55    /// Creates a new TimeZoneConverter instance
56    /// 
57    /// # Arguments
58    /// 
59    /// * `source` - The source timezone identifier (e.g., "America/New_York")
60    /// * `target` - The target timezone identifier (e.g., "Europe/London")
61    /// 
62    /// # Returns
63    /// 
64    /// * `Result<TimeZoneConverter, Errors>` - A new TimeZoneConverter instance or an error
65    /// 
66    /// # Example
67    /// 
68    /// ```rust
69    /// use timezone_converter::TimeZoneConverter;
70    /// 
71    /// let converter = TimeZoneConverter::new("America/New_York", "Europe/London").unwrap();
72    /// ```
73    pub fn new(source: &str, target: &str) -> Result<Self, Errors> {
74        let source_tz = source.parse::<Tz>().map_err(|_| Errors::InvalidTimeZone(source.to_string()))?;
75        let target_tz = target.parse::<Tz>().map_err(|_| Errors::InvalidTimeZone(target.to_string()))?;
76
77        Ok(Self {
78            source_tz,
79            target_tz
80        })
81    }
82
83    /// Converts a datetime from the source timezone to the target timezone
84    /// 
85    /// # Arguments
86    /// 
87    /// * `datetime` - The datetime to convert
88    /// 
89    /// # Returns
90    /// 
91    /// * `Result<DateTime<Tz>, Errors>` - The converted datetime or an error
92    pub fn convert<T: ChronoTimeZone>(&self, datetime: DateTime<T>) -> Result<DateTime<Tz>, Errors> {
93        Ok(
94            datetime.with_timezone(&self.target_tz)
95        )
96    }
97
98    /// Gets the current time in the source timezone
99    /// 
100    /// # Returns
101    /// 
102    /// * `Result<DateTime<Tz>, Errors>` - The current time in the source timezone
103    pub fn get_current_time_source(&self) -> Result<DateTime<Tz>, Errors> {
104        Ok(
105            Utc::now().with_timezone(&self.source_tz)
106        )
107    }
108
109    /// Gets the current time in the target timezone
110    /// 
111    /// # Returns
112    /// 
113    /// * `Result<DateTime<Tz>, Errors>` - The current time in the target timezone
114    pub fn get_current_time_target(&self) -> Result<DateTime<Tz>, Errors> {
115        Ok(
116            Utc::now().with_timezone(&self.target_tz)
117        )
118    }
119
120    /// Gets detailed information about the source timezone
121    /// 
122    /// # Returns
123    /// 
124    /// * `Result<TimeZoneInfo, Errors>` - Information about the timezone including name, offset, and DST status
125    pub fn get_timezone_info(&self) -> Result<TimeZoneInfo, Errors> {
126        let now = Utc::now().with_timezone(&self.source_tz);
127        let offset = now.offset();
128
129        // Calculate the total offset in seconds
130        let total_offset_seconds = offset.fix().local_minus_utc();
131        // Determine if DST is in effect by checking the offset abbreviation
132        let is_dst = match offset.abbreviation() {
133            Some(abbr) => abbr.ends_with("DT"),
134            None => false,
135        };
136
137        Ok(TimeZoneInfo {
138            name: self.source_tz.name().to_string(),
139            offset: Duration::seconds(total_offset_seconds as i64),
140            is_dst,
141        })
142    }
143
144    /// Gets the time difference between source and target timezones in hours
145    /// 
146    /// # Returns
147    /// 
148    /// * `Result<f64, Errors>` - The time difference in hours (positive if source is ahead)
149    /// 
150    /// # Example
151    /// 
152    /// ```rust
153    /// use timezone_converter::TimeZoneConverter;
154    /// 
155    /// let converter = TimeZoneConverter::new("America/New_York", "Europe/London").unwrap();
156    /// let difference = converter.get_time_difference().unwrap();
157    /// println!("Time difference: {} hours", difference);
158    /// ```
159    pub fn get_time_difference(&self) -> Result<f64, Errors> {
160        let now = Utc::now();
161        
162        // Get the offsets for both timezones
163        let source_time = now.with_timezone(&self.source_tz);
164        let target_time = now.with_timezone(&self.target_tz);
165        
166        let source_offset = source_time.offset().fix().local_minus_utc();
167        let target_offset = target_time.offset().fix().local_minus_utc();
168        
169        // Convert seconds to hours (f64 for decimal hours)
170        Ok((source_offset - target_offset) as f64 / 3600.0)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use chrono_tz::America::New_York;
178    use chrono_tz::Africa::Kampala;
179
180    #[test]
181    fn NewYorktoKampala() {
182        let timezone = TimeZoneConverter::new("America/New_York", "Africa/Kampala").unwrap();
183        // Create a specific time in New York
184        let ny_time = New_York.with_ymd_and_hms(2024, 11, 4, 10, 0, 0).unwrap();
185        let time = timezone.convert(ny_time).unwrap();
186        // Print both times to verify the conversion
187        println!("New York: {}", ny_time);
188        println!("Kampala: {}", time);
189    }
190
191    #[test]
192    fn NewYorkToKampala_current() {
193        let timezone = TimeZoneConverter::new("America/New_York", "Africa/Kampala").unwrap();
194        let ny_time = Utc::now().with_timezone(&New_York);
195        let time = timezone.convert(ny_time).unwrap();
196        println!("New York: {}", ny_time);
197        println!("Kampala: {}", time);
198    }
199
200    #[test]
201    fn get_timezone_info() {
202        let timezone = TimeZoneConverter::new("America/New_York", "Africa/Kampala").unwrap();
203        let info = timezone.get_timezone_info().unwrap();
204        println!("{:?}", info);
205    }
206
207    #[test]
208    fn get_time_difference() {
209        let timezone = TimeZoneConverter::new("America/New_York", "Africa/Kampala").unwrap();
210        let difference = timezone.get_time_difference().unwrap();
211        println!("Time difference: {} hours", difference);
212    }
213}