TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.74K subscribers
Post #3312 1.59K
День 2767. #ЗаметкиНаПолях
TimeProvider — Конец Нетестируемого DateTime.Now. Начало

DateTime.Now — один из тех вызовов, который кажется безобидным, пока вы не попытаетесь написать тест вокруг него. Это статическое свойство, которое считывает системные часы, и нет способа контролировать то, что оно возвращает, без обёртывания его во что-то, что можно внедрить. Начиная с .NET 8, мы можем перестать переизобретать одну и ту же абстракцию и использовать вместо неё TimeProvider.

Как было раньше
Стандартным решением для установки определённого времени в коде всегда было определение интерфейса, который оборачивает часы, и внедрение его в качестве зависимости, которую можно заменить фиктивной реализацией в тестах, зависящих от даты/времени:
public interface IDateTimeProvider
{
DateTimeOffset UtcNow { get; }
}

public class SystemDateTimeProvider : IDateTimeProvider
{
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
}

В коде вы регистрируете SystemDateTimeProvider. В тестах — FakeDateTimeProvider с устанавливаемым свойством и передаёте любое время, необходимое тесту. Это работает и несложно, но каждая команда делает это немного по-своему: ISystemClock, IClock, IDateTimeProvider, ITimeService или сразу несколько вариантов в одной кодовой базе, потому что, а почему бы и нет? ASP.NET Core одно время поставлял свой ISystemClock в Microsoft.AspNetCore.Authentication.

Встроенное решение: TimeProvider
.NET 8 представил TimeProvider, абстрактный класс в пространстве имен System. Он является частью базовой библиотеки классов (поэтому зависимость NuGet не требуется). Идея та же: вместо прямого вызова DateTime.UtcNow вы вызываете tp.GetUtcNow(). В коде используется TimeProvider.System. В тестах используется фиктивный объект. Основные методы:
// Текущее время UTC (DateTimeOffset, а не DateTime)
DateTimeOffset utcNow = tp.GetUtcNow();

// Местное время
DateTimeOffset localNow = tp.GetLocalNow();

// Местный часовой пояс
TimeZoneInfo zone = tp.LocalTimeZone;

// Высокоточные метки времени
long start = tp.GetTimestamp();
// ... do work ...
TimeSpan elapsed = tp.GetElapsedTime(start);

Заметьте, что TimeProvider работает с DateTimeOffset (содержащим информацию о смещении часового пояса), а не с DateTime.

В коде используется TimeProvider.System как синглтон. Он считывает данные с системных часов, как и DateTimeOffset.UtcNow. Его необходимо зарегистрировать при запуске системы:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddSingleton<TimeProvider>(TimeProvider.System);

А затем можно внедрять везде, где вашему коду нужно текущее время:
public class SubscriptionReminderService
{
private TimeProvider _tp;
private ISubscriptionRepository _repo;

public SubscriptionReminderService(
TimeProvider tp,
ISubscriptionRepository repo)
{
_tp = tp;
_repo = repo;
}

public async Task<IEnumerable<Subscription>>
GetExpiringSubsAsync()
{
var now = _tp.GetUtcNow();
var cutoff = now.AddDays(7);

return await
_repo.GetExpiringSubsAsync(cutoff);
}
}

Теперь часы являются зависимостью, а зависимости можно заменять. Например, в тестах.

Окончание следует…

Источник:
https://blog.maartenballiauw.be/posts/2026-07-22-timeprovider-and-the-end-of-untestable-datetime-now/
More from @netdeveloperdiary
  1. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  2. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  3. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  4. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  5. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 21, 2026🔍Тестовое собеседование с Senior C# разработчиком уже завтра 22 сентября(уже завтра!) в 1…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →