Co to jest TimeProvider?
System.TimeProvider to abstrakcja czasu, która zapewnia punkt w czasie jako typ DateTimeOffset. Korzystając z TimeProvider
, upewnij się, że kod jest możliwy do testowania i przewidywalny.
TimeProvider
wprowadzono na platformie .NET 8 i jest również dostępny dla programów .NET Framework 4.7+ i .NET Standard 2.0 jako pakiet NuGet.
Klasa TimeProvider definiuje następujące możliwości:
- Zapewnia dostęp do daty i godziny za pośrednictwem TimeProvider.GetUtcNow() i TimeProvider.GetLocalNow().
- Znaczniki czasu o wysokiej częstotliwości z użyciem TimeProvider.GetTimestamp().
- Mierz czas między dwoma znacznikami czasu za pomocą TimeProvider.GetElapsedTime.
- Czasomierze o wysokiej rozdzielczości z TimeProvider.CreateTimer(TimerCallback, Object, TimeSpan, TimeSpan).
- Pobierz bieżącą strefę czasową za pomocą TimeProvider.LocalTimeZone.
Implementacja domyślna
Platforma .NET udostępnia implementację TimeProvider za pośrednictwem właściwości TimeProvider.System o następujących cechach:
- Data i godzina są obliczane przy użyciu DateTimeOffset.UtcNow i TimeZoneInfo.Local.
- Znaczniki czasu są udostępniane przez System.Diagnostics.Stopwatch.
- Czasomierze są implementowane za pośrednictwem klasy wewnętrznej i udostępniane jako System.Threading.ITimer.
W poniższym przykładzie pokazano użycie TimeProvider w celu uzyskania bieżącej daty i godziny:
Console.WriteLine($"Local: {TimeProvider.System.GetLocalNow()}");
Console.WriteLine($"Utc: {TimeProvider.System.GetUtcNow()}");
/* This example produces output similar to the following:
*
* Local: 12/5/2024 10:41:14 AM -08:00
* Utc: 12/5/2024 6:41:14 PM +00:00
*/
Console.WriteLine($"Local: {TimeProvider.System.GetLocalNow()}")
Console.WriteLine($"Utc: {TimeProvider.System.GetUtcNow()}")
' This example produces output similar to the following
'
' Local: 12/5/2024 10:41:14 AM -08:00
' Utc: 12/5/2024 6:41:14 PM +00:00
W poniższym przykładzie pokazano mierzenie upływu czasu z TimeProvider.GetTimestamp():
long stampStart = TimeProvider.System.GetTimestamp();
Console.WriteLine($"Starting timestamp: {stampStart}");
long stampEnd = TimeProvider.System.GetTimestamp();
Console.WriteLine($"Ending timestamp: {stampEnd}");
Console.WriteLine($"Elapsed time: {TimeProvider.System.GetElapsedTime(stampStart, stampEnd)}");
Console.WriteLine($"Nanoseconds: {TimeProvider.System.GetElapsedTime(stampStart, stampEnd).TotalNanoseconds}");
/* This example produces output similar to the following:
*
* Starting timestamp: 55185546133
* Ending timestamp: 55185549929
* Elapsed time: 00:00:00.0003796
* Nanoseconds: 379600
*/
Dim stampStart As Long = TimeProvider.System.GetTimestamp()
Console.WriteLine($"Starting timestamp: {stampStart}")
Dim stampEnd As Long = TimeProvider.System.GetTimestamp()
Console.WriteLine($"Ending timestamp: {stampEnd}")
Console.WriteLine($"Elapsed time: {TimeProvider.System.GetElapsedTime(stampStart, stampEnd)}")
Console.WriteLine($"Nanoseconds: {TimeProvider.System.GetElapsedTime(stampStart, stampEnd).TotalNanoseconds}")
' This example produces output similar to the following:
'
' Starting timestamp: 55185546133
' Ending timestamp: 55185549929
' Elapsed time: 00:00:00.0003796
' Nanoseconds: 379600
Implementacja elementu FakeTimeProvider
Pakiet Microsoft.Extensions.TimeProvider.Testing NuGet zapewnia sterowalną implementację TimeProvider
przeznaczoną do testowania jednostkowego.
Poniższa lista zawiera opis niektórych możliwości klasy FakeTimeProvider:
- Ustaw określoną datę i godzinę.
- Automatycznie przesuń datę i godzinę o określoną kwotę za każdym razem, gdy data i godzina jest odczytywana.
- Ręcznie zwiększ datę i godzinę.
Implementacja niestandardowa
Chociaż FakeTimeProvider powinien obejmować większość scenariuszy wymagających przewidywalności czasu, wciąż można dostarczyć własną implementację. Utwórz nową klasę pochodną od TimeProvider i przesłoń jej metody, aby kontrolować sposób dostarczania informacji o czasie. Na przykład następująca klasa zawiera tylko jedną datę, datę lądowania księżyca:
public class MoonLandingTimeProviderPST: TimeProvider
{
// July 20, 1969, at 20:17:40 UTC
private readonly DateTimeOffset _specificDateTime = new(1969, 7, 20, 20, 17, 40, TimeZoneInfo.Utc.BaseUtcOffset);
public override DateTimeOffset GetUtcNow() => _specificDateTime;
public override TimeZoneInfo LocalTimeZone => TimeZoneInfo.FindSystemTimeZoneById("PST");
}
Public Class MoonLandingTimeProviderPST
Inherits TimeProvider
'July 20, 1969, at 20:17:40 UTC
Private ReadOnly _specificDateTime As New DateTimeOffset(1969, 7, 20, 20, 17, 40, TimeZoneInfo.Utc.BaseUtcOffset)
Public Overrides Function GetUtcNow() As DateTimeOffset
Return _specificDateTime
End Function
Public Overrides ReadOnly Property LocalTimeZone As TimeZoneInfo
Get
Return TimeZoneInfo.FindSystemTimeZoneById("PST")
End Get
End Property
End Class
Jeśli kod korzystający z tej klasy wywołuje MoonLandingTimeProviderPST.GetUtcNow
, zwracana jest data lądowania księżyca w formacie UTC. Jeśli MoonLandingTimeProviderPST.GetLocalNow
jest wywoływana, klasa bazowa stosuje MoonLandingTimeProviderPST.LocalTimeZone
do GetUtcNow
i zwraca datę i godzinę lądowania księżyca w strefie czasowej PST.
Aby zademonstrować użyteczność kontrolowania czasu, rozważmy poniższy przykład. Załóżmy, że piszesz aplikację kalendarza, która wysyła powitanie do użytkownika po pierwszym otwarciu aplikacji każdego dnia. Aplikacja mówi specjalne powitanie, gdy bieżący dzień ma związane z nim wydarzenie, takie jak rocznica lądowania księżyca.
public static class CalendarHelper
{
static readonly DateTimeOffset MoonLandingDateTime = new(1969, 7, 20, 20, 17, 40, TimeZoneInfo.Utc.BaseUtcOffset);
public static void SendGreeting(TimeProvider currentTime, string name)
{
DateTimeOffset localTime = currentTime.GetLocalNow();
Console.WriteLine($"Good morning, {name}!");
Console.WriteLine($"The date is {localTime.Date:d} and the day is {localTime.Date.DayOfWeek}.");
if (localTime.Date.Month == MoonLandingDateTime.Date.Month
&& localTime.Date.Day == MoonLandingDateTime.Date.Day)
{
Console.WriteLine("Did you know that on this day in 1969 humans landed on the Moon?");
}
Console.WriteLine($"I hope you enjoy your day!");
}
}
Public Module CalendarHelper
ReadOnly MoonLandingDateTime As DateTimeOffset = #7/20/1969 20:17:40#
Public Sub SendGreeting(currentTime As TimeProvider, name As String)
Dim localTime As DateTimeOffset = currentTime.GetLocalNow()
Console.WriteLine($"Good morning, {name}!")
Console.WriteLine($"The date is {localTime.Date:d} and the day is {localTime.Date.DayOfWeek}.")
If (localTime.Date.Month = MoonLandingDateTime.Date.Month _
And localTime.Date.Day = MoonLandingDateTime.Date.Day) Then
Console.WriteLine("Did you know that on this day in 1969 humans landed on the Moon?")
End If
Console.WriteLine($"I hope you enjoy your day!")
End Sub
End Module
Możesz chcieć napisać poprzedni kod za pomocą DateTime lub DateTimeOffset, aby uzyskać bieżącą datę i godzinę, zamiast TimeProvider. Jednak w przypadku testów jednostkowych trudno jest obejść DateTime lub DateTimeOffset bezpośrednio. Należy uruchomić testy w dniu i miesiącu lądowania księżyca lub dodatkowo wyodrębnić kod w mniejsze, ale testowalne jednostki.
Normalna operacja aplikacji używa TimeProvider.System
do pobrania bieżącej daty i godziny:
CalendarHelper.SendGreeting(TimeProvider.System, "Eric Solomon");
/* This example produces output similar to the following:
*
* Good morning, Eric Solomon!
* The date is 12/5/2024 and the day is Thursday.
* I hope you enjoy your day!
*/
CalendarHelper.SendGreeting(TimeProvider.System, "Eric Solomon")
' This example produces output similar to the following:
'
' Good morning, Eric Solomon!
' The date is 12/5/2024 and the day is Thursday.
' I hope you enjoy your day!
Testy jednostkowe można napisać w celu przetestowania określonych scenariuszy, takich jak testowanie rocznicy lądowania księżyca:
CalendarHelper.SendGreeting(new MoonLandingTimeProviderPST(), "Eric Solomon");
/* This example produces output similar to the following:
*
* Good morning, Eric Solomon!
* The date is 7/20/1969 and the day is Sunday.
* Did you know that on this day in 1969 humans landed on the Moon?
* I hope you enjoy your day!
*/
CalendarHelper.SendGreeting(New MoonLandingTimeProviderPST(), "Eric Solomon")
' This example produces output similar to the following:
'
' Good morning, Eric Solomon!
' The date is 7/20/1969 and the day is Sunday.
' Did you know that on this day in 1969 humans landed on the Moon?
' I hope you enjoy your day!
Używanie z platformą .NET
Począwszy od platformy .NET 8, klasa TimeProvider jest dostarczana przez bibliotekę środowiska uruchomieniowego. Starsze wersje platformy .NET lub bibliotek przeznaczonych dla platformy .NET Standard 2.0 muszą odwoływać się do Microsoft.Bcl.TimeProvider pakietu NuGet.
Następujące metody związane z programowaniem asynchronicznym współpracują z TimeProvider
:
- CancellationTokenSource(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider, CancellationToken)
- Task.WaitAsync(TimeSpan, TimeProvider)
- Task.WaitAsync(TimeSpan, TimeProvider, CancellationToken)
Używanie z programem .NET Framework
TimeProvider jest implementowany przez pakiet NuGet Microsoft.Bcl.TimeProvider.
Obsługa pracy z TimeProvider
w scenariuszach programowania asynchronicznego została dodana za pomocą następujących metod rozszerzenia:
- TimeProviderTaskExtensions.CreateCancellationTokenSource(TimeProvider, TimeSpan)
- TimeProviderTaskExtensions.Delay(TimeProvider, TimeSpan, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync(Task, TimeSpan, TimeProvider, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync<TResult>(Task<TResult>, TimeSpan, TimeProvider, CancellationToken)