calcard 0.1.1

iCalendar/vCard parser and generator
Documentation
/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 */

use crate::Token;
use chrono::{FixedOffset, NaiveDate, NaiveDateTime};
use mail_parser::DateTime;

pub mod parser;
pub mod timezone;
pub mod tokenizer;
pub mod writer;

#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(
    any(test, feature = "serde"),
    derive(serde::Serialize, serde::Deserialize)
)]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
)]
#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq), derive(Debug)))]
pub struct PartialDateTime {
    pub year: Option<u16>,
    pub month: Option<u8>,
    pub day: Option<u8>,
    pub hour: Option<u8>,
    pub minute: Option<u8>,
    pub second: Option<u8>,
    pub tz_hour: Option<u8>,
    pub tz_minute: Option<u8>,
    pub tz_minus: bool,
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(
    any(test, feature = "serde"),
    derive(serde::Serialize, serde::Deserialize)
)]
#[cfg_attr(any(test, feature = "serde"), serde(tag = "type", content = "data"))]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
)]
#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq), derive(Debug)))]
pub enum CalendarScale {
    #[default]
    Gregorian,
    Chinese,
    IslamicCivil,
    Hebrew,
    Ethiopic,
    Other(String),
}

impl CalendarScale {
    pub fn as_str(&self) -> &str {
        match self {
            CalendarScale::Gregorian => "GREGORIAN",
            CalendarScale::Chinese => "CHINESE",
            CalendarScale::IslamicCivil => "ISLAMIC-CIVIL",
            CalendarScale::Hebrew => "HEBREW",
            CalendarScale::Ethiopic => "ETHIOPIC",
            CalendarScale::Other(ref s) => s,
        }
    }
}

impl AsRef<str> for CalendarScale {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

#[cfg(feature = "rkyv")]
impl ArchivedCalendarScale {
    pub fn as_str(&self) -> &str {
        match self {
            ArchivedCalendarScale::Gregorian => "GREGORIAN",
            ArchivedCalendarScale::Chinese => "CHINESE",
            ArchivedCalendarScale::IslamicCivil => "ISLAMIC-CIVIL",
            ArchivedCalendarScale::Hebrew => "HEBREW",
            ArchivedCalendarScale::Ethiopic => "ETHIOPIC",
            ArchivedCalendarScale::Other(ref s) => s,
        }
    }
}

#[cfg(feature = "rkyv")]
impl AsRef<str> for ArchivedCalendarScale {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

impl From<Token<'_>> for CalendarScale {
    fn from(token: Token<'_>) -> Self {
        hashify::tiny_map_ignore_case!(token.text.as_ref(),
            "gregorian" => CalendarScale::Gregorian,
            "chinese" => CalendarScale::Chinese,
            "islamic-civil" => CalendarScale::IslamicCivil,
            "hebrew" => CalendarScale::Hebrew,
            "ethiopic" => CalendarScale::Ethiopic,
        )
        .unwrap_or_else(|| CalendarScale::Other(token.into_string()))
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Encoding {
    QuotedPrintable,
    Base64,
}

impl Encoding {
    pub fn parse(value: &[u8]) -> Option<Self> {
        hashify::tiny_map_ignore_case!(value,
            b"QUOTED-PRINTABLE" => Encoding::QuotedPrintable,
            b"BASE64" => Encoding::Base64,
            b"Q" => Encoding::QuotedPrintable,
            b"B" => Encoding::Base64,
        )
    }
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[cfg_attr(
    any(test, feature = "serde"),
    derive(serde::Serialize, serde::Deserialize)
)]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
)]
#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq), derive(Debug)))]
pub struct Data {
    pub content_type: Option<String>,
    pub data: Vec<u8>,
}

impl PartialDateTime {
    pub fn from_utc_timestamp(value: i64) -> Self {
        let dt = DateTime::from_timestamp(value);

        PartialDateTime {
            year: dt.year.into(),
            month: dt.month.into(),
            day: dt.day.into(),
            hour: dt.hour.into(),
            minute: dt.minute.into(),
            second: dt.second.into(),
            tz_hour: dt.tz_hour.into(),
            tz_minute: dt.tz_minute.into(),
            tz_minus: false,
        }
    }

    pub fn to_date_time(&self) -> Option<DateTimeResult> {
        let mut dt = DateTimeResult {
            date_time: NaiveDate::from_ymd_opt(
                self.year? as i32,
                self.month? as u32,
                self.day? as u32,
            )?
            .and_hms_opt(
                self.hour.unwrap_or(0) as u32,
                self.minute.unwrap_or(0) as u32,
                self.second.unwrap_or(0) as u32,
            )?,
            offset: None,
        };
        if let Some(tz_hour) = self.tz_hour {
            let secs = (tz_hour as i32 * 3600) + (self.tz_minute.unwrap_or(0) as i32 * 60);
            dt.offset = if self.tz_minus {
                FixedOffset::west_opt(secs)?
            } else {
                FixedOffset::east_opt(secs)?
            }
            .into();
        }
        Some(dt)
    }
}

#[cfg(feature = "rkyv")]
impl ArchivedPartialDateTime {
    pub fn to_date_time(&self) -> Option<DateTimeResult> {
        let mut dt = DateTimeResult {
            date_time: NaiveDate::from_ymd_opt(
                self.year.as_ref()?.to_native() as i32,
                *self.month.as_ref()? as u32,
                *self.day.as_ref()? as u32,
            )?
            .and_hms_opt(
                self.hour.unwrap_or(0) as u32,
                self.minute.unwrap_or(0) as u32,
                self.second.unwrap_or(0) as u32,
            )?,
            offset: None,
        };
        if let Some(tz_hour) = self.tz_hour.as_ref() {
            let secs = (*tz_hour as i32 * 3600) + (self.tz_minute.unwrap_or(0) as i32 * 60);
            dt.offset = if self.tz_minus {
                FixedOffset::west_opt(secs)?
            } else {
                FixedOffset::east_opt(secs)?
            }
            .into();
        }
        Some(dt)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DateTimeResult {
    pub date_time: NaiveDateTime,
    pub offset: Option<FixedOffset>,
}