Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PoC: Allow any TimeZone implementation #85

Closed

Conversation

WhyNotHugo
Copy link

Make the TimeZone parametrised in as many places as possible. This allows using any TimeZone implementations for most of the logic. Most notably, this allows using chrono::Local.

Using chrono::Local makes it much easier to construct TimeZone instances from parsed VTIMEZONE components and to interoperate with other crates.

Some notes:

  • There's a few new .clone() calls. This is because chrono_tz::Tz implements Copy, but other TimeZone implementation do not. In reality, these values were already being copied before anyway, but this was done implicitly.
  • For error messages, to_string() cannot be used, since TimeZone does not implement Display. The implementations in the chrono package are the most obvious offenders here. Debug is now used instead for error messages.
  • The validate_until validation is no longer called. It assumes that all RRules use the rrule::Tz TimeZone implementation. I don't see an obvious way to fix it, and this is still a pending item.

This is a proof of concept (which I wrote on a long train trip). Tests pass, but this probably warrants some extra discussion.

Fixes: #24

@WhyNotHugo WhyNotHugo force-pushed the generic-timezone-rebase branch 2 times, most recently from d167093 to 877e7d2 Compare September 8, 2022 19:39
Make the TimeZone parametrised in as many places as possible. This
allows using any TimeZone implementations for most of the logic. Most
notably, this allows using `chrono::Local`.

Using `chrono::Local` makes it much easier  to construct TimeZone
instances from parsed `VTIMEZONE` components and to interoperate with
other crates.

Some notes:

- There's a few new `.clone()` calls. This is because `chrono_tz::Tz`
  implements `Copy`, but other `TimeZone` implementation do not. In
  reality, these values were already being copied before anyway, but
  this was done implicitly.
- For error messages, `to_string()` cannot be used, since `TimeZone`
  does not implement `Display`. The implementations in the `chrono`
  package are the most obvious offenders here. `Debug` is now used
  instead for error messages.
- The `validate_until` validation is no longer called. It assumes that
  all `RRules` use the `rrule::Tz` TimeZone implementation. I don't see
  an obvious way to fix it, and this is still a pending item.
- `VALIDATION_PIPELINE` and `FILTERS` were defined in a way that breaks
  reusability with generic parameters, so these were dropped in favour
  of just referring to them in-line.

This is a proof of concept (which I wrote on a long train trip). Tests
pass, but this probably warrants some extra discussion.

Fixes: fmeringdal#24
@fmeringdal
Copy link
Owner

fmeringdal commented Sep 9, 2022

Hey, thanks for the contribution!

I am a bit hesitant of introducing a generic type for RRuleSet for a couple of reasons:

  • It forces DTSTART, UNTIL, RDATE and EXDATE values to all use the same timezone. The RFC actually says that UNTIL has to use UTC when DTSTART is not using local timezone. Adding more generic types could help, but it makes for a very bad API.
  • I personally think adding a generic argument to the RRuleSet type makes the API a bit more awkward. The generic type propagates upwards to the parent struct unless the user know they will use a specific timezone everywhere. I imagine that just want to get date-times back with a timezone defined in the TZID parameter for DTSTART and that can't be defined at compile time.
  • The RFC also says that the resulting occurrences from the iteration should be in the same timezone as what is used in the start date.
  • The current rrule::Tz supports almost all relevant timezone formats and can easily be extended with more formats if needed. It is also interoperable with the timezones in both chrono and chrono-tz.

Overall it feels that it restricts a bit more than it enables. I am interested to hear more about your current interoperability issues with the current design as that might help me understand you use-case better. How about adding a all_with_timezone<Tz: TimeZone>(tz: Tz) -> DateTime<Tz> method?

@WhyNotHugo
Copy link
Author

WhyNotHugo commented Sep 9, 2022 via email

@WhyNotHugo
Copy link
Author

WhyNotHugo commented Sep 9, 2022 via email

@fmeringdal
Copy link
Owner

Hey again, I didn't forget about this. Quite busy times these days, but I will try to get back to you within the next week :)

@WhyNotHugo
Copy link
Author

I kinda figured out that std::any could work for the validate_until impementation, but how now hit a bigger roadblock with RDATE.

Checking the spec, I don't see any restrictions on the timezone's for RDATE. Apparently, an RDATE for an RRULE can use any timezone, so the same recurrence rule might include recurrence dates with UTC, floating times, and a VTIMEZONE. I would really really like to be wrong here, but I checked the spec twice and don't see any restrictions on this.

With the approach in this PR, I don't think it's possible to represent this, since we'd need to keep a Vec<DateTime<_>>, where each DateTime instance can have a different generic type. Using rrule::Tz works around this, although it would need to be extended with a new enum variant like Tz::VTimeZone in order to actually use the timezones defined in the icalendar object.

I checked my personal calendar and only found RDATE used inside VTIMEZONE definitions. The offset for these DATE-TIMEs is defined via the TZOFFSETFROM property, and this currently cannot be represented via rrule::Tz either. A new variant (e.g.: Tz::FixedOffset) can cover this too.

In order to extend rrule::Tz with a new VTimeZone variant, its associated type (e.g.: struct VTimeZone) would need to be a bulid dependency of this crate. However, parsing and instantiating these VTimeZone instances requires an rrule implementation. Due to this circular dependency, I don't see how the VTimeZone type and the RRule type can be implemented in different crates. For RRULES components inside VEVENTS and VTODOS (e.g.: RRULEs NOT in a VTIMEZONE component), there's a strong dependency on a VTIMEZONE implementation anyway in order to calculate recurrence.

@fmeringdal
Copy link
Owner

I see, thanks for describing you use-case so well. I am by no means any timezone expert and was under the impression that using timezones found in DB was "good enough", but apparently it is not sufficient as you have pointed out.

My previous point about the generic types on RRule making for an awkward API is also not quite true as the types could default to rrule::Tz and become invisible for users who don't care about them. I assume there is still a bit of exploration that needs to done in this PR to find a good ergonomic API while at the same time being mostly compliant with the RFC (we will probably never be fully compliant nor do I aim for that).

How is the current implementation in this PR working for your personal calendar? Is it able to process most of your entries or is there still a bunch of entries that does not work?

@WhyNotHugo WhyNotHugo marked this pull request as draft September 28, 2022 09:52
@WhyNotHugo
Copy link
Author

This kinda works for my calendar (though I have several bits of the VTimeZone impl still incomplete). This PR enables using that custom VTimeZone impl, but has another issue:

In particular, RDates can have different timezones, so we'd need to keep a Vec<DateTime> with mixed timezones -- and this can only be done using an enum TimeZone like the one being used right now (discussion on chrono on this topic).

As such, I don't think it's a good idea to merge this (hence marking as draft). I think moving VTimeZone into this repo is the only suitable way to address the whole issue, because RRuleSet depends on VTimeZone, but VTimeZone depends on RRuleSet.

Essentially, all enum variants would be:

pub enum Tz {
    /// Floating (e.g.: no timezone).
    Local(Local),
    /// Timezone represented by `chrono_tz::Tz` (mostly for _creating_ instances and backwards compat).
    Tz(chrono_tz::Tz),
    // A timezone read from an icalendar object's VTIMEZONE component.
    VTimeZone(VTimeZone),
}

@reedts
Copy link

reedts commented Mar 4, 2023

Any developments/news on this? Would be super cool if there was a way to make this crate work with other, generic TimeZone implementations!

@WhyNotHugo
Copy link
Author

@reedts: This particular details is a blocker for this to move forward:

In particular, RDates can have different timezones, so we'd need to keep a Vec with mixed timezones -- and this can only be done using an enum TimeZone like the one being used right now (chronotope/chrono#822).

There's some exploratory work for that in chronotope/chrono#830, but that's not a minor undertaking TBH, and I don't fully recall where discussions left off.

@reedts
Copy link

reedts commented Mar 9, 2023

@WhyNotHugo thank you for the headsup.

I myself see no way around implementing VTimeZone in this crate either, for the reasons you already mentioned. Seems not that easy :/...

@WhyNotHugo
Copy link
Author

Indeed. Although only the VTimeZone type itself needs to be in this crate; logic to parse that from a raw icalendar component can be external.

I'm currently focused on some caldav-related work, but I want to jump onto this in future. I have in mind writing a SAX-like parser fro icalendar, so after I have that I might start work on parsing VTIMEZONE and eventually get to this.

@WhyNotHugo
Copy link
Author

I'll go ahead and close this. This approach might not be feasible at all, and #24 exists to continue discussion anyway.

@WhyNotHugo WhyNotHugo closed this Oct 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support more timezone formats
3 participants