6

Given a DateTime and a DayOfWeek should return the date of the last DayOfWeek of that month.

E.g. 1st-March-2009 and Sunday would return 29th-March-2009

Ryan
  • 23,871
  • 24
  • 86
  • 132

9 Answers9

12

Can't find a handy one-liner but this one works:

static DateTime LastDayOfWeekInMonth(DateTime day, DayOfWeek dow)
{
    DateTime lastDay = new DateTime(day.Year, day.Month, 1).AddMonths(1).AddDays(-1);
    DayOfWeek lastDow = lastDay.DayOfWeek;

    int diff = dow - lastDow;

    if (diff > 0) diff -= 7;

    System.Diagnostics.Debug.Assert(diff <= 0);

    return lastDay.AddDays(diff);
}
H H
  • 263,252
  • 30
  • 330
  • 514
  • Like it, thanks Henk. Minor point - you could remove the last .AddDays(-1) and change "if (diff > 0)" to "if (diff >= 0)" - thus removing 1 datetime.addDays operation. – Ryan Mar 08 '09 at 13:11
  • 1
    Ryan, yes, it saves 1 op but it is bordering on what the compiler can optimize. And conceptually "last day of the month" is easier on the mind than "first day of next month". So I normally wouldn't optimize this. – H H Mar 08 '09 at 14:56
11

http://datetimeextensions.codeplex.com/ has a Last(dayOfWeek) extension method that will do this for you

Test included :-)

TFD
  • 23,890
  • 2
  • 34
  • 51
  • Clever implementation, and an extension would be sweet. I'd rename it to something mores specific though. It could be Date.LastDayOfWeekForMonth Date.LastDayOfWeekForYear or Date.LastDayOfWeekCentury Date.LastDayOfWeekForMillenium – Professional Sounding Name Mar 08 '09 at 12:44
  • Great library, thanks TFD! Unfortunately I can't use it as is because I am targeting .NET 2.0 so I've marked Henks answer, but you've got a lot of upvotes for this anyway. – Ryan Mar 08 '09 at 13:13
  • test failed, DateTime dt = DateTime.Today.AddMonths(1).Last(DayOfWeek.Thursday); Assert.AreEqual(new DateTime(2012, 4, 26), dt); – Vincent Mar 15 '12 at 00:00
6

Get the first day in the next month, then find the first mathing weekday in that month. Go back seven days and you are at that weekday in the last week of the corrent month:

DateTime date = new DateTime(2009, 3, 12);
DayOfWeek day = DayOfWeek.Sunday;

DateTime nextMonth = new DateTime(date.Year, date.Month, 1).AddMonths(1);
while (nextMonth.DayOfWeek != day) {
    nextMonth = nextMonth.AddDays(1);
}
DateTime lastInMonth = nextMonth.AddDays(-7);

(You could replace the loop with some arithmetic that calculates the number of days to add based on the numeric values of the DayOfWeek values, but this is more straight forward.)

Edit:
You could of course also get the last day in the current month, then loop backwards until you find the correct weekday.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
5
static DateTime LastDateOfWeekForMonth(DayOfWeek weekday, int month, int year)
{
    DateTime d = new DateTime(year, month, 1).AddMonths(1);
    while (!(d.DayOfWeek == weekday && d.Month == month))
    {
       d = d.AddDays(-1);
    }
    return d;
}
  • I am guessing the downvote is because using a loop for something like this is unlikely to be optimal - but at least you're starting from the end of the month and working backwards so max 6 iterations. – Ryan Mar 08 '09 at 12:41
  • To optimize, if dayofweek > wednesday, do it @Guffa's way, otherwise, mine :) – Professional Sounding Name Mar 08 '09 at 12:45
  • I'd pick simplicity over speed in this case. – Professional Sounding Name Mar 08 '09 at 12:46
  • Erm - yes its very readable but both yours and Guffas solution use looping whilst Henks doesn't, so its likely to be faster (though will be very minimal and you can never be sure without profiling). I've upvoted you anyways, thanks! – Ryan Mar 08 '09 at 13:15
1
    public DateTime GetLastDayOfMonth(int year, int month, DayOfWeek dayOfWeek)
    {
        var daysInMonth = DateTime.DaysInMonth(year, month);
        var lastDay = new DateTime(year, month, daysInMonth);

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

        return lastDay;
    }
Andrei Sedoi
  • 1,534
  • 1
  • 15
  • 28
1

Try this: start at the last day and count back until you hit the day you're looking for

