11

In C#, given an arbitrary set of DayOfWeek end points (like, DayOfWeek.Friday and DayOfWeek.Sunday) how would one test if an arbitrary date falls between those two days, inclusive?

Example:

// result == true; Oct 23, 2010 is a Saturday
var result = InBetweenDaysInclusive(new DateTime(2010, 10, 23),
                                    DayOfWeek.Friday,
                                    DayOfWeek.Sunday);

// result == true; Oct 22, 2010 is a Friday
result = InBetweenDaysInclusive(new DateTime(2010, 10, 22),
                                DayOfWeek.Friday,
                                DayOfWeek.Sunday);

// result == true; Oct 24, 2010 is a Sunday
result = InBetweenDaysInclusive(new DateTime(2010, 10, 24),
                                DayOfWeek.Friday,
                                DayOfWeek.Sunday);

// result == false; Oct 25, 2010 is a Monday
result = InBetweenDaysInclusive(new DateTime(2010, 10, 25),
                                DayOfWeek.Friday,
                                DayOfWeek.Sunday);

Thanks!

russcollier
  • 709
  • 1
  • 9
  • 18
  • Are you assuming that the first given day is occurs before the second day? For example, would you expect your algorithm to always respond with `TRUE` if the days were `Monday` and `Sunday`? – Brad Oct 27 '10 at 13:45
  • 3
    Whether intentional or unintentional, including Sunday as one of the arguments was a good test, at least in most English speaking countries, as it forces you to make sure you handle the week wraparound since Sunday is 0. – Powerlord Oct 27 '10 at 14:13

6 Answers6

14

This function is going to need two separate branches depending on whether the difference between start and end date is negative or positive/zero.

I could be totally off-base, but I think this works for all cases:

// No state in method, so made it static
public static bool InBetweenDaysInclusive(DateTime date, DayOfWeek start, DayOfWeek end)
{
    DayOfWeek curDay = date.DayOfWeek;

    if (start <= end)
    {
        // Test one range: start to end
        return (start <= curDay && curDay <= end);
    }
    else
    {
        // Test two ranges: start to 6, 0 to end
        return (start <= curDay || curDay <= end);
    }
}

For reference, your test data returned the following when I ran it and added Console.WriteLine for each result:

True
True
True
False

Edit: My last explanation was too vague. Here's a fixed one.

The trick is that if end < start, then you have two valid ranges: start to upper bound and lower bound to end. This would result in (start <= curDay && curDay <= upperBound) || curDay <= end && lowerBound <= curDay)

However, since they are bounds, curDay is always <= upperBound and >= lowerBound, thus we omit that code.

Powerlord
  • 87,612
  • 17
  • 125
  • 175
  • I tested this with my same sample data: it works just as mine does. Can you make it culture-aware? I'll let you all keep my extension methods for free :) – Brad Oct 27 '10 at 14:02
  • @Brad: I'm not really sure what I'd need to do to make it culture aware in this case... I just assumed it would work in other cultures, although I haven't tested it. – Powerlord Oct 27 '10 at 14:09
  • @Bemrose, wouldn't all the evaluations of whether `someDayOfWeek is <= to anotherDayOfWeek` presume upon the order of the Sun-Sat (0-6) DayOfWeek enum? – Brad Oct 27 '10 at 14:13
  • @Brad: [MSDN](http://msdn.microsoft.com/en-us/library/system.dayofweek.aspx) implies 0 is always Sunday in this enum. – Powerlord Oct 27 '10 at 14:18
  • @Bemrose, yeah, the enum is the same in every culture. I guess you're right that it doesn't matter what day if the `FirstDayOfTheWeek` in your solution. Don't worry, you got the only upvote I can give you LONG ago! :) – Brad Oct 27 '10 at 14:20
  • @Brad: I actually had to check that... at first I didn't stop and think that the DateTime could have a different culture than the current culture. It's morning, I can tell. ;) – Powerlord Oct 27 '10 at 14:33
  • +1 I think this is the cleanest solution that's been posted. I agree that the culture should be irrelevant to the solution, as long as the DayOfWeek enum always goes from 0 to 6 and consecutive days are next to eachother. My solution is more complex because I figured we needed to solve the intermediate problem of "finding all days of the week between two days of the week". Perhaps the solution to that problem is useful in another circumstance, but it is not the simplest way to solve the OP's question. – Stuart Lange Oct 27 '10 at 17:37
  • Also, your solution passes all my tests as well. – Stuart Lange Oct 27 '10 at 17:39
  • Wow, I wasn't expecting this many responses, many thanks to all! – russcollier Oct 29 '10 at 14:12
