Was ist TimeProvider?
System.TimeProviderist eine Abstraktion der Zeit, die einen Zeitpunkt alsDateTimeOffsetTyp bereitstellt. Mit TimeProvider
stellen Sie sicher, dass Ihr Code testbar und vorhersehbar ist. TimeProvider
wurde in .NET 8 eingeführt und ist auch für .NET Framework 4.7+ und .NET Standard 2.0 als NuGet-Paket verfügbar.
Die TimeProvider Klasse definiert die folgenden Funktionen:
- Bietet Zugriff auf Datum und Uhrzeit über TimeProvider.GetUtcNow() und TimeProvider.GetLocalNow().
- Hochfrequente Zeitstempel mit TimeProvider.GetTimestamp().
- Messen Sie die Zeit zwischen zwei Zeitstempeln mit TimeProvider.GetElapsedTime.
- Hochauflösende Timer mitTimeProvider.CreateTimer(TimerCallback, Object, TimeSpan, TimeSpan).
- Abrufen der aktuellen Zeitzone mitTimeProvider.LocalTimeZone.
Standardimplementierung
.NET bietet eine Implementierung vonTimeProviderdurch dieTimeProvider.SystemEigenschaft mit den folgenden Merkmalen:
- Datum und Uhrzeit werden mit DateTimeOffset.UtcNow und TimeZoneInfo.Localberechnet.
- Zeitstempel werden bereitgestellt vonSystem.Diagnostics.Stopwatch.
- Timer sind durch eine interne Klasse implementiert und werden alsSystem.Threading.ITimer.
Im folgenden Beispiel wird die Verwendung von TimeProvider zum Abrufen des aktuellen Datums und der aktuellen Uhrzeit veranschaulicht:
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
Das folgende Beispiel zeigt die Erfassung der verstrichenen Zeit mitTimeProvider.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
FakeTimeProvider-Implementierung
Das Microsoft.Extensions.TimeProvider.Testing NuGet-Paket bietet eine kontrollierbare TimeProvider
Implementierung, die für Unit-Tests ausgelegt ist.
In der folgenden Liste werden einige der Funktionen der FakeTimeProvider-Klasse beschrieben:
- Legen Sie ein bestimmtes Datum und eine bestimmte Uhrzeit fest.
- Stellt das Datum und die Uhrzeit automatisch um einen bestimmten Betrag vor, wenn das Datum und die Uhrzeit gelesen werden.
- Stellen Sie das Datum und die Uhrzeit manuell vor.
Benutzerdefinierte Implementierung
Während FakeTimeProvider die meisten Szenarien abdecken sollte, die Vorhersehbarkeit in Bezug auf Zeit erfordern, können Sie dennoch Ihre eigene Implementierung bereitstellen. Erstellen Sie eine neue Klasse, die vonTimeProviderabgeleitet ist, und überschreiben Sie Mitglieder, um zu steuern, wie die Zeit bereitgestellt wird. Die folgende Klasse stellt z. B. nur ein einzelnes Datum, das Datum der Mondlandung bereit:
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
Wenn Code, der diese Klasse verwendet, MoonLandingTimeProviderPST.GetUtcNow
aufruft, wird das Datum der Mondlandung in UTC zurückgegeben. Wenn MoonLandingTimeProviderPST.GetLocalNow
aufgerufen wird, wendet die Basisklasse MoonLandingTimeProviderPST.LocalTimeZone
auf GetUtcNow
an und gibt das Mondlandungsdatum und die Uhrzeit in der PST-Zeitzone zurück.
Um die Nützlichkeit der Zeitkontrolle zu veranschaulichen, betrachten Sie das folgende Beispiel. Angenommen, Sie schreiben eine Kalender-App, die dem Benutzer eine Begrüßung sendet, wenn die App jeden Tag zum ersten Mal geöffnet wird. Die App gibt eine besondere Begrüßung aus, wenn dem aktuellen Tag ein Ereignis zugeordnet ist, wie der Jahrestag der Mondlandung.
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
Möglicherweise sind Sie geneigt, den vorherigen Code mit DateTime oder DateTimeOffset zu schreiben, um das aktuelle Datum und die aktuelle Uhrzeit anstelle von TimeProviderabzurufen. Aber mit Unit-Tests ist es schwierig, DateTime oder DateTimeOffset direkt zu umgehen. Sie müssen entweder die Tests am Tag und Monat der Mondlandung ausführen oder den Code weiter in kleinere, aber testbare Einheiten abstrahieren.
Der normale Vorgang Ihrer App verwendet TimeProvider.System
, um das aktuelle Datum und die aktuelle Uhrzeit abzurufen:
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!
Und Einheitstests können geschrieben werden, um bestimmte Szenarien zu testen, z. B. den Jahrestag der Mondlandung:
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!
Verwendung mit .NET
Ab .NET 8 wird die TimeProvider Klasse von der Laufzeitbibliothek bereitgestellt. Ältere Versionen von .NET oder Bibliotheken, die auf .NET Standard 2.0 abzielen, müssen auf das NuGet-Paket Microsoft.Bcl.TimeProvider verweisen.
Die folgenden Methoden im Zusammenhang mit der asynchronen Programmierung arbeiten mit TimeProvider
:
- CancellationTokenSource(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider, CancellationToken)
- Task.WaitAsync(TimeSpan, TimeProvider)
- Task.WaitAsync(TimeSpan, TimeProvider, CancellationToken)
Verwenden mit .NET Framework
TimeProvider wird durch das NuGet-Paket Microsoft.Bcl.TimeProvider implementiert.
Unterstützung für die Arbeit mit TimeProvider
in asynchronen Programmierszenarien wurde durch die folgenden Erweiterungsmethoden hinzugefügt:
- TimeProviderTaskExtensions.CreateCancellationTokenSource(TimeProvider, TimeSpan)
- TimeProviderTaskExtensions.Delay(TimeProvider, TimeSpan, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync(Task, TimeSpan, TimeProvider, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync<TResult>(Task<TResult>, TimeSpan, TimeProvider, CancellationToken)