calcard 0.3.4

iCalendar/JSCalendar and vCard/JSContact parsing, building and conversion library for Rust
Documentation
/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 */

use super::{
    ICalendar, ICalendarComponent, ICalendarComponentType, ICalendarEntry, ICalendarProperty,
    ICalendarValue,
};
use crate::{common::timezone::Tz, icalendar::ICalendarParameterName};
use std::{collections::HashMap, str::FromStr};

pub struct TzResolver<T> {
    tzs: HashMap<T, Tz>,
    default: Tz,
}

impl<T> TzResolver<T>
where
    T: std::borrow::Borrow<str> + std::hash::Hash + Eq,
{
    pub fn resolve(&self, tz_name: &str) -> Option<Tz> {
        self.tzs
            .get(tz_name)
            .copied()
            .or_else(|| Tz::from_str(tz_name).ok())
    }

    pub fn resolve_or_default(&self, tz_name: Option<&str>) -> Tz {
        tz_name
            .and_then(|tz_name| {
                self.tzs
                    .get(tz_name)
                    .copied()
                    .or_else(|| Tz::from_str(tz_name).ok())
            })
            .unwrap_or(self.default)
    }

    pub fn with_default(mut self, default: impl Into<Tz>) -> Self {
        self.default = default.into();
        self
    }
}

impl ICalendar {
    pub fn timezones(&self) -> impl Iterator<Item = &ICalendarComponent> {
        self.components
            .iter()
            .filter(|comp| matches!(comp.component_type, ICalendarComponentType::VTimezone))
    }

    pub fn is_timezone(&self) -> bool {
        self.timezones().count() == 1
    }

    pub fn build_tz_resolver(&self) -> TzResolver<&'_ str> {
        TzResolver {
            tzs: self.timezones().filter_map(|tz| tz.timezone()).collect(),
            default: Tz::Floating,
        }
    }

    pub fn build_owned_tz_resolver(&self) -> TzResolver<String> {
        TzResolver {
            tzs: self
                .timezones()
                .filter_map(|tz| tz.timezone())
                .map(|(name, tz)| (name.to_string(), tz))
                .collect(),
            default: Tz::Floating,
        }
    }
}

impl ICalendarComponent {
    pub fn timezone(&self) -> Option<(&str, Tz)> {
        let mut tz_name = None;
        let mut tz_id = None;
        let mut tz_lic = None;
        let mut tz_cdo_id = None;

        for entry in &self.entries {
            match (&entry.name, entry.values.first()) {
                (ICalendarProperty::Tzid, Some(ICalendarValue::Text(id))) => {
                    tz_id = Tz::from_str(id).ok();
                    tz_name = Some(id.as_str());
                }
                (ICalendarProperty::Other(value), Some(ICalendarValue::Text(id))) => {
                    hashify::fnc_map!(value.as_bytes(),
                        "X-LIC-LOCATION" => {
                            tz_lic = Tz::from_str(id.strip_prefix("SystemV/").unwrap_or(id.as_str())).ok();
                        },
                        "X-MICROSOFT-CDO-TZID" => {
                            tz_cdo_id = Tz::from_ms_cdo_zone_id(id);
                        },
                        _ => {}
                    );
                }
                _ => (),
            }
        }

        tz_name.and_then(|name| tz_id.or(tz_lic).or(tz_cdo_id).map(|tz| (name, tz)))
    }
}

impl ICalendarEntry {
    pub fn tz_id(&self) -> Option<&str> {
        self.parameters(&ICalendarParameterName::Tzid)
            .find_map(|v| v.as_text())
    }
}