Skip to content

Conversation

@axunonb
Copy link
Collaborator

@axunonb axunonb commented Mar 2, 2025

Replace DateTime with CalDateTime in RecurrencePatternEvaluator and related code. Key changes:

  • Modifying the Until property of RecurrencePattern to use CalDateTime.
  • Updating ExceptionDates to directly add CalDateTime instances.
  • Refactoring methods like GetIso8601WeekOfYear to accept CalDateTime parameters.
  • Adjusting serialization and deserialization processes for CalDateTime.
  • Adjusting the RecurrencePatternEvaluator to work with CalDateTime.

@axunonb axunonb force-pushed the pr/datetime-to-caldatetime branch from 4af837a to 20323c8 Compare March 2, 2025 09:51
@axunonb
Copy link
Collaborator Author

axunonb commented Mar 2, 2025

Here are the remaining members of Ical.Net that have a DateTime parameter or return type.
@minichma TBD: Any of them that should be updated, too? (Expecially considering GetOccurrences)

  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.Calendar)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.Calendar)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.Calendar)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.Calendar)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarCollection)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarCollection)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarCollection)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarCollection)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.IGetOccurrences)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.IGetOccurrences)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.IGetOccurrencesTyped)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.IGetOccurrencesTyped)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.VTimeZoneInfo)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.VTimeZoneInfo)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarComponents.CalendarEvent)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarComponents.CalendarEvent)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarComponents.Journal)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarComponents.Journal)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarComponents.RecurringComponent)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarComponents.RecurringComponent)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarComponents.Todo)
  • Has 'DateTime' parameter: GetOccurrences (Ical.Net.CalendarComponents.Todo)

Other:

  • Has 'DateTime' parameter: AddTimeZone (Ical.Net.Calendar)
  • Has 'DateTime' parameter: AddTimeZone (Ical.Net.Calendar)
  • Has 'DateTime' parameter: AddLocalTimeZone (Ical.Net.Calendar)
  • Has 'DateTime' parameter: ToLocal (Ical.Net.DataTypes.UtcOffset)
  • Returns 'DateTime': System.IConvertible.ToDateTime (Ical.Net.FreeBusyStatus)
  • Returns 'DateTime': System.IConvertible.ToDateTime (Ical.Net.FrequencyType)
  • Returns 'DateTime': System.IConvertible.ToDateTime (Ical.Net.FrequencyOccurrence)
  • Has 'DateTime' parameter: AsCalDateTime (Ical.Net.Utility.DateUtil)
  • Has 'DateTime' parameter: ToZonedDateTimeLeniently (Ical.Net.Utility.DateUtil)
  • Has 'DateTime' parameter: FromTimeZoneToTimeZone (Ical.Net.Utility.DateUtil)
  • Has 'DateTime' parameter: FromTimeZoneToTimeZone (Ical.Net.Utility.DateUtil)
  • Returns 'DateTime': Truncate (Ical.Net.Utility.DateUtil)
  • Has 'DateTime' parameter: Truncate (Ical.Net.Utility.DateUtil)
  • Has 'DateTime' parameter: WeekOfMonth (Ical.Net.Utility.DateUtil)
  • Returns 'DateTime': get_AsUtc (Ical.Net.DataTypes.CalDateTime)
  • Returns 'DateTime': get_Value (Ical.Net.DataTypes.CalDateTime)
  • Has 'DateTime' parameter: TruncateTimeToSeconds (Ical.Net.DataTypes.CalDateTime)
  • Returns 'DateTime': System.IConvertible.ToDateTime (Ical.Net.DataTypes.PeriodKind)
  • Returns 'DateTime': ToUtc (Ical.Net.DataTypes.UtcOffset)
  • Has 'DateTime' parameter: ToUtc (Ical.Net.DataTypes.UtcOffset)
  • Returns 'DateTime': ToLocal (Ical.Net.DataTypes.UtcOffset)
  • Has 'DateTime' parameter: FromLocalTimeZone (Ical.Net.CalendarComponents.VTimeZone)
  • Has 'DateTime' parameter: FromSystemTimeZone (Ical.Net.CalendarComponents.VTimeZone)
  • Has 'DateTime' parameter: FromDateTimeZone (Ical.Net.CalendarComponents.VTimeZone)

@axunonb axunonb force-pushed the pr/datetime-to-caldatetime branch 3 times, most recently from 41526ad to b03ffe5 Compare March 2, 2025 11:47
@axunonb axunonb marked this pull request as draft March 2, 2025 16:56
@axunonb axunonb force-pushed the pr/datetime-to-caldatetime branch from b03ffe5 to 2e78621 Compare March 4, 2025 07:16
Replace instances of `DateTime` with `CalDateTime` across the codebase. Key updates include:

