Udostępnij za pośrednictwem


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:

Implementacja domyślna

Platforma .NET udostępnia implementację TimeProvider za pośrednictwem właściwości TimeProvider.System o następujących cechach:

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:

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: