2009-11-30 19 views
23

Jak mogę uzyskać liczbę dni powszednich między dwiema podanymi datami bez jedynie powtarzania dat między i odliczania dni tygodnia?Obliczanie liczby dni między dwiema datami w C#

wydaje się dość proste, ale nie wydaje się znaleźć jednoznacznej poprawną odpowiedź, że trwa brzmienie:

  1. Całkowita powinna być powszechna, więc GetNumberOfWeekdays (new DateTime (2009,11,30) nowy DateTime (2009,12,4)) powinien wynosić 5, czyli od poniedziałku do piątku.
  2. Powinno zezwalać na dni przestępne
  3. NIE tylko przegląda wszystkie daty odliczając dni tygodnia.

Znalazłem similar question z answer że pochodzi blisko, ale nie jest poprawne

+0

Czy chcesz wykluczyć tylko soboty i niedziele, czy też wziąć pod uwagę dni świąteczne? –

+2

nodobime, być może? –

+0

Tylko w soboty i niedziele. Stworzę osobną metodę GetNumberOfBusinessDays (DateTime from, DateTime to, IEnumerable ), która oprócz listy wykluczeń obejmie wykluczenie dni wolnych od pracy. –

Odpowiedz

11

Od tego link:

public static int Weekdays(DateTime dtmStart, DateTime dtmEnd) 
    { 
     // This function includes the start and end date in the count if they fall on a weekday 
     int dowStart = ((int)dtmStart.DayOfWeek == 0 ? 7 : (int)dtmStart.DayOfWeek); 
     int dowEnd = ((int)dtmEnd.DayOfWeek == 0 ? 7 : (int)dtmEnd.DayOfWeek); 
     TimeSpan tSpan = dtmEnd - dtmStart; 
     if (dowStart <= dowEnd) 
     { 
      return (((tSpan.Days/7) * 5) + Math.Max((Math.Min((dowEnd + 1), 6) - dowStart), 0)); 
     } 
     return (((tSpan.Days/7) * 5) + Math.Min((dowEnd + 6) - Math.Min(dowStart, 6), 5)); 
    } 


    [1]: http://www.eggheadcafe.com/community/aspnet/2/44982/how-to-calculate-num-of-w.aspx 

testów (powrotów Test 5):

int ndays = Weekdays(new DateTime(2009, 11, 30), new DateTime(2009, 12, 4)); 
    System.Console.WriteLine(ndays); 

    // leap year test 
    ndays = Weekdays(new DateTime(2000, 2,27), new DateTime(2000, 3, 5)); 
    System.Console.WriteLine(ndays); 

    // non leap year test 
    ndays = Weekdays(new DateTime(2007, 2, 25), new DateTime(2007, 3, 4)); 
    System.Console.WriteLine(ndays); 
+0

Czy możesz wyjaśnić, jak działa ten algorytm? –

+0

OSTRZEŻENIE! Ten algorytm wydaje się nie działać, gdy obie daty są w tym samym tygodniu. Zobacz moją odpowiedź na nieco mniej inteligentny, ale moim zdaniem działający fragment kodu. – eFloh

+0

Zwraca wartość 3 dla dni tygodnia (nowa godzina (2012,3,1), nowa godzina (2012,3,2)). Odpowiedź powinna być 2. – smirkingman

2

To powinno zrobić lepiej niż rozwiązanie przez dcp:

/// <summary> 
    /// Count Weekdays between two dates 
    /// </summary> 
    /// <param name="dtmStart">first date</param> 
    /// <param name="dtmEnd">second date</param> 
    /// <returns>weekdays between the two dates, including the start and end day</returns> 
    internal static int getWeekdaysBetween(DateTime dtmStart, DateTime dtmEnd) 
    { 
     if (dtmStart > dtmEnd) 
     { 
      DateTime temp = dtmStart; 
      dtmStart = dtmEnd; 
      dtmEnd = temp; 
     } 

     /* Move border dates to the monday of the first full week and sunday of the last week */ 
     DateTime startMonday = dtmStart; 
     int startDays = 1; 
     while (startMonday.DayOfWeek != DayOfWeek.Monday) 
     { 
      if (startMonday.DayOfWeek != DayOfWeek.Saturday && startMonday.DayOfWeek != DayOfWeek.Sunday) 
      { 
       startDays++; 
      } 
      startMonday = startMonday.AddDays(1); 
     } 

     DateTime endSunday = dtmEnd; 
     int endDays = 0; 
     while (endSunday.DayOfWeek != DayOfWeek.Sunday) 
     { 
      if (endSunday.DayOfWeek != DayOfWeek.Saturday && endSunday.DayOfWeek != DayOfWeek.Sunday) 
      { 
       endDays++; 
      } 
      endSunday = endSunday.AddDays(1); 
     } 

     int weekDays; 

     /* calculate weeks between full week border dates and fix the offset created by moving the border dates */ 
     weekDays = (Math.Max(0, (int)Math.Ceiling((endSunday - startMonday).TotalDays + 1))/7 * 5) + startDays - endDays; 

     return weekDays; 
    } 
3

Odpowiedź eFloh miała dodatkowy dzień, jeśli ostatnim dniem była sobota lub niedziela. To by to naprawiło.

 public static int Weekdays(DateTime dtmStart, DateTime dtmEnd) 
    { 
     if (dtmStart > dtmEnd) 
     { 
      DateTime temp = dtmStart; 
      dtmStart = dtmEnd; 
      dtmEnd = temp; 
     } 

     /* Move border dates to the monday of the first full week and sunday of the last week */ 
     DateTime startMonday = dtmStart; 
     int startDays = 1; 
     while (startMonday.DayOfWeek != DayOfWeek.Monday) 
     { 
      if (startMonday.DayOfWeek != DayOfWeek.Saturday && startMonday.DayOfWeek != DayOfWeek.Sunday) 
      { 
       startDays++; 
      } 
      startMonday = startMonday.AddDays(1); 
     } 

     DateTime endSunday = dtmEnd; 
     int endDays = 0; 
     while (endSunday.DayOfWeek != DayOfWeek.Sunday) 
     { 
      if (endSunday.DayOfWeek != DayOfWeek.Saturday && endSunday.DayOfWeek != DayOfWeek.Sunday) 
      { 
       endDays++; 
      } 
      endSunday = endSunday.AddDays(1); 
     } 

     int weekDays; 

     /* calculate weeks between full week border dates and fix the offset created by moving the border dates */ 
     weekDays = (Math.Max(0, (int)Math.Ceiling((endSunday - startMonday).TotalDays + 1))/7 * 5) + startDays - endDays; 

     if (dtmEnd.DayOfWeek == DayOfWeek.Saturday || dtmEnd.DayOfWeek == DayOfWeek.Sunday) 
     { 
      weekDays -= 1; 
     } 

     return weekDays; 

    } 
+0

Jest to poprawne, ale wolniejsze niż naiwne rozwiązanie iteracyjne. – smirkingman

2

Potrzebowałem pozytywnych/negatywnych (nie wartości bezwzględne) tak oto w jaki sposób rozwiązać go:

public static int WeekdayDifference(DateTime StartDate, DateTime EndDate) 
    { 
     DateTime thisDate = StartDate; 
     int weekDays = 0; 
     while (thisDate != EndDate) 
     { 
      if (thisDate.DayOfWeek != DayOfWeek.Saturday && thisDate.DayOfWeek != DayOfWeek.Sunday) { weekDays++; } 
      if (EndDate > StartDate) { thisDate = thisDate.AddDays(1); } else { thisDate = thisDate.AddDays(-1); } 
     } 

     /* Determine if value is positive or negative */ 
     if (EndDate > StartDate) { 
      return weekDays; 
     } 
     else 
     { 
      return weekDays * -1; 
     } 
    } 
+1

Co powiesz na 'Math.Abs', aby uzyskać wartość absolutną! – Marshal

