Condividi tramite


Che cos'è TimeProvider?

System.TimeProvider è un'astrazione del tempo che fornisce un punto nel tempo come tipo di DateTimeOffset. Utilizzando TimeProvider, ci si assicura che il codice sia testabile e prevedibile. TimeProvider è stato introdotto in .NET 8 ed è disponibile anche per .NET Framework 4.7+ e .NET Standard 2.0 come pacchetto NuGet.

La classe TimeProvider definisce le funzionalità seguenti:

Implementazione predefinita

.NET offre un'implementazione di TimeProvider tramite la proprietà TimeProvider.System, con le caratteristiche seguenti:

L'esempio seguente illustra l'uso di TimeProvider per ottenere la data e l'ora correnti:

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

L'esempio seguente dimostra come rilevare il tempo trascorso con 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

Implementazione di FakeTimeProvider

Il Microsoft.Extensions.TimeProvider.Testing pacchetto NuGet fornisce un'implementazione TimeProvider controllabile progettata per i test unitari.

L'elenco seguente descrive alcune delle funzionalità della classe FakeTimeProvider:

  • Impostare una data e un'ora specifiche.
  • Avanzare automaticamente la data e l'ora in base a un importo specificato ogni volta che viene letta la data e l'ora.
  • Avanzare manualmente la data e l'ora.

Implementazione personalizzata

Anche se FakeTimeProvider dovrebbe coprire la maggior parte degli scenari che richiedono la prevedibilità con il tempo, è comunque possibile fornire la propria implementazione. Creare una nuova classe che deriva da TimeProvider e sovrascrivere i membri per controllare come viene fornito il tempo. Ad esempio, la classe seguente fornisce solo una singola data, la data dell'atterraggio lunare:

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

Se il codice che usa questa classe chiama MoonLandingTimeProviderPST.GetUtcNow, viene restituita la data dell'atterraggio lunare in formato UTC. Se MoonLandingTimeProviderPST.GetLocalNow viene chiamato, la classe base applica MoonLandingTimeProviderPST.LocalTimeZone a GetUtcNow e restituisce la data e l'ora di atterraggio lunare nel fuso orario PST.

Per illustrare l'utilità del controllo del tempo, si consideri l'esempio seguente. Si supponga di scrivere un'app calendario che invia un messaggio di saluto all'utente quando l'app viene aperta per la prima volta ogni giorno. L'app dice un saluto speciale quando il giorno corrente ha un evento associato, ad esempio l'anniversario dell'atterraggio lunare.

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

Potrebbe essere preferibile scrivere il codice precedente con DateTime o DateTimeOffset per ottenere la data e l'ora correnti, anziché TimeProvider. Tuttavia, con gli unit test, è difficile aggirare direttamente DateTime o DateTimeOffset. È necessario eseguire i test il giorno e il mese dell'atterraggio lunare o astrarre ulteriormente il codice in unità più piccole ma testabili.

Il normale funzionamento dell'app usa TimeProvider.System per recuperare la data e l'ora correnti:

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!

Gli unit test possono essere scritti per testare scenari specifici, ad esempio il test dell'anniversario dell'atterraggio lunare:

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!

Usare con .NET

A partire da .NET 8, la classe TimeProvider viene fornita dalla libreria di runtime. Le versioni precedenti di .NET o librerie destinate a .NET Standard 2.0 devono fare riferimento al pacchetto Microsoft.Bcl.TimeProvider pacchetto NuGet.

I metodi seguenti correlati alla programmazione asincrona funzionano con TimeProvider:

Usare con .NET Framework

TimeProvider viene implementato da Microsoft.Bcl.TimeProvider Pacchetto NuGet.

Il supporto per l'uso di TimeProvider negli scenari di programmazione asincrona è stato aggiunto tramite i metodi di estensione seguenti: