O que é TimeProvider?
System.TimeProvider é uma abstração de tempo que fornece um ponto no tempo como um tipo de DateTimeOffset. Ao usar 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 através de TimeProvider.GetUtcNow() e TimeProvider.GetLocalNow().
- Carimbos de alta frequência com TimeProvider.GetTimestamp().
- Meça o tempo entre dois timestamps 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 através da propriedade TimeProvider.System, com as seguintes características:
- A data e a hora são calculadas com DateTimeOffset.UtcNow e TimeZoneInfo.Local.
- Os carimbos de data/hora são fornecidos pela System.Diagnostics.Stopwatch.
- Os temporizadores são implementados através de uma classe interna e expostos como System.Threading.ITimer.
O exemplo a seguir demonstra o uso de TimeProvider para obter a data e 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 do 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 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.
- Adiante automaticamente a data e a hora por um valor especificado sempre que a data e a hora forem lidas.
- Adiante manualmente a data e a hora.
Implementação personalizada
Embora FakeTimeProvider deva cobrir a maioria dos cenários que exigem previsibilidade com o tempo, ainda pode fornecer a 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 esta classe chamar MoonLandingTimeProviderPST.GetUtcNow
, a data do pouso na lua em UTC é retornada. Se MoonLandingTimeProviderPST.GetLocalNow
for chamado, a classe base aplica MoonLandingTimeProviderPST.LocalTimeZone
a GetUtcNow
e devolve a data e hora de alunagem no fuso horário do Pacífico (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 hora atuais, em vez de TimeProvider. No entanto, com os testes unitários, é difícil lidar diretamente com DateTime ou DateTimeOffset. Você precisaria executar os testes no dia e mês do pouso na lua ou abstrair o código em unidades menores, mas testáveis.
A operação normal do seu 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 testes de unidade podem ser escritos para testar cenários específicos, como testar 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!
Usar com .NET
A partir do .NET 8, a classe TimeProvider é fornecida pela biblioteca de execução. Versões mais antigas do .NET ou bibliotecas destinadas ao .NET Standard 2.0 devem fazer referência ao 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)
Utilizar com o .NET Framework
TimeProvider é implementado pelo Microsoft.Bcl.TimeProvider pacote NuGet.
O suporte para trabalhar com TimeProvider
em cenários de programação assíncrona foi adicionado através 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)