15

For the purposes of this question, let's assume the user will be from the US and will use the standard Gregorian calendar. So, a calendar week starts on Sunday and ends on Saturday.

What I'm trying to do is determine the number of calendar weeks that exist between two dates. A perfect example of my problem exists in October 2010. Between 10/16 and 10/31 there are 4 calendar weeks.


  1. October 10 - October 16
  2. October 17 - October 23
  3. October 24 - October 30
  4. October 31 - November 6

I'd prefer to stay away from any hardcoded logic like:

if (Day == DayOfWeek.Saturday && LastDayOfMonth == 31) { ... }

Can anyone think of a logical way to do this?

UPDATE:
Thanks for all the great responses, after some consideration here is the solution I used:

//get the start and end dates of the current pay period
DateTime currentPeriodStart = SelectedPeriod.Model.PeriodStart;
DateTime currentPeriodEnd = SelectedPeriod.Model.PeriodEnd;

//get the first sunday & last saturday span that encapsulates the current pay period
DateTime firstSunday = DayExtensions.SundayBeforePeriodStart(currentPeriodStart);
DateTime lastSaturday = DayExtensions.SaturdayAfterPeriodEnd(currentPeriodEnd);

//get the number of calendar weeks in the span
int numberOfCalendarWeeks = DayExtensions.CalendarWeeks(firstSunday, lastSaturday);

And here are the methods from the helper class:

    /// <summary>
    /// Get the first Sunday before the pay period start date
    /// </summary>
    /// <param name="periodStartDate">Date of the pay period start date</param>
    /// <returns></returns>
    public static DateTime SundayBeforePeriodStart(DateTime periodStartDate)
    {
        DateTime firstDayOfWeekBeforeStartDate;

        int daysBetweenStartDateAndPreviousFirstDayOfWeek = (int)periodStartDate.DayOfWeek - (int)DayOfWeek.Sunday;

        if (daysBetweenStartDateAndPreviousFirstDayOfWeek >= 0)
        {
            firstDayOfWeekBeforeStartDate = periodStartDate.AddDays(-daysBetweenStartDateAndPreviousFirstDayOfWeek);
        }
        else
        {
            firstDayOfWeekBeforeStartDate = periodStartDate.AddDays(-(daysBetweenStartDateAndPreviousFirstDayOfWeek + 7));
        }

        return firstDayOfWeekBeforeStartDate;
    }

    /// <summary>
    /// Get the first Saturday after the period end date
    /// </summary>
    /// <param name="periodEndDate">Date of the pay period end date</param>
    /// <returns></returns>
    public static DateTime SaturdayAfterPeriodEnd(DateTime periodEndDate)
    {
        DateTime lastDayOfWeekAfterEndDate;

        int daysBetweenEndDateAndFollowingLastDayOfWeek = (int)DayOfWeek.Saturday - (int)periodEndDate.DayOfWeek;

        if (daysBetweenEndDateAndFollowingLastDayOfWeek >= 0)
        {
            lastDayOfWeekAfterEndDate = periodEndDate.AddDays(daysBetweenEndDateAndFollowingLastDayOfWeek);
        }
        else
        {
            lastDayOfWeekAfterEndDate = periodEndDate.AddDays(daysBetweenEndDateAndFollowingLastDayOfWeek + 7);
        }

        return lastDayOfWeekAfterEndDate;
    }

    /// <summary>
    /// Get the calendar weeks between 2 dates
    /// </summary>
    /// <param name="d1">First day of date span</param>
    /// <param name="d2">Last day of date span</param>
    /// <returns></returns>
    public static int CalendarWeeks(DateTime d1, DateTime d2)
    {
        return 1 + (int)((d2 - d1).TotalDays / 7);
    }

And if you're curious, this is what I end up doing with the dates:

//create an array of all the sundays in this span
DateTime[] _sundays = new DateTime[numberOfCalendarWeeks];

//put the first sunday in the period
_sundays[0] = firstSunday;

//step through each week and get each sunday until you reach the last saturday
for (int i = 1; i <= numberOfCalendarWeeks - 1; i++)
{
     DateTime d = new DateTime();
     d = firstSunday.AddDays(i * 7);
      _sundays[i] = d;
}

