TimeProvider とは
System.TimeProvider は、特定の時点を DateTimeOffset 型として提供する時間の抽象化です。 TimeProvider
を使用すると、コードがテスト可能で予測可能であることを確認できます。 TimeProvider
は .NET 8 で導入され、NuGet パッケージとして .NET Framework 4.7 以降および .NET Standard 2.0 でも使用できます。
TimeProvider クラスは、次の機能を定義します。
- TimeProvider.GetUtcNow() と TimeProvider.GetLocalNow()を介して日付と時刻へのアクセスを提供します。
- TimeProvider.GetTimestamp()を含む高頻度タイムスタンプ。
- TimeProvider.GetElapsedTimeを使用して 2 つのタイムスタンプ間の時間を測定します。
- TimeProvider.CreateTimer(TimerCallback, Object, TimeSpan, TimeSpan)付き高解像度タイマー。
- TimeProvider.LocalTimeZoneで現在のタイムゾーンを取得します。
既定の実装
.NET では、TimeProvider.System プロパティを使用した TimeProvider の実装が提供され、次の特性があります。
- 日付と時刻は、DateTimeOffset.UtcNow と TimeZoneInfo.Localで計算されます。
- タイムスタンプは、System.Diagnostics.Stopwatchによって提供されます。
- タイマーは内部クラスを介して実装され、System.Threading.ITimerとして公開されます。
次の例では、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 の実装
Microsoft.Extensions.TimeProvider.Testing NuGet パッケージ は、単体テスト用に設計された制御可能な TimeProvider
実装を提供します。
次の一覧では、FakeTimeProvider クラスの機能の一部について説明します。
- 特定の日付と時刻を設定します。
- 日付と時刻を読み取るたびに、指定した量の日付と時刻を自動的に進める。
- 日付と時刻を手動で進める。
カスタム実装
FakeTimeProvider は、時間と共に予測可能性を必要とするほとんどのシナリオをカバーする必要がありますが、それでも独自の実装を提供することもできます。 TimeProvider から派生する新しいクラスを作成し、メンバーをオーバーライドして時間の指定方法を制御します。 たとえば、次のクラスは、月面着陸の日付である 1 つの日付のみを提供します。
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
が呼び出されると、基底クラスは GetUtcNow
に MoonLandingTimeProviderPST.LocalTimeZone
を適用し、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
以前のコードを記述する際に、TimeProviderではなく、DateTime または DateTimeOffset を使用して現在の日時を取得することを検討してみてください。 ただし、単体テストでは、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 Standard 2.0 をターゲットとする以前のバージョンの .NET またはライブラリは、NuGet パッケージ Microsoft.Bcl.TimeProvider
非同期プログラミングに関連する次のメソッドは、TimeProvider
で動作します。
- CancellationTokenSource(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider)
- Task.Delay(TimeSpan, TimeProvider, CancellationToken)
- Task.WaitAsync(TimeSpan, TimeProvider)
- Task.WaitAsync(TimeSpan, TimeProvider, CancellationToken)
.NET Framework での使用
TimeProvider は、Microsoft.Bcl.TimeProvider NuGet パッケージによって実装されます。
非同期プログラミング シナリオでの TimeProvider
の操作のサポートは、次の拡張メソッドを使用して追加されました。
- TimeProviderTaskExtensions.CreateCancellationTokenSource(TimeProvider, TimeSpan)
- TimeProviderTaskExtensions.Delay(TimeProvider, TimeSpan, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync(Task, TimeSpan, TimeProvider, CancellationToken)
- TimeProviderTaskExtensions.WaitAsync<TResult>(Task<TResult>, TimeSpan, TimeProvider, CancellationToken)
.NET