Finding the Next Date for Day of Week

The Problem

In a recent project at work, I was creating an online form for placing announcements of any kind (specifically: weddings, engagements, wedding anniversaries, birthdays and even more.) It had the following business rule: ads are published every Tuesday and customers must choose the Tuesday when their ad should be published and the order must be placed at least two weeks in advance from the desired publication date.

False Start

About 9 years ago, I had a very challenging Active Server Pages project that also required dealing with dates. I had developed a JavaScript function that performed the calculation that I’m posting about. However, the approach was completely wrong. It got the job done, but it was a quick and dirty solution (please forgive the documentation, which I know isn’t very clear):

function getDateForDay( dateVal, dayNum )
{ // Returns the date matching given 0-based day for this week.
  // Precondition: dateVal is a date. dayNum is a 0-based day of the
  //   week; that is, Sunday = 0, Monday = 1, and so on.
  // Postcondition: The returned date is on the given day of this week.
  //   If the given day is before given date, the date for next week
  //   is returned.

	dateVal = new Date( dateVal );
	var nextDate = new Date( dateVal );

	dayNum = dayNum % 7; // Only accept days between 0 and 6.

	// Traverse this week
	while( Response.IsClientConnected() )
		if( nextDate.getDay() == dayNum )
			return nextDate;
		else  // advance to next day
			nextDate.setTime( nextDate.getTime() + 86400000 )
}

The function takes a date and 0-based day of the week (Sunday = 0, Monday = 1, etc.), validates the supplied day number and then loops until the current date’s day of the week matches the supplied day of the week. Then it returns the matching date.

For the project mentioned at the beginning of this post, I proceeded to convert the above JavaScript implementation to C# because I am using ASP.NET 3.5 for the aforementioned project. This is what I came up with:

/// <summary>
/// Finds the next date whose day of the week equals the specified day of the week.
/// </summary>
/// <param name="startDate">
///		The date to begin the search.
/// </param>
/// <param name="desiredDay">
///		The desired day of the week whose date will be returneed.
/// </param>
/// <returns>
///		The returned date is on the given day of this week.
///		If the given day is before given date, the date for the
///		following week's desired day is returned.
/// </returns>
public static DateTime GetNextDateForDay( DateTime startDate, DayOfWeek desiredDay )
{
    // (There has to be a better way to do this, perhaps mathematically.)
    // Traverse this week
    DateTime nextDate = startDate;
    while( nextDate.DayOfWeek != desiredDay )
        nextDate = nextDate.AddDays( 1D );

    return nextDate;
}

It looks very simple, right? It simply goes through every day of the week, starting with supplied date, until it hits the desired day of the week and then it returns that resulting date. However, as noted in the comments, this approach is wrong. I proceeded to use this implementation anyway until I had time to come up with something better.

I knew there had to be a better approach. I remembered that in Abstract Algebra class, you could use the modulus operator to do wondrous things, such as calculating the check digit of an ISBN. It also could be used for finding how many days to add to a specific day in order to arrive at that same day at some point in the future. The problem was, I didn’t remember how and didn’t have my book with me. I tried googling the answer, but could not find anything. Finally, I rolled up my sleeves and literally sought the answer by myself.

I would explain my approach, but the purpose of this post is not to show how I arrived at the solution but to show what the solution is.

The Solution

The formula I came up with was:

f(c, d) = [7 - (c - d)] \mod 7 = (7 - c + d) \mod 7
where
c is current day of week and 0 <= c < 7
d is desired day of the week and 0 <= d < 7

In C#, that formula is translated to:

/// <summary>
/// Calculates the number of days to add to the given day of
/// the week in order to return the next occurrence of the
/// desired day of the week.
/// </summary>
/// <param name="current">
///		The starting day of the week.
/// </param>
/// <param name="desired">
///		The desired day of the week.
/// </param>
/// <returns>
///		The number of days to add to <var>current</var> day of week
///		in order to achieve the next <var>desired</var> day of week.
/// </returns>
public static int DaysToAdd( this DayOfWeek current, DayOfWeek desired )
{
    // f( c, d ) = [7 - (c - d)] mod 7
    //   where 0 <= c < 7 and 0 <= d < 7

    int c = (int)current;
    int d = (int)desired;
    return (7 - c + d) % 7;
}

But there is a problem. Let me repeat the description of the method (emphasis added):

Calculates the number of days to add to the given day of the week in order to return the next occurrence of the desired day of the week.

