Skip to main content

otter_support/
tz.rs

1// Copyright 2020-2021 Ian Jackson and contributors to Otter
2// SPDX-License-Identifier: AGPL-3.0-or-later
3// There is NO WARRANTY.
4
5use crate::prelude::*;
6
7use parking_lot::{const_rwlock, RwLock};
8
9#[derive(SerializeDisplay)]
10#[derive(DeserializeFromStr)]
11#[derive(Clone,Debug)]
12pub struct Timezone(Arc<TzInfo>);
13
14#[derive(Clone,Debug,Default)]
15struct TzInfo {
16  name: String,
17  ctz: Option<chrono_tz::Tz>,
18}
19
20impl Timezone {
21  pub fn name(&self) -> &str {
22    &self.0.name
23  }
24
25  // Oh my god this API is awful!
26
27  #[throws(as Option)]
28  fn format_tz<'r, TZ: chrono::TimeZone>(
29    tz: &TZ, ts: Timestamp, fmt: &'r str
30  ) -> chrono::format::DelayedFormat<chrono::format::StrftimeItems<'r>>
31    where <TZ as chrono::TimeZone>::Offset: Display
32  {
33    use chrono::DateTime;
34    let dt = tz.timestamp_opt(ts.0.try_into().ok()?, 0).single()?;
35    DateTime::format(&dt, fmt)
36  }
37
38  pub fn format(&self, ts: Timestamp) -> String {
39    match (||{
40      let fmt = "%Y-%m-%d %H:%M:%S %z";
41      let df = match &self.0.ctz {
42        Some(ctz) => Timezone::format_tz(ctz,                  ts, fmt),
43        None      => Timezone::format_tz(&chrono::offset::Utc, ts, fmt),
44      }
45      .ok_or_else(
46        || format!("timestamp {} out of range!", &ts.0)
47      )?;
48      Ok(format!("{}", df))
49    })() {
50      Ok(s) => s,
51      Err(s) => s,
52    }
53  }
54
55  pub fn default_todo() -> Self {
56    Timezone::from_str("").unwrap()
57  }
58}
59
60impl Display for Timezone {
61  #[throws(fmt::Error)]
62  fn fmt(&self, f: &mut Formatter) {
63    write!(f, "{}", &self.0.name)?;
64  }
65}
66
67type MemoTable = Option<HashMap<String, Timezone>>;
68static MEMO: RwLock<MemoTable> = const_rwlock(None);
69
70impl FromStr for Timezone {
71  type Err = Void;
72  #[throws(Void)]
73  fn from_str(name: &str) -> Self {
74    if name.is_empty() { return default() }
75
76    let get = |memor: &MemoTable| memor.as_ref()?.get(name).map(Clone::clone);
77    if let Some(got) = get(&MEMO.read()) { return got }
78
79    // slow path
80    let mut memow = MEMO.write();
81    if let Some(got) = get(&memow) { return got }
82
83    // really slow path
84    let out = {
85      let name = name.to_string();
86      match chrono_tz::Tz::from_str(&name) {
87        Ok(ctz) => {
88          Arc::new(TzInfo { name, ctz: Some(ctz) })
89        },
90        Err(emsg) => {
91          error!("Error loading timezone {:?}: {}, using UTC", name, emsg);
92          Arc::new(TzInfo { name, ctz: None })
93        }
94      }
95    };
96    let out = Timezone(out);
97    memow.get_or_insert_with(default)
98      .insert(name.to_string(), out.clone());
99    out
100  }
101}
102
103impl Default for Timezone {
104  fn default() -> Self {
105    Timezone(Arc::new(TzInfo { ctz: None, name: default() }))
106  }
107}