7

Every date falls between any two given days of the week (think about it)...

You would need to get the endpoint dates for the dayOfWeek closest to the date in question (where day difference < 7). Then, you do a simple comparison.


NOTE: the following solution assumes a week is Sunday to Saturday

Given the following extension methods:

    /// <summary>
    /// Gets the date of the next occurrence of the day of week provided
    /// </summary>
    /// <param name="value"></param>
    /// <param name="nextDay"></param>
    /// <returns></returns>
    public static DateTime NextOccurance(this DateTime value, DayOfWeek nextDay)
    {
        if (value.DayOfWeek == nextDay) { return value; }
        else if (value.DayOfWeek > nextDay) { return value.AddDays(7 - (value.DayOfWeek - nextDay)); }
        else { return value.AddDays(nextDay - value.DayOfWeek); }
    }

    /// <summary>
    /// Gets the date of the last occurrence of the day of week provided
    /// </summary>
    /// <param name="value"></param>
    /// <param name="lastDay"></param>
    /// <returns></returns>
    public static DateTime LastOccurance(this DateTime value, DayOfWeek lastDay)
    {
        if (value.DayOfWeek == lastDay) { return value; }
        else if (value.DayOfWeek > lastDay) { return value.AddDays(-(value.DayOfWeek - lastDay)); }
        else { return value.AddDays((lastDay - value.DayOfWeek) - 7); }
    }

    /// <summary>
    /// Gets the date of the closest occurrence of the day of week provided
    /// </summary>
    /// <param name="value"></param>
    /// <param name="day"></param>
    /// <returns></returns>
    public static DateTime ClosestOccurance(this DateTime value, DayOfWeek day)
    {
        DateTime before = value.LastOccurance(day);
        DateTime after = value.NextOccurance(day);
        return ((value - before) < (after - value))
            ? before
            : after;
    }

You can find out if the dayOfWeek in question falls within two dates like this: (this is the part that assumes a week is Sunday to Saturday)

DayOfWeek dayOne = DayOfWeek.Tuesday;
DayOfWeek dayTwo = DayOfWeek.Friday;
DateTime doesDateFallWithin = DateTime.Today;
bool fallsWithin =
   doesDateFallWithin.ClosestOccurance(dayOne) <= doesDateFallWithin
   && doesDateFallWithin <= doesDateFallWithin.ClosestOccurance(dayTwo);

My results:

dayOne = Friday, dayTwo = Tuesday

10/27/2010 (Wednesday) does not fall within the closest occurrences of Friday (10/29/2010) and Tuesday (10/26/2010)
10/28/2010 (Thursday) does not fall within the closest occurrences of Friday (10/29/2010) and Tuesday (10/26/2010)
10/29/2010 (Friday) does not fall within the closest occurrences of Friday (10/29/2010) and Tuesday (10/26/2010)
10/30/2010 (Saturday) falls within the closest occurrences of Friday (10/29/2010) and Tuesday (11/2/2010)
10/31/2010 (Sunday) falls within the closest occurrences of Friday (10/29/2010) and Tuesday (11/2/2010)
11/1/2010 (Monday) falls within the closest occurrences of Friday (10/29/2010) and Tuesday (11/2/2010)
11/2/2010 (Tuesday) does not fall within the closest occurrences of Friday (11/5/2010) and Tuesday (11/2/2010)
11/3/2010 (Wednesday) does not fall within the closest occurrences of Friday (11/5/2010) and Tuesday (11/2/2010) 

dayOne = Monday, dayTwo = Wednesday

