oxihuman_core/
timezone_offset.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, PartialEq)]
8pub struct TimezoneOffset {
9 pub minutes: i32,
11 pub name: String,
13}
14
15impl TimezoneOffset {
16 pub fn new(minutes: i32, name: &str) -> Self {
17 TimezoneOffset {
18 minutes,
19 name: name.to_string(),
20 }
21 }
22
23 pub fn utc() -> Self {
24 TimezoneOffset::new(0, "UTC")
25 }
26
27 pub fn hours(&self) -> f32 {
28 self.minutes as f32 / 60.0
29 }
30
31 pub fn is_positive(&self) -> bool {
32 self.minutes >= 0
33 }
34
35 pub fn format_offset(&self) -> String {
36 format_offset(self.minutes)
37 }
38}
39
40pub fn format_offset(minutes: i32) -> String {
41 let sign = if minutes >= 0 { '+' } else { '-' };
42 let abs_min = minutes.unsigned_abs();
43 let h = abs_min / 60;
44 let m = abs_min % 60;
45 format!("{}{:02}:{:02}", sign, h, m)
46}
47
48pub fn parse_offset(s: &str) -> Option<TimezoneOffset> {
49 let s = s.trim();
50 if s == "UTC" || s == "Z" {
51 return Some(TimezoneOffset::utc());
52 }
53 let (sign, rest) = if let Some(r) = s.strip_prefix('+') {
54 (1i32, r)
55 } else if let Some(r) = s.strip_prefix('-') {
56 (-1i32, r)
57 } else {
58 return None;
59 };
60 let parts: Vec<&str> = rest.splitn(2, ':').collect();
61 let hours: i32 = parts.first()?.parse().ok()?;
62 let mins: i32 = if parts.len() > 1 {
63 parts[1].parse().ok()?
64 } else {
65 0
66 };
67 Some(TimezoneOffset::new(sign * (hours * 60 + mins), s))
68}
69
70pub fn offset_difference(a: &TimezoneOffset, b: &TimezoneOffset) -> i32 {
71 a.minutes - b.minutes
72}
73
74pub fn convert_utc_minutes(utc_minutes: i64, offset: &TimezoneOffset) -> i64 {
75 utc_minutes + offset.minutes as i64
76}
77
78pub fn known_offsets() -> Vec<TimezoneOffset> {
79 vec![
80 TimezoneOffset::new(0, "UTC"),
81 TimezoneOffset::new(540, "Asia/Tokyo"),
82 TimezoneOffset::new(-300, "America/New_York"),
83 TimezoneOffset::new(-480, "America/Los_Angeles"),
84 TimezoneOffset::new(60, "Europe/Paris"),
85 ]
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_utc_offset() {
94 let tz = TimezoneOffset::utc();
95 assert_eq!(tz.minutes, 0);
96 assert_eq!(tz.format_offset(), "+00:00");
97 }
98
99 #[test]
100 fn test_tokyo_offset() {
101 let tz = TimezoneOffset::new(540, "Asia/Tokyo");
102 assert_eq!(tz.hours(), 9.0);
103 assert_eq!(tz.format_offset(), "+09:00");
104 }
105
106 #[test]
107 fn test_negative_offset() {
108 let tz = TimezoneOffset::new(-300, "America/New_York");
109 assert_eq!(tz.format_offset(), "-05:00");
110 assert!(!tz.is_positive() ,);
111 }
112
113 #[test]
114 fn test_parse_offset_utc() {
115 let tz = parse_offset("UTC").expect("should succeed");
116 assert_eq!(tz.minutes, 0);
117 }
118
119 #[test]
120 fn test_parse_offset_positive() {
121 let tz = parse_offset("+09:00").expect("should succeed");
122 assert_eq!(tz.minutes, 540);
123 }
124
125 #[test]
126 fn test_parse_offset_negative() {
127 let tz = parse_offset("-05:00").expect("should succeed");
128 assert_eq!(tz.minutes, -300);
129 }
130
131 #[test]
132 fn test_offset_difference() {
133 let a = TimezoneOffset::new(540, "JST");
134 let b = TimezoneOffset::new(0, "UTC");
135 assert_eq!(offset_difference(&a, &b), 540);
136 }
137
138 #[test]
139 fn test_convert_utc() {
140 let tz = TimezoneOffset::new(540, "JST");
141 let local = convert_utc_minutes(0, &tz);
142 assert_eq!(local, 540);
143 }
144
145 #[test]
146 fn test_known_offsets_nonempty() {
147 let list = known_offsets();
148 assert!(!list.is_empty(), );
149 }
150}