Working with .NET’s DateTime class can be a pain in the butt. Calculating dates in the future isn’t difficult, but it can take a bit of work to get to the right solution. If you’re reading this, well, you’re in luck!

We’re about to see how to solve for the next occurrence of a particular day (Monday, Tuesday, Wednesday, etc.) given an initial starting date. The solution is in C#, but we could apply the same code to any other programming languages.

Solving For The Next Day With C#

When working with .NET, we often deal with the DateTime class. This class encapsulates multiple data points in on consumable type. Some data points include the date, time, day of the week, ticks, and much more. The two critical pieces of information we need to solve our particular problem are Date and DayOfWeek. The DayOfWeek is an enumeration with an unsurprising set of values.

namespace System
{
  public enum DayOfWeek
  {
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
  }
}

Great, so let’s design our surface API given we know what problem we’re solving.

Given a starting date, we would like the date(s)
of the next day of the week(s) that we provide.

In our case, our method signature might look something like this.

public static DateTime Next(this DateTime from, DayOfWeek dayOfTheWeek)

Note that we’ll use extension methods to invoke our method right off a DateTime instance. Let’s look at a single usage.

private readonly DateTime now = new(2021, 1, 1);

[Fact]
public void Next_saturday_is_January_2nd()
{
    var result = now.Next(Saturday);
    Assert.Equal(new DateTime(2021, 1, 2), result);
}

In addition to getting a single value, I wanted the ability to get multiple calculations back at a time.

[Fact]
public void Next_friday_and_saturday_are_January_2nd_and_8th()
{
    var results = now.Next(Saturday, Friday);
    
    Assert.Equal(2, results.Count);
    Assert.Equal(new DateTime(2021, 1, 2), results[0]);
    Assert.Equal(new DateTime(2021, 1, 8), results[^1]);
}

This implementation will calculate the dates for each distinct DayOfWeek value passed into the method. What about calculating dates based on the previous result?

[Fact]
public void Next_saturday_then_next_saturday_are_January_2nd_and_9th()
{
    var results = now.Next(
        DateCalculationKind.AndThen, Saturday, Saturday);
    
    Assert.Equal(2, results.Count);
    Assert.Equal(new DateTime(2021, 1, 2), results[0]);
    Assert.Equal(new DateTime(2021, 1, 9), results[^1]);
}

Here is an additional overload that allows us to select the calculation kind, either using the default And value or the AndThen value. This overload will treat each new DayOfWeek as expecting to be calculated from the previous result. Imagine looking at a column in your calendar app; it would be as if you were potentially moving from row to row.

So where’s the code?! Well, let’s start with the main calculation.

public static DateTime Next(this DateTime from, DayOfWeek dayOfTheWeek)
{
    var date = from.Date.AddDays(1);
    var days = ((int) dayOfTheWeek - (int) date.DayOfWeek + 7) % 7;
    return date.AddDays(days);
}
  1. We first skip the current date as it may be the same DayOfWeek we’re currently seeking to calculate.
  2. We want to calculate the number of days from our current date to get to the next day of the week. We use the % operator to constrain the result between 0 and 7.
  3. Finally, we add those days to the original date + 1 day (skipping the original date).

Once we have the heart of the calculation sorted out, we can build our additional variants.

public static IReadOnlyList<DateTime> Next(
    this DateTime from, 
    params DayOfWeek[] days
)
{
    return Next(from, DateCalculationKind.And, days);
}

public static IReadOnlyList<DateTime> Next(
    this DateTime from, 
    DateCalculationKind calculationKind, 
    params DayOfWeek[] days
)
{
    if (days == null)
        return new DateTime[0];

    var results = new List<DateTime>();
    
    // And means, we don't want to duplicate data
    days = calculationKind == DateCalculationKind.And 
        ? days.Distinct().ToArray()
        : days;
    
    DateTime? result = null;
    foreach (var dayOfWeek in days)
    {
        result = calculationKind == DateCalculationKind.And || result is null
            ? Next(from, dayOfWeek) 
            : Next(result.Value, dayOfWeek);
        
        results.Add(result.Value);
    }

    return results;
}