for (int i = 0; i <= _sundays.Length-1; i++)
{
      //bind my view model with each sunday.
}
Phil Scholtes
  • 409
  • 2
  • 9
  • 22
  • If I understand you correctly, you want the number of full weeks (which I think should be from *Monday* to Sunday) between two given dates, so from Thursday, 18 June to Wednesday, 24 June, you would not have one week (as in 7 days), but none, since you only pass Monday once, and don't reach Sunday afterwards, is that correct? – Treb Jun 17 '10 at 22:11
  • This particular application calls for Sun-Sat. Think of 2 pay periods in a month (10/1 period and the 10/16 period). A work week in this scenario is Sun-Sat. So, the 10/16 pay period runs through 4 calendar weeks. Make sense? – Phil Scholtes Jun 17 '10 at 22:20
  • Are you looking for a simple formulaic approach, or are you ok with an algorithmic approach? Also, how important would it be that the formula/algorithm take into account leap years/days, etc.? – jrista Jun 18 '10 at 04:17
  • I've given a formulaic answer - it is independent of leap years/days. You could also use an algorithm to simply count how many times you pass the "end of a week", a.k.a. Saturday. So in your example, you pass Saturday 3 times, which means your date range exists in 4 weeks. – Jeff Meatball Yang Jun 18 '10 at 04:42
  • I've provided an algorithmic choice, which should compliment Meatballs formulaic approach. It makes use of the .NET Calendar class and DateTimeFormat information from the current culture, so it should be culturally sound and resilient to oddities like leap year. – jrista Jun 18 '10 at 04:46

7 Answers7

8

Here's a general solution which I believe should work for any choice of week starting and ending days. You could simplify it for your case, but this code gives you the option of changing the week's start and end (e.g. to Monday to Sunday) if it becomes necessary. It's not uncommon in payroll applications for the definition of a calendar week to change.

        DateTime periodStart = new DateTime(2010, 10, 17);
        DateTime periodEnd = new DateTime(2010, 11, 14);

        const DayOfWeek FIRST_DAY_OF_WEEK = DayOfWeek.Monday;
        const DayOfWeek LAST_DAY_OF_WEEK = DayOfWeek.Sunday;
        const int DAYS_IN_WEEK = 7;

        DateTime firstDayOfWeekBeforeStartDate;
        int daysBetweenStartDateAndPreviousFirstDayOfWeek = (int)periodStart.DayOfWeek - (int)FIRST_DAY_OF_WEEK;
        if (daysBetweenStartDateAndPreviousFirstDayOfWeek >= 0)
        {
            firstDayOfWeekBeforeStartDate = periodStart.AddDays(-daysBetweenStartDateAndPreviousFirstDayOfWeek);
        }
        else
        {
            firstDayOfWeekBeforeStartDate = periodStart.AddDays(-(daysBetweenStartDateAndPreviousFirstDayOfWeek + DAYS_IN_WEEK));
        }

        DateTime lastDayOfWeekAfterEndDate;
        int daysBetweenEndDateAndFollowingLastDayOfWeek = (int)LAST_DAY_OF_WEEK - (int)periodEnd.DayOfWeek;
        if (daysBetweenEndDateAndFollowingLastDayOfWeek >= 0)
        {
            lastDayOfWeekAfterEndDate = periodEnd.AddDays(daysBetweenEndDateAndFollowingLastDayOfWeek);
        }
        else
        {
            lastDayOfWeekAfterEndDate = periodEnd.AddDays(daysBetweenEndDateAndFollowingLastDayOfWeek + DAYS_IN_WEEK);
        }

        int calendarWeeks = 1 + (int)((lastDayOfWeekAfterEndDate - firstDayOfWeekBeforeStartDate).TotalDays / DAYS_IN_WEEK);
Malcolm Haar
  • 256
  • 1
  • 4
7

The way I would tackle this is to get the beginning of the week for any given date. Using that, you would subtract the result of the start date from the result of the end date. Your day difference would then always be a multiple of 7, so divide and add 1 (0 days => 1 week, 7 days => 2 weeks, etc.). This would tell you how many calendar weeks were covered or represented by any two dates.

static int GetWeeksCovered(DateTime startDate, DateTime endDate)
{
    if (endDate < startDate)
        throw new ArgumentException("endDate cannot be less than startDate");

    return (GetBeginningOfWeek(endDate).Subtract(GetBeginningOfWeek(startDate)).Days / 7) + 1;
}

static DateTime GetBeginningOfWeek(DateTime date)
{
    return date.AddDays(-1 * (int)date.DayOfWeek).Date;
}
  • 16-Oct-2010 and 16-Oct-2010 => 1 week covered (or represented).
  • 16-Oct-2010 and 31-Oct-2010 => 4 weeks covered, as per the spec.
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
4

