简体   繁体   中英

How do I find the nth DayOfWeek for a given month?

I am trying to find the nth DayOfWeek for a given month (in a given year).

For example: I am looking for the 3rd Saturday of May (2019).

I failed to come up with a working solution using the DayOfWeek extension method. Do I have to loop through the entire month to find the third Saturday?

You could of course loop through the entire month but I think this is a more elegant way (taken from here ):

private static DateTime FindTheNthDayOfWeek(int year, int month, int nthOccurrence, DayOfWeek dayOfWeek)
    {
        if (month < 1 || month > 12)
        {
            throw new ArgumentOutOfRangeException("Invalid month");
        }

        if (nthOccurrence < 0 || nthOccurrence > 5)
        {
            throw new ArgumentOutOfRangeException("Invalid nth occurrence");
        }

        var dt = new DateTime(year, month, 1);

        while (dt.DayOfWeek != dayOfWeek)
        {
            dt = dt.AddDays(1);
        }

        dt = dt.AddDays((nthOccurrence - 1) * 7);

        if (dt.Month != month)
        {
            throw new ArgumentOutOfRangeException(string.Format("The given month has less than {0} {1}s", nthOccurrence, dayOfWeek));
        }

        return dt;
    }

This private method doesn't loop through the entire month but stops already once the first DayOfWeek has been found. Then you simply add a week for each nth occurrence (minus the already added week ;-) ).

If you talk about dates it should be related to some calendar, in my example Gregorian.

public static class DataTimeExt
{
    public static IEnumerable<DateTime> TakeWhileInclusive(this DateTime value,
        Func<DateTime, bool> func)
    {
        DateTime dt = value;

        yield return dt; //[first            
        while (func(dt = dt.AddDays(1))) yield return dt; //in between            
        yield return dt; //last]
    }
}

then you could just iterate through the dates until Sunday and then add 14 days.

var calendar = new GregorianCalendar();

var dates = new DateTime(2019, 5, 1, calendar)
    .TakeWhileInclusive(dt => calendar.GetDayOfWeek(dt) != DayOfWeek.Sunday);

Console.WriteLine(dates.Last().AddDays(14));

I created two extension methods, where one gets the next DayOfWeek from the date, optionally including the date itself, and the other for the previous DayOfWeek, with the same functionality.

public static DateTime Next(
  this      DateTime source,
  DayOfWeek dayOfWeek,
  bool      considerSameDate
) => ( dayOfWeek - source.DayOfWeek) is var difference
  && difference < (considerSameDate ? 0 : 1)
  ? source.AddDays(difference + 7)
  : source.AddDays(difference)
  ;

and

public static DateTime Previous(
  this DateTime source,
  DayOfWeek     dayOfWeek,
  bool          considerSameDate
) => dayOfWeek == source.DayOfWeek
     ? ( considerSameDate ? source : source.AddDays(-7) )
     : source.AddDays(
         ( dayOfWeek - source.DayOfWeek ) is var difference
         && difference > 0
         ? difference - 7
         : difference
     );

Having these, one can ask the questions you posed:

var x = new System.DateTime(2019, 5, 1).Next(System.DayOfWeek.Saturday, true).AddDays(14);

I create a new DateTime (2019-05-01), call Next with Saturday and consider 5/1 as a candidate, and then add 14 days, which makes it to the third Saturday of May, 2019.

This is simple and clean with no looping. Just a little arithmetic.

static DateTime? NthWeekDayOfMonth( int n, DayOfWeek dow, int year , int month)
{
  DateTime startOfMonth      = new DateTime( year, month, 1 ) ;
  int      offset            = ( 7 + dow - startOfMonth.DayOfWeek ) % 7 ;
  DateTime nthWeekDayOfMonth = startOfMonth
                             .AddDays( offset    )
                             .AddDays( 7 * (n-1) )
                             ;
  bool isSameMonth           =  startOfMonth.Year  == nthWeekDayOfMonth.Year
                             && startOfMonth.Month == nthWeekDayOfMonth.Month
                             ;
  return isSameMonth
    ? nthWeekDayOfMonth
    : (DateTime?) null
    ;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM