A type for Date only in C# - why is there no Date type?
In our C# project we have the need for representing a date without a time. I know of the existence of the DateTime, however, it incorporates a time of day as well. I want to make explicit that certain variables and method-arguments are date-based. Hence I can't use the DateTime.Date
property
What are the standard approaches to this problem? Surely I'm not the first to encounter this? Why is there no Date
class in C#?
Does anyone have a nice implementation using a struct and maybe some extensionmethods on DateTime and maybe implementing some operators such as == and <, > ?
Solution 1:
Allow me to add an update to this classic question:
-
DateOnly
(andTimeOnly
) types have been added to .NET 6, starting with Preview 4. See my other answer here. -
Jon Skeet's Noda Time library is now quite mature, and has a date-only type called
LocalDate
. (Local in this case just means local to someone, not necessarily local to the computer where the code is running.)
I've studied this problem significantly, so I'll also share several reasons for the necessity of these types:
- There is a logical discrepancy between a date-only, and a date-at-midnight value.
-
Not every local day has a midnight in every time zone. Example: Brazil's spring-forward daylight saving time transition moves the clock from 11:59:59 to 01:00:00.
-
A date-time always refers to a specific time within the day, while a date-only may refer to the beginning of the day, the end of the day, or the entire range of the day.
-
Attaching a time to a date can lead to the date changing as the value is passed from one environment to another, if time zones are not watched very carefully. This commonly occurs in JavaScript (whose
Date
object is really a date+time), but can easily happen in .NET also, or in the serialization as data is passed between JavaScript and .NET. -
Serializing a
DateTime
with XML or JSON (and others) will always include the time, even if it's not important. This is very confusing, especially considering things like birth dates and anniversaries, where the time is irrelevant. -
Architecturally,
DateTime
is a DDD value-object, but it violates the Single Responsibly Principle in several ways:
-
It is designed as a date+time type, but often is used as date-only (ignoring the time), or time-of-day-only (ignoring the date). (
TimeSpan
is also often used for time-of-day, but that's another topic.) -
The
DateTimeKind
value attached to the.Kind
property splits the single type into three, TheUnspecified
kind is really the original intent of the structure, and should be used that way. TheUtc
kind aligns the value specifically with UTC, and theLocal
kind aligns the value with the environment's local time zone.The problem with having a separate flag for kind is that every time you consume a
DateTime
, you are supposed to check.Kind
to decide what behavior to take. The framework methods all do this, but others often forget. This is truly a SRP violation, as the type now has two different reasons to change (the value, and the kind). -
The two of these lead to API usages that compile, but are often nonsensical, or have strange edge cases caused by side effects. Consider:
// nonsensical, caused by mixing types DateTime dt = DateTime.Today - TimeSpan.FromHours(3); // when on today?? // strange edge cases, caused by impact of Kind var london = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time"); var paris = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); var dt = new DateTime(2016, 3, 27, 2, 0, 0); // unspecified kind var delta = paris.GetUtcOffset(dt) - london.GetUtcOffset(dt); // side effect! Console.WriteLine(delta.TotalHours); // 0, when should be 1 !!!
In summary, while a DateTime
can be used for a date-only, it should only do so when when every place that uses it is very careful to ignore the time, and is also very careful not to try to convert to and from UTC or other time zones.
Solution 2:
I suspect there is no dedicate pure Date
class because you already have DateTime
which can handle it. Having Date
would lead to duplication and confusion.
If you want the standard approach look at the DateTime.Date
property which gives just the date portion of a DateTime
with the time value set to 12:00:00 midnight (00:00:00).