Vad är TimeProvider?
System.TimeProvider är en abstraktion av tiden som ger en DateTimeOffset-typ av tidpunkt. Genom att använda TimeProvider
ser du till att koden är testbar och förutsägbar.
TimeProvider
introducerades i .NET 8 och är även tillgängligt för .NET Framework 4.7+ och .NET Standard 2.0 som ett NuGet-paket.
Klassen TimeProvider definierar följande funktioner:
- Ger åtkomst till datum och tid via TimeProvider.GetUtcNow() och TimeProvider.GetLocalNow().
- Högfrekventa tidsstämplar med TimeProvider.GetTimestamp().
- Mät tid mellan två tidsstämplar med TimeProvider.GetElapsedTime.
- Högupplösta timerar med TimeProvider.CreateTimer(TimerCallback, Object, TimeSpan, TimeSpan).
- Hämta den aktuella tidszonen med TimeProvider.LocalTimeZone.
Förvaltsimplementering
.NET tillhandahåller en implementering av TimeProvider via egenskapen TimeProvider.System med följande egenskaper:
- Datum och tid beräknas med DateTimeOffset.UtcNow och TimeZoneInfo.Local.
- Tidsstämplar tillhandahålls av System.Diagnostics.Stopwatch.
- Timers implementeras via en intern klass och tillgängliggörs som System.Threading.ITimer.
I följande exempel visas hur du använder TimeProvider för att hämta aktuellt datum och tid:
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
I följande exempel visas hur du fångar upp förfluten tid med 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
Implementering av FakeTimeProvider
Microsoft.Extensions.TimeProvider.Testing NuGet-paketet tillhandahåller en kontrollbar TimeProvider
implementering som är utformad för enhetstestning.
I följande lista beskrivs några av funktionerna i klassen FakeTimeProvider:
- Ange ett specifikt datum och en viss tid.
- För fram datum och tid automatiskt med ett angivet belopp när datum och tid läss.
- Föra fram datum och tid manuellt.
Specialanpassad implementering
Även om FakeTimeProvider bör omfatta de flesta scenarier som kräver förutsägbarhet med tiden, kan du fortfarande tillhandahålla din egen implementering. Skapa en ny klass som härleds från TimeProvider och åsidosätt medlemmar för att styra hur tid som ges. Följande klass innehåller till exempel bara ett enda datum, datumet för månlandningen:
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
Om kod som använder den här klassen anropar MoonLandingTimeProviderPST.GetUtcNow
returneras datumet för månlandningen i UTC. Om MoonLandingTimeProviderPST.GetLocalNow
anropas gäller basklassen MoonLandingTimeProviderPST.LocalTimeZone
för GetUtcNow
och returnerar månens landningsdatum och tid i PST-tidszonen.
Tänk på följande exempel för att visa hur användbart det är att kontrollera tiden. Anta att du skriver en kalenderapp som skickar en hälsning till användaren när appen öppnas varje dag. Appen säger en speciell hälsning när den aktuella dagen har en händelse associerad med den, till exempel årsdagen av månlandningen.
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
Du kan vara benägen att skriva den tidigare koden med DateTime eller DateTimeOffset för att hämta aktuellt datum och tid i stället för TimeProvider. Med enhetstester är det dock svårt att direkt hantera DateTime eller DateTimeOffset. Du skulle antingen behöva köra testerna på månlandningens dag och månad eller ytterligare abstrahera koden till mindre men testbara enheter.
Den normala driften av din app använder TimeProvider.System
för att hämta aktuellt datum och tid:
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!
Och enhetstester kan skrivas för att testa specifika scenarier, till exempel för att testa årsdagen av månlandningen:
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!
Använd med .NET
Från och med .NET 8 tillhandahålls klassen TimeProvider av körtidsbiblioteket. Äldre versioner av .NET eller bibliotek som riktar sig till .NET Standard 2.0 måste referera till Microsoft.Bcl.TimeProvider NuGet-paketet.
Följande metoder som rör asynkron programmering fungerar med TimeProvider
:
- CancellationTokenSource(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider, CancellationToken)
- Task.WaitAsync(TimeSpan, TimeProvider)
- Task.WaitAsync(TimeSpan, TimeProvider, CancellationToken)
Använda med .NET Framework
TimeProvider implementeras av Microsoft.Bcl.TimeProvider NuGet-paketet.
Stöd för att arbeta med TimeProvider
i asynkrona programmeringsscenarier har lagts till via följande tilläggsmetoder:
- TimeProviderTaskExtensions.CreateCancellationTokenSource(TimeProvider, TimeSpan)
- TimeProviderTaskExtensions.Delay(TimeProvider, TimeSpan, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync(Task, TimeSpan, TimeProvider, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync<TResult>(Task<TResult>, TimeSpan, TimeProvider, CancellationToken)