Kevin
Kevin

Reputation: 7309

How do I determine if a given date is the Nth weekday of the month?

Here is what I am trying to do: Given a date, a day of the week, and an integer n, determine whether the date is the nth day of the month.

For example:

How can I improve this implementation?

private bool NthDayOfMonth(DateTime date, DayOfWeek dow, int n)
{
    int d = date.Day;
    return date.DayOfWeek == dow && (d/ 7 == n || (d/ 7 == (n - 1) && d % 7 > 0));
}

Upvotes: 18

Views: 15332

Answers (9)

Darrel K.
Darrel K.

Reputation: 1729

Here is a simple DateTime extension method to get the nth occurrence in a month.

/// <summary>
/// Gets the Nth occurrence of the specified weekday in the month.
/// </summary>
public static DateTime GetNthOfWeekDayInMonth(this DateTime dt, DayOfWeek dayOfWeek, int n)
{
  //Get the first day of the month
  DateTime fd = dt.AddDays(-dt.Day).AddDays(1);
  // Get the FIRST occurrence of the specified weekday in the month.
  fd = dayOfWeek >= fd.DayOfWeek ? fd.AddDays(dayOfWeek - fd.DayOfWeek) : fd.AddDays((7 + ((int)dayOfWeek)) - ((int)fd.DayOfWeek));
  // Get the nth occurrence by adding the weeks
  fd = fd.AddDays((n-1) * 7);
  //Throw exception if you do not want to go past the specified month?
  if (fd.Month != dt.Month) throw new Exception($"There is no {n} week in this month");
  return fd;
}

Sample:

new DateTime(2022, 05, 12).GetNthOfWeekDayInMonth(DayOfWeek.Tuesday, 2);
// Output: [2022/05/10 00:00:00]

Upvotes: 0

In case you want a list of dates for a span of time (not just one) for the Nth DayOfWeek of a Month, you can use this:

internal static List<DateTime> GetDatesForNthDOWOfMonth(int weekNum, DayOfWeek DOW, DateTime beginDate, DateTime endDate)
{
    List<DateTime> datesForNthDOWOfMonth = new List<DateTime>();
    int earliestDayOfMonth = 1;
    int latestDayOfMonth = 7;
    DateTime currentDate = beginDate;

    switch (weekNum)
    {
        case 1:
            earliestDayOfMonth = 1;
            latestDayOfMonth = 7;
            break;
        case 2:
            earliestDayOfMonth = 8;
            latestDayOfMonth = 14;
            break;
        case 3:
            earliestDayOfMonth = 15;
            latestDayOfMonth = 21;
            break;
        case 4:
            earliestDayOfMonth = 22;
            latestDayOfMonth = 28;
            break;
    }

    while (currentDate < endDate)
    {
        DateTime dateToInc = currentDate;
        DateTime endOfMonth = new DateTime(dateToInc.Year, dateToInc.Month, DateTime.DaysInMonth(dateToInc.Year, dateToInc.Month));
        bool dateFound = false;
        while (!dateFound)
        {
            dateFound = dateToInc.DayOfWeek.Equals(DOW);
            if (dateFound)
            {
                if ((dateToInc.Day >= earliestDayOfMonth) && 
                    (dateToInc.Day <= latestDayOfMonth))
                {
                    datesForNthDOWOfMonth.Add(dateToInc);
                }
            }
            if (dateToInc.Date.Equals(endOfMonth.Date)) continue;
            dateToInc = dateToInc.AddDays(1);
        }
        currentDate = new DateTime(currentDate.Year, currentDate.Month, 1);
        currentDate = currentDate.AddMonths(1);
    }
    return datesForNthDOWOfMonth;
}

...and call it this way:

// This is to get the 1st Monday in each month from today through one year from today
DateTime beg = DateTime.Now;
DateTime end = DateTime.Now.AddYears(1);
List<DateTime> dates = GetDatesForNthDOWOfMonth(1, DayOfWeek.Monday, beg, end);
// To see the list of dateTimes, for verification
foreach (DateTime d in dates)
{
    MessageBox.Show(string.Format("Found {0}", d.ToString()));
}

You could get the 2nd Friday of each month like so:

List<DateTime> dates = GetDatesForNthDOWOfMonth(2, DayOfWeek.Friday, beg, end);

...etc.

Upvotes: 0

George Stocker
George Stocker

Reputation: 57877

The answer is from this website. Copy/pasted here in case that site is ever lost.

