Поделиться через


Что такое TimeProvider?

System.TimeProvider — это абстракция времени, которая предоставляет точку во времени в виде типа DateTimeOffset. Используя TimeProvider, вы гарантируете, что код можно тестировать и прогнозировать. TimeProvider появилась в .NET 8, а также доступна для .NET Framework 4.7+ и .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, должны ссылаться на пакете NuGet Microsoft.Bcl.TimeProvider.

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

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

реализуется пакетом NuGet Microsoft.Bcl.TimeProvider пакета NuGet.

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