13
\$\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\$
  • 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\$ – Clockwork-Muse Oct 18 '11 at 18:14
7
\$\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\$
  • 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 '11 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 '11 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 '11 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 '14 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 '15 at 16:16
2
\$\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\$
  • 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\$ – Konrad Morawski Oct 26 '15 at 13:30
  • \$\begingroup\$ What is day in new DateTime(reference.Year, reference.Month, day)? \$\endgroup\$ – 200_success Oct 26 '15 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 '15 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 '15 at 15:34
  • \$\begingroup\$ Amazing solution. Just what I needed! to run a script in batches... one week at a time. :-) \$\endgroup\$ – Leniel Maccaferri May 5 '18 at 18:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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