public static DateTime FindTheNthSpecificWeekday(int year, int month,int nth, System.DayOfWeek day_of_the_week)
{
    // validate month value
    if(month < 1 || month > 12)
    {
        throw new ArgumentOutOfRangeException("Invalid month value.");
    }

    // validate the nth value
    if(nth < 0 || nth > 5)
    {
        throw new ArgumentOutOfRangeException("Invalid nth value.");
    }

    // start from the first day of the month
    DateTime dt = new DateTime(year, month, 1);

    // loop until we find our first match day of the week
    while(dt.DayOfWeek != day_of_the_week)
    {
        dt = dt.AddDays(1);
    }

    if(dt.Month != month)
    {
        // we skip to the next month, we throw an exception
        throw new ArgumentOutOfRangeException(string.Format("The given month has less than {0} {1}s", nth, day_of_the_week));
    }

    // Complete the gap to the nth week
    dt = dt.AddDays((nth - 1) * 7);

    return dt;
}

Upvotes: 12

reexmonkey
reexmonkey

Reputation: 2475

Most of the answers above are partially accurate or unnecessarily complex. You could try this simpler function, which also checks if the given date is the last but Nth day of the month.

    public static bool IsNthDayofMonth(this DateTime date, DayOfWeek weekday, int N)
    {
        if (N > 0)
        {
            var first = new DateTime(date.Year, date.Month, 1);
            return (date.Day - first.Day)/ 7 == N - 1 && date.DayOfWeek == weekday;
        }
        else
        {

            var last = new DateTime(date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
            return (last.Day - date.Day) / 7 == (Math.Abs(N) - 1) && date.DayOfWeek == weekday;
        }

Upvotes: 0

Dex
Dex

Reputation:

In this answer, the following code needs to be flipped:

// Complete the gap to the nth week
dt = dt.AddDays((nth - 1) * 7);

if(dt.Month != month)
{
// we skip to the next month, we throw an exception
throw new ArgumentOutOfRangeException(”The given month has less than ” nth.ToString() ” ”
day_of_the_week.ToString() “s”);
}

Upvotes: 0

Robert Wagner
Robert Wagner

Reputation: 17793

You could change the check of the week so the function would read:

private bool NthDayOfMonth(DateTime date, DayOfWeek dow, int n){
  int d = date.Day;
  return date.DayOfWeek == dow && (d-1)/7 == (n-1);
}

Other than that, it looks pretty good and efficient.

Upvotes: 13

Chirag Darji
Chirag Darji

Reputation: 11

You can find a function which returns a date for the nth occurrence of particular week day in any month.

See http://chiragrdarji.wordpress.com/2010/08/23/find-second-saturday-and-fourth-saturday-of-month/

Upvotes: 1

waynecolvin
waynecolvin

Reputation: 72

It looks like the language supplies date/day methods for a given date. If anybody was interested you can read about Zeller's congruence.

I don't think that's what they wanted you to do but you could find the day of week of the first day of a month from that. Now that I thought about it you could find the day of week for the given day as N and get that modulo 7.

Oh wait, is that the Nth occurance of a day of the week (like Sunday) or like the Nth weekday of the month! Okay I see the examples.

Maybe it would make a difference if you could construct a date such as the 1st of a month..

Given that it is Nth occurance of a day of the week, and that you can't fiddle with whatever datetime datatype, and that you have access to both a get day of week and get day of month functions. Would Sunday be a zero?

1) First, the day of the week would have to match the day of the week given.
2) N would have to be at least 1 and at most 4.
3) The day of the month would range between n*7*dayOfWeek + 1 and n*7*dayOfWeek + 6 for the same n.
- Let me think about that. If Sunday was the first.. 0*7*0+1 = 1 and Saturday the 6th would be 0*7*0+6.

Think 1 and 3 above are sufficient since a get day of month function shouldn't violate 2.

(* first try, this code sucks *)

function isNthGivenDayInMonth(date : dateTime;
                              dow : dayOfWeek;
                              N : integer) : boolean;
    var B, A : integer (* on or before and after day of month *)
    var Day : integer (* day of month *)
    begin
    B := (N-1)*7 + 1; A := (N-1)*7 + 6;
    D := getDayOfMonth(date);
    if (dow <> getDayOfWeek(date) 
        then return(false)
        else return( (B <= Day) and (A >= Day) );
    end; (* function *)

Hope there isn't a bug in that lol!
[edit: Saturday would have been the 7th, and the upper bound above (N-1)*7 + 7.]
Your solution looks like it would match 2 different weeks? Looks like it would always return zero for Sundays? Should have done pseudocode in C#.. short circuit && is like my if.. hey shouldn't Sunday the first match for N = 1 in months that start on Sunday?

 d/ 7 == n

That would result in (either 0 or 1)/7 == 1, that can't be right! Your || catches the (n-1) also, Robert has that. Go with Robert Wagner's answer! It's only 2 lines, short is good! Having (Day-1) mod 7
[edit: (Day-1) div 7] eliminates my unnecessary variables and 2 lines of setup.

For the record this should be checked for boundary cases and so forth like what if August 31st was a Sunday or Saturday.
[edit: Should have checked the end of week case too. Sorry!]

Upvotes: 1

Andrew Bullock
Andrew Bullock

Reputation: 37378

Here is what the MSDN have to say. Its VB, but it translates easily.

Upvotes: 1

Related Questions