Miałem już takie zadanie i mam rozwiązanie. Unikałbym wyliczania wszystkich dni pomiędzy, kiedy można tego uniknąć, co ma miejsce w tym przypadku. Nie wspominam nawet o tworzeniu kilku instancji DateTime, jak widziałem w jednej z powyższych odpowiedzi. To naprawdę strata mocy obliczeniowej. Zwłaszcza w prawdziwej sytuacji, kiedy trzeba badać kilkumiesięczne odstępy czasu. Zobacz mój kod z komentarzami poniżej.
///<summary>/// Calculates number of business days, taking into account:/// - weekends (Saturdays and Sundays)/// - bank holidays in the middle of the week///</summary>///<param name="firstDay">First day in the time interval</param>///<param name="lastDay">Last day in the time interval</param>///<param name="bankHolidays">List of bank holidays excluding weekends</param>///<returns>Number of business days during the 'span'</returns>publicstaticintBusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
thrownew ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeksif (businessDays > fullWeekCount*7)
{
// we are here to find out if there is a 1-day or 2-days weekend// in the time interval remaining after subtracting the complete weeksint firstDayOfWeek = (int) firstDay.DayOfWeek;
int lastDayOfWeek = (int) lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
elseif (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
elseif (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
// subtract the number of bank holidays during the time intervalforeach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
return businessDays;
}
Edycja autorstwa Slauma, sierpień 2011
Świetna odpowiedź! Jest jednak mały błąd. Mam swobodę edycji tej odpowiedzi, ponieważ osoba odpowiadająca jest nieobecna od 2009 roku.
Powyższy kod zakłada, że DayOfWeek.Sundayma wartość, 7która nie jest prawdą. Wartość jest faktycznie 0. To prowadzi do błędnej kalkulacji jeżeli na przykład firstDayi lastDaysą takie same zarówno w niedzielę. Metoda zwraca 1w tym przypadku, ale powinna 0.
Najłatwiejsza naprawa tego błędu: Zastąp w kodzie powyżej wiersze, w których firstDayOfWeeki lastDayOfWeeksą zadeklarowane przez:
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)lastDay.DayOfWeek;
+1 To prawdopodobnie najłatwiejszy i najbardziej efektywny sposób na zrobienie tego (moje rozwiązanie pochodzące z C ++ nie korzysta ze wsparcia TimeSpan, C # znacznie ułatwia niektóre zadania). BankHolidays to też miły akcent!
RedGlyph
2
Upewnij się również, że święta państwowe w następujący sposób: if (firstDay <= bh && bh <= lastDay && bh.IsWorkingDay ())
Tawani
5
Dzięki za metodę. Chociaż musiałem dodać następujące wyrażenie do odejmowania / iteracji wyrażenia warunkowego dni wolnych od pracy: w przeciwnym && !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)razie odejmowałoby to ten sam dzień dwa razy, jeśli święto wypada w weekend.
Dobra robota, ale może użyć samych wyliczeń DayOfWeek zamiast rzutować je na int?
Neo
3
Poważnie, najlepsze rozwiązanie. Pozdrawiam Alec
Mizmor
6
Zauważ, że nawet jeśli ta funkcja zwraca wartość podwójną, należy jej ufać tylko w przypadku pełnych dni roboczych. Nie zwraca poprawnej odpowiedzi dla ułamków dni, gdy chodzi o czasy.
Pakman
4
Wystarczy zauważyć, że „1+” zakłada początek pierwszego dnia do końca ostatniego dnia, bez „1+” zakłada koniec pierwszego dnia do końca ostatniego dnia. Zajęło mi trochę czasu, zanim to rozgryzłem, ponieważ zakładałem początek pierwszego dnia do początku dnia ostatniego, co miało dla mnie więcej sensu.
Jeffry van de Vuurst
11
To NIE jest poprawna odpowiedź. Dni mogą minąć nawet do 4. Prawie dobrze, nie bierze pod uwagę tego, kiedy początek i koniec dnia przypadają w weekend, co jest najtrudniejsze. Początek - koniec również nie powinien znajdować się w nawiasach. To nie ma nic wspólnego z problemem. W 60% przypadków to rozwiązanie jest NIEPOPRAWNE .
Sowa
47
Wiem, że to pytanie zostało już rozwiązane, ale pomyślałem, że mogę udzielić prostszej odpowiedzi, która może pomóc innym odwiedzającym w przyszłości.
Znacznie jaśniejsze, a wymienione rozwiązania pozwalają na wyeliminowanie dni wolnych od pracy. Są jednak znacznie wolniejsze w masie; W LINQPad obliczenie dni roboczych dla 90-dniowych przerw w 1 milionowej pętli iteracji przy tym rozwiązaniu zajmuje 10s, a przy akceptowanej odpowiedzi lub znacznie ładniejszej Aleca Pojidaeva zajmuje tylko około 0,2s.
Whelkaholizm
Aby być włącznie, kod powinien wyglądać następująco: return Enumerable .Range (0, dayDifference + 1) ...
Edza
nie zwraca dni w przeszłości. Jak -18 dni roboczych.
iwtu
@iwtu To zakłada, że to > from. Może to jest problem?
Alpha
22
Zdefiniuj metodę rozszerzenia w DateTime w następujący sposób:
Następnie użyj klauzuli Where, aby przefiltrować szerszą listę dat:
var allDates = GetDates(); // method which returns a list of dates// filter dates by working day's var countOfWorkDays = allDates
.Where(day => day.IsWorkingDay())
.Count() ;
Czy nie poszedłbyś dalej i nie przedłużałby również okresu czasu, abyś mógł tego użyć - skoro powiedział, że chce użyć odległości między dwiema datami, a nie listy dat?
WesleyJohnson
Odległość między dwiema datami to liczba dni między nimi, więc Count () jest wystarczający.
Carles Company
3
Nie jestem pewien, dlaczego to jest odpowiednia odpowiedź ... nie ma listy poszczególnych dni, ma dwie daty i chce znaleźć liczbę dni roboczych między nimi. Aby skorzystać z tego rozwiązania, musiałbyś podać inną funkcję, która generuje listę wszystkich dat między twyp.
Adam Robinson
1
Adam, to jest prosty przykład z minimalną ilością kodu potrzebnego do zademonstrowania koncepcji. W mojej oryginalnej odpowiedzi zawarłem również pętlę, która zapełniła listę allDates, którą od tamtej pory wyodrębniłem do funkcji "GetDates". Test IsWorkingDay można łatwo przenieść z instrukcji LINQ do tej pętli. Osobiście podoba mi się to, jak jest teraz, ponieważ jest to bardzo czytelne dla człowieka, co się dzieje.
Qwerty
10
Można to skrócić, zmieniając opcję Where to Count i eliminując Count
rekurencyjne
12
Użyłem poniższego kodu, aby uwzględnić również dni wolne od pracy:
publicclassWorkingDays
{
public List<DateTime> GetHolidays()
{
var client = new WebClient();
var json = client.DownloadString("https://www.gov.uk/bank-holidays.json");
var js = new JavaScriptSerializer();
var holidays = js.Deserialize <Dictionary<string, Holidays>>(json);
return holidays["england-and-wales"].events.Select(d => d.date).ToList();
}
publicintGetWorkingDays(DateTime from, DateTime to)
{
var totalDays = 0;
var holidays = GetHolidays();
for (var date = from.AddDays(1); date <= to; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday
&& !holidays.Contains(date))
totalDays++;
}
return totalDays;
}
}
publicclassHolidays
{
publicstring division { get; set; }
public List<Event> events { get; set; }
}
publicclassEvent
{
public DateTime date { get; set; }
publicstring notes { get; set; }
publicstring title { get; set; }
}
I testy jednostkowe:
[TestClass]
publicclassWorkingDays
{
[TestMethod]
publicvoidSameDayIsZero()
{
var service = new WorkingDays();
varfrom = new DateTime(2013, 8, 12);
Assert.AreEqual(0, service.GetWorkingDays(from, from));
}
[TestMethod]
publicvoidCalculateDaysInWorkingWeek()
{
var service = new WorkingDays();
varfrom = new DateTime(2013, 8, 12);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(4, service.GetWorkingDays(from, to), "Mon - Fri = 4");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Mon - Tues = 1");
}
[TestMethod]
publicvoidNotIncludeWeekends()
{
var service = new WorkingDays();
varfrom = new DateTime(2013, 8, 9);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(5, service.GetWorkingDays(from, to), "Fri - Fri = 5");
Assert.AreEqual(2, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Fri - Tues = 2");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 12)), "Fri - Mon = 1");
}
[TestMethod]
publicvoidAccountForHolidays()
{
var service = new WorkingDays();
varfrom = new DateTime(2013, 8, 23);
Assert.AreEqual(0, service.GetWorkingDays(from, new DateTime(2013, 8, 26)), "Fri - Mon = 0");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 27)), "Fri - Tues = 1");
}
}
dlaczego zaczynasz liczyć od dodania 1 dni do „od” @ for (var date = from.AddDays (1); date <= to; date = date.AddDays (1))?
Oncel Umut TURER
6
Cóż, ten został pobity na śmierć. :) Jednak mam zamiar udzielić innej odpowiedzi, ponieważ potrzebowałem czegoś innego. To rozwiązanie różni się tym, że zwraca Business TimeSpan między początkiem a końcem, a także można ustawić godziny pracy dnia i dodać święta. Możesz więc użyć go do obliczenia, czy dzieje się to w ciągu jednego dnia, dni, weekendów, a nawet świąt. Możesz uzyskać tylko dni robocze lub nie, po prostu pobierając to, czego potrzebujesz, ze zwróconego obiektu TimeSpan. A sposób, w jaki używa list dni, możesz zobaczyć, jak bardzo łatwo byłoby dodać listę dni wolnych od pracy, jeśli nie są to typowe soboty i niedziele. Testowałem przez rok i wydaje się super szybki.
Mam tylko nadzieję, że wklejenie kodu jest prawidłowe. Ale wiem, że to działa.
publicstatic TimeSpan GetBusinessTimespanBetween(
DateTime start, DateTime end,
TimeSpan workdayStartTime, TimeSpan workdayEndTime,
List<DateTime> holidays = null)
{
if (end < start)
thrownew ArgumentException("start datetime must be before end datetime.");
// Just create an empty list for easier coding.if (holidays == null) holidays = new List<DateTime>();
if (holidays.Where(x => x.TimeOfDay.Ticks > 0).Any())
thrownew ArgumentException("holidays can not have a TimeOfDay, only the Date.");
var nonWorkDays = new List<DayOfWeek>() { DayOfWeek.Saturday, DayOfWeek.Sunday };
var startTime = start.TimeOfDay;
// If the start time is before the starting hours, set it to the starting hour.if (startTime < workdayStartTime) startTime = workdayStartTime;
var timeBeforeEndOfWorkDay = workdayEndTime - startTime;
// If it's after the end of the day, then this time lapse doesn't count.if (timeBeforeEndOfWorkDay.TotalSeconds < 0) timeBeforeEndOfWorkDay = new TimeSpan();
// If start is during a non work day, it doesn't count.if (nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay = new TimeSpan();
elseif (holidays.Contains(start.Date)) timeBeforeEndOfWorkDay = new TimeSpan();
var endTime = end.TimeOfDay;
// If the end time is after the ending hours, set it to the ending hour.if (endTime > workdayEndTime) endTime = workdayEndTime;
var timeAfterStartOfWorkDay = endTime - workdayStartTime;
// If it's before the start of the day, then this time lapse doesn't count.if (timeAfterStartOfWorkDay.TotalSeconds < 0) timeAfterStartOfWorkDay = new TimeSpan();
// If end is during a non work day, it doesn't count.if (nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay = new TimeSpan();
elseif (holidays.Contains(end.Date)) timeAfterStartOfWorkDay = new TimeSpan();
// Easy scenario if the times are during the day day.if (start.Date.CompareTo(end.Date) == 0)
{
if (nonWorkDays.Contains(start.DayOfWeek)) returnnew TimeSpan();
elseif (holidays.Contains(start.Date)) returnnew TimeSpan();
return endTime - startTime;
}
else
{
var timeBetween = end - start;
var daysBetween = (int)Math.Floor(timeBetween.TotalDays);
var dailyWorkSeconds = (int)Math.Floor((workdayEndTime - workdayStartTime).TotalSeconds);
var businessDaysBetween = 0;
// Now the fun begins with calculating the actual Business days.if (daysBetween > 0)
{
var nextStartDay = start.AddDays(1).Date;
var dayBeforeEnd = end.AddDays(-1).Date;
for (DateTime d = nextStartDay; d <= dayBeforeEnd; d = d.AddDays(1))
{
if (nonWorkDays.Contains(d.DayOfWeek)) continue;
elseif (holidays.Contains(d.Date)) continue;
businessDaysBetween++;
}
}
var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween;
var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay;
output = output + new TimeSpan(0, 0, dailyWorkSecondsToAdd);
return output;
}
}
A oto kod testowy: Zauważ, że wystarczy umieścić tę funkcję w klasie o nazwie DateHelper, aby kod testowy działał.
[TestMethod]
publicvoidTestGetBusinessTimespanBetween()
{
var workdayStart = new TimeSpan(8, 0, 0);
var workdayEnd = new TimeSpan(17, 0, 0);
var holidays = new List<DateTime>()
{
new DateTime(2018, 1, 15), // a Mondaynew DateTime(2018, 2, 15) // a Thursday
};
var testdata = new[]
{
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 9, 50, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 10, 0, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 7, 50, 0),
end = new DateTime(2016, 10, 19, 8, 5, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 16, 55, 0),
end = new DateTime(2016, 10, 19, 17, 5, 0)
},
new
{
expectedMinutes = 15,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 7, 55, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 7, 5, 0)
},
new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 20, 12, 15, 0)
},
// Spanning multiple weekdaysnew
{
expectedMinutes = 835,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning multiple weekdaysnew
{
expectedMinutes = 1375,
start = new DateTime(2016, 10, 18, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins short of complete day.new
{
expectedMinutes = 1615,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins beyond complete day.new
{
expectedMinutes = 1625,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins beyond complete day.new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins short complete day.new
{
expectedMinutes = 535,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Monday, 5 mins short complete day.new
{
expectedMinutes = 245,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Sunday, 5 mins beyond complete day.new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Times within the same Saturday.new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Spanning from a Saturday to the Sunday next week.new
{
expectedMinutes = 2700,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 30, 12, 15, 0)
},
// Spanning a year.new
{
expectedMinutes = 143355,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2017, 10, 30, 12, 15, 0)
},
// Spanning a year with 2 holidays.new
{
expectedMinutes = 142815,
start = new DateTime(2017, 10, 22, 12, 10, 0),
end = new DateTime(2018, 10, 30, 12, 15, 0)
},
};
foreach (var item in testdata)
{
Assert.AreEqual(item.expectedMinutes,
DateHelper.GetBusinessTimespanBetween(
item.start, item.end,
workdayStart, workdayEnd,
holidays)
.TotalMinutes);
}
}
To rozwiązanie pozwala uniknąć iteracji, działa dla różnic + ve i -ve w dni robocze i obejmuje zestaw testów jednostkowych do regresji względem wolniejszej metody liczenia dni tygodnia. Zawarłem również zwięzłą metodę dodawania dni tygodnia, która również działa w ten sam sposób bez iteracji.
Testy jednostkowe obejmują kilka tysięcy kombinacji dat w celu wyczerpującego przetestowania wszystkich kombinacji początku / końca dnia tygodnia zarówno w małych, jak i dużych zakresach dat.
Ważne : Zakładamy, że liczymy dni, wykluczając datę początkową, w tym datę końcową. Jest to ważne przy liczeniu dni tygodnia, ponieważ określone dni rozpoczęcia / zakończenia, które uwzględniasz / wykluczasz, wpływają na wynik. Gwarantuje to również, że różnica między dwoma równymi dniami wynosi zawsze zero i że uwzględniamy tylko pełne dni robocze, ponieważ zazwyczaj chcesz, aby odpowiedź była poprawna dla dowolnego czasu w bieżącej dacie rozpoczęcia (często dzisiaj) i zawierała pełną datę zakończenia (np. termin).
UWAGA: Ten kod wymaga dodatkowej korekty na dni wolne, ale zgodnie z powyższym założeniem, kod ten musi wykluczać dni wolne w dniu rozpoczęcia.
staticreadonlyint[,] _diffOffset =
{
// Su M Tu W Th F Sa
{0, 1, 2, 3, 4, 5, 5}, // Su
{4, 0, 1, 2, 3, 4, 4}, // M
{3, 4, 0, 1, 2, 3, 3}, // Tu
{2, 3, 4, 0, 1, 2, 2}, // W
{1, 2, 3, 4, 0, 1, 1}, // Th
{0, 1, 2, 3, 4, 0, 0}, // F
{0, 1, 2, 3, 4, 5, 0}, // Sa
};
publicstaticintGetWeekdaysDiff(this DateTime dtStart, DateTime dtEnd)
{
int daysDiff = (int)(dtEnd - dtStart).TotalDays;
return daysDiff >= 0
? 5 * (daysDiff / 7) + _diffOffset[(int) dtStart.DayOfWeek, (int) dtEnd.DayOfWeek]
: 5 * (daysDiff / 7) - _diffOffset[6 - (int) dtStart.DayOfWeek, 6 - (int) dtEnd.DayOfWeek];
}
Odkryłem, że większość innych rozwiązań dotyczących przepełnienia stosu była albo powolna (iteracyjna), albo zbyt skomplikowana, a wiele z nich było po prostu błędnych. Morał z tej historii jest taki ... Nie ufaj jej, jeśli nie przetestujesz jej dokładnie !!
Pomysł na to wziął się z rozwiązania SQL, które znalazłem przy przepełnieniu stosu. Ich pomysł był solidny, ale niestety miał też błąd. To działało dla wartości + ve, ale ich mapowanie tabeli odnośników było niepoprawne dla wartości -ve.
Tony O'Hagan
4
Oto kod do tego celu, ze szwedzkimi świętami, ale możesz dostosować, które święta się liczyć. Zauważ, że dodałem limit, który możesz chcieć usunąć, ale był to system internetowy i nie chciałem, aby ktokolwiek wprowadzał jakąś wielką datę, aby uciąć ten proces
publicstaticintGetWorkdays(DateTime from ,DateTime to)
{
int limit = 9999;
int counter = 0;
DateTime current = from;
int result = 0;
if (from > to)
{
DateTime temp = from;
from = to;
to = temp;
}
if (from >= to)
{
return0;
}
while (current <= to && counter < limit)
{
if (IsSwedishWorkday(current))
{
result++;
}
current = current.AddDays(1);
counter++;
}
return result;
}
publicstaticboolIsSwedishWorkday(DateTime date)
{
return (!IsSwedishHoliday(date) && date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday);
}
publicstaticboolIsSwedishHoliday(DateTime date)
{
return (
IsSameDay(GetEpiphanyDay(date.Year), date) ||
IsSameDay(GetMayDay(date.Year), date) ||
IsSameDay(GetSwedishNationalDay(date.Year), date) ||
IsSameDay(GetChristmasDay(date.Year), date) ||
IsSameDay(GetBoxingDay(date.Year), date) ||
IsSameDay(GetGoodFriday(date.Year), date) ||
IsSameDay(GetAscensionDay(date.Year), date) ||
IsSameDay(GetAllSaintsDay(date.Year), date) ||
IsSameDay(GetMidsummersDay(date.Year), date) ||
IsSameDay(GetPentecostDay(date.Year), date) ||
IsSameDay(GetEasterMonday(date.Year), date) ||
IsSameDay(GetNewYearsDay(date.Year), date) ||
IsSameDay(GetEasterDay(date.Year), date)
);
}
// Trettondagenpublicstatic DateTime GetEpiphanyDay(int year)
{
returnnew DateTime(year, 1, 6);
}
// Första majpublicstatic DateTime GetMayDay(int year)
{
returnnew DateTime(year,5,1);
}
// Juldagenpublicstatic DateTime GetSwedishNationalDay(int year)
{
returnnew DateTime(year, 6, 6);
}
// Juldagenpublicstatic DateTime GetNewYearsDay(int year)
{
returnnew DateTime(year,1,1);
}
// Juldagenpublicstatic DateTime GetChristmasDay(int year)
{
returnnew DateTime(year,12,25);
}
// Annandag julpublicstatic DateTime GetBoxingDay(int year)
{
returnnew DateTime(year, 12, 26);
}
// Långfredagenpublicstatic DateTime GetGoodFriday(int year)
{
return GetEasterDay(year).AddDays(-3);
}
// Kristi himmelsfärdsdagpublicstatic DateTime GetAscensionDay(int year)
{
return GetEasterDay(year).AddDays(5*7+4);
}
// Midsommarpublicstatic DateTime GetAllSaintsDay(int year)
{
DateTime result = new DateTime(year,10,31);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Midsommarpublicstatic DateTime GetMidsummersDay(int year)
{
DateTime result = new DateTime(year, 6, 20);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Pingstdagenpublicstatic DateTime GetPentecostDay(int year)
{
return GetEasterDay(year).AddDays(7 * 7);
}
// Annandag påskpublicstatic DateTime GetEasterMonday(int year)
{
return GetEasterDay(year).AddDays(1);
}
publicstatic DateTime GetEasterDay(int y)
{
double c;
double n;
double k;
double i;
double j;
double l;
double m;
double d;
c = System.Math.Floor(y / 100.0);
n = y - 19 * System.Math.Floor(y / 19.0);
k = System.Math.Floor((c - 17) / 25.0);
i = c - System.Math.Floor(c / 4) - System.Math.Floor((c - k) / 3) + 19 * n + 15;
i = i - 30 * System.Math.Floor(i / 30);
i = i - System.Math.Floor(i / 28) * (1 - System.Math.Floor(i / 28) * System.Math.Floor(29 / (i + 1)) * System.Math.Floor((21 - n) / 11));
j = y + System.Math.Floor(y / 4.0) + i + 2 - c + System.Math.Floor(c / 4);
j = j - 7 * System.Math.Floor(j / 7);
l = i - j;
m = 3 + System.Math.Floor((l + 40) / 44);// month
d = l + 28 - 31 * System.Math.Floor(m / 4);// daydouble days = ((m == 3) ? d : d + 31);
DateTime result = new DateTime(y, 3, 1).AddDays(days-1);
return result;
}
brakuje funkcji issamedate, ale jest to po prostu public static bool IsSameDay (DateTime date1, DateTime date2) {return date1.Date == date2.Date; }
Choco Smith
Zamiast tworzyć instancje nowych obiektów Date, można użyć tablicy przeglądowej tablicy int.
TheRealChx101
3
Oto krótki przykładowy kod. Jest to metoda klasowa, więc będzie działać tylko w Twojej klasie. Jeśli chcesz static, zmień podpis na private static(lub public static).
private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed)
{
for (var d = sd; d <= ed; d = d.AddDays(1))
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
yieldreturn d;
}
Ta metoda tworzy zmienną pętli d, inicjuje ją na dzień rozpoczęcia sd, a następnie zwiększa o jeden dzień w każdej iteracji ( d = d.AddDays(1)).
Zwraca żądane wartości za pomocą yield, co tworzy plik iterator. Fajną rzeczą w iteratorach jest to, że nie przechowują one wszystkich wartości IEnumerablew pamięci, a jedynie wywołują je sekwencyjnie. Oznacza to, że możesz wywoływać tę metodę od zarania dziejów do teraz bez martwienia się o wyczerpanie pamięci.
Ta metoda nie zwraca liczby dni roboczych między dwiema datami, zwraca daty biznesowe między dwiema datami. Kod, który proponujesz, jest bardzo czysty i podoba mi się użycie wydajności, ale nie odpowiada na pytanie.
Martin
3
Dużo szukałem łatwego do przyswojenia algorytmu do obliczania dni roboczych pomiędzy 2 datami, a także wykluczania świąt państwowych i ostatecznie decyduję się na takie podejście:
publicstaticintNumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays)
{
var dic = new Dictionary<DateTime, DayOfWeek>();
var totalDays = (due - start).Days;
for (int i = 0; i < totalDays + 1; i++)
{
if (!holidays.Any(x => x == start.AddDays(i)))
dic.Add(start.AddDays(i), start.AddDays(i).DayOfWeek);
}
return dic.Where(x => x.Value != DayOfWeek.Saturday && x.Value != DayOfWeek.Sunday).Count();
}
Zasadniczo chciałem iść z każdą datą i ocenić moje warunki:
To nie sobota
To nie jest niedziela
To nie jest święto narodowe
ale także chciałem uniknąć iteracji dat.
Uruchamiając i mierząc czas potrzebny do oceny 1 pełnego roku, otrzymuję następujący wynik:
staticvoidMain(string[] args)
{
var start = new DateTime(2017, 1, 1);
var due = new DateTime(2017, 12, 31);
var sw = Stopwatch.StartNew();
var days = NumberOfWorkingDaysBetween2Dates(start, due,NationalHolidays());
sw.Stop();
Console.WriteLine($"Total working days = {days} --- time: {sw.Elapsed}");
Console.ReadLine();
// result is:// Total working days = 249-- - time: 00:00:00.0269087
}
Myślę, że żadna z powyższych odpowiedzi nie jest w rzeczywistości poprawna. Żaden z nich nie rozwiązuje wszystkich szczególnych przypadków, takich jak kiedy daty zaczynają się i kończą w środku weekendu, kiedy data zaczyna się w piątek, a kończy w następny poniedziałek, itd. Poza tym wszystkie zaokrągla obliczenia do całości dni, więc jeśli data rozpoczęcia przypada na przykład w środku soboty, odejmie cały dzień od dni roboczych, dając błędne wyniki ...
W każdym razie, oto moje rozwiązanie, które jest dość wydajne i proste i działa we wszystkich przypadkach. Sztuczka polega po prostu na znalezieniu poprzedniego poniedziałku jako dat rozpoczęcia i zakończenia, a następnie dokonaniu niewielkiej rekompensaty, gdy początek i koniec mają miejsce w weekend:
Ta metoda nie używa żadnych pętli i jest w rzeczywistości dość prosta. Rozszerza zakres dat do pełnych tygodni, ponieważ wiemy, że każdy tydzień ma 5 dni roboczych. Następnie używa tabeli przeglądowej, aby znaleźć liczbę dni roboczych do odjęcia od początku i końca, aby uzyskać właściwy wynik. Rozszerzyłem obliczenia, aby pomóc pokazać, co się dzieje, ale w razie potrzeby całość można skondensować w jednej linii.
W każdym razie to działa dla mnie, więc pomyślałem, że opublikuję to tutaj, na wypadek, gdyby mogło to pomóc innym. Miłego kodowania.
Obliczenie
t: Całkowita liczba dni między datami (1 jeśli min = max)
a + b: Dodatkowe dni potrzebne do rozszerzenia sumy do pełnych tygodni
k: 1,4 to liczba dni tygodnia w tygodniu, tj. (t / 7) * 5
c: Liczba dni tygodnia do odjęcia od sumy
m: tabela przeglądowa używana do znajdowania wartości „c” dla każdego dnia tygodnia
Kultura
Kod zakłada tydzień pracy od poniedziałku do piątku. W przypadku innych kultur, takich jak od niedzieli do czwartku, należy przesunąć daty przed obliczeniem.
metoda
publicintWeekdays(DateTime min, DateTime max)
{
if (min.Date > max.Date) thrownew Exception("Invalid date span");
var t = (max.AddDays(1).Date - min.Date).TotalDays;
var a = (int) min.DayOfWeek;
var b = 6 - (int) max.DayOfWeek;
var k = 1.4;
var m = newint[]{0, 0, 1, 2, 3, 4, 5};
var c = m[a] + m[b];
return (int)((t + a + b) / k) - c;
}
Po prostu podzielę się moim rozwiązaniem. U mnie zadziałało, może po prostu nie zauważam / nie wiem, że jest błąd. Zacząłem od pierwszego niepełnego tygodnia, jeśli taki istnieje. pełny tydzień przypadał od niedzieli do soboty, więc jeśli (int) _now.DayOfWeek nie był równy 0 (niedziela), pierwszy tydzień był niekompletny.
Po prostu odejmuję 1 do liczby pierwszych tygodni dla soboty pierwszego tygodnia, a następnie dodaję ją do nowej liczby;
Następnie otrzymuję ostatni niekompletny tydzień, potem odejmuję 1 na niedzielę, a następnie dodaję do nowej liczby.
Wreszcie, liczba pełnych tygodni pomnożona przez 5 (dni tygodnia) została dodana do nowej liczby.
Miałem problem ze znalezieniem solidnej wersji TSQL tego kodu. Poniżej znajduje się zasadniczo konwersja kodu C # tutaj z dodatkiem tabeli świąt, której należy użyć do wstępnego obliczenia dni wolnych.
CREATE TABLE dbo.Holiday
(
HolidayDt DATE NOT NULL,
Name NVARCHAR(50) NOT NULL,
IsWeekday BIT NOT NULL,
CONSTRAINT PK_Holiday PRIMARY KEY (HolidayDt)
)
GO
CREATE INDEX IDX_Holiday ON Holiday (HolidayDt, IsWeekday)
GO
CREATE function dbo.GetBusinessDays
(
@FirstDay datetime,
@LastDay datetime
)
RETURNS INT
AS
BEGIN
DECLARE @BusinessDays INT, @FullWeekCount INT
SELECT @FirstDay = CONVERT(DATETIME,CONVERT(DATE,@FirstDay))
, @LastDay = CONVERT(DATETIME,CONVERT(DATE,@LastDay))
IF @FirstDay > @LastDay
RETURN NULL;
SELECT @BusinessDays = DATEDIFF(DAY, @FirstDay, @LastDay) + 1
SELECT @FullWeekCount = @BusinessDays / 7;
-- find outif there are weekends during the time exceedng the full weeks
IF @BusinessDays > (@FullWeekCount * 7)
BEGIN
-- we are here to find outif there is a 1-day or 2-days weekend
-- in the time interval remaining after subtracting the complete weeks
DECLARE @firstDayOfWeek INT, @lastDayOfWeek INT;
SELECT @firstDayOfWeek = DATEPART(DW, @FirstDay), @lastDayOfWeek = DATEPART(DW, @LastDay);
IF @lastDayOfWeek < @firstDayOfWeek
SELECT @lastDayOfWeek = @lastDayOfWeek + 7;
IF @firstDayOfWeek <= 6BEGIN
IF (@lastDayOfWeek >= 7) --Both Saturday and Sunday are in the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 2
END
ELSE IF @lastDayOfWeek>=6 --Only Saturday isin the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 1
END
END
ELSE IF @firstDayOfWeek <= 7 AND @lastDayOfWeek >=7 -- Only Sunday isin the remaining time interval
BEGIN
SELECT @BusinessDays = @BusinessDays - 1
END
END
-- subtract the weekends during the full weeks in the interval
DECLARE @Holidays INT;
SELECT @Holidays = COUNT(*)
FROM Holiday
WHERE HolidayDt BETWEEN @FirstDay AND @LastDay
AND IsWeekday = CAST(1 AS BIT)
SELECT @BusinessDays = @BusinessDays - (@FullWeekCount + @FullWeekCount) -- - @Holidays
RETURN @BusinessDays
END
Oto jedno bardzo proste rozwiązanie tego problemu. Mamy datę początkową, datę końcową i „pętlę for” do wydłużania dnia i obliczania, czy jest to dzień roboczy, czy weekend, konwertując na ciąg DayOfWeek.
classProgram
{
staticvoidMain(string[] args)
{
DateTime day = new DateTime();
Console.Write("Inser your end date (example: 01/30/2015): ");
DateTime endDate = DateTime.Parse(Console.ReadLine());
int numberOfDays = 0;
for (day = DateTime.Now.Date; day.Date < endDate.Date; day = day.Date.AddDays(1))
{
string dayToString = Convert.ToString(day.DayOfWeek);
if (dayToString != "Saturday" && dayToString != "Sunday") numberOfDays++;
}
Console.WriteLine("Number of working days (not including local holidays) between two dates is "+numberOfDays);
}
}
Na podstawie komentarza oznaczonego jako odpowiedź i zalecana łatka, a także -> Ta wersja chce przekonwertować dni na godziny pracy ... Uwzględnia również godziny tego samego dnia.
///<summary>/// Calculates number of business days, taking into account:/// - weekends (Saturdays and Sundays)/// - bank holidays in the middle of the week///</summary>///<param name="firstDay">First day in the time interval</param>///<param name="lastDay">Last day in the time interval</param>///<param name="bankHolidays">List of bank holidays excluding weekends</param>///<returns>Number of business hours during the 'span'</returns>publicstaticintBusinessHoursUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
var original_firstDay = firstDay;
var original_lastDay = lastDay;
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
return-1; //// throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeksif (businessDays > fullWeekCount * 7)
{
// we are here to find out if there is a 1-day or 2-days weekend// in the time interval remaining after subtracting the complete weeksint firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
elseif (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
elseif (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
if (bankHolidays != null && bankHolidays.Any())
{
// subtract the number of bank holidays during the time intervalforeach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
}
int total_business_hours = 0;
if (firstDay.Date == lastDay.Date)
{//If on the same day, go granular with Hours from the Orginial_*Day values
total_business_hours = (int)(original_lastDay - original_firstDay).TotalHours;
}
else
{//Convert Business-Days to TotalHours
total_business_hours = (int)(firstDay.AddDays(businessDays).AddHours(firstDay.Hour) - firstDay).TotalHours;
}
return total_business_hours;
}
Właśnie poprawiłem odpowiedź @Alexander i @Slauma, aby wspierać tydzień biznesowy jako parametr, w przypadkach, w których sobota jest dniem roboczym, a nawet w przypadkach, w których jest tylko kilka dni tygodnia, które są uważane za dni robocze:
///<summary>/// Calculate the number of business days between two dates, considering:/// - Days of the week that are not considered business days./// - Holidays between these two dates.///</summary>///<param name="fDay">First day of the desired 'span'.</param>///<param name="lDay">Last day of the desired 'span'.</param>///<param name="BusinessDaysOfWeek">Days of the week that are considered to be business days, if NULL considers monday, tuesday, wednesday, thursday and friday as business days of the week.</param>///<param name="Holidays">Holidays, if NULL, considers no holiday.</param>///<returns>Number of business days during the 'span'</returns>publicstaticintBusinessDaysUntil(this DateTime fDay, DateTime lDay, DayOfWeek[] BusinessDaysOfWeek = null, DateTime[] Holidays = null)
{
if (BusinessDaysOfWeek == null)
BusinessDaysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday };
if (Holidays == null)
Holidays = new DateTime[] { };
fDay = fDay.Date;
lDay = lDay.Date;
if (fDay > lDay)
thrownew ArgumentException("Incorrect last day " + lDay);
int bDays = (lDay - fDay).Days + 1;
int fullWeekCount = bDays / 7;
int fullWeekCountMult = 7 - WeekDays.Length;
// Find out if there are weekends during the time exceedng the full weeksif (bDays > (fullWeekCount * 7))
{
int fDayOfWeek = (int)fDay.DayOfWeek;
int lDayOfWeek = (int)lDay.DayOfWeek;
if (fDayOfWeek > lDayOfWeek)
lDayOfWeek += 7;
// If they are the same, we already covered it right before the Holiday subtractionif (lDayOfWeek != fDayOfWeek)
{
// Here we need to see if any of the days between are considered business daysfor (int i = fDayOfWeek; i <= lDayOfWeek; i++)
if (!WeekDays.Contains((DayOfWeek)(i > 6 ? i - 7 : i)))
bDays -= 1;
}
}
// Subtract the days that are not in WeekDays[] during the full weeks in the interval
bDays -= (fullWeekCount * fullWeekCountMult);
// Subtract the number of bank holidays during the time interval
bDays = bDays - Holidays.Select(x => x.Date).Count(x => fDay <= x && x <= lDay);
return bDays;
}
Oto funkcja, której możemy użyć do obliczenia dni roboczych między dwiema datami. Nie używam listy wakacyjnej, ponieważ może się ona różnić w zależności od kraju / regionu.
Jeśli i tak chcemy go użyć, możemy przyjąć trzeci argument jako listę wakacji i przed zwiększeniem liczby powinniśmy sprawdzić, czy lista nie zawiera d
publicstaticintGetBussinessDaysBetweenTwoDates(DateTime StartDate, DateTime EndDate)
{
if (StartDate > EndDate)
return-1;
int bd = 0;
for (DateTime d = StartDate; d < EndDate; d = d.AddDays(1))
{
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
bd++;
}
return bd;
}
Oto kolejny pomysł - ta metoda pozwala określić dowolny tydzień pracy i święta.
Chodzi o to, że znajdujemy rdzeń zakresu dat od pierwszego roboczego dnia tygodnia do ostatniego weekendowego dnia tygodnia. Dzięki temu możemy łatwo obliczyć całe tygodnie ( bez iteracji po wszystkich datach). Wszystko, co musimy wtedy zrobić, to dodać dni robocze przypadające przed początkiem i końcem tego podstawowego zakresu.
publicstaticintCalculateWorkingDays(
DateTime startDate,
DateTime endDate,
IList<DateTime> holidays,
DayOfWeek firstDayOfWeek,
DayOfWeek lastDayOfWeek)
{
// Make sure the defined working days run contiguouslyif (lastDayOfWeek < firstDayOfWeek)
{
thrownew Exception("Last day of week cannot fall before first day of week!");
}
// Create a list of the days of the week that make-up the weekend by working back// from the firstDayOfWeek and forward from lastDayOfWeek to get the start and end// the weekendvar weekendStart = lastDayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : lastDayOfWeek + 1;
var weekendEnd = firstDayOfWeek == DayOfWeek.Sunday ? DayOfWeek.Saturday : firstDayOfWeek - 1;
var weekendDays = new List<DayOfWeek>();
var w = weekendStart;
do {
weekendDays.Add(w);
if (w == weekendEnd) break;
w = (w == DayOfWeek.Saturday) ? DayOfWeek.Sunday : w + 1;
} while (true);
// Force simple dates - no time
startDate = startDate.Date;
endDate = endDate.Date;
// Ensure a progessive date rangeif (endDate < startDate)
{
var t = startDate;
startDate = endDate;
endDate = t;
}
// setup some working variables and constantsconstint daysInWeek = 7; // yeah - really!var actualStartDate = startDate; // this will end up on startOfWeek boundaryvar actualEndDate = endDate; // this will end up on weekendEnd boundaryint workingDaysInWeek = daysInWeek - weekendDays.Count;
int workingDays = 0; // the result we are trying to findint leadingDays = 0; // the number of working days leading up to the firstDayOfWeek boundaryint trailingDays = 0; // the number of working days counting back to the weekendEnd boundary// Calculate leading working days// if we aren't on the firstDayOfWeek we need to step forward to the nearestif (startDate.DayOfWeek != firstDayOfWeek)
{
var d = startDate;
do {
if (d.DayOfWeek == firstDayOfWeek || d >= endDate)
{
actualStartDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
leadingDays++;
}
d = d.AddDays(1);
} while(true);
}
// Calculate trailing working days// if we aren't on the weekendEnd we step back to the nearestif (endDate >= actualStartDate && endDate.DayOfWeek != weekendEnd)
{
var d = endDate;
do {
if (d.DayOfWeek == weekendEnd || d < actualStartDate)
{
actualEndDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
trailingDays++;
}
d = d.AddDays(-1);
} while(true);
}
// Calculate the inclusive number of days between the actualStartDate and the actualEndDatevar coreDays = (actualEndDate - actualStartDate).Days + 1;
var noWeeks = coreDays / daysInWeek;
// add together leading, core and trailing days
workingDays += noWeeks * workingDaysInWeek;
workingDays += leadingDays;
workingDays += trailingDays;
// Finally remove any holidays that fall within the range.if (holidays != null)
{
workingDays -= holidays.Count(h => h >= startDate && (h <= endDate));
}
return workingDays;
}
Ponieważ nie mogę komentować. Jest jeszcze jeden problem z przyjętym rozwiązaniem, w którym dni wolne od pracy są odejmowane, nawet jeśli przypadają w weekend. Widząc, jak sprawdzane są inne dane wejściowe, wystarczy, że tak jest.
Dlatego foreach powinno być:
// subtract the number of bank holidays during the time intervalforeach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
// Do not subtract bank holidays when they fall in the weekend to avoid double subtractionif (bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday)
continue;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
Oto podejście, jeśli używasz MVC. Obliczyłem również święta państwowe lub inne dni świąteczne, które mają zostać wykluczone, pobierając je z kalendarza świątecznego, który będziesz musiał utworzyć.
Oto funkcja pomocnicza, którą napisałem dla tego zadania. zwraca również liczbę weekendów za pośrednictwem outparametru. jeśli chcesz, możesz dostosować dni „weekendowe” w czasie wykonywania dla krajów, które używają różnych dni weekendowych lub uwzględnić dni wolne od pracy za pomocą weekendDays[]opcjonalnego parametru:
publicstaticintGetNetworkDays(DateTime startDate, DateTime endDate,outint totalWeekenDays, DayOfWeek[] weekendDays = null)
{
if (startDate >= endDate)
{
thrownew Exception("start date can not be greater then or equel to end date");
}
DayOfWeek[] weekends = new DayOfWeek[] { DayOfWeek.Sunday, DayOfWeek.Saturday };
if (weekendDays != null)
{
weekends = weekendDays;
}
var totaldays = (endDate - startDate).TotalDays + 1; // add one to include the first day tovar counter = 0;
var workdaysCounter = 0;
var weekendsCounter = 0;
for (int i = 0; i < totaldays; i++)
{
if (weekends.Contains(startDate.AddDays(counter).DayOfWeek))
{
weekendsCounter++;
}
else
{
workdaysCounter++;
}
counter++;
}
totalWeekenDays = weekendsCounter;
return workdaysCounter;
}
publicstaticintCalculateBusinessDaysInRange(this DateTime startDate, DateTime endDate, params DateTime[] holidayDates)
{
endDate = endDate.Date;
if(startDate > endDate)
thrownew ArgumentException("The end date can not be before the start date!", nameof(endDate));
int accumulator = 0;
DateTime itterator = startDate.Date;
do
{
if(itterator.DayOfWeek != DayOfWeek.Saturday && itterator.DayOfWeek != DayOfWeek.Sunday && !holidayDates.Any(hol => hol.Date == itterator))
{ accumulator++; }
}
while((itterator = itterator.AddDays(1)).Date <= endDate);
return accumulator
}
Publikuję to tylko dlatego, że pomimo wszystkich doskonałych odpowiedzi, które zostały podane, żadna z matematyki nie miała dla mnie sensu. Jest to zdecydowanie metoda KISS, która powinna działać i być dość łatwa do utrzymania. Przyznane, jeśli obliczasz zakresy większe niż 2-3 miesiące, nie będzie to najbardziej efektywny sposób. Po prostu określamy, czy jest to sobota czy niedziela, czy też data jest daną datą świąteczną. Jeśli tak nie jest, dodajemy dzień roboczy. Jeśli tak, wszystko jest w porządku.
Jestem pewien, że może to być jeszcze bardziej uproszczone dzięki LINQ, ale w ten sposób jest znacznie łatwiejsze do zrozumienia.
Odpowiedzi:
Miałem już takie zadanie i mam rozwiązanie. Unikałbym wyliczania wszystkich dni pomiędzy, kiedy można tego uniknąć, co ma miejsce w tym przypadku. Nie wspominam nawet o tworzeniu kilku instancji DateTime, jak widziałem w jednej z powyższych odpowiedzi. To naprawdę strata mocy obliczeniowej. Zwłaszcza w prawdziwej sytuacji, kiedy trzeba badać kilkumiesięczne odstępy czasu. Zobacz mój kod z komentarzami poniżej.
/// <summary> /// Calculates number of business days, taking into account: /// - weekends (Saturdays and Sundays) /// - bank holidays in the middle of the week /// </summary> /// <param name="firstDay">First day in the time interval</param> /// <param name="lastDay">Last day in the time interval</param> /// <param name="bankHolidays">List of bank holidays excluding weekends</param> /// <returns>Number of business days during the 'span'</returns> public static int BusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays) { firstDay = firstDay.Date; lastDay = lastDay.Date; if (firstDay > lastDay) throw new ArgumentException("Incorrect last day " + lastDay); TimeSpan span = lastDay - firstDay; int businessDays = span.Days + 1; int fullWeekCount = businessDays / 7; // find out if there are weekends during the time exceedng the full weeks if (businessDays > fullWeekCount*7) { // we are here to find out if there is a 1-day or 2-days weekend // in the time interval remaining after subtracting the complete weeks int firstDayOfWeek = (int) firstDay.DayOfWeek; int lastDayOfWeek = (int) lastDay.DayOfWeek; if (lastDayOfWeek < firstDayOfWeek) lastDayOfWeek += 7; if (firstDayOfWeek <= 6) { if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval businessDays -= 2; else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval businessDays -= 1; } else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval businessDays -= 1; } // subtract the weekends during the full weeks in the interval businessDays -= fullWeekCount + fullWeekCount; // subtract the number of bank holidays during the time interval foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; if (firstDay <= bh && bh <= lastDay) --businessDays; } return businessDays; }
Edycja autorstwa Slauma, sierpień 2011
Świetna odpowiedź! Jest jednak mały błąd. Mam swobodę edycji tej odpowiedzi, ponieważ osoba odpowiadająca jest nieobecna od 2009 roku.
Powyższy kod zakłada, że
DayOfWeek.Sunday
ma wartość,7
która nie jest prawdą. Wartość jest faktycznie0
. To prowadzi do błędnej kalkulacji jeżeli na przykładfirstDay
ilastDay
są takie same zarówno w niedzielę. Metoda zwraca1
w tym przypadku, ale powinna0
.Najłatwiejsza naprawa tego błędu: Zastąp w kodzie powyżej wiersze, w których
firstDayOfWeek
ilastDayOfWeek
są zadeklarowane przez:int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek; int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek;
Teraz wynik jest:
źródło
&& !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)
razie odejmowałoby to ten sam dzień dwa razy, jeśli święto wypada w weekend.Ok. Myślę, że czas zamieścić właściwą odpowiedź:
public static double GetBusinessDays(DateTime startD, DateTime endD) { double calcBusinessDays = 1 + ((endD - startD).TotalDays * 5 - (startD.DayOfWeek - endD.DayOfWeek) * 2) / 7; if (endD.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--; if (startD.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--; return calcBusinessDays; }
Pierwotnym źródłem:
http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
PS Solutions zamieszczone powyżej, z jakiegoś powodu mnie sic.
źródło
Wiem, że to pytanie zostało już rozwiązane, ale pomyślałem, że mogę udzielić prostszej odpowiedzi, która może pomóc innym odwiedzającym w przyszłości.
Oto moje spojrzenie na to:
public int GetWorkingDays(DateTime from, DateTime to) { var dayDifference = (int)to.Subtract(from).TotalDays; return Enumerable .Range(1, dayDifference) .Select(x => from.AddDays(x)) .Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday); }
To było moje oryginalne zgłoszenie:
public int GetWorkingDays(DateTime from, DateTime to) { var totalDays = 0; for (var date = from; date < to; date = date.AddDays(1)) { if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday) totalDays++; } return totalDays; }
źródło
to > from
. Może to jest problem?Zdefiniuj metodę rozszerzenia w DateTime w następujący sposób:
public static class DateTimeExtensions { public static bool IsWorkingDay(this DateTime date) { return date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday; } }
Następnie użyj klauzuli Where, aby przefiltrować szerszą listę dat:
var allDates = GetDates(); // method which returns a list of dates // filter dates by working day's var countOfWorkDays = allDates .Where(day => day.IsWorkingDay()) .Count() ;
źródło
Użyłem poniższego kodu, aby uwzględnić również dni wolne od pracy:
public class WorkingDays { public List<DateTime> GetHolidays() { var client = new WebClient(); var json = client.DownloadString("https://www.gov.uk/bank-holidays.json"); var js = new JavaScriptSerializer(); var holidays = js.Deserialize <Dictionary<string, Holidays>>(json); return holidays["england-and-wales"].events.Select(d => d.date).ToList(); } public int GetWorkingDays(DateTime from, DateTime to) { var totalDays = 0; var holidays = GetHolidays(); for (var date = from.AddDays(1); date <= to; date = date.AddDays(1)) { if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday && !holidays.Contains(date)) totalDays++; } return totalDays; } } public class Holidays { public string division { get; set; } public List<Event> events { get; set; } } public class Event { public DateTime date { get; set; } public string notes { get; set; } public string title { get; set; } }
I testy jednostkowe:
[TestClass] public class WorkingDays { [TestMethod] public void SameDayIsZero() { var service = new WorkingDays(); var from = new DateTime(2013, 8, 12); Assert.AreEqual(0, service.GetWorkingDays(from, from)); } [TestMethod] public void CalculateDaysInWorkingWeek() { var service = new WorkingDays(); var from = new DateTime(2013, 8, 12); var to = new DateTime(2013, 8, 16); Assert.AreEqual(4, service.GetWorkingDays(from, to), "Mon - Fri = 4"); Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Mon - Tues = 1"); } [TestMethod] public void NotIncludeWeekends() { var service = new WorkingDays(); var from = new DateTime(2013, 8, 9); var to = new DateTime(2013, 8, 16); Assert.AreEqual(5, service.GetWorkingDays(from, to), "Fri - Fri = 5"); Assert.AreEqual(2, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Fri - Tues = 2"); Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 12)), "Fri - Mon = 1"); } [TestMethod] public void AccountForHolidays() { var service = new WorkingDays(); var from = new DateTime(2013, 8, 23); Assert.AreEqual(0, service.GetWorkingDays(from, new DateTime(2013, 8, 26)), "Fri - Mon = 0"); Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 27)), "Fri - Tues = 1"); } }
źródło
Cóż, ten został pobity na śmierć. :) Jednak mam zamiar udzielić innej odpowiedzi, ponieważ potrzebowałem czegoś innego. To rozwiązanie różni się tym, że zwraca Business TimeSpan między początkiem a końcem, a także można ustawić godziny pracy dnia i dodać święta. Możesz więc użyć go do obliczenia, czy dzieje się to w ciągu jednego dnia, dni, weekendów, a nawet świąt. Możesz uzyskać tylko dni robocze lub nie, po prostu pobierając to, czego potrzebujesz, ze zwróconego obiektu TimeSpan. A sposób, w jaki używa list dni, możesz zobaczyć, jak bardzo łatwo byłoby dodać listę dni wolnych od pracy, jeśli nie są to typowe soboty i niedziele. Testowałem przez rok i wydaje się super szybki.
Mam tylko nadzieję, że wklejenie kodu jest prawidłowe. Ale wiem, że to działa.
public static TimeSpan GetBusinessTimespanBetween( DateTime start, DateTime end, TimeSpan workdayStartTime, TimeSpan workdayEndTime, List<DateTime> holidays = null) { if (end < start) throw new ArgumentException("start datetime must be before end datetime."); // Just create an empty list for easier coding. if (holidays == null) holidays = new List<DateTime>(); if (holidays.Where(x => x.TimeOfDay.Ticks > 0).Any()) throw new ArgumentException("holidays can not have a TimeOfDay, only the Date."); var nonWorkDays = new List<DayOfWeek>() { DayOfWeek.Saturday, DayOfWeek.Sunday }; var startTime = start.TimeOfDay; // If the start time is before the starting hours, set it to the starting hour. if (startTime < workdayStartTime) startTime = workdayStartTime; var timeBeforeEndOfWorkDay = workdayEndTime - startTime; // If it's after the end of the day, then this time lapse doesn't count. if (timeBeforeEndOfWorkDay.TotalSeconds < 0) timeBeforeEndOfWorkDay = new TimeSpan(); // If start is during a non work day, it doesn't count. if (nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay = new TimeSpan(); else if (holidays.Contains(start.Date)) timeBeforeEndOfWorkDay = new TimeSpan(); var endTime = end.TimeOfDay; // If the end time is after the ending hours, set it to the ending hour. if (endTime > workdayEndTime) endTime = workdayEndTime; var timeAfterStartOfWorkDay = endTime - workdayStartTime; // If it's before the start of the day, then this time lapse doesn't count. if (timeAfterStartOfWorkDay.TotalSeconds < 0) timeAfterStartOfWorkDay = new TimeSpan(); // If end is during a non work day, it doesn't count. if (nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay = new TimeSpan(); else if (holidays.Contains(end.Date)) timeAfterStartOfWorkDay = new TimeSpan(); // Easy scenario if the times are during the day day. if (start.Date.CompareTo(end.Date) == 0) { if (nonWorkDays.Contains(start.DayOfWeek)) return new TimeSpan(); else if (holidays.Contains(start.Date)) return new TimeSpan(); return endTime - startTime; } else { var timeBetween = end - start; var daysBetween = (int)Math.Floor(timeBetween.TotalDays); var dailyWorkSeconds = (int)Math.Floor((workdayEndTime - workdayStartTime).TotalSeconds); var businessDaysBetween = 0; // Now the fun begins with calculating the actual Business days. if (daysBetween > 0) { var nextStartDay = start.AddDays(1).Date; var dayBeforeEnd = end.AddDays(-1).Date; for (DateTime d = nextStartDay; d <= dayBeforeEnd; d = d.AddDays(1)) { if (nonWorkDays.Contains(d.DayOfWeek)) continue; else if (holidays.Contains(d.Date)) continue; businessDaysBetween++; } } var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween; var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay; output = output + new TimeSpan(0, 0, dailyWorkSecondsToAdd); return output; } }
A oto kod testowy: Zauważ, że wystarczy umieścić tę funkcję w klasie o nazwie DateHelper, aby kod testowy działał.
[TestMethod] public void TestGetBusinessTimespanBetween() { var workdayStart = new TimeSpan(8, 0, 0); var workdayEnd = new TimeSpan(17, 0, 0); var holidays = new List<DateTime>() { new DateTime(2018, 1, 15), // a Monday new DateTime(2018, 2, 15) // a Thursday }; var testdata = new[] { new { expectedMinutes = 0, start = new DateTime(2016, 10, 19, 9, 50, 0), end = new DateTime(2016, 10, 19, 9, 50, 0) }, new { expectedMinutes = 10, start = new DateTime(2016, 10, 19, 9, 50, 0), end = new DateTime(2016, 10, 19, 10, 0, 0) }, new { expectedMinutes = 5, start = new DateTime(2016, 10, 19, 7, 50, 0), end = new DateTime(2016, 10, 19, 8, 5, 0) }, new { expectedMinutes = 5, start = new DateTime(2016, 10, 19, 16, 55, 0), end = new DateTime(2016, 10, 19, 17, 5, 0) }, new { expectedMinutes = 15, start = new DateTime(2016, 10, 19, 16, 50, 0), end = new DateTime(2016, 10, 20, 8, 5, 0) }, new { expectedMinutes = 10, start = new DateTime(2016, 10, 19, 16, 50, 0), end = new DateTime(2016, 10, 20, 7, 55, 0) }, new { expectedMinutes = 5, start = new DateTime(2016, 10, 19, 17, 10, 0), end = new DateTime(2016, 10, 20, 8, 5, 0) }, new { expectedMinutes = 0, start = new DateTime(2016, 10, 19, 17, 10, 0), end = new DateTime(2016, 10, 20, 7, 5, 0) }, new { expectedMinutes = 545, start = new DateTime(2016, 10, 19, 12, 10, 0), end = new DateTime(2016, 10, 20, 12, 15, 0) }, // Spanning multiple weekdays new { expectedMinutes = 835, start = new DateTime(2016, 10, 19, 12, 10, 0), end = new DateTime(2016, 10, 21, 8, 5, 0) }, // Spanning multiple weekdays new { expectedMinutes = 1375, start = new DateTime(2016, 10, 18, 12, 10, 0), end = new DateTime(2016, 10, 21, 8, 5, 0) }, // Spanning from a Thursday to a Tuesday, 5 mins short of complete day. new { expectedMinutes = 1615, start = new DateTime(2016, 10, 20, 12, 10, 0), end = new DateTime(2016, 10, 25, 12, 5, 0) }, // Spanning from a Thursday to a Tuesday, 5 mins beyond complete day. new { expectedMinutes = 1625, start = new DateTime(2016, 10, 20, 12, 10, 0), end = new DateTime(2016, 10, 25, 12, 15, 0) }, // Spanning from a Friday to a Monday, 5 mins beyond complete day. new { expectedMinutes = 545, start = new DateTime(2016, 10, 21, 12, 10, 0), end = new DateTime(2016, 10, 24, 12, 15, 0) }, // Spanning from a Friday to a Monday, 5 mins short complete day. new { expectedMinutes = 535, start = new DateTime(2016, 10, 21, 12, 10, 0), end = new DateTime(2016, 10, 24, 12, 5, 0) }, // Spanning from a Saturday to a Monday, 5 mins short complete day. new { expectedMinutes = 245, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2016, 10, 24, 12, 5, 0) }, // Spanning from a Saturday to a Sunday, 5 mins beyond complete day. new { expectedMinutes = 0, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2016, 10, 23, 12, 15, 0) }, // Times within the same Saturday. new { expectedMinutes = 0, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2016, 10, 23, 12, 15, 0) }, // Spanning from a Saturday to the Sunday next week. new { expectedMinutes = 2700, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2016, 10, 30, 12, 15, 0) }, // Spanning a year. new { expectedMinutes = 143355, start = new DateTime(2016, 10, 22, 12, 10, 0), end = new DateTime(2017, 10, 30, 12, 15, 0) }, // Spanning a year with 2 holidays. new { expectedMinutes = 142815, start = new DateTime(2017, 10, 22, 12, 10, 0), end = new DateTime(2018, 10, 30, 12, 15, 0) }, }; foreach (var item in testdata) { Assert.AreEqual(item.expectedMinutes, DateHelper.GetBusinessTimespanBetween( item.start, item.end, workdayStart, workdayEnd, holidays) .TotalMinutes); } }
źródło
To rozwiązanie pozwala uniknąć iteracji, działa dla różnic + ve i -ve w dni robocze i obejmuje zestaw testów jednostkowych do regresji względem wolniejszej metody liczenia dni tygodnia. Zawarłem również zwięzłą metodę dodawania dni tygodnia, która również działa w ten sam sposób bez iteracji.
Testy jednostkowe obejmują kilka tysięcy kombinacji dat w celu wyczerpującego przetestowania wszystkich kombinacji początku / końca dnia tygodnia zarówno w małych, jak i dużych zakresach dat.
Ważne : Zakładamy, że liczymy dni, wykluczając datę początkową, w tym datę końcową. Jest to ważne przy liczeniu dni tygodnia, ponieważ określone dni rozpoczęcia / zakończenia, które uwzględniasz / wykluczasz, wpływają na wynik. Gwarantuje to również, że różnica między dwoma równymi dniami wynosi zawsze zero i że uwzględniamy tylko pełne dni robocze, ponieważ zazwyczaj chcesz, aby odpowiedź była poprawna dla dowolnego czasu w bieżącej dacie rozpoczęcia (często dzisiaj) i zawierała pełną datę zakończenia (np. termin).
UWAGA: Ten kod wymaga dodatkowej korekty na dni wolne, ale zgodnie z powyższym założeniem, kod ten musi wykluczać dni wolne w dniu rozpoczęcia.
Dodaj dni tygodnia:
private static readonly int[,] _addOffset = { // 0 1 2 3 4 {0, 1, 2, 3, 4}, // Su 0 {0, 1, 2, 3, 4}, // M 1 {0, 1, 2, 3, 6}, // Tu 2 {0, 1, 4, 5, 6}, // W 3 {0, 1, 4, 5, 6}, // Th 4 {0, 3, 4, 5, 6}, // F 5 {0, 2, 3, 4, 5}, // Sa 6 }; public static DateTime AddWeekdays(this DateTime date, int weekdays) { int extraDays = weekdays % 5; int addDays = weekdays >= 0 ? (weekdays / 5) * 7 + _addOffset[(int)date.DayOfWeek, extraDays] : (weekdays / 5) * 7 - _addOffset[6 - (int)date.DayOfWeek, -extraDays]; return date.AddDays(addDays); }
Oblicz różnicę w dni robocze:
static readonly int[,] _diffOffset = { // Su M Tu W Th F Sa {0, 1, 2, 3, 4, 5, 5}, // Su {4, 0, 1, 2, 3, 4, 4}, // M {3, 4, 0, 1, 2, 3, 3}, // Tu {2, 3, 4, 0, 1, 2, 2}, // W {1, 2, 3, 4, 0, 1, 1}, // Th {0, 1, 2, 3, 4, 0, 0}, // F {0, 1, 2, 3, 4, 5, 0}, // Sa }; public static int GetWeekdaysDiff(this DateTime dtStart, DateTime dtEnd) { int daysDiff = (int)(dtEnd - dtStart).TotalDays; return daysDiff >= 0 ? 5 * (daysDiff / 7) + _diffOffset[(int) dtStart.DayOfWeek, (int) dtEnd.DayOfWeek] : 5 * (daysDiff / 7) - _diffOffset[6 - (int) dtStart.DayOfWeek, 6 - (int) dtEnd.DayOfWeek]; }
Odkryłem, że większość innych rozwiązań dotyczących przepełnienia stosu była albo powolna (iteracyjna), albo zbyt skomplikowana, a wiele z nich było po prostu błędnych. Morał z tej historii jest taki ... Nie ufaj jej, jeśli nie przetestujesz jej dokładnie !!
Testy jednostkowe oparte na testach NUnit Combinatorial i rozszerzeniu ShouldBe NUnit.
[TestFixture] public class DateTimeExtensionsTests { /// <summary> /// Exclude start date, Include end date /// </summary> /// <param name="dtStart"></param> /// <param name="dtEnd"></param> /// <returns></returns> private IEnumerable<DateTime> GetDateRange(DateTime dtStart, DateTime dtEnd) { Console.WriteLine(@"dtStart={0:yy-MMM-dd ddd}, dtEnd={1:yy-MMM-dd ddd}", dtStart, dtEnd); TimeSpan diff = dtEnd - dtStart; Console.WriteLine(diff); if (dtStart <= dtEnd) { for (DateTime dt = dtStart.AddDays(1); dt <= dtEnd; dt = dt.AddDays(1)) { Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", dt); yield return dt; } } else { for (DateTime dt = dtStart.AddDays(-1); dt >= dtEnd; dt = dt.AddDays(-1)) { Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", dt); yield return dt; } } } [Test, Combinatorial] public void TestGetWeekdaysDiff( [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)] int startDay, [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)] int endDay, [Values(7)] int startMonth, [Values(7)] int endMonth) { // Arrange DateTime dtStart = new DateTime(2016, startMonth, startDay); DateTime dtEnd = new DateTime(2016, endMonth, endDay); int nDays = GetDateRange(dtStart, dtEnd) .Count(dt => dt.DayOfWeek != DayOfWeek.Saturday && dt.DayOfWeek != DayOfWeek.Sunday); if (dtEnd < dtStart) nDays = -nDays; Console.WriteLine(@"countBusDays={0}", nDays); // Act / Assert dtStart.GetWeekdaysDiff(dtEnd).ShouldBe(nDays); } [Test, Combinatorial] public void TestAddWeekdays( [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)] int startDay, [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)] int weekdays) { DateTime dtStart = new DateTime(2016, 7, startDay); DateTime dtEnd1 = dtStart.AddWeekdays(weekdays); // ADD dtStart.GetWeekdaysDiff(dtEnd1).ShouldBe(weekdays); DateTime dtEnd2 = dtStart.AddWeekdays(-weekdays); // SUBTRACT dtStart.GetWeekdaysDiff(dtEnd2).ShouldBe(-weekdays); } }
źródło
Oto kod do tego celu, ze szwedzkimi świętami, ale możesz dostosować, które święta się liczyć. Zauważ, że dodałem limit, który możesz chcieć usunąć, ale był to system internetowy i nie chciałem, aby ktokolwiek wprowadzał jakąś wielką datę, aby uciąć ten proces
public static int GetWorkdays(DateTime from ,DateTime to) { int limit = 9999; int counter = 0; DateTime current = from; int result = 0; if (from > to) { DateTime temp = from; from = to; to = temp; } if (from >= to) { return 0; } while (current <= to && counter < limit) { if (IsSwedishWorkday(current)) { result++; } current = current.AddDays(1); counter++; } return result; } public static bool IsSwedishWorkday(DateTime date) { return (!IsSwedishHoliday(date) && date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday); } public static bool IsSwedishHoliday(DateTime date) { return ( IsSameDay(GetEpiphanyDay(date.Year), date) || IsSameDay(GetMayDay(date.Year), date) || IsSameDay(GetSwedishNationalDay(date.Year), date) || IsSameDay(GetChristmasDay(date.Year), date) || IsSameDay(GetBoxingDay(date.Year), date) || IsSameDay(GetGoodFriday(date.Year), date) || IsSameDay(GetAscensionDay(date.Year), date) || IsSameDay(GetAllSaintsDay(date.Year), date) || IsSameDay(GetMidsummersDay(date.Year), date) || IsSameDay(GetPentecostDay(date.Year), date) || IsSameDay(GetEasterMonday(date.Year), date) || IsSameDay(GetNewYearsDay(date.Year), date) || IsSameDay(GetEasterDay(date.Year), date) ); } // Trettondagen public static DateTime GetEpiphanyDay(int year) { return new DateTime(year, 1, 6); } // Första maj public static DateTime GetMayDay(int year) { return new DateTime(year,5,1); } // Juldagen public static DateTime GetSwedishNationalDay(int year) { return new DateTime(year, 6, 6); } // Juldagen public static DateTime GetNewYearsDay(int year) { return new DateTime(year,1,1); } // Juldagen public static DateTime GetChristmasDay(int year) { return new DateTime(year,12,25); } // Annandag jul public static DateTime GetBoxingDay(int year) { return new DateTime(year, 12, 26); } // Långfredagen public static DateTime GetGoodFriday(int year) { return GetEasterDay(year).AddDays(-3); } // Kristi himmelsfärdsdag public static DateTime GetAscensionDay(int year) { return GetEasterDay(year).AddDays(5*7+4); } // Midsommar public static DateTime GetAllSaintsDay(int year) { DateTime result = new DateTime(year,10,31); while (result.DayOfWeek != DayOfWeek.Saturday) { result = result.AddDays(1); } return result; } // Midsommar public static DateTime GetMidsummersDay(int year) { DateTime result = new DateTime(year, 6, 20); while (result.DayOfWeek != DayOfWeek.Saturday) { result = result.AddDays(1); } return result; } // Pingstdagen public static DateTime GetPentecostDay(int year) { return GetEasterDay(year).AddDays(7 * 7); } // Annandag påsk public static DateTime GetEasterMonday(int year) { return GetEasterDay(year).AddDays(1); } public static DateTime GetEasterDay(int y) { double c; double n; double k; double i; double j; double l; double m; double d; c = System.Math.Floor(y / 100.0); n = y - 19 * System.Math.Floor(y / 19.0); k = System.Math.Floor((c - 17) / 25.0); i = c - System.Math.Floor(c / 4) - System.Math.Floor((c - k) / 3) + 19 * n + 15; i = i - 30 * System.Math.Floor(i / 30); i = i - System.Math.Floor(i / 28) * (1 - System.Math.Floor(i / 28) * System.Math.Floor(29 / (i + 1)) * System.Math.Floor((21 - n) / 11)); j = y + System.Math.Floor(y / 4.0) + i + 2 - c + System.Math.Floor(c / 4); j = j - 7 * System.Math.Floor(j / 7); l = i - j; m = 3 + System.Math.Floor((l + 40) / 44);// month d = l + 28 - 31 * System.Math.Floor(m / 4);// day double days = ((m == 3) ? d : d + 31); DateTime result = new DateTime(y, 3, 1).AddDays(days-1); return result; }
źródło
Oto krótki przykładowy kod. Jest to metoda klasowa, więc będzie działać tylko w Twojej klasie. Jeśli chcesz
static
, zmień podpis naprivate static
(lubpublic static
).private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed) { for (var d = sd; d <= ed; d = d.AddDays(1)) if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday) yield return d; }
Ta metoda tworzy zmienną pętli
d
, inicjuje ją na dzień rozpoczęciasd
, a następnie zwiększa o jeden dzień w każdej iteracji (d = d.AddDays(1)
).Zwraca żądane wartości za pomocą
yield
, co tworzy plikiterator
. Fajną rzeczą w iteratorach jest to, że nie przechowują one wszystkich wartościIEnumerable
w pamięci, a jedynie wywołują je sekwencyjnie. Oznacza to, że możesz wywoływać tę metodę od zarania dziejów do teraz bez martwienia się o wyczerpanie pamięci.źródło
Dużo szukałem łatwego do przyswojenia algorytmu do obliczania dni roboczych pomiędzy 2 datami, a także wykluczania świąt państwowych i ostatecznie decyduję się na takie podejście:
public static int NumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays) { var dic = new Dictionary<DateTime, DayOfWeek>(); var totalDays = (due - start).Days; for (int i = 0; i < totalDays + 1; i++) { if (!holidays.Any(x => x == start.AddDays(i))) dic.Add(start.AddDays(i), start.AddDays(i).DayOfWeek); } return dic.Where(x => x.Value != DayOfWeek.Saturday && x.Value != DayOfWeek.Sunday).Count(); }
Zasadniczo chciałem iść z każdą datą i ocenić moje warunki:
ale także chciałem uniknąć iteracji dat.
Uruchamiając i mierząc czas potrzebny do oceny 1 pełnego roku, otrzymuję następujący wynik:
static void Main(string[] args) { var start = new DateTime(2017, 1, 1); var due = new DateTime(2017, 12, 31); var sw = Stopwatch.StartNew(); var days = NumberOfWorkingDaysBetween2Dates(start, due,NationalHolidays()); sw.Stop(); Console.WriteLine($"Total working days = {days} --- time: {sw.Elapsed}"); Console.ReadLine(); // result is: // Total working days = 249-- - time: 00:00:00.0269087 }
Edycja: nowa metoda prostsza:
public static int ToBusinessWorkingDays(this DateTime start, DateTime due, DateTime[] holidays) { return Enumerable.Range(0, (due - start).Days) .Select(a => start.AddDays(a)) .Where(a => a.DayOfWeek != DayOfWeek.Sunday) .Where(a => a.DayOfWeek != DayOfWeek.Saturday) .Count(a => !holidays.Any(x => x == a)); }
źródło
Myślę, że żadna z powyższych odpowiedzi nie jest w rzeczywistości poprawna. Żaden z nich nie rozwiązuje wszystkich szczególnych przypadków, takich jak kiedy daty zaczynają się i kończą w środku weekendu, kiedy data zaczyna się w piątek, a kończy w następny poniedziałek, itd. Poza tym wszystkie zaokrągla obliczenia do całości dni, więc jeśli data rozpoczęcia przypada na przykład w środku soboty, odejmie cały dzień od dni roboczych, dając błędne wyniki ...
W każdym razie, oto moje rozwiązanie, które jest dość wydajne i proste i działa we wszystkich przypadkach. Sztuczka polega po prostu na znalezieniu poprzedniego poniedziałku jako dat rozpoczęcia i zakończenia, a następnie dokonaniu niewielkiej rekompensaty, gdy początek i koniec mają miejsce w weekend:
public double WorkDays(DateTime startDate, DateTime endDate){ double weekendDays; double days = endDate.Subtract(startDate).TotalDays; if(days<0) return 0; DateTime startMonday = startDate.AddDays(DayOfWeek.Monday - startDate.DayOfWeek).Date; DateTime endMonday = endDate.AddDays(DayOfWeek.Monday - endDate.DayOfWeek).Date; weekendDays = ((endMonday.Subtract(startMonday).TotalDays) / 7) * 2; // compute fractionary part of weekend days double diffStart = startDate.Subtract(startMonday).TotalDays - 5; double diffEnd = endDate.Subtract(endMonday).TotalDays - 5; // compensate weekenddays if(diffStart>0) weekendDays -= diffStart; if(diffEnd>0) weekendDays += diffEnd; return days - weekendDays; }
źródło
using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { DateTime start = new DateTime(2014, 1, 1); DateTime stop = new DateTime(2014, 12, 31); int totalWorkingDays = GetNumberOfWorkingDays(start, stop); Console.WriteLine("There are {0} working days.", totalWorkingDays); } private static int GetNumberOfWorkingDays(DateTime start, DateTime stop) { TimeSpan interval = stop - start; int totalWeek = interval.Days / 7; int totalWorkingDays = 5 * totalWeek; int remainingDays = interval.Days % 7; for (int i = 0; i <= remainingDays; i++) { DayOfWeek test = (DayOfWeek)(((int)start.DayOfWeek + i) % 7); if (test >= DayOfWeek.Monday && test <= DayOfWeek.Friday) totalWorkingDays++; } return totalWorkingDays; } } }
źródło
Działa i bez pętli
Ta metoda nie używa żadnych pętli i jest w rzeczywistości dość prosta. Rozszerza zakres dat do pełnych tygodni, ponieważ wiemy, że każdy tydzień ma 5 dni roboczych. Następnie używa tabeli przeglądowej, aby znaleźć liczbę dni roboczych do odjęcia od początku i końca, aby uzyskać właściwy wynik. Rozszerzyłem obliczenia, aby pomóc pokazać, co się dzieje, ale w razie potrzeby całość można skondensować w jednej linii.
W każdym razie to działa dla mnie, więc pomyślałem, że opublikuję to tutaj, na wypadek, gdyby mogło to pomóc innym. Miłego kodowania.
Obliczenie
Kultura
Kod zakłada tydzień pracy od poniedziałku do piątku. W przypadku innych kultur, takich jak od niedzieli do czwartku, należy przesunąć daty przed obliczeniem.
metoda
public int Weekdays(DateTime min, DateTime max) { if (min.Date > max.Date) throw new Exception("Invalid date span"); var t = (max.AddDays(1).Date - min.Date).TotalDays; var a = (int) min.DayOfWeek; var b = 6 - (int) max.DayOfWeek; var k = 1.4; var m = new int[]{0, 0, 1, 2, 3, 4, 5}; var c = m[a] + m[b]; return (int)((t + a + b) / k) - c; }
źródło
Po prostu podzielę się moim rozwiązaniem. U mnie zadziałało, może po prostu nie zauważam / nie wiem, że jest błąd. Zacząłem od pierwszego niepełnego tygodnia, jeśli taki istnieje. pełny tydzień przypadał od niedzieli do soboty, więc jeśli (int) _now.DayOfWeek nie był równy 0 (niedziela), pierwszy tydzień był niekompletny.
Po prostu odejmuję 1 do liczby pierwszych tygodni dla soboty pierwszego tygodnia, a następnie dodaję ją do nowej liczby;
Następnie otrzymuję ostatni niekompletny tydzień, potem odejmuję 1 na niedzielę, a następnie dodaję do nowej liczby.
Wreszcie, liczba pełnych tygodni pomnożona przez 5 (dni tygodnia) została dodana do nowej liczby.
public int RemoveNonWorkingDays(int numberOfDays){ int workingDays = 0; int firstWeek = 7 - (int)_now.DayOfWeek; if(firstWeek < 7){ if(firstWeek > numberOfDays) return numberOfDays; workingDays += firstWeek-1; numberOfDays -= firstWeek; } int lastWeek = numberOfDays % 7; if(lastWeek > 0){ numberOfDays -= lastWeek; workingDays += lastWeek - 1; } workingDays += (numberOfDays/7)*5; return workingDays; }
źródło
Miałem problem ze znalezieniem solidnej wersji TSQL tego kodu. Poniżej znajduje się zasadniczo konwersja kodu C # tutaj z dodatkiem tabeli świąt, której należy użyć do wstępnego obliczenia dni wolnych.
CREATE TABLE dbo.Holiday ( HolidayDt DATE NOT NULL, Name NVARCHAR(50) NOT NULL, IsWeekday BIT NOT NULL, CONSTRAINT PK_Holiday PRIMARY KEY (HolidayDt) ) GO CREATE INDEX IDX_Holiday ON Holiday (HolidayDt, IsWeekday) GO CREATE function dbo.GetBusinessDays ( @FirstDay datetime, @LastDay datetime ) RETURNS INT AS BEGIN DECLARE @BusinessDays INT, @FullWeekCount INT SELECT @FirstDay = CONVERT(DATETIME,CONVERT(DATE,@FirstDay)) , @LastDay = CONVERT(DATETIME,CONVERT(DATE,@LastDay)) IF @FirstDay > @LastDay RETURN NULL; SELECT @BusinessDays = DATEDIFF(DAY, @FirstDay, @LastDay) + 1 SELECT @FullWeekCount = @BusinessDays / 7; -- find out if there are weekends during the time exceedng the full weeks IF @BusinessDays > (@FullWeekCount * 7) BEGIN -- we are here to find out if there is a 1-day or 2-days weekend -- in the time interval remaining after subtracting the complete weeks DECLARE @firstDayOfWeek INT, @lastDayOfWeek INT; SELECT @firstDayOfWeek = DATEPART(DW, @FirstDay), @lastDayOfWeek = DATEPART(DW, @LastDay); IF @lastDayOfWeek < @firstDayOfWeek SELECT @lastDayOfWeek = @lastDayOfWeek + 7; IF @firstDayOfWeek <= 6 BEGIN IF (@lastDayOfWeek >= 7) --Both Saturday and Sunday are in the remaining time interval BEGIN SELECT @BusinessDays = @BusinessDays - 2 END ELSE IF @lastDayOfWeek>=6 --Only Saturday is in the remaining time interval BEGIN SELECT @BusinessDays = @BusinessDays - 1 END END ELSE IF @firstDayOfWeek <= 7 AND @lastDayOfWeek >=7 -- Only Sunday is in the remaining time interval BEGIN SELECT @BusinessDays = @BusinessDays - 1 END END -- subtract the weekends during the full weeks in the interval DECLARE @Holidays INT; SELECT @Holidays = COUNT(*) FROM Holiday WHERE HolidayDt BETWEEN @FirstDay AND @LastDay AND IsWeekday = CAST(1 AS BIT) SELECT @BusinessDays = @BusinessDays - (@FullWeekCount + @FullWeekCount) -- - @Holidays RETURN @BusinessDays END
źródło
int BusinessDayDifference(DateTime Date1, DateTime Date2) { int Sign = 1; if (Date2 > Date1) { Sign = -1; DateTime TempDate = Date1; Date1 = Date2; Date2 = TempDate; } int BusDayDiff = (int)(Date1.Date - Date2.Date).TotalDays; if (Date1.DayOfWeek == DayOfWeek.Saturday) BusDayDiff -= 1; if (Date2.DayOfWeek == DayOfWeek.Sunday) BusDayDiff -= 1; int Week1 = GetWeekNum(Date1); int Week2 = GetWeekNum(Date2); int WeekDiff = Week1 - Week2; BusDayDiff -= WeekDiff * 2; foreach (DateTime Holiday in Holidays) if (Date1 >= Holiday && Date2 <= Holiday) BusDayDiff--; BusDayDiff *= Sign; return BusDayDiff; } private int GetWeekNum(DateTime Date) { return (int)(Date.AddDays(-(int)Date.DayOfWeek).Ticks / TimeSpan.TicksPerDay / 7); }
źródło
Oto jedno bardzo proste rozwiązanie tego problemu. Mamy datę początkową, datę końcową i „pętlę for” do wydłużania dnia i obliczania, czy jest to dzień roboczy, czy weekend, konwertując na ciąg DayOfWeek.
class Program { static void Main(string[] args) { DateTime day = new DateTime(); Console.Write("Inser your end date (example: 01/30/2015): "); DateTime endDate = DateTime.Parse(Console.ReadLine()); int numberOfDays = 0; for (day = DateTime.Now.Date; day.Date < endDate.Date; day = day.Date.AddDays(1)) { string dayToString = Convert.ToString(day.DayOfWeek); if (dayToString != "Saturday" && dayToString != "Sunday") numberOfDays++; } Console.WriteLine("Number of working days (not including local holidays) between two dates is "+numberOfDays); } }
źródło
Na podstawie komentarza oznaczonego jako odpowiedź i zalecana łatka, a także -> Ta wersja chce przekonwertować dni na godziny pracy ... Uwzględnia również godziny tego samego dnia.
/// <summary> /// Calculates number of business days, taking into account: /// - weekends (Saturdays and Sundays) /// - bank holidays in the middle of the week /// </summary> /// <param name="firstDay">First day in the time interval</param> /// <param name="lastDay">Last day in the time interval</param> /// <param name="bankHolidays">List of bank holidays excluding weekends</param> /// <returns>Number of business hours during the 'span'</returns> public static int BusinessHoursUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays) { var original_firstDay = firstDay; var original_lastDay = lastDay; firstDay = firstDay.Date; lastDay = lastDay.Date; if (firstDay > lastDay) return -1; //// throw new ArgumentException("Incorrect last day " + lastDay); TimeSpan span = lastDay - firstDay; int businessDays = span.Days + 1; int fullWeekCount = businessDays / 7; // find out if there are weekends during the time exceedng the full weeks if (businessDays > fullWeekCount * 7) { // we are here to find out if there is a 1-day or 2-days weekend // in the time interval remaining after subtracting the complete weeks int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek; int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek; if (lastDayOfWeek < firstDayOfWeek) lastDayOfWeek += 7; if (firstDayOfWeek <= 6) { if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval businessDays -= 2; else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval businessDays -= 1; } else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval businessDays -= 1; } // subtract the weekends during the full weeks in the interval businessDays -= fullWeekCount + fullWeekCount; if (bankHolidays != null && bankHolidays.Any()) { // subtract the number of bank holidays during the time interval foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; if (firstDay <= bh && bh <= lastDay) --businessDays; } } int total_business_hours = 0; if (firstDay.Date == lastDay.Date) {//If on the same day, go granular with Hours from the Orginial_*Day values total_business_hours = (int)(original_lastDay - original_firstDay).TotalHours; } else {//Convert Business-Days to TotalHours total_business_hours = (int)(firstDay.AddDays(businessDays).AddHours(firstDay.Hour) - firstDay).TotalHours; } return total_business_hours; }
źródło
Właśnie poprawiłem odpowiedź @Alexander i @Slauma, aby wspierać tydzień biznesowy jako parametr, w przypadkach, w których sobota jest dniem roboczym, a nawet w przypadkach, w których jest tylko kilka dni tygodnia, które są uważane za dni robocze:
/// <summary> /// Calculate the number of business days between two dates, considering: /// - Days of the week that are not considered business days. /// - Holidays between these two dates. /// </summary> /// <param name="fDay">First day of the desired 'span'.</param> /// <param name="lDay">Last day of the desired 'span'.</param> /// <param name="BusinessDaysOfWeek">Days of the week that are considered to be business days, if NULL considers monday, tuesday, wednesday, thursday and friday as business days of the week.</param> /// <param name="Holidays">Holidays, if NULL, considers no holiday.</param> /// <returns>Number of business days during the 'span'</returns> public static int BusinessDaysUntil(this DateTime fDay, DateTime lDay, DayOfWeek[] BusinessDaysOfWeek = null, DateTime[] Holidays = null) { if (BusinessDaysOfWeek == null) BusinessDaysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }; if (Holidays == null) Holidays = new DateTime[] { }; fDay = fDay.Date; lDay = lDay.Date; if (fDay > lDay) throw new ArgumentException("Incorrect last day " + lDay); int bDays = (lDay - fDay).Days + 1; int fullWeekCount = bDays / 7; int fullWeekCountMult = 7 - WeekDays.Length; // Find out if there are weekends during the time exceedng the full weeks if (bDays > (fullWeekCount * 7)) { int fDayOfWeek = (int)fDay.DayOfWeek; int lDayOfWeek = (int)lDay.DayOfWeek; if (fDayOfWeek > lDayOfWeek) lDayOfWeek += 7; // If they are the same, we already covered it right before the Holiday subtraction if (lDayOfWeek != fDayOfWeek) { // Here we need to see if any of the days between are considered business days for (int i = fDayOfWeek; i <= lDayOfWeek; i++) if (!WeekDays.Contains((DayOfWeek)(i > 6 ? i - 7 : i))) bDays -= 1; } } // Subtract the days that are not in WeekDays[] during the full weeks in the interval bDays -= (fullWeekCount * fullWeekCountMult); // Subtract the number of bank holidays during the time interval bDays = bDays - Holidays.Select(x => x.Date).Count(x => fDay <= x && x <= lDay); return bDays; }
źródło
Oto funkcja, której możemy użyć do obliczenia dni roboczych między dwiema datami. Nie używam listy wakacyjnej, ponieważ może się ona różnić w zależności od kraju / regionu.
Jeśli i tak chcemy go użyć, możemy przyjąć trzeci argument jako listę wakacji i przed zwiększeniem liczby powinniśmy sprawdzić, czy lista nie zawiera d
public static int GetBussinessDaysBetweenTwoDates(DateTime StartDate, DateTime EndDate) { if (StartDate > EndDate) return -1; int bd = 0; for (DateTime d = StartDate; d < EndDate; d = d.AddDays(1)) { if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday) bd++; } return bd; }
źródło
Uważam, że może to być prostszy sposób:
public int BusinessDaysUntil(DateTime start, DateTime end, params DateTime[] bankHolidays) { int tld = (int)((end - start).TotalDays) + 1; //including end day int not_buss_day = 2 * (tld / 7); //Saturday and Sunday int rest = tld % 7; //rest. if (rest > 0) { int tmp = (int)start.DayOfWeek - 1 + rest; if (tmp == 6 || start.DayOfWeek == DayOfWeek.Sunday) not_buss_day++; else if (tmp > 6) not_buss_day += 2; } foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; if (!(bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) && (start <= bh && bh <= end)) { not_buss_day++; } } return tld - not_buss_day; }
źródło
Oto kolejny pomysł - ta metoda pozwala określić dowolny tydzień pracy i święta.
Chodzi o to, że znajdujemy rdzeń zakresu dat od pierwszego roboczego dnia tygodnia do ostatniego weekendowego dnia tygodnia. Dzięki temu możemy łatwo obliczyć całe tygodnie ( bez iteracji po wszystkich datach). Wszystko, co musimy wtedy zrobić, to dodać dni robocze przypadające przed początkiem i końcem tego podstawowego zakresu.
public static int CalculateWorkingDays( DateTime startDate, DateTime endDate, IList<DateTime> holidays, DayOfWeek firstDayOfWeek, DayOfWeek lastDayOfWeek) { // Make sure the defined working days run contiguously if (lastDayOfWeek < firstDayOfWeek) { throw new Exception("Last day of week cannot fall before first day of week!"); } // Create a list of the days of the week that make-up the weekend by working back // from the firstDayOfWeek and forward from lastDayOfWeek to get the start and end // the weekend var weekendStart = lastDayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : lastDayOfWeek + 1; var weekendEnd = firstDayOfWeek == DayOfWeek.Sunday ? DayOfWeek.Saturday : firstDayOfWeek - 1; var weekendDays = new List<DayOfWeek>(); var w = weekendStart; do { weekendDays.Add(w); if (w == weekendEnd) break; w = (w == DayOfWeek.Saturday) ? DayOfWeek.Sunday : w + 1; } while (true); // Force simple dates - no time startDate = startDate.Date; endDate = endDate.Date; // Ensure a progessive date range if (endDate < startDate) { var t = startDate; startDate = endDate; endDate = t; } // setup some working variables and constants const int daysInWeek = 7; // yeah - really! var actualStartDate = startDate; // this will end up on startOfWeek boundary var actualEndDate = endDate; // this will end up on weekendEnd boundary int workingDaysInWeek = daysInWeek - weekendDays.Count; int workingDays = 0; // the result we are trying to find int leadingDays = 0; // the number of working days leading up to the firstDayOfWeek boundary int trailingDays = 0; // the number of working days counting back to the weekendEnd boundary // Calculate leading working days // if we aren't on the firstDayOfWeek we need to step forward to the nearest if (startDate.DayOfWeek != firstDayOfWeek) { var d = startDate; do { if (d.DayOfWeek == firstDayOfWeek || d >= endDate) { actualStartDate = d; break; } if (!weekendDays.Contains(d.DayOfWeek)) { leadingDays++; } d = d.AddDays(1); } while(true); } // Calculate trailing working days // if we aren't on the weekendEnd we step back to the nearest if (endDate >= actualStartDate && endDate.DayOfWeek != weekendEnd) { var d = endDate; do { if (d.DayOfWeek == weekendEnd || d < actualStartDate) { actualEndDate = d; break; } if (!weekendDays.Contains(d.DayOfWeek)) { trailingDays++; } d = d.AddDays(-1); } while(true); } // Calculate the inclusive number of days between the actualStartDate and the actualEndDate var coreDays = (actualEndDate - actualStartDate).Days + 1; var noWeeks = coreDays / daysInWeek; // add together leading, core and trailing days workingDays += noWeeks * workingDaysInWeek; workingDays += leadingDays; workingDays += trailingDays; // Finally remove any holidays that fall within the range. if (holidays != null) { workingDays -= holidays.Count(h => h >= startDate && (h <= endDate)); } return workingDays; }
źródło
Ponieważ nie mogę komentować. Jest jeszcze jeden problem z przyjętym rozwiązaniem, w którym dni wolne od pracy są odejmowane, nawet jeśli przypadają w weekend. Widząc, jak sprawdzane są inne dane wejściowe, wystarczy, że tak jest.
Dlatego foreach powinno być:
// subtract the number of bank holidays during the time interval foreach (DateTime bankHoliday in bankHolidays) { DateTime bh = bankHoliday.Date; // Do not subtract bank holidays when they fall in the weekend to avoid double subtraction if (bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) continue; if (firstDay <= bh && bh <= lastDay) --businessDays; }
źródło
Oto podejście, jeśli używasz MVC. Obliczyłem również święta państwowe lub inne dni świąteczne, które mają zostać wykluczone, pobierając je z kalendarza świątecznego, który będziesz musiał utworzyć.
foreach (DateTime day in EachDay(model)) { bool key = false; foreach (LeaveModel ln in holidaycalendar) { if (day.Date == ln.Date && day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday) { key = true; break; } } if (day.DayOfWeek == DayOfWeek.Saturday || day.DayOfWeek == DayOfWeek.Sunday) { key = true; } if (key != true) { leavecount++; } }
Leavemodel to lista tutaj
źródło
Oto funkcja pomocnicza, którą napisałem dla tego zadania.
zwraca również liczbę weekendów za pośrednictwem
out
parametru.jeśli chcesz, możesz dostosować dni „weekendowe” w czasie wykonywania dla krajów, które używają różnych dni weekendowych lub uwzględnić dni wolne od pracy za pomocą
weekendDays[]
opcjonalnego parametru:public static int GetNetworkDays(DateTime startDate, DateTime endDate,out int totalWeekenDays, DayOfWeek[] weekendDays = null) { if (startDate >= endDate) { throw new Exception("start date can not be greater then or equel to end date"); } DayOfWeek[] weekends = new DayOfWeek[] { DayOfWeek.Sunday, DayOfWeek.Saturday }; if (weekendDays != null) { weekends = weekendDays; } var totaldays = (endDate - startDate).TotalDays + 1; // add one to include the first day to var counter = 0; var workdaysCounter = 0; var weekendsCounter = 0; for (int i = 0; i < totaldays; i++) { if (weekends.Contains(startDate.AddDays(counter).DayOfWeek)) { weekendsCounter++; } else { workdaysCounter++; } counter++; } totalWeekenDays = weekendsCounter; return workdaysCounter; }
źródło
Wymyśliłem następujące rozwiązanie
var dateStart = new DateTime(2019,01,10); var dateEnd = new DateTime(2019,01,31); var timeBetween = (dateEnd - dateStart).TotalDays + 1; int numberOf7DayWeeks = (int)(timeBetween / 7); int numberOfWeekendDays = numberOf7DayWeeks * 2; int workingDays =(int)( timeBetween - numberOfWeekendDays); if(dateStart.DayOfWeek == DayOfWeek.Saturday || dateEnd.DayOfWeek == DayOfWeek.Sunday){ workingDays -=2; } if(dateStart.DayOfWeek == DayOfWeek.Sunday || dateEnd.DayOfWeek == DayOfWeek.Saturday){ workingDays -=1; }
źródło
Wystarczy powtórzyć każdy dzień w zakresie czasu i odjąć dzień od licznika, jeśli jest to sobota lub niedziela.
private float SubtractWeekend(DateTime start, DateTime end) { float totaldays = (end.Date - start.Date).Days; var iterationVal = totalDays; for (int i = 0; i <= iterationVal; i++) { int dayVal = (int)start.Date.AddDays(i).DayOfWeek; if(dayVal == 6 || dayVal == 0) { // saturday or sunday totalDays--; } } return totalDays; }
źródło
public static int CalculateBusinessDaysInRange(this DateTime startDate, DateTime endDate, params DateTime[] holidayDates) { endDate = endDate.Date; if(startDate > endDate) throw new ArgumentException("The end date can not be before the start date!", nameof(endDate)); int accumulator = 0; DateTime itterator = startDate.Date; do { if(itterator.DayOfWeek != DayOfWeek.Saturday && itterator.DayOfWeek != DayOfWeek.Sunday && !holidayDates.Any(hol => hol.Date == itterator)) { accumulator++; } } while((itterator = itterator.AddDays(1)).Date <= endDate); return accumulator }
Publikuję to tylko dlatego, że pomimo wszystkich doskonałych odpowiedzi, które zostały podane, żadna z matematyki nie miała dla mnie sensu. Jest to zdecydowanie metoda KISS, która powinna działać i być dość łatwa do utrzymania. Przyznane, jeśli obliczasz zakresy większe niż 2-3 miesiące, nie będzie to najbardziej efektywny sposób. Po prostu określamy, czy jest to sobota czy niedziela, czy też data jest daną datą świąteczną. Jeśli tak nie jest, dodajemy dzień roboczy. Jeśli tak, wszystko jest w porządku.
Jestem pewien, że może to być jeszcze bardziej uproszczone dzięki LINQ, ale w ten sposób jest znacznie łatwiejsze do zrozumienia.
źródło
Jeszcze inne podejście do obliczania dni roboczych, bez uwzględnienia świąt, ale uwzględniające porę dnia zwracającą ułamkową liczbę dni:
public static double GetBusinessDays(DateTime startD, DateTime endD) { while (IsWeekend(startD)) startD = startD.Date.AddDays(1); while (IsWeekend(endD)) endD = endD.Date.AddDays(-1); var bussDays = (endD - startD).TotalDays - (2 * ((int)(endD - startD).TotalDays / 7)) - (startD.DayOfWeek > endD.DayOfWeek ? 2 : 0); return bussDays; } public static bool IsWeekend(DateTime d) { return d.DayOfWeek == DayOfWeek.Saturday || d.DayOfWeek == DayOfWeek.Sunday; }
Możesz się nim bawić tutaj: https://rextester.com/ASHRS53997
źródło
To jest ogólne rozwiązanie.
startdayvalue to numer dnia w dniu rozpoczęcia.
weekendday_1 to numer dnia końca tygodnia.
numer dnia - PON - 1, WT - 2, ... SOB - 6, NIEDZ -7.
różnica to różnica między dwiema datami.
Przykład: Data rozpoczęcia: 4 kwietnia 2013 r., Data zakończenia: 14 kwietnia 2013 r
Różnica: 10, startday, wartość: 4, weekend_1: 7 (jeśli NIEDZIELA to weekend dla Ciebie).
To da ci liczbę wakacji.
Liczba dni roboczych = (różnica + 1) - święto 1
if (startdayvalue > weekendday_1) { if (difference > ((7 - startdayvalue) + weekendday_1)) { holiday1 = (difference - ((7 - startdayvalue) + weekendday_1)) / 7; holiday1 = holiday1 + 1; } else { holiday1 = 0; } } else if (startdayvalue < weekendday_1) { if (difference > (weekendday_1 - startdayvalue)) { holiday1 = (difference - (weekendday_1 - startdayvalue)) / 7; holiday1 = holiday1 + 1; } else if (difference == (weekendday_1 - startdayvalue)) { holiday1 = 1; } else { holiday1 = 0; } } else { holiday1 = difference / 7; holiday1 = holiday1 + 1; }
źródło