Skip to content

Calendar time zone provider#959

Open
maknapp wants to merge 9 commits into
ical-org:mainfrom
maknapp:maknapp/cal-time-zone
Open

Calendar time zone provider#959
maknapp wants to merge 9 commits into
ical-org:mainfrom
maknapp:maknapp/cal-time-zone

Conversation

@maknapp

@maknapp maknapp commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

This adds a Calendar.TimeZoneProvider property to allow users to specify the NodaTime.IDateTimeZoneProvider to use during calendar/event evaluation. The default time zone provider for a calendar is CalendarTimeZoneProviders.TzdbWithAliases with the calendar defined time zones as the fallback. The default time zone provider for components without a calendar parent is CalendarTimeZoneProviders.TzdbWithAliases.

User choice

Users can choose the time zone provider:

  • Just use the default
  • Change the fallback(s) by using CalendarTimeZoneProviders.Combine() to create their own NodaTime.IDateTimeZoneProvider that searches the specified providers in the given order for a matching time zone
  • Be more strict by using NodaTime's DateTimeZoneProviders.Tzdb directly - with or without the calendar's VTIMEZONE components
  • Use only the calendar's VTIMEZONE components without any other provider (cal.TimeZoneProvider = cal.CreateTimeZoneProvider())

VTIMEZONE

VTIMEZONE changes are not automatically handled. VTIMEZONE components are converted to NodaTime.DateTimeZone values on demand and cached. If a VTIMEZONE component is added/removed/changed AFTER being used by the time zone provider, the Calendar.TimeZoneProvider will need to be recreated to be up-to-date with the calendar VTIMEZONE components.

One (unlikely?) issue users might run into is that NodaTime.ZonedDateTime checks equality by ReferenceEquals of its DateTimeZone, so recreating the Calendar.TimeZoneProvider could cause issues if the user tries to compare a ZonedDateTime with another created from a different time zone provider because they would have two different DateTimeZone objects even though they represent the same VTIMEZONE.

Exceptions

Missing time zone errors will now throw NodaTime's DateTimeZoneNotFoundException instead of ArgumentException. I'm not sure if there are downsides vs catching and rethrowing with our own TimeZoneNotFoundException. I do like having a more specific exception instead of ArgumentException. Related #648.

No more time zone exceptions in Calendar.Load(). As stated above, exceptions due to missing time zones are now thrown during evaluation.

Time zone searching

The Calendar.TimeZoneProvider is created on demand with a default value if none is specifically set. The default is NOT an exact copy of the previous TimeZoneResolver. Since this brings more time zone control to the user, it seemed reasonable to remove the more non-standard searching by default: case-insensitive and string.Contains searching. I did add a case-insensitive time zone provider in case that is actually needed - I would like to remove that commit though unless users actually need it. I ignored adding a string.Contains replacement because that seems very inaccurate to me.

Searching XmlSerializationSettings was also removed completely because it is, by NodaTime default, just the TZDB. What is the purpose of searching NodaTime.Xml.XmlSerializationSettings.DateTimeZoneProvider? The user can add this provider if they actually use this, so I think it is safe to ignore it completely.

Global time zone handling removed

DefaultTimeZoneResolver and TimeZoneResolvers were removed. Having a default time zone provider on the calendar is essentially the same thing for users who currently ignore time zones.

CalDateTime

This now requires a time zone provider to convert to a ZonedDateTime. @axunonb mentioned in #749 that there should be a resolver-less overload still, but a user converting to a ZonedDateTime should always be paying attention to the time zone provider anyway. If a user really wants to ignore it, they can just use extensions to use a default time zone provider.

ToDateTimeUtc() is also removed just because I did not want to require a NodaTime time zone provider to create a DateTime. Users avoiding NodaTime can still use ToDateTimeUnspecified() to get just the local time. Users can also use extensions to create a ToDateTimeUtc() however they want.

None of these changes are breaking changes for CalDateTime because they are not in v5.

Fixes #749

maknapp added 9 commits July 11, 2026 09:55
Implements Nodatime's IDateTimeZoneProvider for Calendar using its
VTIMEZONE components.
Evaluate events in custom time zone
This also stops caching the system time zone, which should not have
been happening anyway because the system time zone can change.
Adds a case-insensitive time zone provider to more closely
match previous behavior.
Events within a calendar can be evaluated in parallel, which would
make multiple threads access TimeZoneProvider at the same time.
@maknapp
maknapp requested a review from axunonb July 11, 2026 15:34
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.26891% with 66 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
Ical.Net/CalendarComponents/VTimeZone.cs 67.5% 18 Missing and 8 partials ⚠️
Ical.Net/CalendarTimeZoneProviders.cs 73.4% 9 Missing and 8 partials ⚠️
Ical.Net/Evaluation/TodoEvaluator.cs 10.0% 9 Missing ⚠️
Ical.Net/Evaluation/EventEvaluator.cs 80.8% 2 Missing and 3 partials ⚠️
Ical.Net/Calendar.cs 85.0% 1 Missing and 2 partials ⚠️
Ical.Net/DataTypes/FreeBusyEntry.cs 87.5% 2 Missing ⚠️
Ical.Net/DataTypes/Occurrence.cs 0.0% 2 Missing ⚠️
Ical.Net/CollectionExtensions.cs 0.0% 1 Missing ⚠️
Ical.Net/DataTypes/CalendarDataType.cs 0.0% 0 Missing and 1 partial ⚠️

❌ Your patch check has failed because the patch coverage (72.3%) is below the target coverage (80.0%). You can increase the patch coverage or adjust the target coverage.

Impacted file tree graph

@@           Coverage Diff           @@
##            main    #959     +/-   ##
=======================================
+ Coverage   71.5%   71.9%   +0.5%     
=======================================
  Files        118     116      -2     
  Lines       4585    4698    +113     
  Branches    1068    1082     +14     
=======================================
+ Hits        3277    3379    +102     
- Misses       955     963      +8     
- Partials     353     356      +3     
Files with missing lines Coverage Δ
Ical.Net/CalendarComponents/FreeBusy.cs 79.5% <100.0%> (+0.5%) ⬆️
Ical.Net/CalendarComponents/RecurringComponent.cs 87.4% <100.0%> (ø)
Ical.Net/CalendarObject.cs 74.4% <100.0%> (+10.1%) ⬆️
Ical.Net/DataTypes/CalDateTime.cs 98.8% <100.0%> (-<0.1%) ⬇️
Ical.Net/DataTypes/Period.cs 95.9% <100.0%> (-0.2%) ⬇️
Ical.Net/DataTypes/RecurrenceIdentifier.cs 100.0% <ø> (ø)
Ical.Net/Evaluation/RecurrenceRuleEvaluator.cs 93.2% <100.0%> (+<0.1%) ⬆️
Ical.Net/Evaluation/RecurringEvaluator.cs 95.3% <100.0%> (ø)
Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs 50.0% <100.0%> (+30.0%) ⬆️
Ical.Net/CollectionExtensions.cs 66.7% <0.0%> (ø)
... and 8 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@axunonb

axunonb commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

That sounds pretty nice. I'll need some time to go through changes.

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.

Unrecognized time zone id Customized Time Zone

2 participants