| 1 | |
| 2 | |
| 3 | use jiff::{Timestamp, tz::TimeZone}; |
| 4 | |
| 5 | use crate::error::{Error, Result}; |
| 6 | |
| 7 | |
| 8 | fn zone_or_utc(tz: &str) -> TimeZone { |
| 9 | TimeZone::get(tz).unwrap_or(TimeZone::UTC) |
| 10 | } |
| 11 | |
| 12 | |
| 13 | pub fn validate_timezone(name: &str) -> Result<()> { |
| 14 | TimeZone::get(name) |
| 15 | .map(|_| ()) |
| 16 | .map_err(|_| Error::Invalid(format!("timezone {name:?}"))) |
| 17 | } |
| 18 | |
| 19 | |
| 20 | pub fn all_timezones() -> Vec<String> { |
| 21 | let mut names: Vec<String> = jiff::tz::db() |
| 22 | .available() |
| 23 | .map(|name| name.to_string()) |
| 24 | .collect(); |
| 25 | names.sort(); |
| 26 | names.dedup(); |
| 27 | names |
| 28 | } |
| 29 | |
| 30 | |
| 31 | pub fn format_in_tz(unix: i64, tz: &str) -> String { |
| 32 | let Ok(ts) = Timestamp::from_second(unix) else { |
| 33 | return "invalid time".to_string(); |
| 34 | }; |
| 35 | let formatted = ts |
| 36 | .to_zoned(zone_or_utc(tz)) |
| 37 | .strftime("%Y-%m-%d %H:%M %:z") |
| 38 | .to_string(); |
| 39 | |
| 40 | match formatted.strip_suffix(":00") { |
| 41 | Some(short) => short.to_string(), |
| 42 | None => formatted, |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | |
| 47 | pub fn local_time_now(tz: &str, now_unix: i64) -> String { |
| 48 | let Ok(ts) = Timestamp::from_second(now_unix) else { |
| 49 | return "invalid time".to_string(); |
| 50 | }; |
| 51 | ts.to_zoned(zone_or_utc(tz)) |
| 52 | .strftime("%H:%M (UTC%:z)") |
| 53 | .to_string() |
| 54 | } |
| 55 | |
| 56 | #[cfg(test)] |
| 57 | mod tests { |
| 58 | use super::*; |
| 59 | |
| 60 | #[test] |
| 61 | fn validates_known_and_unknown_names() { |
| 62 | assert!(validate_timezone("Europe/Istanbul").is_ok()); |
| 63 | assert!(validate_timezone("UTC").is_ok()); |
| 64 | assert!(validate_timezone("Mars/Base").is_err()); |
| 65 | assert!(validate_timezone("").is_err()); |
| 66 | } |
| 67 | |
| 68 | #[test] |
| 69 | fn formats_absolute_time_in_zone() { |
| 70 | |
| 71 | let unix = 1_792_062_300; |
| 72 | assert_eq!( |
| 73 | format_in_tz(unix, "Europe/Istanbul"), |
| 74 | "2026-10-15 14:05 +03" |
| 75 | ); |
| 76 | assert_eq!(format_in_tz(unix, "Mars/Base"), "2026-10-15 11:05 +00"); |
| 77 | } |
| 78 | |
| 79 | #[test] |
| 80 | fn local_time_shows_offset() { |
| 81 | let unix = 1_792_062_300; |
| 82 | assert_eq!(local_time_now("Europe/Istanbul", unix), "14:05 (UTC+03:00)"); |
| 83 | assert_eq!(local_time_now("UTC", unix), "11:05 (UTC+00:00)"); |
| 84 | } |
| 85 | |
| 86 | #[test] |
| 87 | fn lists_timezones_sorted() { |
| 88 | let zones = all_timezones(); |
| 89 | assert!(zones.iter().any(|z| z == "UTC")); |
| 90 | assert!(zones.iter().any(|z| z == "America/New_York")); |
| 91 | assert!(zones.windows(2).all(|w| w[0] < w[1])); |
| 92 | } |
| 93 | } |