1
public static List<DateTime> Weekdays(DateTime startDate, DateTime endDate) 
    { 
     if (startDate > endDate) 
     { 
      Swap(ref startDate, ref endDate); 
     } 
     List<DateTime> days = new List<DateTime>(); 

     var ts = endDate - startDate; 
     for (int i = 0; i < ts.TotalDays; i++) 
     { 
      var cur = startDate.AddDays(i); 
      if (cur.DayOfWeek != DayOfWeek.Saturday && cur.DayOfWeek != DayOfWeek.Sunday) 
       days.Add(cur); 
      //if (startingDate.AddDays(i).DayOfWeek != DayOfWeek.Saturday || startingDate.AddDays(i).DayOfWeek != DayOfWeek.Sunday) 
      //yield return startingDate.AddDays(i); 
     } 
     return days; 
    } 

i zamieniać Terminy

private static void Swap(ref DateTime startDate, ref DateTime endDate) 
    { 
     object a = startDate; 
     startDate = endDate; 
     endDate = (DateTime)a; 
    } 
+0

Możesz pobrać daty i datę. –

1

funkcji użytkowych, aby uzyskać zakres dat:

public IEnumerable<DateTime> GetDates(DateTime begin, int count) 
{ 
    var first = new DateTime(begin.Year, begin.Month, begin.Day); 
    var maxYield = Math.Abs(count); 
    for (int i = 0; i < maxYield; i++) 
    { 
     if(count < 0) 
      yield return first - TimeSpan.FromDays(i); 
     else 
      yield return first + TimeSpan.FromDays(i);  
    } 
    yield break; 
} 

public IEnumerable<DateTime> GetDates(DateTime begin, DateTime end) 
{ 
    var days = (int)Math.Ceiling((end - begin).TotalDays); 
    return GetDates(begin, days); 
} 

LINQPad kod Demo:

var begin = DateTime.Now; 
var end = begin + TimeSpan.FromDays(14); 

var dates = GetDates(begin, end); 
var weekdays = dates.Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday); 
var mondays = dates.Count(x => x.DayOfWeek == DayOfWeek.Monday); 
var firstThursday = dates 
    .OrderBy(d => d) 
    .FirstOrDefault(d => d.DayOfWeek == DayOfWeek.Thursday); 

dates.Dump("Dates in Range"); 
weekdays.Dump("Count of Weekdays"); 
mondays.Dump("Count of Mondays"); 
firstThursday.Dump("First Thursday"); 
13

O (1) Roztwór:

// Count days from d0 to d1 inclusive, excluding weekends 
public static int countWeekDays(DateTime d0, DateTime d1) 
{ 
    int ndays = 1 + Convert.ToInt32((d1 - d0).TotalDays); 
    int nsaturdays = (ndays + Convert.ToInt32(d0.DayOfWeek))/7; 
    return ndays - 2 * nsaturdays 
      - (d0.DayOfWeek == DayOfWeek.Sunday ? 1 : 0) 
      + (d1.DayOfWeek == DayOfWeek.Saturday ? 1 : 0); 
} 

Przykłady stycznia 2014 r

January 2014 
Su Mo Tu We Th Fr Sa 
      1 2 3 4 
5 6 7 8 9 10 11 
12 13 14 15 16 17 18 
19 20 21 22 23 24 25 
26 27 28 29 30 31 

countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 1)); // 1 
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 2)); // 2 
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 3)); // 3 
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 4)); // 3 
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 5)); // 3 
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 6)); // 4 

N.B. Wejścia DateTime powinny znajdować się mniej więcej o tej samej porze dnia. Jeśli tworzysz obiekty DateTime oparte wyłącznie na roku, miesiącu i dniu, jak w powyższych przykładach, powinieneś być w porządku. Jako przykład przykrościowy, 12:01 od 1 stycznia do 23:59 Jan 2 obejmuje tylko 2 dni, ale powyższa funkcja będzie liczyć 3, jeśli użyjesz tych czasów.

+0

zdecydowanie najprostsze rozwiązanie. –

