Co je TimeProvider?
System.TimeProvider je abstrakce času, která poskytuje bod v čase jako typ DateTimeOffset. Pomocí TimeProvider
zajistíte, že je váš kód testovatelný a předvídatelný.
TimeProvider
byl zaveden v .NET 8 a je k dispozici také pro rozhraní .NET Framework 4.7+ a .NET Standard 2.0 jako balíček NuGet.
Třída TimeProvider definuje následující možnosti:
- Poskytuje přístup k datu a času prostřednictvím TimeProvider.GetUtcNow() a TimeProvider.GetLocalNow().
- Časová razítka s vysokou frekvencí TimeProvider.GetTimestamp().
- Měřit čas mezi dvěma časovými razítky pomocí TimeProvider.GetElapsedTime.
- Časovače s vysokým rozlišením s TimeProvider.CreateTimer(TimerCallback, Object, TimeSpan, TimeSpan).
- Získejte aktuální časové pásmo pomocí TimeProvider.LocalTimeZone.
Výchozí implementace
.NET poskytuje implementaci TimeProvider prostřednictvím vlastnosti TimeProvider.System s následujícími vlastnostmi:
- Datum a čas se počítají pomocí DateTimeOffset.UtcNow a TimeZoneInfo.Local.
- Časové razítka poskytuje System.Diagnostics.Stopwatch.
- Časovače jsou implementovány prostřednictvím interní třídy a vystaveny jako System.Threading.ITimer.
Následující příklad ukazuje použití TimeProvider k získání aktuálního data a času:
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
Následující příklad ukazuje zachycení uplynulého času s 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
FakeTimeProvider – implementace
Balíček NuGet Microsoft.Extensions.TimeProvider.Testing poskytuje ovladatelnou TimeProvider
implementaci určenou pro jednotkové testování.
Následující seznam popisuje některé možnosti třídy FakeTimeProvider:
- Nastavte konkrétní datum a čas.
- Automaticky upřesní datum a čas o zadanou částku vždy, když se přečte datum a čas.
- Ručně posunout datum a čas.
Vlastní implementace
I když FakeTimeProvider by měla pokrýt většinu scénářů vyžadujících předvídatelnost s časem, můžete i nadále poskytovat vlastní implementaci. Vytvořte novou třídu, která je odvozena od TimeProvider, a přepište její členy, abyste mohli řídit způsob poskytování času. Například následující třída poskytuje pouze jedno datum, datum přistání na Měsíci:
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
Pokud kód, který používá tuto třídu, volá MoonLandingTimeProviderPST.GetUtcNow
, je vráceno datum přistání na Měsíci v UTC. Pokud je volána MoonLandingTimeProviderPST.GetLocalNow
, základní třída použije MoonLandingTimeProviderPST.LocalTimeZone
na GetUtcNow
a vrátí datum a čas přistání na Měsíci v časovém pásmu PST.
Abyste si ukázali užitečnost řízení času, zvažte následující příklad. Řekněme, že píšete aplikaci kalendáře, která uživateli pošle pozdrav při prvním otevření aplikace každý den. Aplikace říká zvláštní pozdrav, když má aktuální den přidruženou událost, například výročí přistání na Měsíci.
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ůžete mít sklon psát předchozí kód pomocí DateTime nebo DateTimeOffset, abyste získali aktuální datum a čas místo TimeProvider. Ale s jednotkovým testováním je těžké pracovat přímo s DateTime nebo DateTimeOffset. Testy byste museli spustit buď v den a měsíc přistání na měsíci, nebo kód dále abstraktovat do menších, ale testovatelných jednotek.
Normální provoz aplikace používá TimeProvider.System
k načtení aktuálního data a času:
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!
Jednotkové testy lze napsat pro konkrétní scénáře, například pro testování výročí přistání na Měsíci.
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!
Použití s .NET
Počínaje rozhraním .NET 8 je třída TimeProvider poskytována knihovnou modulu runtime. Starší verze .NET nebo knihoven, které cílí na .NET Standard 2.0, musí odkazovat na Microsoft.Bcl.TimeProvider balíček NuGet.
Následující metody související s asynchronním programováním pracují s TimeProvider
:
- CancellationTokenSource(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider, CancellationToken)
- Task.WaitAsync(TimeSpan, TimeProvider)
- Task.WaitAsync(TimeSpan, TimeProvider, CancellationToken)
Použití s rozhraním .NET Framework
TimeProvider je implementována balíčkem NuGet Microsoft.Bcl.TimeProvider NuGet.
Podpora práce s TimeProvider
ve scénářích asynchronního programování byla přidána pomocí následujících metod rozšíření:
- TimeProviderTaskExtensions.CreateCancellationTokenSource(TimeProvider, TimeSpan)
- TimeProviderTaskExtensions.Delay(TimeProvider, TimeSpan, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync(Task, TimeSpan, TimeProvider, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync<TResult>(Task<TResult>, TimeSpan, TimeProvider, CancellationToken)