O que é TimeProvider?
System.TimeProvider é uma abstração de tempo que fornece um ponto no tempo como um tipo de DateTimeOffset. Usando TimeProvider
, você garante que seu código seja testável e previsível. TimeProvider
foi introduzido no .NET 8 e também está disponível para .NET Framework 4.7+ e .NET Standard 2.0 como um pacote NuGet.
A classe TimeProvider define os seguintes recursos:
- Fornece acesso à data e hora por TimeProvider.GetUtcNow() e TimeProvider.GetLocalNow().
- Carimbos de data/hora de alta frequência com TimeProvider.GetTimestamp().
- Meça o tempo entre dois carimbos de data/hora com TimeProvider.GetElapsedTime.
- Temporizadores de alta resolução com TimeProvider.CreateTimer(TimerCallback, Object, TimeSpan, TimeSpan).
- Obtenha o fuso horário atual com TimeProvider.LocalTimeZone.
Implementação padrão
O .NET fornece uma implementação de TimeProvider por meio da propriedade TimeProvider.System, com as seguintes características:
- Data e hora são calculadas com DateTimeOffset.UtcNow e TimeZoneInfo.Local.
- Os carimbos de data/hora são fornecidos por System.Diagnostics.Stopwatch.
- Os temporizadores são implementados por meio de uma classe interna e expostos como System.Threading.ITimer.
O exemplo a seguir demonstra como usar TimeProvider para obter a data e a hora atuais:
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
O exemplo a seguir demonstra a captura de tempo decorrido com 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
Implementação do FakeTimeProvider
O pacote Microsoft.Extensions.TimeProvider.Testing NuGet fornece uma implementação de TimeProvider
controlável projetada para testes de unidade.
A lista a seguir descreve alguns dos recursos da classe FakeTimeProvider:
- Defina uma data e hora específicas.
- Avance automaticamente a data e a hora em um valor especificado sempre que a data e a hora forem lidas.
- Avance manualmente a data e a hora.
Implementação personalizada
Embora FakeTimeProvider deva abranger a maioria dos cenários que exigem previsibilidade quanto ao tempo, você ainda pode fornecer sua própria implementação. Crie uma nova classe que deriva de TimeProvider e substitua membros para controlar como o tempo é fornecido. Por exemplo, a classe a seguir fornece apenas uma única data, a data do pouso na Lua:
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 o código que usa essa classe chamar MoonLandingTimeProviderPST.GetUtcNow
, a data do pouso na Lua em UTC será retornada. Se MoonLandingTimeProviderPST.GetLocalNow
for chamado, a classe base aplicará MoonLandingTimeProviderPST.LocalTimeZone
a GetUtcNow
e retornará a data e a hora do pouso na Lua no fuso horário PST.
Para demonstrar a utilidade de controlar o tempo, considere o exemplo a seguir. Digamos que você esteja escrevendo um aplicativo de calendário que envia uma saudação ao usuário quando o aplicativo é aberto pela primeira vez todos os dias. O aplicativo diz uma saudação especial quando o dia atual tem um evento associado a ele, como o aniversário do pouso na Lua.
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
Você pode estar inclinado a escrever o código anterior com DateTime ou DateTimeOffset para obter a data e a hora atuais, em vez de TimeProvider. Mas com testes unitários, é difícil contornar DateTime ou DateTimeOffset diretamente. Você precisaria executar os testes no dia e no mês do pouso na Lua ou abstrair ainda mais o código em unidades menores, mas testáveis.
A operação normal do aplicativo usa TimeProvider.System
para recuperar a data e a hora atuais:
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!
E os testes de unidade podem ser escritos para avaliar cenários específicos, como o aniversário do pouso na Lua.
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 com .NET
A partir do .NET 8, a classe TimeProvider é fornecida pela biblioteca de runtime. Versões mais antigas do .NET ou bibliotecas direcionadas ao .NET Standard 2.0 devem referenciar o pacote Microsoft.Bcl.TimeProvider nuGet.
Os seguintes métodos relacionados à programação assíncrona funcionam com TimeProvider
:
- CancellationTokenSource(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider, CancellationToken)
- Task.WaitAsync(TimeSpan, TimeProvider)
- Task.WaitAsync(TimeSpan, TimeProvider, CancellationToken)
Uso com o .NET Framework
TimeProvider é implementado pelo pacote Microsoft.Bcl.TimeProvider NuGet.
O suporte para trabalhar com TimeProvider
em cenários de programação assíncrona foi adicionado por meio dos seguintes métodos de extensão:
- TimeProviderTaskExtensions.CreateCancellationTokenSource(TimeProvider, TimeSpan)
- TimeProviderTaskExtensions.Delay(TimeProvider, TimeSpan, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync(Task, TimeSpan, TimeProvider, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync<TResult>(Task<TResult>, TimeSpan, TimeProvider, CancellationToken)