+0

Doskonałe rozwiązanie - potwierdzone testami z dowolnego dnia tygodnia jako datą rozpoczęcia i zakończenia z tego samego dnia do 3 tygodni i działa idealnie za każdym razem –

+0

To nie działa. spróbuj tego: countWeekDays (new DateTime (2016, 10, 1), new DateTime (2016, 10, 31)); // 22. Powinno to być 21 – Tawani

0

Tutaj funkcja, która oblicza liczbę DayOfWeek między dwiema datami.Być starannie oblicza go włącznie (zawiera zaczynają dzień, a kończąc dzień w obliczeniach):

private int GetWeekdayCount(DayOfWeek dayOfWeek, DateTime begin, DateTime end) 
    { 
     if (begin < end) 
     { 
      var timeSpan = end.Subtract(begin); 
      var fullDays = timeSpan.Days; 
      var count = fullDays/7; // количество дней равно как минимум количеству полных недель попавших в диапазон 
      var remain = fullDays % 7; // остаток от деления 

      // и вычислим попал ли еще один день в те куски недели, что остаются от полной 
      if (remain > 0) 
      { 
       var dowBegin = (int)begin.DayOfWeek; 
       var dowEnd = (int)end.DayOfWeek; 
       var dowDay = (int)dayOfWeek; 
       if (dowBegin < dowEnd) 
       { 
        // когда день недели начала меньше дня недели конца, например: 
        // начало  конец 
        // \/   \/ 
        // -- -- -- -- -- 
        // Вс Пн Вт Ср Чт Пт Сб 
        if (dowDay >= dowBegin && dowDay <= dowEnd) 
         count++; 
       } 
       else 
       { 
        // когда день недели начала больше дня недели конца, например: 
        // конец  начало 
        // \/   \/ 
        // -- --   -- -- 
        // Вс Пн Вт Ср Чт Пт Сб 
        if (dowDay <= dowEnd || dowDay >= dowBegin) 
         count++; 
       } 
      } 
      else if (begin.DayOfWeek == dayOfWeek) 
       count++; 

      return count; 
     } 
     return 0; 
    } 

Oto kolejny prosty analogowy z poprzedniej funkcji:

private int GetWeekdayCountStupid(DayOfWeek dayOfWeek, DateTime begin, DateTime end) 
    { 
     if (begin < end) 
     { 
      var count = 0; 
      var day = begin; 
      while (day <= end) 
      { 
       if (day.DayOfWeek == dayOfWeek) 
        count++; 
       day = day.AddDays(1); 
      } 
      return count; 
     } 
     return 0; 
    } 

i testów dla obu funkcji:

[TestMethod()] 
    public void TestWeekdayCount() 
    { 
     var init = new DateTime(2000, 01, 01); 
     for (int day = 0; day < 7; day++) 
     { 
      var dayOfWeek = (DayOfWeek)day; 
      for (int shift = 0; shift < 8; shift++) 
      { 
       for (int i = 0; i < 365; i++) 
       { 
        var begin = init.AddDays(shift); 
        var end = init.AddDays(shift + i); 
        var count1 = GetWeekdayCount(dayOfWeek, begin, end); 
        var count2 = GetWeekdayCountStupid(dayOfWeek, begin, end); 
        Assert.AreEqual(count1, count2); 
       } 
      } 
     } 
    } 
0
var dates = new List<DateTime>(); 

     for (var dt = YourStartDate; dt <= YourEndDate; dt = dt.AddDays(1)) 
     { 

      if (dt.DayOfWeek != DayOfWeek.Sunday && dt.DayOfWeek != DayOfWeek.Saturday) 
      { dates.Add(dt); } 

     } 

w tym kodzie możesz mieć listę wszystkich dni roboczych między dwoma datami es.

Jeśli chcesz podać liczbę tych dat, możesz uzyskać dates.Count jako liczbę całkowitą. lub jeśli chcesz dostać każdego dnia, możesz dołączyć do listy do łańcucha znaków.

Powiązane problemy