10/27/2010 (Wednesday) falls within the closest occurrences of Monday (10/25/2010) and Wednesday (10/27/2010)
10/28/2010 (Thursday) does not fall within the closest occurrences of Monday (10/25/2010) and Wednesday (10/27/2010)
10/29/2010 (Friday) does not fall within the closest occurrences of Monday (11/1/2010) and Wednesday (10/27/2010)
10/30/2010 (Saturday) does not fall within the closest occurrences of Monday (11/1/2010) and Wednesday (10/27/2010)
10/31/2010 (Sunday) does not fall within the closest occurrences of Monday (11/1/2010) and Wednesday (11/3/2010)
11/1/2010 (Monday) falls within the closest occurrences of Monday (11/1/2010) and Wednesday (11/3/2010)
11/2/2010 (Tuesday) falls within the closest occurrences of Monday (11/1/2010) and Wednesday (11/3/2010)
11/3/2010 (Wednesday) falls within the closest occurrences of Monday (11/1/2010) and Wednesday (11/3/2010) 
Brad
  • 15,361
  • 6
  • 36
  • 57
  • I wish I could give you another vote. Seems everyone else (except Øyvind Bråthen) had stupid for breakfast :) – leppie Oct 27 '10 at 12:51
  • @leppie, looks like I scared off all the others! @OP, I'm working on a real solution for you... – Brad Oct 27 '10 at 12:58
  • apologies - today I learnt not to post answers on stackoverflow with a raging hangover – Adam Ralph Oct 27 '10 at 13:00
  • @Brad: have a look at my suggestion below. I believe it doesn't require that a week is Saturday - Sunday, but a pair of extra eyes on it wouldn't hurt :) – Julian Oct 27 '10 at 13:51
  • 3
    IMHO, your overcomplicating the problem here. – Powerlord Oct 27 '10 at 13:58
  • @R. Bemrose, you are correct. I've tested your solution and it works beautifully, but is culture ignorant. – Brad Oct 27 '10 at 14:02
  • @Bemrose, rescinding my previous comment about your code not being considering culture. It's very elegant! – Brad Oct 27 '10 at 14:33
6

@Brad's point that any day of the week falls between any two days of the week is valid. However, we are assuming the two days of the week in question are ordered.

That is, when we say, "is October 30, 2010 (a Saturday) between Friday and Sunday?", we are really asking, "is October 30, 2010 either a Friday, a Saturday, or a Sunday?".

This observation allows us to break down the problem into two components and solve the full problem easily:

1) Determine if a particular day of the week is one of a particular set of days of the week (this is trivial).

2) Determine the set of days of the week that take you from one day to another. That is, we want a function that returns "Friday, Saturday, Sunday" when given "Friday" and "Sunday", and that returns "Monday, Tuesday, Wednesday, Thursday, Friday" when given "Monday" and "Friday". This is the tricky part of the problem.

To solve the second problem, we basically walk from the first day to the second day, returning all days in-between. To do this correctly, we have to account for the fact that the second day may be less than the first day (in the representational sense of Sunday = 0 being less than Friday = 5). So, we perform the "walk" in an integer space, and add 7 to the second day if it is less than the first day. We convert to days-of-the-week space (which is the integers modulo 7) on the "way out".

Below is the code and a series of tests that solve this. The "GetDaysBetweenInclusive" method solves problem #2, and "IsDayOfWeekBetween" adds the solution to problem #1 and solves the OP's problem.

Enjoy.

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace DayOfWeekUtilities
{
    public static class DayOfWeekHelpers
    {
        /// <summary>
        /// returns all days of the week, inclusive, from day1 to day2
        /// </summary>
        public static IEnumerable<DayOfWeek> GetDaysBetweenInclusive(DayOfWeek day1,
                                                                     DayOfWeek day2)
        {
            var final = (int)day2;
            if(day2 < day1)
            {
                final += 7;
            }
            var curr = (int)day1;
            do
            {
                yield return (DayOfWeek) (curr%7);
                curr++;
            } while (curr <= final);
        }

        /// <summary>
        /// returns true if the provided date falls on a day of the 
        /// week between day1 and day2, inclusive
        /// </summary>
        public static bool IsDayOfWeekBetween(this DateTime date,
                                              DayOfWeek day1,
                                              DayOfWeek day2)
        {
            return GetDaysBetweenInclusive(day1, day2).Contains(date.DayOfWeek);
        }
    }

    [TestFixture]
    public class Tests
    {
        [Test]
        public void Test()
        {
            Assert.IsTrue(new DateTime(2010, 10, 22).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Sunday));
            Assert.IsTrue(new DateTime(2010, 10, 23).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Sunday));
            Assert.IsTrue(new DateTime(2010, 10, 24).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Sunday));
            Assert.IsFalse(new DateTime(2010, 10, 25).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Sunday));
            Assert.IsFalse(new DateTime(2010, 10, 26).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Sunday));
            Assert.IsFalse(new DateTime(2010, 10, 27).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Sunday));
            Assert.IsFalse(new DateTime(2010, 10, 28).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Sunday));
            Assert.IsTrue(new DateTime(2010, 10, 29).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Sunday));

            Assert.IsTrue(new DateTime(2010, 10, 22).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Friday));
            Assert.IsFalse(new DateTime(2010, 10, 23).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Friday));
            Assert.IsFalse(new DateTime(2010, 10, 24).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Friday));
            Assert.IsFalse(new DateTime(2010, 10, 25).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Friday));
            Assert.IsFalse(new DateTime(2010, 10, 26).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Friday));
            Assert.IsFalse(new DateTime(2010, 10, 27).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Friday));
            Assert.IsFalse(new DateTime(2010, 10, 28).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Friday));
            Assert.IsTrue(new DateTime(2010, 10, 29).IsDayOfWeekBetween(DayOfWeek.Friday, DayOfWeek.Friday));

            Assert.IsTrue(new DateTime(2010, 10, 22).IsDayOfWeekBetween(DayOfWeek.Monday, DayOfWeek.Friday));
            Assert.IsFalse(new DateTime(2010, 10, 23).IsDayOfWeekBetween(DayOfWeek.Monday, DayOfWeek.Friday));
            Assert.IsFalse(new DateTime(2010, 10, 24).IsDayOfWeekBetween(DayOfWeek.Monday, DayOfWeek.Friday));
            Assert.IsTrue(new DateTime(2010, 10, 25).IsDayOfWeekBetween(DayOfWeek.Monday, DayOfWeek.Friday));
            Assert.IsTrue(new DateTime(2010, 10, 26).IsDayOfWeekBetween(DayOfWeek.Monday, DayOfWeek.Friday));
            Assert.IsTrue(new DateTime(2010, 10, 27).IsDayOfWeekBetween(DayOfWeek.Monday, DayOfWeek.Friday));
            Assert.IsTrue(new DateTime(2010, 10, 28).IsDayOfWeekBetween(DayOfWeek.Monday, DayOfWeek.Friday));
            Assert.IsTrue(new DateTime(2010, 10, 29).IsDayOfWeekBetween(DayOfWeek.Monday, DayOfWeek.Friday));

            Assert.IsTrue(new DateTime(2010, 10, 22).IsDayOfWeekBetween(DayOfWeek.Thursday, DayOfWeek.Tuesday));
            Assert.IsTrue(new DateTime(2010, 10, 23).IsDayOfWeekBetween(DayOfWeek.Thursday, DayOfWeek.Tuesday));
            Assert.IsTrue(new DateTime(2010, 10, 24).IsDayOfWeekBetween(DayOfWeek.Thursday, DayOfWeek.Tuesday));
            Assert.IsTrue(new DateTime(2010, 10, 25).IsDayOfWeekBetween(DayOfWeek.Thursday, DayOfWeek.Tuesday));
            Assert.IsTrue(new DateTime(2010, 10, 26).IsDayOfWeekBetween(DayOfWeek.Thursday, DayOfWeek.Tuesday));
            Assert.IsFalse(new DateTime(2010, 10, 27).IsDayOfWeekBetween(DayOfWeek.Thursday, DayOfWeek.Tuesday));
            Assert.IsTrue(new DateTime(2010, 10, 28).IsDayOfWeekBetween(DayOfWeek.Thursday, DayOfWeek.Tuesday));
            Assert.IsTrue(new DateTime(2010, 10, 29).IsDayOfWeekBetween(DayOfWeek.Thursday, DayOfWeek.Tuesday));
        }
    }
}
Stuart Lange
  • 4,049
  • 6
  • 24
  • 30
  • +1 for a solution that doesn't only solve the problem completely, but does it in a very elegant way. It might not be the cleanest solution, but it is definitely the one which is easiest to understand. Nice way of analysing the problem Stuart :) – Julian Oct 27 '10 at 18:34
0

Thomas Levesque's answer is good here. Remember that DayOfWeek.Sunday is 0 and not 6 inside this enum.

This is problematic for me since in Norway Monday is the first day of the week, and not Sunday.

