views:

2980

answers:

11

Given a date range, I need to know how many Mondays (or Tuesdays, Wednesdays, etc) are in that range.

I am currently working in C#.

+3  A: 

Which language would you like to see an implementation in?

You might find the Calculating the day of the week Wikipedia page helpful.

Gabe Hollombe
+3  A: 

Any particular language and therefore date format?

If dates are represented as a count of days, then the difference between two values plus one (day), and divide by 7, is most of the answer. If both end dates are the day in question, add one.

Edited: corrected 'modulo 7' to 'divide by 7' - thanks. And that is integer division.

Jonathan Leffler
his tags include C#
Simucal
Unless I misunderstand the algorithm you are proposing, the maximum result for any date range would be 6 since that is the max result for Modulo 7 of any number. What am I missing?
EBGreen
Yeah, it should be divided by 7, not mod 7
Moe
That makes more sense.
EBGreen
Mea culpa - I meant (integer) division!
Jonathan Leffler
@Simucal: When originally asked, there were just 3 tags - not C# too.
Jonathan Leffler
+1  A: 

Add the smallest possible number to make the first day a Monday. Subtract the smallest possible number to make the last day a Monday. Calculate the difference in days and divide by 7.

Mark Ransom
+4  A: 

Since you're using C#, if you're using C#3.0, you can use LINQ.

Assuming you have an Array/List/IQueryable etc that contains your dates as DateTime types:

DateTime[] dates = { new DateTime(2008,10,6), new DateTime(2008,10,7)}; //etc....

var mondays = dates.Where(d => d.DayOfWeek == DayOfWeek.Monday); // = {10/6/2008}

Added:

Not sure if you meant grouping them and counting them, but here's how to do that in LINQ as well:

var datesgrouped = from d in dates
                   group d by d.DayOfWeek into grouped
                   select new { WeekDay = grouped.Key, Days = grouped };

foreach (var g in datesgrouped)
{
    Console.Write (String.Format("{0} : {1}", g.WeekDay,g.Days.Count());
}
Codewerks
Why is this being voted down?
Codewerks
Don't know; but I voted it up one to compensate. I like your LINQ samples.
Cyberherbalist
Thanks @Cyberherbalist...:)
Codewerks
Agreed, the samples are great.
GnomeCubed
+1 Thats a very sexy way of getting days within a range!
Jim McKerchar
+4  A: 

Here's some pseudocode:

DifferenceInDays(Start, End) / 7   // Integer division discarding remainder
+ 1 if DayOfWeek(Start) <= DayImLookingFor
+ 1 if DayOfWeek(End)   >= DayImLookingFor
- 1

Where DifferenceInDays returns End - Start in days, and DayOfWeek returns the day of the week as an integer. It doesn't really matter what mapping DayOfWeek uses, as long as it is increasing and matches up with DayImLookingFor.

Note that this algorithm assumes the date range is inclusive. If End should not be part of the range, you'll have to adjust the algorithm slightly.

Translating to C# is left as an exercise for the reader.

cjm
+5  A: 

It's fun to look at different algorithm's for calculating day of week, and @Gabe Hollombe's pointing to WP on the subject was a great idea (and I remember implementing Zeller's Congruence in COBOL about twenty years ago), but it was rather along the line of handing someone a blueprint of a clock when all they asked what time it was.

In C#:

    private int CountMondays(DateTime startDate, DateTime endDate)
    {
        int mondayCount = 0;

        for (DateTime dt = startDate; dt < endDate; dt = dt.AddDays(1.0))
        {
            if (dt.DayOfWeek == DayOfWeek.Monday)
            {
                mondayCount++;
            }
        }

        return mondayCount;
    }

This of course does not evaluate the end date for "Mondayness", so if this was desired, make the for loop evaluate

dt < endDate.AddDays(1.0)
Cyberherbalist
I really like how you use a DateTime for the iterator.
Sandor Davidhazi
+1  A: 

Convert the dates to Julian Day Number, then do a little bit of math. Since Mondays are zero mod 7, you could do the calculation like this:

JD1=JulianDayOf(the_first_date)
JD2=JulianDayOf(the_second_date)
Round JD1 up to nearest multiple of 7
Round JD2 up to nearest multiple of 7
d = JD2-JD1
nMondays = (JD2-JD1+7)/7    # integer divide
DarenW
A: 

