44//
55
66using System ;
7- using System . Collections . Concurrent ;
87using System . Globalization ;
8+ using System . Runtime . CompilerServices ;
99using Ical . Net . DataTypes ;
1010using Ical . Net . Utility ;
1111using NodaTime ;
@@ -18,62 +18,44 @@ namespace Ical.Net.Evaluation;
1818/// </summary>
1919public static class CalDateTimeExtensions
2020{
21- /// <summary>
22- /// This struct is used as a key for the cache.
23- /// It holds a weak reference to the <see cref="CalDateTime"/> object.
24- /// </summary>
25- private struct WeakCalDateTimeKey : IEquatable < WeakCalDateTimeKey >
21+ private class ZonedDateTimeBox ( ZonedDateTime value )
2622 {
27- private readonly WeakReference < CalDateTime > _weakRef ;
28- private readonly int _hashCode ;
29-
30- public WeakCalDateTimeKey ( CalDateTime calDateTime )
31- {
32- _weakRef = new WeakReference < CalDateTime > ( calDateTime ) ;
33- _hashCode = calDateTime . GetHashCode ( ) ;
34- }
35-
36- public bool TryGetTarget ( out CalDateTime ? target ) => _weakRef . TryGetTarget ( out target ) ;
37-
38- public bool Equals ( WeakCalDateTimeKey other )
39- {
40- if ( ! _weakRef . TryGetTarget ( out var thisTarget ) || ! other . _weakRef . TryGetTarget ( out var otherTarget ) )
41- return false ;
42-
43- return ReferenceEquals ( thisTarget , otherTarget ) || thisTarget . Equals ( otherTarget ) ;
44- }
45-
46- public override bool Equals ( object ? obj ) => obj is WeakCalDateTimeKey other && Equals ( other ) ;
47-
48- public override int GetHashCode ( ) => _hashCode ;
23+ public ZonedDateTime Value { get ; } = value ;
4924 }
5025
5126 /// <summary>
52- /// The cache for storing resolved <see cref="ZonedDateTime"/> objects for <see cref="CalDateTime"/> objects.
27+ /// The cache for storing resolved <see cref="ZonedDateTime"/> objects for <see cref="CalDateTime"/> objects
5328 /// This is a thread-safe dictionary that uses weak references to avoid memory leaks.
29+ /// Dead <see cref="CalDateTime"/> keys are automatically removed from the cache.
30+ /// Key and Value must be reference types, so we use <see cref="ZonedDateTimeBox"/> to box the value.
31+ /// <para/>
32+ /// If a <see cref="CalDateTime"/> is instantiated from a lenient timezone conversion, this leniently
33+ /// determined <see cref="CalDateTime"/> must not be converted again.
34+ /// Lenient conversions resolve non-existing or ambiguous times caused by DST transitions.
35+ /// <para/>
36+ /// Therefore, when converting from one timezone to another, the conversion is done with the
37+ /// <see cref="ZonedDateTime"/> object created at the time of (first) conversion,
38+ /// which is stored in the cache.
5439 /// </summary>
55- private static readonly ConcurrentDictionary < WeakCalDateTimeKey , ZonedDateTime > _cache = new ( ) ;
40+ private static readonly ConditionalWeakTable < CalDateTime , ZonedDateTimeBox > _cache = new ( ) ;
5641
5742 /// <summary>
5843 /// Stores a <see cref="ZonedDateTime"/> in the cache for the given <see cref="CalDateTime"/>.
5944 /// </summary>
6045 private static void AddToCache ( CalDateTime calDateTime , ZonedDateTime zonedDateTime )
61- {
62- var key = new WeakCalDateTimeKey ( calDateTime ) ;
63- _cache [ key ] = zonedDateTime ;
64- }
46+ => _cache . Add ( calDateTime , new ZonedDateTimeBox ( zonedDateTime ) ) ;
6547
6648 /// <summary>
6749 /// Resolves a <see cref="CalDateTime"/> to a <see cref="ZonedDateTime"/>,
6850 /// using the cache if possible.
6951 /// </summary>
7052 public static ZonedDateTime ? ResolveZonedDateTime ( CalDateTime calDateTime )
7153 {
72- var key = new WeakCalDateTimeKey ( calDateTime ) ;
73-
7454 // Try to get from cache
75- if ( _cache . TryGetValue ( key , out var zoned ) )
76- return zoned ;
55+ if ( _cache . TryGetValue ( calDateTime , out var zoned ) )
56+ {
57+ return zoned . Value ;
58+ }
7759
7860 // Compute and cache
7961 ZonedDateTime ? result = null ;
@@ -88,23 +70,11 @@ private static void AddToCache(CalDateTime calDateTime, ZonedDateTime zonedDateT
8870 }
8971
9072 if ( result . HasValue )
91- _cache [ key ] = result . Value ;
73+ AddToCache ( calDateTime , result . Value ) ;
9274
9375 return result ;
9476 }
9577
96- /// <summary>
97- /// Removes dead entries from the cache.
98- /// </summary>
99- public static void CleanupCache ( )
100- {
101- foreach ( var key in _cache . Keys )
102- {
103- if ( ! key . TryGetTarget ( out _ ) )
104- _cache . TryRemove ( key , out _ ) ;
105- }
106- }
107-
10878 /// <summary>
10979 /// Converts the date/time to UTC (Coordinated Universal Time)
11080 /// If <see cref="CalDateTime.IsFloating"/>==<see langword="true"/>
0 commit comments