Fixes to event timestamps

This commit is contained in:
Lennart
2024-08-03 16:27:41 +02:00
parent d3ab140c3a
commit 3dd9a048ac
2 changed files with 65 additions and 34 deletions

View File

@@ -1,13 +1,13 @@
use crate::{
timestamps::{parse_datetime, parse_duration},
timestamps::{parse_duration, CalDateTime},
Error,
};
use anyhow::{anyhow, Result};
use chrono::{Duration, NaiveDateTime, Timelike};
use chrono::Duration;
use ical::parser::{ical::component::IcalCalendar, Component};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::io::BufReader;
use std::{io::BufReader, str::FromStr};
#[derive(Debug, Clone)]
pub struct Event {
@@ -87,7 +87,7 @@ impl Event {
&self.ics
}
pub fn get_first_occurence(&self) -> Result<NaiveDateTime> {
pub fn get_first_occurence(&self) -> Result<CalDateTime> {
// This is safe since we enforce the event's existance in the constructor
let event = self.cal.events.first().unwrap();
let dtstart = event
@@ -96,10 +96,10 @@ impl Event {
.value
.to_owned()
.ok_or(anyhow!("DTSTART property has no value!"))?;
parse_datetime(&dtstart)
Ok(CalDateTime::from_str(&dtstart)?)
}
pub fn get_last_occurence(&self) -> Result<NaiveDateTime> {
pub fn get_last_occurence(&self) -> Result<CalDateTime> {
// This is safe since we enforce the event's existence in the constructor
let event = self.cal.events.first().unwrap();
@@ -113,7 +113,7 @@ impl Event {
.value
.to_owned()
.ok_or(anyhow!("DTEND property has no value!"))?;
return parse_datetime(&dtend);
return Ok(CalDateTime::from_str(&dtend)?);
}
if let Some(dtend_prop) = event.get_property("DURATION") {
@@ -126,10 +126,9 @@ impl Event {
}
let dtstart = self.get_first_occurence()?;
if dtstart.num_seconds_from_midnight() == 0 {
// no explicit time given => whole-day event
if let CalDateTime::Date(_) = dtstart {
return Ok(dtstart + Duration::days(1));
};
}
Err(anyhow!("help, couldn't determine any last occurence"))
}

View File

@@ -1,12 +1,66 @@
use std::{ops::Add, str::FromStr};
use crate::Error;
use anyhow::Result;
use chrono::{Duration, NaiveDateTime};
use anyhow::{anyhow, Result};
use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use lazy_static::lazy_static;
lazy_static! {
static ref RE_DURATION: regex::Regex = regex::Regex::new(r"^(?<sign>[+-])?P((?P<W>\d+)W)?((?P<D>\d+)D)?(T((?P<H>\d+)H)?((?P<M>\d+)M)?((?P<S>\d+)S)?)?$").unwrap();
}
const LOCAL_DATE_TIME: &str = "%Y%m%dT%H%M%S";
const UTC_DATE_TIME: &str = "%Y%m%dT%H%M%SZ";
const LOCAL_DATE: &str = "%Y%m%d";
pub enum CalDateTime {
// Form 1, example: 19980118T230000
Local(NaiveDateTime),
// Form 2, example: 19980119T070000Z
Utc(DateTime<Utc>),
// Form 3, example: TZID=America/New_York:19980119T020000
// TODO: implement timezone parsing
ExplicitTZ((String, NaiveDateTime)),
Date(NaiveDate),
}
impl Add<Duration> for CalDateTime {
type Output = Self;
fn add(self, duration: Duration) -> Self::Output {
match self {
Self::Local(datetime) => Self::Local(datetime + duration),
Self::Utc(datetime) => Self::Utc(datetime + duration),
Self::ExplicitTZ((tz, datetime)) => Self::ExplicitTZ((tz, datetime + duration)),
Self::Date(date) => Self::Local(date.and_time(NaiveTime::default()) + duration),
}
}
}
impl FromStr for CalDateTime {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if let Ok(datetime) = NaiveDateTime::parse_from_str(value, LOCAL_DATE_TIME) {
return Ok(CalDateTime::Local(datetime));
}
if let Ok(datetime) = NaiveDateTime::parse_from_str(value, UTC_DATE_TIME) {
return Ok(CalDateTime::Utc(datetime.and_utc()));
}
if let Ok(date) = NaiveDate::parse_from_str(value, LOCAL_DATE) {
return Ok(CalDateTime::Date(date));
}
Err(Error::Other(anyhow!("Invalid datetime format")))
}
}
#[test]
fn test_parse_cal_datetime() {
CalDateTime::from_str("19980118T230000").unwrap();
CalDateTime::from_str("19980118T230000Z").unwrap();
CalDateTime::from_str("19980118").unwrap();
}
pub fn parse_duration(string: &str) -> Result<Duration, Error> {
let captures = RE_DURATION
.captures(string)
@@ -45,25 +99,3 @@ fn test_parse_duration() {
assert_eq!(parse_duration("PT12M").unwrap(), Duration::minutes(12));
assert_eq!(parse_duration("PT12S").unwrap(), Duration::seconds(12));
}
pub fn parse_datetime(string: &str) -> Result<NaiveDateTime> {
// TODO: respect timezones
//
// Format: ^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?P<utc>Z)?$
// if Z?
// UTC time
// else
// if TZID given?
// time in TZ
// else
// local time of attendee (can be different actual times for different attendees)
// BUT for this implementation will be UTC for now since this case is annoying
// (sabre-dav does same)
let (datetime, _tz_remainder) = NaiveDateTime::parse_and_remainder(string, "%Y%m%dT%H%M%S")?;
Ok(datetime)
}
#[test]
fn test_parse_datetime() {
dbg!(parse_datetime("19960329T133000Z").unwrap());
}