Bear in mind, that week calculations are done differently in different cultures and there is not a bug if you see week number 53!

using System.Globalization;

CultureInfo cultInfo = CultureInfo.CurrentCulture;
int weekNumNow = cultInfo.Calendar.GetWeekOfYear(DateTime.Now,
                     cultInfo.DateTimeFormat.CalendarWeekRule,
                         cultInfo.DateTimeFormat.FirstDayOfWeek); 
IrishChieftain
  • 15,108
  • 7
  • 50
  • 91
0
private static int weekDifference(DateTime startDate, DateTime endDate)
{
int monthsApart = 12 * (startDate.Year - endDate.Year) + startDate.Month - endDate.Month;
 return Math.Abs(monthsApart*4);
}

Cant See a better way right off the bat.

Jake Kalstad
  • 2,045
  • 14
  • 20
0
private static int weekDifference(DateTime startDate, DateTime endDate)
{
     const int firstDayOfWeek = 0;  // Sunday
     int wasteDaysStart = (7+startDate.DatOfWeek-firstDayOfWeek)%7;
     return (int)(((endDate-startDate).TotalDays() + wasteDaysStart + 6)/7);
}

Warning: untested code. Please test and remove note.

Pavel Radzivilovsky
  • 18,794
  • 5
  • 57
  • 67
  • (10/31/2010) - (10/16/2010) returns 15. 15/7 = 2.14. How would I truncate differently to get 4 from 2.14? – Phil Scholtes Jun 17 '10 at 22:18
  • 2
    I think this only finds the number of 7-day spans between two dates...not the number of calendar weeks the range of dates span. Keep in mind, a date can fall on the last day of a week or the first day of a week, meaning it covers all the full weeks inbetween, as well as the week where the first day fell on the last week day, and the week where the last day fell on the first week day. Given that...I don't think this calculation will solve the problem in the OP. – jrista Jun 18 '10 at 04:16
  • thanks, fixed code, now that I understood your question :) please test carefully. – Pavel Radzivilovsky Jun 18 '10 at 11:06
0

The following seems to work for any date range. It should be culturally sound, and should account for leap years/days or other calendar oddities:

    private static int getWeeksSpannedBy(DateTime first, DateTime last)
    {
        var calendar = CultureInfo.CurrentCulture.Calendar;
        var weekRule = CultureInfo.CurrentCulture.DateTimeFormat.CalendarWeekRule;
        var firstDayOfWeek = DayOfWeek.Sunday;

        int lastWeek = calendar.GetWeekOfYear(last, weekRule, firstDayOfWeek);
        int firstWeek = calendar.GetWeekOfYear(first, weekRule, firstDayOfWeek);

        int weekDiff = lastWeek - firstWeek + 1;

        return weekDiff;
    }

    static void Main(string[] args)
    {
        int weeks1 = getWeeksSpannedBy(new DateTime(2010, 1, 3), new DateTime(2010, 1, 9));
        int weeks2 = getWeeksSpannedBy(new DateTime(2010, 10, 16), new DateTime(2010, 10, 31));
        int weeks3 = getWeeksSpannedBy(new DateTime(2008, 2, 1), new DateTime(2008, 2, 29));
        int weeks4 = getWeeksSpannedBy(new DateTime(2012, 2, 1), new DateTime(2012, 2, 29));

        Console.WriteLine("Weeks Difference #1: " + weeks1);
        Console.WriteLine("Weeks Difference #2: " + weeks2);
        Console.WriteLine("Weeks Difference #3: " + weeks3);
        Console.WriteLine("Weeks Difference #4: " + weeks4);
        Console.ReadLine();
    }

Prints out the following, which is correct for all date ranges, past, present, or future (leap year 2008 and 2012 both have 5 weeks between Feb 1 and Feb 29):

Weeks Difference #1: 1
Weeks Difference #2: 4
Weeks Difference #3: 5
Weeks Difference #4: 5

jrista
  • 32,447
  • 15
  • 90
  • 130
0

Saturday is the last day of the week huh?

public int CalendarWeeks(DateTime from, DateTime to) {

    // number of multiples of 7 
    // (rounded up, since 15 days would span at least 3 weeks)
    // and if we end on a day before we start, we know it's another week

    return (int)Math.Ceiling(to.Subtract(from).Days / 7.0) + 
            (to.DayOfWeek <= from.DayOfWeek) ? 1 : 0;
}
Jeff Meatball Yang
  • 37,839
  • 27
  • 91
  • 125