static DateTime LastOccurenceOfDay(DateTime dt, DayOfWeek dow)
    {
        DateTime looperDate = new DateTime(dt.Year, dt.Month, 1)
                                     .AddMonths(1).AddDays(-1);
        while (looperDate.DayOfWeek!=dow)                            
            looperDate =looperDate.AddDays(-1);            
        return looperDate;

Edit: Recovered the old version Dangph is refering to in the comments.

  static DateTime LastOccurenceOfDay(DateTime dt, DayOfWeek dow)
     {
         //set start of loop
         DateTime looperDate = new DateTime(dt.Year, dt.Month, 1); 
         //initialze return value
         DateTime returnDate = dt;
         //loop until the month is over
         while (looperDate.Month == dt.Month)
         {
             //if the current DayOfWeek is the date you're looking for
             if (looperDate.DayOfWeek == dow) 
                 //remember it.
                 returnDate = looperDate;
             //increase day
             looperDate=looperDate.AddDays(1);
         }
         return returnDate;
     }
Sorskoot
  • 10,190
  • 6
  • 55
  • 98
  • That's your definition of simple? – Jan Jungnickel Mar 08 '09 at 12:20
  • I liked your first version more. This one is harder to think about. And I highly doubt that it could have any detectable performance gain unless you were doing a gazillion of these calculations. I was going to suggest in the old version that you could just add 7 days at a time to make it simpler. – dan-gph Mar 08 '09 at 12:45
1

Here is both LastDayOfMonth() and LastDayOfMonth(DayOfWeek) plus unit tests, which were inspired by the "Last Friday" implementation in How to find the 3rd Friday in a month with C#? by @Paul Fryer:

/**
    /// <summary>An extension method that returns the last day of the month.</summary>
    /// <param name="d">A date within the month to calculate.</param>
    /// <returns>The last day of the current month.</returns>
    **/
    public static DateTime LastDayOfMonth(this DateTime d)
    {
        DateTime nextMonth = new DateTime(d.Year, d.Month, 1).AddMonths(1);
        return nextMonth.AddDays(-1);
    }

    /**
    /// <summary>An extension method that returns the last <see cref="DayOfWeek"> of the month.</summary>
    /// <param name="d">A date within the month to calculate.</param>
    /// <returns>The last day of the current month.</returns>
    **/
    public static DateTime LastDayOfMonth(this DateTime d, DayOfWeek dayOfWeek)
    {
        DateTime lastDayOfMonth = d.LastDayOfMonth();
        int vector = (((int)lastDayOfMonth.DayOfWeek - (int)dayOfWeek + DaysInWeek) % DaysInWeek);
        return lastDayOfMonth.AddDays(-vector);
    }

 #region LastDayOfMonth Tests

    [TestCase("1/1/2011", "1/31/2011")]
    [TestCase("2/1/2009", "2/28/2009")] //Leap Year
    [TestCase("2/1/2008", "2/29/2008")] //Leap Year
    [TestCase("3/10/2011", "3/31/2011")]
    [TestCase("4/20/2011", "4/30/2011")]
    [TestCase("5/15/2011", "5/31/2011")]
    [TestCase("6/30/2011", "6/30/2011")]
    [TestCase("12/1/2011", "12/31/2011")]
    public void LastDayOfMonthReturnsCorrectDay(string startingDate, string expectedDate)
    {
        //Arrange
        DateTime testDate = DateTime.Parse(startingDate);
        DateTime expected = DateTime.Parse(expectedDate);

        //Act
        DateTime actual = testDate.LastDayOfMonth();

        //Assert
        Assert.That(actual, Is.EqualTo(expected));
    }

    [TestCase("1/1/2011", DayOfWeek.Monday, "1/31/2011")]
    [TestCase("2/1/2009", DayOfWeek.Saturday, "2/28/2009")]
    [TestCase("2/1/2009", DayOfWeek.Sunday, "2/22/2009")] 
    [TestCase("2/1/2008", DayOfWeek.Friday, "2/29/2008")] //Leap Year
    [TestCase("2/1/2008", DayOfWeek.Thursday, "2/28/2008")] //Leap Year
    [TestCase("3/10/2011", DayOfWeek.Wednesday, "3/30/2011")]
    [TestCase("4/20/2011", DayOfWeek.Friday, "4/29/2011")]
    [TestCase("4/20/2011", DayOfWeek.Saturday, "4/30/2011")]
    [TestCase("5/15/2011", DayOfWeek.Tuesday, "5/31/2011")]
    [TestCase("5/15/2011", DayOfWeek.Saturday, "5/28/2011")]
    [TestCase("9/30/2011", DayOfWeek.Sunday, "9/25/2011")]
    [TestCase("12/1/2011", DayOfWeek.Monday, "12/26/2011")]
    public void LastDayOfMonthReturnsCorrectDayOfWeek(string startingDate, DayOfWeek dayOfWeek, string expectedDate)
    {
        //Arrange
        DateTime testDate = DateTime.Parse(startingDate);
        DateTime expected = DateTime.Parse(expectedDate);

        //Act
        DateTime actual = testDate.LastDayOfMonth(dayOfWeek);

        //Assert
        Assert.That(actual, Is.EqualTo(expected));
    }       

    #endregion
Community
  • 1
  • 1
ScottBai
  • 1,471
  • 1
  • 14
  • 15
0
static DateTime GetDayOfWeekInMonthFromWeekIndex(this DateTime date, DayOfWeek dow, int weekIndex)
{
    if (weekIndex > 4)
        return LastDayOfWeekInMonth(date, dow);

    DateTime newDate = new DateTime(date.Year, date.Month, 1);
    DayOfWeek newDow = newDate.DayOfWeek;
    int diff = dow - newDow;
    diff += ((weekIndex - 1) * 7);
    return newDate.AddDays(diff);
}
sth
  • 222,467
  • 53
  • 283
  • 367
0

It bears mentioning:

int LastDayOfMonth = DateTime.DaysInMonth(now.year, now.month);

dwneder
  • 19
  • 3