Aller au contenu

TimeProvider : une alternative à DateTime.Now

Introduction

Avant la version 8 de .NET, il était courant de créer sa propre abstraction afin de pouvoir remplacer la date courante lors des tests. Et pour déterminer si une date / heure est expirée par rapport à la date et l'heure courante, nous avons l'habitude d'écrire :

public class SubscriptionService
{
    public bool IsExpired(DateTime expirationDate)
    {
        return expirationDate <= DateTime.Now;
    }
}

Depuis la version 8 de .NET, dans ce genre de situation, TimeProvider fournit désormais une abstraction standard directement intégrée à .NET, ce qui évite de maintenir une implémentation spécifique à chaque projet :

public class SubscriptionService
{
    private readonly TimeProvider _timeProvider;

    public SubscriptionService(TimeProvider timeProvider)
    {
        _timeProvider = timeProvider;
    }

    public bool IsExpired(DateTimeOffset expirationDate)
    {
        return expirationDate <= _timeProvider.GetLocalNow();
    }
}

L'exemple ci-dessus utilise désormais le type DateTimeOffset, recommandé avec TimeProvider, afin de conserver également le décalage par rapport à UTC.

Une seule instance de TimeProvider.System est enregistrée dans le conteneur d'injection de dépendances, puis injectée dans les services qui ont besoin de connaître l'heure courante.

builder.Services.AddSingleton(TimeProvider.System);

TimeProvider n'a pas vocation à remplacer systématiquement DateTime. Il prend tout son intérêt lorsque le temps influence une règle métier : expiration, relance, délai, planification, expiration de cache, etc.

Pourquoi ce choix d'implémentation ? Ce remplacement présente plusieurs intérêts, principalement lorsque la date et l'heure courantes sont utilisées dans des règles métier :

  • Meilleure abstraction : l'heure système n'est plus utilisée directement mais exposée au travers d'une abstraction (TimeProvider)

  • Meilleure testabilité : possibilité de remplacer la date / heure courante par un service qui rend la date / heure déterministe dans des tests unitaires

  • Meilleure précision sémantique : GetLocalNow() retourne un DateTimeOffset, qui contient la date, l'heure et le décalage par rapport à UTC. Cette information permet de représenter un instant précis en conservant son décalage par rapport à UTC, ce qui limite certaines ambiguïtés liées aux fuseaux horaires et aux changements d'heure.

Aussi, de la même manière que DateTime propose la propriété UtcNow, TimeProvider propose la méthode GetUtcNow().