Qu’est-ce que TimeProvider ?
System.TimeProvider est une abstraction du temps qui fournit un point dans le temps en tant que type DateTimeOffset. En utilisant TimeProvider
, vous assurez que votre code est testable et prévisible. TimeProvider
a été introduit dans .NET 8 et est également disponible pour .NET Framework 4.7+ et .NET Standard 2.0 en tant que package NuGet.
La classe TimeProvider définit les fonctionnalités suivantes :
- Fournit l’accès à la date et à l’heure via TimeProvider.GetUtcNow() et TimeProvider.GetLocalNow().
- Horodatage à haute fréquence avec TimeProvider.GetTimestamp().
- Mesurez le temps entre deux horodatages avec TimeProvider.GetElapsedTime.
- Minuteries à haute résolution avec TimeProvider.CreateTimer(TimerCallback, Object, TimeSpan, TimeSpan).
- Obtenez le fuseau horaire actuel avec TimeProvider.LocalTimeZone.
Implémentation par défaut
.NET fournit une implémentation de TimeProvider via la propriété TimeProvider.System, avec les caractéristiques suivantes :
- La date et l’heure sont calculées avec DateTimeOffset.UtcNow et TimeZoneInfo.Local.
- Les horodatages sont fournis par System.Diagnostics.Stopwatch.
- Les minuteurs sont implémentés par le biais d’une classe interne et exposés en tant que System.Threading.ITimer.
L’exemple suivant montre comment utiliser TimeProvider pour obtenir la date et l’heure actuelles :
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’exemple suivant illustre la capture du temps écoulé avec 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
Implémentation de FakeTimeProvider
Le package NuGet Microsoft.Extensions.TimeProvider.Testing fournit une implémentation TimeProvider
contrôlable conçue pour les tests unitaires.
La liste suivante décrit certaines des fonctionnalités de la classe FakeTimeProvider :
- Définissez une date et une heure spécifiques.
- Avancez automatiquement la date et l’heure par un montant spécifié chaque fois que la date et l’heure sont lues.
- Avancez manuellement la date et l’heure.
Implémentation personnalisée
Bien que FakeTimeProvider couvre la plupart des scénarios nécessitant une prévisibilité avec le temps, vous pouvez toujours fournir votre propre implémentation. Créez une nouvelle classe qui dérive de TimeProvider et remplacez les membres pour contrôler la façon dont le temps est fourni. Par exemple, la classe suivante fournit uniquement une date unique, la date de l’atterrissage lunaire :
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 le code utilisant cette classe appelle MoonLandingTimeProviderPST.GetUtcNow
, la date de l’atterrissage lunaire au format UTC est retournée. Si MoonLandingTimeProviderPST.GetLocalNow
est appelée, la classe de base applique MoonLandingTimeProviderPST.LocalTimeZone
à GetUtcNow
et retourne la date et l’heure d’atterrissage de la lune dans le fuseau horaire PST.
Pour illustrer l’utilité du contrôle du temps, considérez l’exemple suivant. Supposons que vous écrivez une application de calendrier qui envoie un message d’accueil à l’utilisateur lorsque l’application est ouverte chaque jour. L’application indique un message d’accueil spécial lorsque la journée actuelle a un événement associé à celui-ci, tel que l’anniversaire de l’atterrissage lunaire.
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
Vous pouvez être enclin à écrire le code précédent avec DateTime ou DateTimeOffset pour obtenir la date et l’heure actuelles, au lieu de TimeProvider. Mais avec des tests unitaires, il est difficile de contourner DateTime ou DateTimeOffset directement. Vous devrez soit exécuter les tests le jour et le mois de l’atterrissage lunaire, soit extraire davantage le code en unités plus petites mais testables.
L’opération normale de votre application utilise TimeProvider.System
pour récupérer la date et l’heure actuelles :
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!
Et les tests unitaires peuvent être écrits pour tester des scénarios spécifiques, tels que le test de l’anniversaire de l’atterrissage lunaire :
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!
Utiliser avec .NET
À compter de .NET 8, la classe TimeProvider est fournie par la bibliothèque runtime. Les anciennes versions de .NET ou les bibliothèques ciblant .NET Standard 2.0 doivent faire référence au package NuGet Microsoft.Bcl.TimeProvider.
Les méthodes suivantes liées à la programmation asynchrone fonctionnent avec TimeProvider
:
- CancellationTokenSource(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider, CancellationToken)
- Task.WaitAsync(TimeSpan, TimeProvider)
- Task.WaitAsync(TimeSpan, TimeProvider, CancellationToken)
Utiliser avec .NET Framework
TimeProvider est implémenté par le package NuGet Microsoft.Bcl.TimeProvider.
La prise en charge de l’utilisation de TimeProvider
dans les scénarios de programmation asynchrone a été ajoutée via les méthodes d’extension suivantes :
- TimeProviderTaskExtensions.CreateCancellationTokenSource(TimeProvider, TimeSpan)
- TimeProviderTaskExtensions.Delay(TimeProvider, TimeSpan, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync(Task, TimeSpan, TimeProvider, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync<TResult>(Task<TResult>, TimeSpan, TimeProvider, CancellationToken)