I had a similar problem for a report. I needed the number of workdays between two dates. I could have cycled through the dates and counted but my discrete math training wouldn't let me. Here is a function I wrote in VBA to get the number of workdays between two dates. I'm sure .net has a similar WeekDay function.

   1  
   2  ' WorkDays
   3  ' returns the number of working days between two dates
   4  Public Function WorkDays(ByVal dtBegin As Date, ByVal dtEnd As Date) As Long
   5  
   6     Dim dtFirstSunday As Date
   7     Dim dtLastSaturday As Date
   8     Dim lngWorkDays As Long
   9  
  10     ' get first sunday in range
  11     dtFirstSunday = dtBegin + ((8 - Weekday(dtBegin)) Mod 7)
  12  
  13     ' get last saturday in range
  14     dtLastSaturday = dtEnd - (Weekday(dtEnd) Mod 7)
  15  
  16     ' get work days between first sunday and last saturday
  17     lngWorkDays = (((dtLastSaturday - dtFirstSunday) + 1) / 7) * 5
  18  
  19     ' if first sunday is not begin date
  20     If dtFirstSunday <> dtBegin Then
  21  
  22        ' assume first sunday is after begin date
  23        ' add workdays from begin date to first sunday
  24        lngWorkDays = lngWorkDays + (7 - Weekday(dtBegin))
  25  
  26     End If
  27  
  28     ' if last saturday is not end date
  29     If dtLastSaturday <> dtEnd Then
  30  
  31        ' assume last saturday is before end date
  32        ' add workdays from last saturday to end date
  33        lngWorkDays = lngWorkDays + (Weekday(dtEnd) - 1)
  34  
  35     End If
  36  
  37     ' return working days
  38     WorkDays = lngWorkDays
  39  
  40  End Function
Will Rickards
WeekDay is property on the DateTime type in .NET, so aren't WorkDays just dates where the Weekday is in 2,3,4,5 (assuming 1=Sunday)?
Codewerks
+4  A: 

Try this:

    static int CountDays(DayOfWeek day, DateTime start, DateTime end)
    {
        TimeSpan ts = end - start;                       // Total duration
        int count = (int)Math.Floor(ts.TotalDays / 7);   // Number of whole weeks
        int remainder = (int)(ts.TotalDays % 7);         // Number of remaining days
        int sinceLastDay = (int)(end.DayOfWeek - day);   // Number of days since last [day]
        if (sinceLastDay < 0) sinceLastDay += 7;         // Adjust for negative days since last [day]

        // If the days in excess of an even week are greater than or equal to the number days since the last [day], then count this one, too.
        if (remainder >= sinceLastDay) count++;          

        return count;
    }
Jon B
Thanks, Jon B...this is the answer I was looking for. I was close, my missing link was what to do with the remainder.
ThePeeje
A: 
private System.Int32 CountDaysOfWeek(System.DayOfWeek dayOfWeek, System.DateTime date1, System.DateTime date2)
{
  System.DateTime EndDate;
  System.DateTime StartDate;

  if (date1 > date2)
  {
    StartDate = date2;
    EndDate = date1;
  }
  else
  {
    StartDate = date1;
    EndDate = date2;
  }

  while (StartDate.DayOfWeek != dayOfWeek)
    StartDate = StartDate.AddDays(1);

  return EndDate.Subtract(StartDate).Days / 7 + 1;
}
Paul Osterhout
A: 

I have had the same need today. I started with the cjm function since I don't understand the JonB function and since the Cyberherbalist function is not linear.

I had have to correct

DifferenceInDays(Start, End) / 7   // Integer division discarding remainder
+ 1 if DayOfWeek(Start) <= DayImLookingFor
+ 1 if DayOfWeek(End)   >= DayImLookingFor
- 1

to

DifferenceInDays(Start, End) / 7   // Integer division discarding remainder
+ 1 if DayImLookingFor is between Start.Day and End.Day

With the between function that return true if, starting from the start day, we meet first the dayImLookingFor before the endDay.

I have done the between function by computing the number of day from startDay to the other two days:

private int CountDays(DateTime start, DateTime end, DayOfWeek selectedDay)
{
 if (start.Date > end.Date)
 {
  return 0;
 }
 int totalDays = (int)end.Date.Subtract(start.Date).TotalDays;
 DayOfWeek startDay = start.DayOfWeek;
 DayOfWeek endDay = end.DayOfWeek;
 ///look if endDay appears before or after the selectedDay when we start from startDay.
 int startToEnd = (int)endDay - (int)startDay;
 if (startToEnd < 0)
 {
  startToEnd += 7;
 }
 int startToSelected = (int)selectedDay - (int)startDay;
 if (startToSelected < 0)
 {
  startToSelected += 7;
 }
 bool isSelectedBetweenStartAndEnd = startToEnd >= startToSelected;
 if (isSelectedBetweenStartAndEnd)
 {
  return totalDays / 7 + 1;
 }
 else
 {
  return totalDays / 7;
 }
}
Olivier de Rivoyre