14
\$\begingroup\$

I need to take a month (defined as a start and end date) and return a set of date ranges for each week in that month. A week is defined as Sunday through Saturday. A good way to visualize it is if you double click on your Windows date in the start bar:

enter image description here

The month of October 2011 has 6 weeks:

10/1-10/1

10/2-10/8

10/9-10/15

10/16-10/22

10/23-10/29

10/30-10/31

I can describe each week as a struct:

   struct Range
   {
      public DateTime Start;
      public DateTime End;

      public Range(DateTime start, DateTime end)
      {
         Start = start;
         End = end;
      }
   }

I need to write a function that takes a month and returns an array of ranges within it. Here's my first attempt, which appears to work and addresses the obvious edge cases:

public static IEnumerable<Range> GetRange(DateTime start, DateTime end)
{
   DateTime curStart = start;
   DateTime curPtr = start;
   do
   {
      if (curPtr.DayOfWeek == DayOfWeek.Saturday)
      {
         yield return new Range(curStart, curPtr);
         curStart = curPtr.AddDays(1);
      }

      curPtr = curPtr.AddDays(1);
   } while (curPtr <= end);

   if(curStart <= end)
      yield return new Range(curStart, end);
}

I would like to know if there's a cleaner or more obvious approach to do the same. I'm not overly concerned about performance, but I'd like to improve code readability and make the algorithm a bit more concise. Perhaps there's a very creative solution involving a single LINQ expression or something.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ What happens when somebody flips the start and the end values? And what do you want when someone inputs dates over more than one month (dates in February and April, for example)? Different years? How do you determine the month the week belongs to - is it the month the starting Saturday is in, or must there be some minimum count in a given month? The final if clause isn't necessary if you add an or condition to the if clause in the loop. curStart isn't necessary if you put curPtr.AddDays(1) inside the Range creation. Your method name should be plural. \$\endgroup\$ Oct 18, 2011 at 18:14

2 Answers 2

8
\$\begingroup\$

Well, first of all your range only has to store the start date, as the weeks are always the same length:

struct Range {

  public DateTime Start { get; private set; }

  public DateTime End { get { return Start.AddDays(6); } }

  public Range(DateTime start) {
     Start = start;
  }

}

Getting the weeks can simply be done by looking for sundays starting six days into the previous month:

public static IEnumerable<Range> GetRange(int year, int month) {
  DateTime start = new DateTime(year, month, 1).AddDays(-6);
  DateTime end = new DateTime(year, month, 1).AddMonths(1).AddDays(-1);
  for (DateTime date = start; date <= end; date = date.AddDays(1)) {
    if (date.DayOfWeek == DayOfWeek.Sunday) {
      yield return new Range(date);
    }
  }
}

To clarify: This returns the whole weeks that have days in the month, not partial weeks created from the days in the month by grouping them by what week they belong to.

\$\endgroup\$
5
  • 2
    \$\begingroup\$ Actually, I want my +1 back. :p range only has to store the start date, as the weeks are always the same length: in OP's example the first week has a single day; for a month to have all its weeks with the same length the 1st must be on the start of week and it must have days multiple of 7 (7,14,21,28,35), which means 1/7 of 3/4 of the Februaries, which means a 3/(4*12*7) = 3/336 = ~0.9% chance. \$\endgroup\$
    – ANeves
    Oct 19, 2011 at 7:13
  • \$\begingroup\$ @ANeves: Well, the confusion is due to the contradictions in the question. The OP starts out by specifically defining a week as seven days, then goes on to give an example will "weeks" of varying length. This code returns the actual weeks. \$\endgroup\$
    – Guffa
    Oct 19, 2011 at 7:23
  • \$\begingroup\$ I feel the OP's intention was to not have weeks spill out of the month (or whatever the start/end range is defined as). But I can't +0 unless you edit; and if you do edit, the answer will become proper and deserve the +1 anyway; so, I guess it does not matter too much. \$\endgroup\$
    – ANeves
    Oct 19, 2011 at 7:34
  • 2
    \$\begingroup\$ Why the downvote? If you don't explain what it is that you think is wrong, it can't improve the answer. \$\endgroup\$
    – Guffa
    Dec 16, 2014 at 9:10
  • \$\begingroup\$ Why the downvote? If you don't explain what it is that you think is wrong, it can't improve the answer. \$\endgroup\$
    – Guffa
    Oct 26, 2015 at 16:16
3
\$\begingroup\$

Updated

You can generate the dates for the month and use Linq to group them by the week of the year. I think this should give you a closer more elegant result.

    DateTime reference = DateTime.Now;
    Calendar calendar = CultureInfo.CurrentCulture.Calendar;

    IEnumerable<int> daysInMonth = Enumerable.Range(1, calendar.GetDaysInMonth(reference.Year, reference.Month));

    List<Tuple<DateTime, DateTime>> weeks = daysInMonth.Select(day => new DateTime(reference.Year, reference.Month, day))
        .GroupBy(d => calendar.GetWeekOfYear(d, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Sunday))
        .Select(g => new Tuple<DateTime, DateTime>(g.First(), g.Last()))
        .ToList();

    weeks.ForEach(x => Console.WriteLine("{0:MM/dd/yyyy} - {1:MM/dd/yyyy}", x.Item1, x.Item2));

If you pick any date from October 2015 as a reference, this is the result:

10/01/2015 - 10/03/2015
10/04/2015 - 10/10/2015
10/11/2015 - 10/17/2015
10/18/2015 - 10/24/2015
10/25/2015 - 10/31/2015

... you can play with it in the dotnetfiddle I created: .Net Fiddle - Week Ranges

\$\endgroup\$
5
  • 3
    \$\begingroup\$ What's with the underscore? Why is reference named normally but then, all of a sudden, _cal. I know it's nitpicking, but hey that's CodeReview ;) \$\endgroup\$ Oct 26, 2015 at 13:30
  • \$\begingroup\$ What is day in new DateTime(reference.Year, reference.Month, day)? \$\endgroup\$ Oct 26, 2015 at 14:32
  • 1
    \$\begingroup\$ Hi @KonradMorawski... thanks. You are right, I didn't clean the code enough. I just took it from a project I was working on. On that project _cal is a class variable, hence the underscore. \$\endgroup\$
    – jfeston
    Oct 26, 2015 at 15:31
  • 1
    \$\begingroup\$ Hi @200_success, in that line day is the control variable in the lambda expression. day => new DateTime(reference.Year, reference.Month, day \$\endgroup\$
    – jfeston
    Oct 26, 2015 at 15:34
  • \$\begingroup\$ Amazing solution. Just what I needed! to run a script in batches... one week at a time. :-) \$\endgroup\$ May 5, 2018 at 18:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.