Compartir vía


¿Qué es TimeProvider?

System.TimeProvider es una abstracción de tiempo que proporciona un momento dado como un tipo de DateTimeOffset. Al usar TimeProvider, aseguras que tu código sea comprobable y predecible. TimeProvider se introdujo en .NET 8 y también está disponible para .NET Framework 4.7+ y .NET Standard 2.0 como paquete NuGet.

La clase TimeProvider define las siguientes funcionalidades:

Implementación predeterminada

.NET proporciona una implementación de TimeProvider a través de la propiedad TimeProvider.System, con las siguientes características:

En el ejemplo siguiente se muestra el uso de TimeProvider para obtener la fecha y hora actuales:

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

En el ejemplo siguiente se muestra la captura del tiempo transcurrido 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

Implementación de FakeTimeProvider

El paquete NuGet Microsoft.Extensions.TimeProvider.Testing proporciona una implementación de TimeProvider controlable diseñada para pruebas unitarias.

En la lista siguiente se describen algunas de las funcionalidades de la clase FakeTimeProvider:

  • Establezca una fecha y hora específicas.
  • Avance automáticamente la fecha y hora por una cantidad especificada cada vez que se lea la fecha y hora.
  • Avance manualmente la fecha y la hora.

Implementación personalizada

Si bien FakeTimeProvider debe abarcar la mayoría de los escenarios que requieren predictibilidad con el tiempo, puede seguir implementando su propia versión. Cree una nueva clase procedente de TimeProvider e invalide miembros para controlar cómo se proporciona el tiempo. Por ejemplo, la siguiente clase solo proporciona una fecha única, la fecha del aterrizaje lunar:

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

Si el código que usa esta clase llama a MoonLandingTimeProviderPST.GetUtcNow, se devuelve la fecha del aterrizaje lunar en UTC. Si se llama a MoonLandingTimeProviderPST.GetLocalNow, la clase base aplica MoonLandingTimeProviderPST.LocalTimeZone a GetUtcNow y devuelve la fecha y hora de aterrizaje de la luna en la zona horaria PST.

Para demostrar la utilidad de controlar el tiempo, considere el ejemplo siguiente. Supongamos que está escribiendo una aplicación de calendario que envía un saludo al usuario cuando la aplicación se abre por primera vez cada día. La aplicación dice un saludo especial cuando el día actual tiene un evento asociado, como el aniversario del aterrizaje lunar.

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

Es posible que esté inclinado a escribir el código anterior con DateTime o DateTimeOffset para obtener la fecha y hora actuales, en lugar de TimeProvider. Pero con las pruebas unitarias, es difícil solucionar DateTime o DateTimeOffset directamente. Tendría que ejecutar las pruebas en el día y mes del aterrizaje lunar o bien abstraer aún más el código en unidades más pequeñas pero comprobables.

El funcionamiento normal de la aplicación usa TimeProvider.System para recuperar la fecha y hora actuales:

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!

Y las pruebas unitarias se pueden escribir para probar escenarios específicos, como probar el aniversario del aterrizaje lunar:

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!

Uso con .NET

A partir de .NET 8, la biblioteca en tiempo de ejecución proporciona la clase TimeProvider. Las versiones anteriores de .NET o bibliotecas destinadas a .NET Standard 2.0 deben hacer referencia al paquete NuGet Microsoft.Bcl.TimeProvider .

Los métodos siguientes relacionados con la programación asincrónica funcionan con TimeProvider:

Uso con .NET Framework

TimeProvider se implementa con el paquete NuGet Microsoft.Bcl.TimeProvider.

Se ha agregado compatibilidad para trabajar con TimeProvider en escenarios de programación asincrónica mediante los siguientes métodos de extensión: