Что такое TimeProvider?

System.TimeProvider — это абстракция времени, которая предоставляет точку во времени в виде типа DateTimeOffset. Используя TimeProvider, вы гарантируете, что код можно тестировать и прогнозировать. TimeProvider доступен на следующих платформах:

Платформа Примечания.
.NET 8+ Включена в фреймворк.
.NET 5 — .NET 7 Предоставляется в пакетеMicrosoft.Bcl.TimeProvider NuGet.
.NET Framework 4.6.2+ Предоставляется в пакете NuGet.
.NET Standard 2.0 Предоставляется в пакете NuGet.

Класс TimeProvider определяет следующие возможности:

Реализация по умолчанию

.NET предоставляет реализацию TimeProvider через свойство TimeProvider.System со следующими характеристиками:

В следующем примере показано, как использовать TimeProvider для получения текущей даты и времени:

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

В следующем примере показано, как захватить истекшее время с помощью 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

Реализация FakeTimeProvider

Пакет NuGet Microsoft.Extensions.TimeProvider.Testing предоставляет управляемую реализацию TimeProvider, предназначенную для модульного тестирования.

В следующем списке описаны некоторые возможности класса FakeTimeProvider:

  • Задайте определенную дату и время.
  • Автоматически перемещайте дату и время по указанному количеству при чтении даты и времени.
  • Вручную измените дату и время.

Настраиваемая реализация

Хотя FakeTimeProvider охватывает большинство сценариев, требующих прогнозируемости с течением времени, вы по-прежнему можете обеспечить собственную реализацию. Создайте новый класс, производный от TimeProvider, и переопределите элементы, чтобы управлять предоставлением времени. Например, следующий класс предоставляет только одну дату, дату посадки луны:

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

Если код, использующий этот класс, вызывает MoonLandingTimeProviderPST.GetUtcNow, возвращается дата посадки луны в формате UTC. Если MoonLandingTimeProviderPST.GetLocalNow вызывается, базовый класс применяет MoonLandingTimeProviderPST.LocalTimeZone к GetUtcNow и возвращает дату и время посадки на Луну в часовом поясе Тихоокеанского стандартного времени (PST).

Чтобы продемонстрировать полезность управления временем, рассмотрим следующий пример. Предположим, что вы пишете приложение календаря, которое отправляет приветствие пользователю при первом открытии приложения каждый день. Приложение произносит специальное приветствие, когда с текущим днем связано событие, например, годовщина высадки на Луну.

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

Возможно, вы склонны писать предыдущий код с DateTime или DateTimeOffset, чтобы получить текущую дату и время вместо TimeProvider. Но с модульным тестированием трудно обойти DateTime или DateTimeOffset напрямую. Вам потребуется либо запустить тесты в день и месяц посадки луны, либо дополнительно абстрагировать код в меньшие, но тестируемые единицы.

Обычная операция приложения использует TimeProvider.System для получения текущей даты и времени:

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!

И модульные тесты можно записать для тестирования конкретных сценариев, таких как тестирование годовщины посадки луны:

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!

Использование с .NET

Начиная с .NET 8 библиотека среды выполнения предоставляет TimeProvider класс. Старые версии .NET или библиотек, предназначенные для .NET Standard 2.0, должны ссылаться на Microsoft.Bcl.TimeProvider пакет NuGet.

Следующие методы, связанные с асинхронным программированием, работают с TimeProvider:

Использование с .NET Framework

Пакет Microsoft.Bcl.TimeProvider NuGet реализует TimeProvider.

Добавлена поддержка работы с TimeProvider в сценариях асинхронного программирования с помощью следующих методов расширения: