День 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/
.NET Разработчик: post #3312 — TG.ME
August 28, 2026 644 3 10