In that case, you should consider checking if the enum equals DayOfWeek.Sunday, and if it is to add 7 to the value before you do the comparison to make sure that Sunday is considered in the correct way.

If you have Sunday as the first day of week where you live, it's not a problem anyway ;)

Øyvind Bråthen
  • 59,338
  • 27
  • 124
  • 151
  • No, it's not good! @Thomas' algorithm will NEVER equate to true in the case presented by the OP. – Brad Oct 27 '10 at 12:53
  • Yes, that is why I said that you have to modify DayOfWeek.Sunday before the comparion takes place, because then it WILL be true for the cases that OP wants. Also, some countries count Sunday as the first day of week, so in that case, yes, Tuesday is between Sunday and Wednesday (think about it)... ;) – Øyvind Bråthen Oct 27 '10 at 13:40
  • The issue is not whether `Sunday` is the first day of the week or not, the problem is that if you pick a second day that occurs **earlier** in the week than the first day, you will NEVER get a **positive** result. To give another example, using `DayOfWeek` enum math, `Tuesday` (2) will *never* be between `Monday` (1) and `Sunday` (0) – Brad Oct 27 '10 at 13:44
  • If you do the proper conversion first, Sunday will not be 0, it will be 7, so it will check if Tuesday(2) is between Monday(1) and Sunday(7) which it is. – Øyvind Bråthen Oct 27 '10 at 14:41
0

I think this should work. It should also work regardless of what you consider to be the first day of the week:

private bool InBetweenDaysInclusive(DateTime dateToCheck, DayOfWeek lowerLimit, DayOfWeek upperLimit)
{
    CultureInfo ci = CultureInfo.CurrentCulture;

    int diffDateToCheckFirstDayOfWeek = dateToCheck.DayOfWeek - ci.DateTimeFormat.FirstDayOfWeek;
    if (diffDateToCheckFirstDayOfWeek < 0)
        diffDateToCheckFirstDayOfWeek += 7;

    int diffLowerLimitFirstDayOfWeek = lowerLimit - ci.DateTimeFormat.FirstDayOfWeek;
    if (diffLowerLimitFirstDayOfWeek < 0)
        diffLowerLimitFirstDayOfWeek += 7;

    int diffUpperLimitFirstDayOfWeek = upperLimit - ci.DateTimeFormat.FirstDayOfWeek;
    if (diffUpperLimitFirstDayOfWeek < 0)
        diffUpperLimitFirstDayOfWeek += 7;

    if (diffUpperLimitFirstDayOfWeek < diffLowerLimitFirstDayOfWeek)
        throw new Exception("The lower-limit day must be earlier in the week than the upper-limit day");

    return diffDateToCheckFirstDayOfWeek >= diffLowerLimitFirstDayOfWeek && diffDateToCheckFirstDayOfWeek <= diffUpperLimitFirstDayOfWeek;
}

Edit:
In case you wonder, the if < 0 and += 7 is to get around that subtracting two days isn't culture aware. The code could probably be made a bit cleaner, but you get the point I think.

Julian
  • 20,008
  • 17
  • 77
  • 108
  • based on the OP's examples, they want to be able to pass in a `upperLimit` that occurs "earlier" in the week than the `lowerLimit`. Can you handle this case? – Brad Oct 27 '10 at 13:54
  • @Brad: where in the OP does it say that the `upperLimit` should be able to occur "earlier" than the `lowerLimit`? And even if that's the case, my code would easily handle that as well, by adding a bit more logic instead of throwing an exception. My gives the desired result for all examples given in the OP at least. – Julian Oct 27 '10 at 13:57
  • The OP uses the days `Friday` and `Sunday`, respectively. Your code would throw an error. – Brad Oct 27 '10 at 14:01
  • 1
    @Brad: and what makes you believe that `Sunday` is the first day of the week in the OP's culture, and not `Monday`? – Julian Oct 27 '10 at 14:05
0

Stuart Lauge say: ...perform the "walk" in an integer space. I understand:

private bool InBetweenDaysInclusive(DateTime date, DayOfWeek day1, DayOfWeek day2)
{
    int d1 = (int)day1;
    int d2 = (int)day2;
    int dx = (int)date.DayOfWeek;
    if (d2 < d1) d2 += 7;
    if (dx < d1) dx += 7;
    return (dx >= d1 && dx <= d2);
}
juanma
  • 9
  • 2