Using DateOnly and TimeOnly in .NET 6 to Simplify Your Code

Using DateOnly and TimeOnly in .NET 6 to Simplify Your Code

This blog post is part of the 2021 C# Advent Calendar. Go check out the other 49 great posts after you read mine, of course.

.NET 6 introduced two new types that many developers have been eagerly awaiting; DateOnly and TimeOnly. Both represent Date and Time separately; instead of relying on the DateTime object, they're also Structs. This is great because sometimes, we, as developers, don't care about one of these types. Some object we've created might only care about the date and have no use for time, which could be significant as DateOnly can now match the SQL Server date type. Wouldn't it be great not to have to parse that the time out?

I'm excited to use this is in my personal communications service that I run locally. I really should make a blog post about it; it essentially handles emails and texts for any of my local services or applications. Often, I just don't care about the date or time. I want something to go out every day at 8 A.M., like a status report, or go out at midnight on the first day of the month.

Some things were changed for this example, but here's what the old Message class looked like:

public class MessageOldWay
{
    public string? Text { get; init; }
    public DateTime DateAndTime { get; set; }
}

In C# 9, we'd have to use DateTime regardless of which method was called, which meant breaking things up. In the below example, we don't care about the time; we just need to know the content of the message and what the date is.

var messageOldWay = new MessageOldWay
{
    Text = "Something Important!", DateAndTime = new DateTime(2021, 12, 21, 8, 0, 0)
};

SendMessageOldWay(messageOldWay);

void SendMessageOldWay(MessageOldWay message)
{
    var text = message.Text;
    var date = message.DateAndTime.ToShortDateString();

    Console.WriteLine($"Message: {text} \nDate: {date}");
}

It's not terribly complicated, but it is annoying to deal with unneeded code.

Here's what the new message class looks like:

public class Message
{
    public string Text { get; init; } = default!;
    public DateOnly Date { get; init; }

    internal void Deconstruct(out string text, out DateOnly date)
    {
        text = Text;
        date = Date;
    }
}

We've added a Deconstruct in the new class to take advantage of .NET 6's improvements on deconstructing syntax; it makes unpacking things a bit easier. Usually, you'd want to use Linq to handle collections, but today we're going to play with some more .NET 6 goodness.

Now everything is a whole lot simpler:

var message = new Message
{
    Text = "Don't forget to take a walk!", Date = new DateOnly(2021, 12, 21)
};

SendMessage(message);

void SendMessage(Message message)
{
    (var text, var date) = message;

    Console.WriteLine($"Message: {text} \nDate: {date}");
}

If we wanted to use a new method with just the time, we could make that, but we're going inherit from Message and use both!

public class MessageWithTime : Message
{
    public TimeOnly Time { get; init; }

    internal void Deconstruct(out string text, out DateOnly date, out TimeOnly time)
    {
        text = Text;
        date = Date;
        time = Time;
    }

}

Now that the new class has a Deconstruct we can do something like this!

var messages = new Dictionary<int, MessageWithTime>
{
    { 1, new MessageWithTime{ Text = "Start your work day", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(8, 0) } },
    { 2, new MessageWithTime{ Text = "Coffee time!", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(9, 0) } },
    { 3, new MessageWithTime{ Text = "Take a short walk", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(10, 0) } },
    { 4, new MessageWithTime{ Text = "Did you feed the cats?", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(11, 30) } },
    { 5, new MessageWithTime{ Text = "Don't forget to eat lunch!", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(12, 0) } },
    { 6, new MessageWithTime{ Text = "Drink Water!", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(13, 0) } },
    { 7, new MessageWithTime{ Text = "Drink MORE Water", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(14, 0) } },
    { 8, new MessageWithTime{ Text = "Go for a walk", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(15, 15) } },
    { 9, new MessageWithTime{ Text = "I don't know what to say", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(16, 0) } },
    { 10, new MessageWithTime{ Text = "Work is over for the day!", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(17, 0) } },
    { 11, new MessageWithTime{ Text = "Eat Dinner!", Date = new DateOnly(2021, 12, 21), Time = new TimeOnly(18, 10) } },
};

SendMessages(messages);

void SendMessages(Dictionary<int, MessageWithTime> messages)
{
    foreach (var (postionInQue, message) in messages)
    {
        (var text, var date, var time) = message;
        Console.WriteLine($"You're place in Que is: {postionInQue} \nMessage: {text} \nDate: {date} \nTime: {time}\n");
    }
    Console.WriteLine();

}

Thanks to the pre-existing Deconstruct in Dictionaries, we can unpack the Key/Value Pair in our foreach statement, making it more readable. One of the great things is that many .NET types come with Deconstruct built-in!

foreach (var (postionInQue, message) in messages)

Before this, we'd have to do something like:

foreach (var message in messages)
    {
        var key = message.Key;
        var value = message.Value;
        var text = value.Text;
        var date = value.Date;
        var time = value.Time;

        Console.WriteLine($"You're place in Que is: {key} \nMessage: {text} \nDate: {date} \nTime: {time}\n");
    }

Since we built a Deconstruct into our class, we're able to unpack everything in one line!

(var text, var date, var time) = message;

I hope you'll take advantage of the new DateOnly and TimeOnly data types. This example may be simplistic, but I really expect these two new types to change how systems are designed.

How about you? Are you planning on taking advantage of these new types? What has you excited about them? Have you started adding deconstructs to your codebase? Do you even like Deconstructs? Why or why not?

Seriously, I really want to know what you think. Drop a comment below!

Stay safe and hydrated.