My approach doesn’t conform to what the method claims to do. Say for example, today is Saturday and you want to find out how many days you need to add to today in order to obtain next Saturday’s date. The answer is 7, but the method and formula above return zero (0). The problem is the use of the modulus operator (% in C#). It is obvious that this still needs further refinement. When I was originally coming up with the formula, my calculations were correct but the derived formula wasn’t. That is, I knew you needed to add 7 days to Saturday in order to return the next Saturday. I didn’t realize the problem until I decided to write an article about this topic today actually. This raises an interesting question.

Dilemma: What is considered “next”?

Let’s pretend today is Sunday and you want to calculate how many days are left until next Tuesday, do you add 2 or is it 9? In fact, observe what the above formula does. Let c = 0 (Sunday) and d = 2 (Tuesday), then:

f(0, 2) = (7 - 0 + 2) \mod 7 = 9 \mod 7 = 2

Look at the 9 in the left side of the mod operator and the result of 2. The 9 represents the number of days to add to Sunday in order to return not this week’s Tuesday but the following week’s Tuesday. The result of 2 simply tells us that you need to add 2 days to Sunday to return this week’s Tuesday.

Up to this point I’m undecided as to what is considered the correct answer, but going back to the DaysToAdd method description, I’m opting for what I consider the “soonest next” Tuesday, which is 2.

The new formula now becomes:

Part 1: f(c, d) = g(c, d) \mod 7,  when g(c, d) > 7
Part 2: f(c, d) = g(c, d), when g(c, d) \leq 7
where
g(c, d) = [7 - (c - d)] = 7 - c + d
c is current day of week and 0 <= c < 7
d is desired day of the week and 0 <= d < 7

And in C# it is:

/// <summary>
/// Calculates the number of days to add to the given day of
/// the week in order to return the next occurrence of the
/// desired day of the week.
/// </summary>
/// <param name="current">
///		The starting day of the week.
/// </param>
/// <param name="desired">
///		The desired day of the week.
/// </param>
/// <returns>
///		The number of days to add to <var>current</var> day of week
///		in order to achieve the next <var>desired</var> day of week.
/// </returns>
public static int DaysToAdd( DayOfWeek current, DayOfWeek desired )
{
    // f( c, d ) = g( c, d ) mod 7, g( c, d ) > 7
    //           = g( c, d ), g( c, d ) <= 7
    // g( c, d ) = [7 - (c - d)] = 7 - c + d
    //   where 0 <= c < 7 and 0 <= d < 7

    int c = (int)current;
    int d = (int)desired;
    int n = (7 - c + d);

    return (n > 7) ? n % 7 : n;
}

The above approach yields the result I am looking for.

The final implementation of the GetNextDateForDay() method is:

/// <summary>
/// Finds the next date whose day of the week equals the specified day of the week.
/// </summary>
/// <param name="startDate">
///		The date to begin the search.
/// </param>
/// <param name="desiredDay">
///		The desired day of the week whose date will be returneed.
/// </param>
/// <returns>
///		The returned date occurs on the given date's week.
///		If the given day occurs before given date, the date for the
///		following week's desired day is returned.
/// </returns>
public static DateTime GetNextDateForDay( DateTime startDate, DayOfWeek desiredDay )
{
    // Given a date and day of week,
    // find the next date whose day of the week equals the specified day of the week.
    return startDate.AddDays( DaysToAdd( startDate.DayOfWeek, desiredDay ) );
}

/// <summary>
/// Calculates the number of days to add to the given day of
/// the week in order to return the next occurrence of the
/// desired day of the week.
/// </summary>
/// <param name="current">
///		The starting day of the week.
/// </param>
/// <param name="desired">
///		The desired day of the week.
/// </param>
/// <returns>
///		The number of days to add to <var>current</var> day of week
///		in order to achieve the next <var>desired</var> day of week.
/// </returns>
public static int DaysToAdd( DayOfWeek current, DayOfWeek desired )
{
    // f( c, d ) = g( c, d ) mod 7, g( c, d ) > 7
    //           = g( c, d ), g( c, d ) < = 7
    //   where 0 <= c < 7 and 0 <= d < 7

    int c = (int)current;
    int d = (int)desired;
    int n = (7 - c + d);

    return (n > 7) ? n % 7 : n;
}

Closing Thoughts

I spent several hours trying to simplify the wrong loop-based approach to calculate the next date for a given day. My inspiration was the keynote speech by http://www.joshholmes.com/ at the Cincinnati Day of .NET Conference last Saturday, April 18. He talked about the “Lost Art of Simplicity”. It made me rethink how I approach problems and that what I consider sometimes an innovative solution (a point of pride, so to speak) may not be the best solution. He said that simplicity is hard. Josh, you are absolutely right. Thank you for inspiring me to challenge myself.

In a following entry, I will show how to apply the above methods to extend the System.DateTime structure by using extension methods.

5 thoughts on “Finding the Next Date for Day of Week

  1. On the same principles, in DaysToAdd we could also do:

    int n=(d-c);
    if (n<=0) n+=7;

  2. Pingback: Working for the weekend « John Kaster

  3. I was about to give up looking for a non loop solution to this but fortunately I found your post.
    I only use Part1 of your solution ’cause I need to find out next desired day but not in the same week.
    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>