runtime: Eliminate ambiguous date and time values
aka The Date project
Background and Motivation
Dotnet Core contains several types for representing dates and times. As noted in the guidance documentation, developers should “consider DateTimeOffset
as the default date and time type for application development” (https://docs.microsoft.com/en-us/dotnet/standard/datetime/choosing-between-datetime)
However in many cases, in examples, component packages, and business software, DateTime
is used, even though it is the incorrect type – part of this is historical because DateTime
existed prior to DateTimeOffset
. This issue even affects Dotnet and many examples from Microsoft, where DateTime
is often used where DateTimeOffset
would be more appropriate.
This proposal - Everywhere that an ambigious DateTime
has been used in Dotnet, it should be supplemented with an unambiguous DateTimeOffset
, and these values should be promoted as the default for future use.
It is currently difficult to follow the guidance advice and use DateTimeOffset
when basic classes like FileInfo
only use DateTime
.
There are still some uses for DateTime
. For some, such as working with UTC dates and times, DateTimeOffset
can also be used, and should still be considered the default, or for working with times only then TimeSpan
can be used.
The cases where DateTime
is the correct class are:
-
working with dates only, for example a birthdate for calculating something like drinking age only the date part is relevant – it is generally not relevant which timezone you were born in or your current timezone.
-
working with abstract dates and times, for example in a calendar application; a common example is having a meeting at 09:00 on the first of each month, where the instance of time will vary in line with timezones. In these cases the date and time components need to be treated separately.
In both these cases, only the date part is used, not the full DateTime
, and would be better served by a Date
only structure; for all other cases either DateTimeOffset
or another type (TimeSpan
) is more appropriate. In some cases (e.g. calendar) both a Date
and TimeSpan
value would be needed, but also need to be handled independently (and not combined into a single DateTime
value).
Promoting the use of Date
, for date only, and DateTimeOffset
, for other scenarios, will help eliminate the issues that can arise from using ambiguous dates, e.g. where a field is supposed to be date only bug a bug has introduce a time component into the DateTime
field – such a bug is not possible with a date only structure.
The long term goal is to eventually be able to mark DateTime
as Obsolete
– something that I have proposed before.
Proposed API
The new API changes would consist of adding a Date
struct, and adding DateTimeOffset
properties through Dotnet to supplement where there is currently only a DateTime
.
To implement unambiguous single points of time across the entire Dotnet is a large, long term project, that can be done incrementally, with the following roadmap:
- Implement a Date structure for those scenarios that require only date. This will eliminate the need to ambiguously use
DateTime
with a time component, where only date is needed.
A basic structure can be introduced, and then incrementally added to as needed.
- Incrementally add supplementary
DateTimeOffset
properties anywhere thatDateTime
is used in Dotnet. This would allow the guidance of “considerDateTimeOffset
as the default” to be acted upon.
In some cases these values may already exist, e.g. FileSystemInfo
already uses DateTimeOffset
internally but just doesn’t expose it in the API.
Searching the code for something like “[\s.()=±/!&[]{}]DateTime[\s.()=±/!&[]{}]” within .cs files gives an idea of the scope – 10,000 references across 1,000 files.
- Remove the dependency from
DateTimeOffset
onDateTime
. InternallyDateTimeOffset
usesDateTime
, plus the offset. This should be changed to a ulong, and makeDateTimeOffset
a stand alone structure without dependency onDateTIme
limited to conversions.
Initially, this would involve duplication of some code from DateTime
to DateTimeOffset
(there is also duplication in some places like comdatetime
). Further incremental changes may reverse the dependency, e.g. DateTime
could call an IsValidTimeWithLeapSeconds
in DateTimeOffset
, to then remove the duplication.
- As an additional aid, any example code, training material, documentation, and other guidance can also be incrementally updated, e.g. any examples that reference
FileInfo
should be updated to show examples usingDateTimeOffset
fields, not date time.
Steps 1-4 can be done in parallel, incrementally.
- Once all functionality has been moved across, places where
DateTime
is used can be incrementally markedObsolete
and/or hidden from editors (e.g. Intellisense), via[EditorBrowsable(EditorBrowsableState.Never)]
.
Note that marking something Obsolete
is not a directly breaking change, the API is the same and does not break any runtime dependencies. There may, however, be some indirect issues when recompiling any dependencies if “treat warnings as errors” is turned on – these would be good indicators to fix those dependencies to also use the DateTimeOffset
alternative. You could potentially start this before steps 1-4 are complete, but it may be clearer to wait.
- Eventually,
DateTime
itself can be markedObsolete
, and/or hidden, having been fully replaced byDateTimeOffset
and, in some cases,Date
.
There may be opposition to marking DateTime
Obsolete
, due to the possibility of breaking build systems that have treat warnings as errors turned on (not a runtime breaking change), but if all instances within Dotnet have been replaced, and after sufficient time, this should be possible. Once this is achieved, ambiguous dates and times will have been eliminated from Dotnet.
A compromise may be to hide the class from editors, which will not break builds but help encourage future developers follow the guidance and use DateTimeOffset
.
Note: I have previously raised a suggestion to mark DateTime
as Obsolete
.
Usage Examples
The overview example of How to get information about files, folders, and drives, (https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-get-information-about-files-folders-and-drives) would be changed to use the DateTimeOffset
version of the property.
foreach (System.IO.FileInfo fi in fileNames)
{
Console.WriteLine("{0}: {1}: {2}", fi.Name, fi.LastAccessAt, fi.Length);
}
The corresponding current properties on FileInfo
(FileSystemInfo
) are LastAccessTime
and LastAccessTimeUtc
. The specific pattern used for DateTimeOffset
properties can be discussed and agreed.
Other possible variations could be LastAccess
, LastAccessTimeAt
, LastAccessedAt
, or LastAccessedTimeAt
. Another possible variation is a past tense form LastAccessed
similar to the IFileInfo
interface from Microsoft.Extensions.FileProviders
, although this could be confused with the conventions for event names.
Alternative Designs
The main well known alternative is probably Noda Time (https://nodatime.org/).
Risks
There is probably a lot of opposition to trying to replace a core construct such as DateTime
, although the professional guidance is that in most cases DateTimeOffset
is far more appropriate to use.
Marking it Obsolete
could also indirectly cause issues with build processes in some cases, although it does not break runtime API compatibility.
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 4
- Comments: 25 (19 by maintainers)
… we’ve had
System.Time.Date
andSystem.Time.Time
types in corefxlab for years, although they’ve never been pulled in.Personally, I rather wish that we’d get a good, complete, first-party date/time library, like I proposed years ago. For one thing, it allows representations much closer to conceptual/semantic than the limited types you’ve proposed. Although the migration plan you’ve outlined here is essentially what I imagined.
Note that
DateTimeOffset
is often not the correct semantic type - normally you want either an equivalent to NodaTime’sInstant
orDateTimeZoned
(it often ends up being the correct “solution” type, since it’s what’s available). For instance, file access times should really be represented as anInstant
(since any zone, including UTC, is irrelevant).