- Remove the `CalDateTime implicit operator` for converstion of `DateTime` to `CalDateTime`
- Modifying the Until property of `RecurrencePattern` to use `CalDateTime`.
- Updating `ExceptionDates` to directly add `CalDateTime` instances.
- Refactoring methods like `GetIso8601WeekOfYear` to accept `CalDateTime` parameters.
- Adjusting serialization and deserialization processes for `CalDateTime`.
- Adjusting the `RecurrencePatternEvaluator` to work with `CalDateTime`.
@axunonb axunonb force-pushed the pr/datetime-to-caldatetime branch from 2e78621 to dda621a Compare March 4, 2025 07:19
@minichma
Copy link
Collaborator

minichma commented Mar 4, 2025

@axunonb

TBD: Any of them that should be updated, too? (Expecially considering GetOccurrences)

I'd say ideally most of them, especially the GetOccurrences, except maybe some internal ones and those intended for conversion to/from DateTime.

@axunonb axunonb changed the title Refactor date-time handling to use CalDateTime instead of DateTime Replace DateTime with CalDateTime in CalDateTime in RecurrencePatternEvaluator and related code Mar 4, 2025
@axunonb
Copy link
Collaborator Author

axunonb commented Mar 4, 2025

Gonna create another PR for GetOccurrences updates.

@axunonb axunonb marked this pull request as ready for review March 4, 2025 08:59
@axunonb axunonb requested a review from minichma March 4, 2025 08:59
@axunonb axunonb changed the title Replace DateTime with CalDateTime in CalDateTime in RecurrencePatternEvaluator and related code Replace DateTime with CalDateTime RecurrencePatternEvaluator and related code Mar 4, 2025
@axunonb axunonb changed the title Replace DateTime with CalDateTime RecurrencePatternEvaluator and related code Replace DateTime with CalDateTime in RecurrencePatternEvaluator and related code Mar 4, 2025
/// in, according to the week numbering required by RFC 5545.
/// </summary>
private static DateTime GetStartOfWeek(this DateTime t, DayOfWeek firstDayOfWeek)
private static CalDateTime GetStartOfWeek(this CalDateTime t, DayOfWeek firstDayOfWeek)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also be DateOnly

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

@axunonb
Copy link
Collaborator Author

axunonb commented Mar 4, 2025

Maybe check the performance impact of changing the internals of this class to CalDateTime. Could be measurable.

Yes, true, quite significant... You think the price is too high?

v4.3.1

// * Summary *

BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores
.NET SDK 9.0.200
  [Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

Toolchain=InProcessNoEmitToolchain

| Method            | Mean         | Error      | StdDev     | Gen0     | Gen1     | Allocated   |
|------------------ |-------------:|-----------:|-----------:|---------:|---------:|------------:|
| SerializeCalendar |     6.222 us |  0.0748 us |  0.0700 us |   1.2207 |        - |    18.99 KB |
| GetOccurrences    | 4,017.977 us | 46.3923 us | 43.3953 us | 679.6875 | 515.6250 | 10426.99 KB |

// * Hints *
Outliers
  RecurrenceBenchmark.SerializeCalendar: Toolchain=InProcessNoEmitToolchain -> 2 outliers were detected (6.15 us, 6.19 us)
  RecurrenceBenchmark.GetOccurrences: Toolchain=InProcessNoEmitToolchain    -> 2 outliers were detected (1.31 ms, 1.32 ms)

// * Legends *
  Mean      : Arithmetic mean of all measurements
  Error     : Half of 99.9% confidence interval
  StdDev    : Standard deviation of all measurements
  Gen0      : GC Generation 0 collects per 1000 operations
  Gen1      : GC Generation 1 collects per 1000 operations
  Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  1 us      : 1 Microsecond (0.000001 sec)

// * Diagnostic Output - MemoryDiagnoser *


// ***** BenchmarkRunner: End *****
Run time: 00:00:23 (23.44 sec), executed benchmarks: 2

Global total time: 00:00:23 (23.81 sec), executed benchmarks: 2

Current Main

// * Summary *

BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores
.NET SDK 9.0.200
  [Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

Toolchain=InProcessNoEmitToolchain

| Method            | Mean       | Error     | StdDev    | Gen0     | Gen1     | Allocated |
|------------------ |-----------:|----------:|----------:|---------:|---------:|----------:|
| SerializeCalendar |   6.318 us | 0.0514 us | 0.0456 us |   1.4038 |   0.0229 |  21.61 KB |
| GetOccurrences    | 660.939 us | 2.7257 us | 2.5496 us | 143.5547 | 113.2813 |   2210 KB |

// * Hints *
Outliers
  RecurrenceBenchmark.SerializeCalendar: Toolchain=InProcessNoEmitToolchain -> 1 outlier  was  removed (6.58 us)

// * Legends *
  Mean      : Arithmetic mean of all measurements
  Error     : Half of 99.9% confidence interval
  StdDev    : Standard deviation of all measurements
  Gen0      : GC Generation 0 collects per 1000 operations
  Gen1      : GC Generation 1 collects per 1000 operations
  Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  1 us      : 1 Microsecond (0.000001 sec)

// * Diagnostic Output - MemoryDiagnoser *


// ***** BenchmarkRunner: End *****
Run time: 00:00:39 (39.13 sec), executed benchmarks: 2

Global total time: 00:00:39 (39.52 sec), executed benchmarks: 2

PR #742

// * Summary *

BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores
.NET SDK 9.0.200
  [Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

Toolchain=InProcessNoEmitToolchain

| Method            | Mean       | Error     | StdDev    | Gen0     | Gen1    | Allocated  |
|------------------ |-----------:|----------:|----------:|---------:|--------:|-----------:|
| SerializeCalendar |   6.261 us | 0.0405 us | 0.0379 us |   1.4038 |  0.0229 |   21.61 KB |
| GetOccurrences    | 829.253 us | 2.2819 us | 1.7816 us | 156.2500 | 94.7266 | 2397.47 KB |

// * Hints *
Outliers
  RecurrenceBenchmark.GetOccurrences: Toolchain=InProcessNoEmitToolchain -> 3 outliers were removed, 5 outliers were detected (825.28 us, 826.41 us, 833.97 us..835.15 us)

// * Legends *
  Mean      : Arithmetic mean of all measurements
  Error     : Half of 99.9% confidence interval
  StdDev    : Standard deviation of all measurements
  Gen0      : GC Generation 0 collects per 1000 operations
  Gen1      : GC Generation 1 collects per 1000 operations
  Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  1 us      : 1 Microsecond (0.000001 sec)

// * Diagnostic Output - MemoryDiagnoser *


// ***** BenchmarkRunner: End *****
Run time: 00:00:43 (43.06 sec), executed benchmarks: 2

Global total time: 00:00:43 (43.44 sec), executed benchmarks: 2

Benchmark

[MemoryDiagnoser]
public class RecurrenceBenchmark
{
    private Calendar _calendar;

    [GlobalSetup]
    public void Setup()
    {
        _calendar = GenerateCalendarWithRecurrences();
    }

    private Calendar GenerateCalendarWithRecurrences()
    {
        var calendar = new Calendar();
        var dailyEvent = new CalendarEvent
        {
            Start = new CalDateTime(2025, 3, 1),
            End = null,
            RecurrenceRules = new List<RecurrencePattern>
            {
                new RecurrencePattern(FrequencyType.Daily, 1) { Count = 1000 }
            }
        };
        calendar.Events.Add(dailyEvent);
        return calendar;
    }

    [Benchmark]
    public void SerializeCalendar()
    {
        var serializer = new CalendarSerializer();
        var serializedCalendar = serializer.SerializeToString(_calendar);
    }

    [Benchmark]
    public void GetOccurrences()
    {
        var occurrences = _calendar.GetOccurrences().ToList();
    }
}

@minichma
Copy link
Collaborator

minichma commented Mar 4, 2025

The problem with CalDateTime is that its a class while DateTime, DateOnly, etc. are structs. So for all the little operations in RecurrenceEvaluator, where tons of objects are allocated, this makes a difference. But the difference is lower than I'd have expected, so I wouldn't expect many complaints from users either way. You could either revert the change just for the internals of RecurrencePatternEvaluator (other classes won't be affected too much), or just leave it as is. Both is ok from my perspective. At some point we might either change CalDateTime to struct or move evaluation to different types as discussed in other threads. ZonedDateTime et al for instance are structs.

@axunonb
Copy link
Collaborator Author

axunonb commented Mar 4, 2025

@minichma I was actually hesitating decreasing performance.
Now I added the same benchmark test against v4.3.1, because I was confident that the new IEnumerable for occurrences had to be a lot better than the v4.3.1 HashSet. And it was, even when using CalDateTime, kudos to your effort.
So finally we should proceed (with a clear conscience) as you suggested.

@minichma
Copy link
Collaborator

minichma commented Mar 5, 2025

@axunonb Great, always good to be confident by measuring things!

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 5, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@axunonb axunonb merged commit a0db138 into ical-org:main Mar 5, 2025
3 of 4 checks passed
@axunonb axunonb deleted the pr/datetime-to-caldatetime branch March 5, 2025 08:11
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.

2 participants