1+ // Licensed to the .NET Foundation under one or more agreements.
2+ // The .NET Foundation licenses this file to you under the MIT license.
3+
4+ using System ;
5+ using System . Collections . Generic ;
6+ using System . Diagnostics ;
7+ using System . Globalization ;
8+ using System . Threading ;
9+
10+ namespace Microsoft . Extensions . Time . Testing ;
11+
12+ /// <summary>
13+ /// A synthetic clock used to provide deterministic behavior in tests.
14+ /// </summary>
15+ public class FakeTimeProvider : TimeProvider
16+ {
17+ internal static readonly DateTimeOffset Epoch = new ( 2000 , 1 , 1 , 0 , 0 , 0 , 0 , TimeSpan . Zero ) ;
18+
19+ internal readonly List < FakeTimeProviderTimer . Waiter > Waiters = new ( ) ;
20+
21+ private DateTimeOffset _now = Epoch ;
22+ private TimeZoneInfo _localTimeZone ;
23+
24+ /// <summary>
25+ /// Initializes a new instance of the <see cref="FakeTimeProvider"/> class.
26+ /// </summary>
27+ /// <remarks>
28+ /// This creates a clock whose time is set to midnight January 1st 2000.
29+ /// The clock is set to not automatically advance every time it is read.
30+ /// </remarks>
31+ public FakeTimeProvider ( )
32+ {
33+ _localTimeZone = TimeZoneInfo . Utc ;
34+ }
35+
36+ /// <summary>
37+ /// Initializes a new instance of the <see cref="FakeTimeProvider"/> class.
38+ /// </summary>
39+ /// <param name="startTime">The initial time reported by the clock.</param>
40+ public FakeTimeProvider ( DateTimeOffset startTime )
41+ : this ( )
42+ {
43+ _now = startTime ;
44+ }
45+
46+ /// <inheritdoc />
47+ public override DateTimeOffset GetUtcNow ( )
48+ {
49+ return _now ;
50+ }
51+
52+ /// <summary>
53+ /// Sets the date and time in the UTC timezone.
54+ /// </summary>
55+ /// <param name="value">The date and time in the UTC timezone.</param>
56+ public void SetUtcNow ( DateTimeOffset value )
57+ {
58+ List < FakeTimeProviderTimer . Waiter > waiters ;
59+ lock ( Waiters )
60+ {
61+ _now = value ;
62+ waiters = GetWaitersToWake ( ) ;
63+ }
64+
65+ WakeWaiters ( waiters ) ;
66+ }
67+
68+ /// <summary>
69+ /// Advances the clock's time by a specific amount.
70+ /// </summary>
71+ /// <param name="delta">The amount of time to advance the clock by.</param>
72+ public void Advance ( TimeSpan delta )
73+ {
74+ List < FakeTimeProviderTimer . Waiter > waiters ;
75+ lock ( Waiters )
76+ {
77+ _now += delta ;
78+ waiters = GetWaitersToWake ( ) ;
79+ }
80+
81+ WakeWaiters ( waiters ) ;
82+ }
83+
84+ /// <summary>
85+ /// Advances the clock's time by one millisecond.
86+ /// </summary>
87+ public void Advance ( ) => Advance ( TimeSpan . FromMilliseconds ( 1 ) ) ;
88+
89+ /// <inheritdoc />
90+ public override long GetTimestamp ( )
91+ {
92+ // Notionally we're multiplying by frequency and dividing by ticks per second,
93+ // which are the same value for us. Don't actually do the math as the full
94+ // precision of ticks (a long) cannot be represented in a double during division.
95+ // For test stability we want a reproducible result.
96+ //
97+ // The same issue could occur converting back, in GetElapsedTime(). Unfortunately
98+ // that isn't virtual so we can't do the same trick. However, if tests advance
99+ // the clock in multiples of 1ms or so this loss of precision will not be visible.
100+ Debug . Assert ( TimestampFrequency == TimeSpan . TicksPerSecond , "Assuming frequency equals ticks per second" ) ;
101+ return _now . Ticks ;
102+ }
103+
104+ /// <inheritdoc />
105+ public override TimeZoneInfo LocalTimeZone => _localTimeZone ;
106+
107+ /// <summary>
108+ /// Sets the local timezone.
109+ /// </summary>
110+ /// <param name="localTimeZone">The local timezone.</param>
111+ public void SetLocalTimeZone ( TimeZoneInfo localTimeZone )
112+ {
113+ _localTimeZone = localTimeZone ;
114+ }
115+
116+ /// <summary>
117+ /// Gets the amount that the value from <see cref="GetTimestamp"/> increments per second.
118+ /// </summary>
119+ /// <remarks>
120+ /// We fix it here for test instability which would otherwise occur within
121+ /// <see cref="GetTimestamp"/> when the result of multiplying underlying ticks
122+ /// by frequency and dividing by ticks per second is truncated to long.
123+ ///
124+ /// Similarly truncation could occur when reversing this calculation to figure a time
125+ /// interval from the difference between two timestamps.
126+ ///
127+ /// As ticks per second is always 10^7, setting frequency to 10^7 is convenient.
128+ /// It happens that the system usually uses 10^9 or 10^6 so this could expose
129+ /// any assumption made that it is one of those values.
130+ /// </remarks>
131+ public override long TimestampFrequency => TimeSpan . TicksPerSecond ;
132+
133+ /// <summary>
134+ /// Returns a string representation this clock's current time.
135+ /// </summary>
136+ /// <returns>A string representing the clock's current time.</returns>
137+ public override string ToString ( ) => GetUtcNow ( ) . ToString ( "yyyy-MM-ddTHH:mm:ss.fff" , CultureInfo . InvariantCulture ) ;
138+
139+ /// <inheritdoc />
140+ public override ITimer CreateTimer ( TimerCallback callback , object ? state , TimeSpan dueTime , TimeSpan period )
141+ {
142+ return new FakeTimeProviderTimer ( this , dueTime , period , callback , state ) ;
143+ }
144+
145+ internal void AddWaiter ( FakeTimeProviderTimer . Waiter waiter )
146+ {
147+ lock ( Waiters )
148+ {
149+ Waiters . Add ( waiter ) ;
150+ }
151+ }
152+
153+ internal void RemoveWaiter ( FakeTimeProviderTimer . Waiter waiter )
154+ {
155+ lock ( Waiters )
156+ {
157+ _ = Waiters . Remove ( waiter ) ;
158+ }
159+ }
160+
161+ private List < FakeTimeProviderTimer . Waiter > GetWaitersToWake ( )
162+ {
163+ var l = new List < FakeTimeProviderTimer . Waiter > ( Waiters . Count ) ;
164+ foreach ( var w in Waiters )
165+ {
166+ if ( _now >= w . WakeupTime )
167+ {
168+ l . Add ( w ) ;
169+ }
170+ }
171+
172+ return l ;
173+ }
174+
175+ private void WakeWaiters ( List < FakeTimeProviderTimer . Waiter > waiters )
176+ {
177+ foreach ( var w in waiters )
178+ {
179+ if ( _now >= w . WakeupTime )
180+ {
181+ w . TriggerAndSchedule ( false ) ;
182+ }
183+ }
184+ }
185+ }
0 commit comments