diff --git a/docs/fundamentals/toc.yml b/docs/fundamentals/toc.yml index f89a709e279d5..84191f5856eb6 100644 --- a/docs/fundamentals/toc.yml +++ b/docs/fundamentals/toc.yml @@ -445,6 +445,8 @@ items: href: ../standard/datetime/how-to-use-dateonly-timeonly.md - name: Perform arithmetic operations href: ../standard/datetime/performing-arithmetic-operations.md + - name: Use TimeProvider to write predictable code + href: ../standard/datetime/timeprovider-overview.md - name: "DateTime and DateTimeOffset support in System.Text.Json" href: ../standard/datetime/system-text-json-support.md - name: Time zones diff --git a/docs/standard/datetime/choosing-between-datetime.md b/docs/standard/datetime/choosing-between-datetime.md index 569d5d9b2c2cc..6e9b310b33412 100644 --- a/docs/standard/datetime/choosing-between-datetime.md +++ b/docs/standard/datetime/choosing-between-datetime.md @@ -80,7 +80,7 @@ Although you could use `DateTime` while ignoring the time component, there are a For more information about `DateOnly`, see [How to use the DateOnly and TimeOnly structures](how-to-use-dateonly-timeonly.md). > [!IMPORTANT] -> `DateOnly` isn't available in .NET Framework. +> `DateOnly` isn't available for .NET Framework. ## The TimeSpan structure @@ -115,7 +115,7 @@ Prior to the `TimeOnly` type being introduced, programmers typically used either For more information about `TimeOnly`, see [How to use the DateOnly and TimeOnly structures](how-to-use-dateonly-timeonly.md). > [!IMPORTANT] -> `TimeOnly` isn't available in .NET Framework. +> `TimeOnly` isn't available for .NET Framework. ## The TimeZoneInfo class diff --git a/docs/standard/datetime/how-to-use-dateonly-timeonly.md b/docs/standard/datetime/how-to-use-dateonly-timeonly.md index cb62c12058bfa..8f1cfec99f39a 100644 --- a/docs/standard/datetime/how-to-use-dateonly-timeonly.md +++ b/docs/standard/datetime/how-to-use-dateonly-timeonly.md @@ -1,7 +1,7 @@ --- title: How to use DateOnly and TimeOnly description: Learn about the DateOnly and TimeOnly structures in .NET. -ms.date: 01/11/2023 +ms.date: 12/05/2024 dev_langs: - "csharp" - "vb" @@ -22,7 +22,7 @@ The and structures were introduced `DateOnly` and `TimeOnly` are types that represent those particular portions of a `DateTime` type. > [!IMPORTANT] -> and types aren't available in .NET Framework. +> and types aren't available for .NET Framework. ## The DateOnly structure diff --git a/docs/standard/datetime/index.md b/docs/standard/datetime/index.md index e4bcb4c7aa8c2..3a78e32677ade 100644 --- a/docs/standard/datetime/index.md +++ b/docs/standard/datetime/index.md @@ -1,7 +1,7 @@ --- description: "Learn more about: Dates, times, and time zones" title: "Dates, times, and time zones" -ms.date: 12/07/2021 +ms.date: 12/05/2024 helpviewer_keywords: - "time zone objects [.NET]" - "date and time data [.NET]" @@ -12,11 +12,9 @@ ms.assetid: 295c16e0-641b-4771-94b3-39c1ffa98c13 --- # Dates, times, and time zones -In addition to the basic structure, .NET provides the following classes that support working with time zones: - -* +.NET provides types that represent dates, times, and time zones. This article describes those types. - Use this class to work with the system's local time zone and the Coordinated Universal Time (UTC) zone. The functionality of the class is largely superseded by the class. +In addition to the basic structure, .NET provides the following classes that support working with time zones: * @@ -26,16 +24,38 @@ In addition to the basic structure, .NET provides the fol Use this structure to work with dates and times whose offset (or difference) from UTC is known. The structure combines a date and time value with that time's offset from UTC. Because of its relationship to UTC, an individual date and time value unambiguously identifies a single point in time. This makes a value more portable from one computer to another than a value. -Starting with .NET 6, the following types are available: +The following classes to support working with time: -* +* - Use this structure when working with a value that only represents a date. The date represents the entire day, from the start of the day to the end. `DateOnly` has a range of `0001-01-01` through `9999-12-31`. And, this type represents the month, day, and year combination without a specific time. If you previously used a `DateTime` type in your code to represent a date that disregarded the time, use this type in its place. For more information, see [How to use the DateOnly and TimeOnly structures](how-to-use-dateonly-timeonly.md). + Use this structure to represents a time interval, such as an elapsed amount of time or the difference between two dates. * Use this structure to represent a time without a date. The time represents the hours, minutes, and seconds of a non-specific day. `TimeOnly` has a range of `00:00:00.0000000` to `23:59:59.9999999`. This type can be used to replace `DateTime` and `TimeSpan` types in your code when you used those types to represent a time. For more information, see [How to use the DateOnly and TimeOnly structures](how-to-use-dateonly-timeonly.md). + > [!IMPORTANT] + > isn't available for .NET Framework. + +* + + This is a base class that provides an abstraction of time. A common way to check the current time is by using `DateTime.UtcNow` or `DateTimeOffset.UtcNow`. However, these types don't provide any control over what's considered "now." Why would you want to control that? Testability. For example, consider you're writing an event tracking application that provides reminders 1 day before the event. The app's logic is to check the event time every hour, and alert the user once it's 24 hours before the event. As you write your tests for the app, you would provide your own type that wraps `DateTimeOffset.UtcNow` to test this logic, but now .NET provides this abstraction class for you. + + For more information, see [What is TimeProvider](timeprovider-overview.md). + + The `TimeProvider` type is included in .NET. + + For .NET Framework and .NET Standard, `TimeProvider` is provided by the [**Microsoft.Bcl.TimeProvider** NuGet package](https://www.nuget.org/packages/Microsoft.Bcl.TimeProvider/). + +The following classes to support working with dates: + +* + + Use this structure when working with a value that only represents a date. The date represents the entire day, from the start of the day to the end. `DateOnly` has a range of `0001-01-01` through `9999-12-31`. And, this type represents the month, day, and year combination without a specific time. If you previously used a `DateTime` type in your code to represent a date that disregarded the time, use this type in its place. For more information, see [How to use the DateOnly and TimeOnly structures](how-to-use-dateonly-timeonly.md). + + > [!IMPORTANT] + > isn't available for .NET Framework. + The next section provides the information that you need to work with time zones and to create time zone-aware applications that can convert dates and times from one time zone to another. ## In this section diff --git a/docs/standard/datetime/snippets/timeprovider-overview/csharp/CalendarHelper.cs b/docs/standard/datetime/snippets/timeprovider-overview/csharp/CalendarHelper.cs new file mode 100644 index 0000000000000..8cf681196b4e7 --- /dev/null +++ b/docs/standard/datetime/snippets/timeprovider-overview/csharp/CalendarHelper.cs @@ -0,0 +1,24 @@ +namespace ExampleProject; + +// +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!"); + } +} +// diff --git a/docs/standard/datetime/snippets/timeprovider-overview/csharp/MoonLandingTimeProviderPST.cs b/docs/standard/datetime/snippets/timeprovider-overview/csharp/MoonLandingTimeProviderPST.cs new file mode 100644 index 0000000000000..9c5ad09ff18f5 --- /dev/null +++ b/docs/standard/datetime/snippets/timeprovider-overview/csharp/MoonLandingTimeProviderPST.cs @@ -0,0 +1,13 @@ +namespace ExampleProject; + +// +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"); +} +// diff --git a/docs/standard/datetime/snippets/timeprovider-overview/csharp/Program.cs b/docs/standard/datetime/snippets/timeprovider-overview/csharp/Program.cs new file mode 100644 index 0000000000000..1b27b2378309f --- /dev/null +++ b/docs/standard/datetime/snippets/timeprovider-overview/csharp/Program.cs @@ -0,0 +1,62 @@ +using ExampleProject; + +Console.WriteLine(@"_./-==--======- getlocal -======--==-\._"); + +// +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(@"_./-==--======- timestamp -======--==-\._"); + +// +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 +*/ +// + +Console.WriteLine(@"_./-==--======- greeting-normal -======--==-\._"); + +// +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! +*/ +// + +Console.WriteLine(@"_./-==--======- greeting-moon -======--==-\._"); + +// +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! +*/ +// diff --git a/docs/standard/datetime/snippets/timeprovider-overview/csharp/myproject.csproj b/docs/standard/datetime/snippets/timeprovider-overview/csharp/myproject.csproj new file mode 100644 index 0000000000000..694035b3acd5f --- /dev/null +++ b/docs/standard/datetime/snippets/timeprovider-overview/csharp/myproject.csproj @@ -0,0 +1,10 @@ + + + + Exe + net9.0 + enable + enable + + + diff --git a/docs/standard/datetime/snippets/timeprovider-overview/vb/CalendarHelper.vb b/docs/standard/datetime/snippets/timeprovider-overview/vb/CalendarHelper.vb new file mode 100644 index 0000000000000..64403f6f3f8b3 --- /dev/null +++ b/docs/standard/datetime/snippets/timeprovider-overview/vb/CalendarHelper.vb @@ -0,0 +1,24 @@ +' +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 +' diff --git a/docs/standard/datetime/snippets/timeprovider-overview/vb/MoonLandingTimeProviderPST.vb b/docs/standard/datetime/snippets/timeprovider-overview/vb/MoonLandingTimeProviderPST.vb new file mode 100644 index 0000000000000..b568080e5f901 --- /dev/null +++ b/docs/standard/datetime/snippets/timeprovider-overview/vb/MoonLandingTimeProviderPST.vb @@ -0,0 +1,19 @@ +' +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 +' diff --git a/docs/standard/datetime/snippets/timeprovider-overview/vb/Program.vb b/docs/standard/datetime/snippets/timeprovider-overview/vb/Program.vb new file mode 100644 index 0000000000000..48e05b631b8dd --- /dev/null +++ b/docs/standard/datetime/snippets/timeprovider-overview/vb/Program.vb @@ -0,0 +1,70 @@ +Imports System +Imports System.Globalization +Imports System.Text.Json + +Module Program + Sub Main(args As String()) + Console.WriteLine("_./-==--======- getlocal -======--==-\._") + + ' + 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("_./-==--======- timestamp -======--==-\._") + + ' + 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 + + ' + + Console.WriteLine("_./-==--======- greeting-normal -======--==-\._") + + ' + 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! + + ' + + Console.WriteLine("_./-==--======- greeting-moon -======--==-\._") + + ' + 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! + + ' + + End Sub + +End Module diff --git a/docs/standard/datetime/snippets/timeprovider-overview/vb/myproject.vbproj b/docs/standard/datetime/snippets/timeprovider-overview/vb/myproject.vbproj new file mode 100644 index 0000000000000..cacdff353fc16 --- /dev/null +++ b/docs/standard/datetime/snippets/timeprovider-overview/vb/myproject.vbproj @@ -0,0 +1,9 @@ + + + + Exe + ExampleProject + net9.0 + + + diff --git a/docs/standard/datetime/timeprovider-overview.md b/docs/standard/datetime/timeprovider-overview.md new file mode 100644 index 0000000000000..c105d1a2bdec0 --- /dev/null +++ b/docs/standard/datetime/timeprovider-overview.md @@ -0,0 +1,102 @@ +--- +title: What is the TimeProvider class +description: Learn about the TimeProvider class in .NET and .NET Framework. TimeProvider provides an abstraction over time. +ms.date: 12/03/2024 +ms.topic: overview +dev_langs: + - "csharp" + - "vb" +helpviewer_keywords: + - "TimeProvider class" + - "date and time classes [.NET]" +#customer intent: As a developer, I want to understand what TimeProvider is so that I can use it. +--- + +# What is TimeProvider? + + is an abstraction of time that provides a point in time as a type. By using `TimeProvider`, you ensure that your code is testable and predictable. `TimeProvider` was introduced in .NET 8 and is also available for .NET Framework 4.7+ and .NET Standard 2.0 as a NuGet package. + +The class defines the following capabilities: + +- Provides access to the date and time through and . +- High-frequency timestamps with . +- Measure time between two timestamps with . +- High-resolution timers with . +- Get the current timezone with . + +## Default implementation + +.NET provides an implementation of through the property, with the following characteristics: + +- Date and time are calculated with and . +- Timestamps are provided by . +- Timers are implemented through an internal class and exposed as . + +The following example demonstrates using to get the current date and time: + +:::code language="csharp" source="./snippets/timeprovider-overview/csharp/Program.cs" id="GetLocal"::: +:::code language="vb" source="./snippets/timeprovider-overview/vb/Program.vb" id="GetLocal"::: + +The following example demonstrates capturing elapsed time with : + +:::code language="csharp" source="./snippets/timeprovider-overview/csharp/Program.cs" id="Timestamp"::: +:::code language="vb" source="./snippets/timeprovider-overview/vb/Program.vb" id="Timestamp"::: + +## FakeTimeProvider implementation + +The [**Microsoft.Extensions.TimeProvider.Testing** NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.TimeProvider.Testing/) provides a controllable `TimeProvider` implementation designed for unit testing. + +The following list describes some of the capabilities of the class: + +- Set a specific date and time. +- Automatically advance the date and time by a specified amount whenever the date and time is read. +- Manually advance the date and time. + +## Custom implementation + +While [FakeTimeProvider](#faketimeprovider-implementation) should cover most scenarios requiring predictability with time, you can still provide your own implementation. Create a new class that derives from and override members to control how time is provided. For example, the following class only provides a single date, the date of the moon landing: + +:::code language="csharp" source="./snippets/timeprovider-overview/csharp/MoonLandingTimeProviderPST.cs" id="CustomProvider"::: +:::code language="vb" source="./snippets/timeprovider-overview/vb/MoonLandingTimeProviderPST.vb" id="CustomProvider"::: + +If code using this class calls `MoonLandingTimeProviderPST.GetUtcNow`, the date of the moon landing in UTC is returned. If `MoonLandingTimeProviderPST.GetLocalNow` is called, the base class applies `MoonLandingTimeProviderPST.LocalTimeZone` to `GetUtcNow` and returns the moon landing date and time in the PST timezone. + +To demonstrate the usefulness of controlling time, consider the following example. Let's say that you're writing a calendar app that sends a greeting to the user when the app is first opened each day. The app says a special greeting when the current day has an event associated with it, such as the anniversary of the moon landing. + +:::code language="csharp" source="./snippets/timeprovider-overview/csharp/CalendarHelper.cs" id="CalendarHelper"::: +:::code language="vb" source="./snippets/timeprovider-overview/vb/CalendarHelper.vb" id="CalendarHelper"::: + +You might be inclined to write the previous code with or to get the current date and time, instead of . But with unit testing, it's hard to work around or directly. You would need to either run the tests on the day and month of the moon landing or further abstract the code into smaller but testable units. + +The normal operation of your app uses `TimeProvider.System` to retrieve the current date and time: + +:::code language="csharp" source="./snippets/timeprovider-overview/csharp/Program.cs" id="GreetingNormal"::: +:::code language="vb" source="./snippets/timeprovider-overview/vb/Program.vb" id="GreetingNormal"::: + +And unit tests can be written to test specific scenarios, such as testing the anniversary of the moon landing: + +:::code language="csharp" source="./snippets/timeprovider-overview/csharp/Program.cs" id="GreetingMoon"::: +:::code language="vb" source="./snippets/timeprovider-overview/vb/Program.vb" id="GreetingMoon"::: + +## Use with .NET + +Starting with .NET 8, the class is provided by the runtime library. Older versions of .NET or libraries targeting .NET Standard 2.0, must reference the [**Microsoft.Bcl.TimeProvider** NuGet package](https://www.nuget.org/packages/Microsoft.Bcl.TimeProvider/). + +The following methods related to asynchronous programming work with `TimeProvider`: + +- +- +- +- +- + +## Use with .NET Framework + + is implemented by the [**Microsoft.Bcl.TimeProvider** NuGet package](https://www.nuget.org/packages/Microsoft.Bcl.TimeProvider/). + +Support for working with `TimeProvider` in asynchronous programming scenarios was added through the following extension methods: + +- +- +- +-