.NET 6 is currently scheduled to be released-to-production on 9th Nov 2021. So, to get ready for that, we're kicking off a short new series that shows some of the smaller, but cool, things we can do in .NET 6 with some new framework features.
Let's kick off this new series by discussing a long-awaited feature: DateOnly
and TimeOnly
!
Current Implementation
To represent a date without a time, we are forced to use the DateTime
class, which has several overloads that help us get the date.
var myDate = new DateTime(2021, 9, 23);
var datePart = DateTime.Now.Date;
However, these overloads and properties will always return type DateTime
, which will include a time, regardless of whether or not we need it.
Similarly, before .NET 6 there was no real way to represent a time of day without a date. We could use the TimeSpan
class to represent time elapsed (e.g. 7 hours, 21 minutes and 9 seconds), but there was no way to represent a specific time of day (e.g. 7:21:09 AM) without using DateTime
.
New Implementation
.NET 6 introduces the new DateOnly
and TimeOnly
structs, which allow us to represent dates without time of day, or time of day without a date.
DateOnly sep10th = new DateOnly(2021, 9, 10);
var dec31st = new DateOnly(1999, 12, 31);
DateOnly aug3rd = new(1988, 8, 3);
TimeOnly nineThirtyPM = new TimeOnly(21, 30); //21:30, or 9:30 PM
TimeOnly fourTwentyThreeAM = new(4, 23, 19); //04:23:19, or 4:23:19 AM
We can create instances of DateOnly
or TimeOnly
from corresponding DateTime
instances using a special new overload, FromDateTime()
.
DateTime dateOnlyExample = new DateTime(2004, 5, 19, 4, 45, 30);
DateOnly date3 = DateOnly.FromDateTime(dateOnlyExample); //May 19th, 2004
DateTime sevenFortyFiveDT = new DateTime(2011, 11, 11, 7, 45, 00);
TimeOnly sevenFortyFive = TimeOnly.FromDateTime(sevenFortyFiveDT); //07:45 AM
DateOnly Details
Automatically Determines Local Culture
The DateOnly
struct automatically determines the local culture, and uses that to output the date.
DateOnly cultureExample = new DateOnly(2004, 5, 19); //May 19 2004
Console.WriteLine(cultureExample);
//American: 5/19/2004, European: 19/5/2004, Universal: 2004-05-19
AddDays(), AddMonths(), AddYears()
Similarly to DateTime
, we can use overload methods like AddDays()
to modify the value of a DateOnly
instance.
DateOnly addTimeExample = new DateOnly(2004, 5, 19); //May 19 2004
addTimeExample = addTimeExample.AddYears(2).AddMonths(2).AddDays(5);
//Jul 24th, 2006
Parsing from Strings
We can also parse DateOnly
instances from strings using TryParse()
, just like we did with DateTime
.
if(DateOnly.TryParse("09/21/2013", out DateOnly result))
Console.WriteLine(result);
//American: 9/21/2013, European: 21/9/2013, Universal: 2013-09-21
Stores Value as Integer
Internally, DateOnly
stores its value as an integer, where 0 is January 1st, 0001. We can get that integer value from a DateOnly
instance, and use the method DateOnly.FromDayNumber
to convert that integer to a DateOnly
instance.
DateOnly integerTest = new(2019, 7, 1); //July 1st 2019
int dayNumber = integerTest.DayNumber;
DateOnly integerResult = DateOnly.FromDayNumber(dayNumber); //July 1st 2019
TimeOnly Details
Remember that TimeOnly
represents a time of day, not time elapsed (the latter is what TimeSpan
is used for).
Stores Value as Ticks from Midnight
Similarly to DateOnly
, TimeOnly
internally stores its value as a long
, which are the ticks (100 nanoseconds) since 00:00:00 (midnight). We can use the ticks to create a new TimeOnly
value.
TimeOnly sixTen = new TimeOnly(6, 10);
long ticks = sixTen.Ticks;
TimeOnly sixTenAgain = new TimeOnly(ticks);
Math Operations result in TimeSpans
We can perform math operations on instances of TimeOnly
, which give us TimeSpan
results.
var afternoon = new TimeOnly(15, 15); //3:15 PM
var morning = new TimeOnly(9, 10); //9:10 AN
TimeSpan difference = afternoon - morning; //6 hours 5 minutes
Checking for Value in a Range
We can check if a TimeOnly
instance falls in a given range using the IsBetween()
method.
var now = TimeOnly.FromDateTime(DateTime.Now);
var nineAM = new TimeOnly(9, 0);
var fivePM = new TimeOnly(17, 0);
if(now.IsBetween(nineAM, fivePM))
Console.WriteLine("Work time!");
else
Console.WriteLine("Anything other than work time!");
We can even check for a value in range that goes across midnight.
var tenPM = new TimeOnly(22, 0);
var twoAM = new TimeOnly(2, 0);
var midnight = new TimeOnly(0); //0 ticks == midnight
if(midnight.IsBetween(tenPM, twoAM))
Console.WriteLine("It's getting late...");
Comparison Operations
We can use comparison operators (such as <
and >
) to compare two TimeOnly
instances.
TimeOnly noon = new(12, 0);
if (now < noon)
Console.WriteLine("Good Morning!");
Other Considerations
If you're wondering why these classes were not simply called Date
and Time
, apparently that has to do with several considerations. Partly, it is because VB has a Date
object and .NET needs to be compatible with VB. You can read more in the following blog post:
Demo Project
As with all posts in this series, there is a demo project you can check out over on GitHub that shows examples of DateOnly
, TimeOnly
, and the various things we can do with them. Check it out!
Happy Coding!