public enum DateCalculationKind
{
    /// <summary>
    /// Will uniquely calculate the next day of the
    /// week. This will perform a `distinct` on the
    /// collection of days, and give back a unique result
    /// set with each calculation performed with
    /// the `from` parameter.
    /// </summary>
    And,
    /// <summary>
    /// Will use the previous result to calculate the
    /// next date. This allows for duplicate days of
    /// the week and will be calculated in the order
    /// of the days passed in.
    /// </summary>
    AndThen
}

These two additional methods allow us to pass in more than one day of the week at a time and calculate multiple dates. Here are all the scenarios in use.

using System;
using Xunit;
using static System.DayOfWeek;

namespace WorkingDates
{
    public class Scenarios
    {
        private readonly DateTime now = new(2021, 1, 1);

        [Fact]
        public void Now_is_a_friday()
        {
            Assert.Equal(Friday, now.DayOfWeek);
        }

        [Fact]
        public void Next_saturday_is_January_2nd()
        {
            var result = now.Next(Saturday);
            Assert.Equal(new DateTime(2021, 1, 2), result);
        }

        [Fact]
        public void Next_friday_is_January_8th()
        {
            var result = now.Next(Friday);
            Assert.Equal(new DateTime(2021, 1, 8), result);
        }

        [Fact]
        public void Next_friday_and_saturday_are_January_2nd_and_8th()
        {
            var results = now.Next(Saturday, Friday);
            
            Assert.Equal(2, results.Count);
            Assert.Equal(new DateTime(2021, 1, 2), results[0]);
            Assert.Equal(new DateTime(2021, 1, 8), results[^1]);
        }

        [Fact]
        public void Next_saturday_then_next_saturday_are_January_2nd_and_9th()
        {
            var results = now.Next(
                DateCalculationKind.AndThen, Saturday, Saturday);
            
            Assert.Equal(2, results.Count);
            Assert.Equal(new DateTime(2021, 1, 2), results[0]);
            Assert.Equal(new DateTime(2021, 1, 9), results[^1]);
        }
    }
}

And here is the complete implementation.

using System;
using System.Collections.Generic;
using System.Linq;

namespace WorkingDates
{
    public static class DateTimeExtensions
    {
        public static DateTime Next(this DateTime from, DayOfWeek dayOfTheWeek)
        {
            var date = from.Date.AddDays(1);
            var days = ((int) dayOfTheWeek - (int) date.DayOfWeek + 7) % 7;
            return date.AddDays(days);
        }

        public static IReadOnlyList<DateTime> Next(
            this DateTime from, 
            params DayOfWeek[] days
        )
        {
            return Next(from, DateCalculationKind.And, days);
        }
        
        public static IReadOnlyList<DateTime> Next(
            this DateTime from, 
            DateCalculationKind calculationKind, 
            params DayOfWeek[] days
        )
        {
            if (days == null)
                return new DateTime[0];

            var results = new List<DateTime>();
            
            // And means, we don't want to duplicate data
            days = calculationKind == DateCalculationKind.And 
                ? days.Distinct().ToArray()
                : days;
            
            DateTime? result = null;
            foreach (var dayOfWeek in days)
            {
                result = calculationKind == DateCalculationKind.And || result is null
                    ? Next(from, dayOfWeek) 
                    : Next(result.Value, dayOfWeek);
                
                results.Add(result.Value);
            }

            return results;
        }
    }

    public enum DateCalculationKind
    {
        /// <summary>
        /// Will uniquely calculate the next day of the
        /// week. This will perform a `distinct` on the
        /// collection of days, and give back a unique result
        /// set with each calculation performed with
        /// the `from` parameter.
        /// </summary>
        And,
        /// <summary>
        /// Will use the previous result to calculate the
        /// next date. This allows for duplicate days of
        /// the week and will be calculated in the order
        /// of the days passed in.
        /// </summary>
        AndThen
    }
}

I hope you found this post useful, and please let me know in the comments if you have other calculations that you’re using for your applications.