J'ai déjà eu une telle tâche et j'ai la solution. J'éviterais d'énumérer tous les jours entre les deux quand c'est évitable, ce qui est le cas ici. Je ne mentionne même pas la création d'un groupe d'instances DateTime, comme je l'ai vu dans l'une des réponses ci-dessus. C'est vraiment un gaspillage de puissance de traitement. Surtout dans la situation du monde réel, lorsque vous devez examiner des intervalles de temps de plusieurs mois. Voir mon code, avec des commentaires, ci-dessous.
/// <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(thisDateTime firstDay,DateTime lastDay,paramsDateTime[] bankHolidays){
firstDay = firstDay.Date;
lastDay = lastDay.Date;if(firstDay > lastDay)thrownewArgumentException("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;}
Edit par Slauma, août 2011
Très bonne réponse! Il y a cependant un petit bug. Je prends la liberté d'éditer cette réponse puisque le répondeur est absent depuis 2009.
Le code ci-dessus suppose qu'il DayOfWeek.Sundaya la valeur 7qui n'est pas le cas. La valeur est en fait 0. Cela conduit à un calcul erroné si par exemple firstDayet lastDaysont tous les deux le même dimanche. La méthode retourne 1dans ce cas, mais elle devrait l'être 0.
Solution la plus simple pour ce bogue: Remplacez dans le code au-dessus des lignes où firstDayOfWeeket lastDayOfWeeksont déclarés par ce qui suit:
int firstDayOfWeek = firstDay.DayOfWeek==DayOfWeek.Sunday?7:(int)firstDay.DayOfWeek;int lastDayOfWeek = lastDay.DayOfWeek==DayOfWeek.Sunday?7:(int)lastDay.DayOfWeek;
+1 C'est probablement le moyen le plus simple et le plus efficace de le faire (ma solution issue de C ++ n'utilise pas le support de TimeSpan, C # rend certaines tâches tellement plus faciles). Le bankHolidays est également une bonne idée!
RedGlyph
2
Assurez-vous également que les jours fériés sont les suivants: if (firstDay <= bh && bh <= lastDay && bh.IsWorkingDay ())
Tawani
5
Merci pour la méthode. Bien que, je devais ajouter ce qui suit à l'instruction if de soustraction / itération des jours fériés:, && !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)sinon il soustrait le même jour deux fois, si un jour férié tombe dans un week-end.
KristianB
J'ai changé la dernière boucle pour une déclaration Linq: businessDays - = bankHolidays.Select (bankHoliday => bankHoliday.Date) .Count (bh => firstDay <= bh && bh <= lastDay);
Bon travail, mais peut-être utiliser les énumérations DayOfWeek plutôt que les convertir en entiers?
Neo
3
Sérieusement, la meilleure solution là-bas. Cheers Alec
Mizmor
6
Notez que même si cette fonction renvoie un double, elle ne doit être approuvée que pour donner des jours ouvrables entiers . Il ne renvoie pas la bonne réponse pour les fractions de jours lorsque des heures sont impliquées.
Pakman
4
Juste pour faire remarquer, avec le «1+», il suppose le début du premier jour jusqu'à la fin du dernier jour, sans le «1+», il suppose la fin du premier jour jusqu'à la fin du dernier jour. Il m'a fallu un certain temps pour comprendre cela, car je supposais le début du premier jour jusqu'au début du dernier jour, ce qui avait plus de sens pour moi.
Jeffry van de Vuurst
11
Ce n'est PAS la bonne réponse. Les jours peuvent être jusqu'à 4. Presque juste, ne tient pas compte du moment où le jour de début et de fin se terminent autour du week-end, ce qui est le plus délicat. Le début - fin ne doit pas non plus être entre parenthèses. Cela n'a rien à voir avec le problème. 60% du temps, cette solution est FAUX .
Owl
47
Je sais que cette question est déjà résolue, mais j'ai pensé que je pourrais fournir une réponse plus simple qui pourrait aider d'autres visiteurs à l'avenir.
publicintGetWorkingDays(DateTimefrom,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;}
Beaucoup plus clair et les solutions énumérées se prêtent à l'élimination des jours fériés. Ils sont cependant beaucoup plus lents en vrac; Dans LINQPad, calculer les jours ouvrables pour des intervalles de 90 jours dans une boucle d'itération de 1 million prend 10 secondes en utilisant cette solution, et seulement environ 0,2 seconde en utilisant la réponse acceptée ou celle beaucoup plus agréable d'Alec Pojidaev.
Whelkaholism
Pour être inclusif, le code doit être: return Enumerable .Range (0, dayDifference + 1) ...
Edza
ne retourne pas des jours dans le passé. Comme -18 jours ouvrables.
iwtu
@iwtu Cela suppose que to > from. C'est peut-être le problème?
Alpha du
22
Définissez une méthode d'extension sur DateTime comme ceci:
Ensuite, utilisez is dans une clause Where pour filtrer une liste plus large de dates:
var allDates =GetDates();// method which returns a list of dates// filter dates by working day's var countOfWorkDays = allDates.Where(day => day.IsWorkingDay()).Count();
Ne vous contenteriez-vous pas d’aller de l’avant et de prolonger la durée pour pouvoir l’utiliser - car il a dit qu’il voulait utiliser la distance entre deux dates et non une liste de dates?
WesleyJohnson
La distance entre les deux dates est le nombre de jours entre les deux, donc le Count () est suffisant.
Carles Company
3
Je ne sais pas pourquoi c'est une réponse appropriée ... il n'a pas de liste de jours individuels, il a deux dates et il veut trouver le nombre de jours ouvrables entre eux. Pour utiliser cette solution, vous devez fournir une autre fonction qui produit une liste de toutes les dates entre les deux.
Adam Robinson
1
adam, ceci est un exemple simple avec la quantité minimale de code nécessaire pour démontrer un concept. Dans ma réponse originale, j'ai également inclus une boucle qui remplissait la liste allDates que j'ai depuis abstraite dans la fonction "GetDates". Le test IsWorkingDay peut facilement être déplacé hors de l'instruction LINQ et dans cette boucle. Personnellement, j'aime comment c'est maintenant parce que c'est très lisible par un humain quant à ce qui se passe.
Qwerty
10
Pourrait être court-circuité en changeant Where to Count et en éliminant Count
récursif
12
J'ai utilisé le code suivant pour prendre également en compte les jours fériés:
publicclassWorkingDays{publicList<DateTime>GetHolidays(){var client =newWebClient();var json = client.DownloadString("https://www.gov.uk/bank-holidays.json");var js =newJavaScriptSerializer();var holidays = js.Deserialize<Dictionary<string,Holidays>>(json);return holidays["england-and-wales"].events.Select(d => d.date).ToList();}publicintGetWorkingDays(DateTimefrom,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;}publicList<Event> events {get;set;}}publicclassEvent{publicDateTime date {get;set;}publicstring notes {get;set;}publicstring title {get;set;}}
Et tests unitaires:
[TestClass]publicclassWorkingDays{[TestMethod]publicvoidSameDayIsZero(){var service =newWorkingDays();varfrom=newDateTime(2013,8,12);Assert.AreEqual(0, service.GetWorkingDays(from,from));}[TestMethod]publicvoidCalculateDaysInWorkingWeek(){var service =newWorkingDays();varfrom=newDateTime(2013,8,12);var to =newDateTime(2013,8,16);Assert.AreEqual(4, service.GetWorkingDays(from, to),"Mon - Fri = 4");Assert.AreEqual(1, service.GetWorkingDays(from,newDateTime(2013,8,13)),"Mon - Tues = 1");}[TestMethod]publicvoidNotIncludeWeekends(){var service =newWorkingDays();varfrom=newDateTime(2013,8,9);var to =newDateTime(2013,8,16);Assert.AreEqual(5, service.GetWorkingDays(from, to),"Fri - Fri = 5");Assert.AreEqual(2, service.GetWorkingDays(from,newDateTime(2013,8,13)),"Fri - Tues = 2");Assert.AreEqual(1, service.GetWorkingDays(from,newDateTime(2013,8,12)),"Fri - Mon = 1");}[TestMethod]publicvoidAccountForHolidays(){var service =newWorkingDays();varfrom=newDateTime(2013,8,23);Assert.AreEqual(0, service.GetWorkingDays(from,newDateTime(2013,8,26)),"Fri - Mon = 0");Assert.AreEqual(1, service.GetWorkingDays(from,newDateTime(2013,8,27)),"Fri - Tues = 1");}}
pourquoi commencez-vous à compter en ajoutant 1 jours à "from" @ for (var date = from.AddDays (1); date <= to; date = date.AddDays (1))?
Oncel Umut TURER
6
Eh bien, cela a été battu à mort. :) Cependant, je vais toujours fournir une autre réponse car j'avais besoin de quelque chose d'un peu différent. Cette solution est différente en ce qu'elle renvoie un Business TimeSpan entre le début et la fin, et vous pouvez définir les heures d'ouverture de la journée et ajouter des jours fériés. Vous pouvez donc l'utiliser pour calculer si cela se produit dans une journée, sur plusieurs jours, pendant les week-ends et même les jours fériés. Et vous pouvez obtenir uniquement les jours ouvrables ou non en obtenant simplement ce dont vous avez besoin à partir de l'objet TimeSpan retourné. Et la façon dont il utilise les listes de jours, vous pouvez voir à quel point il serait très facile d'ajouter la liste des jours sans travail si ce n'est pas le samedi et le dimanche typiques. Et j'ai testé pendant un an, et ça me semble super rapide.
J'espère juste que le collage du code est exact. Mais je sais que ça marche.
publicstaticTimeSpanGetBusinessTimespanBetween(DateTime start,DateTime end,TimeSpan workdayStartTime,TimeSpan workdayEndTime,List<DateTime> holidays =null){if(end < start)thrownewArgumentException("start datetime must be before end datetime.");// Just create an empty list for easier coding.if(holidays ==null) holidays =newList<DateTime>();if(holidays.Where(x => x.TimeOfDay.Ticks>0).Any())thrownewArgumentException("holidays can not have a TimeOfDay, only the Date.");var nonWorkDays =newList<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 =newTimeSpan();// If start is during a non work day, it doesn't count.if(nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay =newTimeSpan();elseif(holidays.Contains(start.Date)) timeBeforeEndOfWorkDay =newTimeSpan();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 =newTimeSpan();// If end is during a non work day, it doesn't count.if(nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay =newTimeSpan();elseif(holidays.Contains(end.Date)) timeAfterStartOfWorkDay =newTimeSpan();// Easy scenario if the times are during the day day.if(start.Date.CompareTo(end.Date)==0){if(nonWorkDays.Contains(start.DayOfWeek))returnnewTimeSpan();elseif(holidays.Contains(start.Date))returnnewTimeSpan();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 +newTimeSpan(0,0, dailyWorkSecondsToAdd);return output;}}
Et voici le code de test: Notez qu'il vous suffit de mettre cette fonction dans une classe appelée DateHelper pour que le code de test fonctionne.
[TestMethod]publicvoidTestGetBusinessTimespanBetween(){var workdayStart =newTimeSpan(8,0,0);var workdayEnd =newTimeSpan(17,0,0);var holidays =newList<DateTime>(){newDateTime(2018,1,15),// a MondaynewDateTime(2018,2,15)// a Thursday};var testdata =new[]{new{
expectedMinutes =0,
start =newDateTime(2016,10,19,9,50,0),
end =newDateTime(2016,10,19,9,50,0)},new{
expectedMinutes =10,
start =newDateTime(2016,10,19,9,50,0),
end =newDateTime(2016,10,19,10,0,0)},new{
expectedMinutes =5,
start =newDateTime(2016,10,19,7,50,0),
end =newDateTime(2016,10,19,8,5,0)},new{
expectedMinutes =5,
start =newDateTime(2016,10,19,16,55,0),
end =newDateTime(2016,10,19,17,5,0)},new{
expectedMinutes =15,
start =newDateTime(2016,10,19,16,50,0),
end =newDateTime(2016,10,20,8,5,0)},new{
expectedMinutes =10,
start =newDateTime(2016,10,19,16,50,0),
end =newDateTime(2016,10,20,7,55,0)},new{
expectedMinutes =5,
start =newDateTime(2016,10,19,17,10,0),
end =newDateTime(2016,10,20,8,5,0)},new{
expectedMinutes =0,
start =newDateTime(2016,10,19,17,10,0),
end =newDateTime(2016,10,20,7,5,0)},new{
expectedMinutes =545,
start =newDateTime(2016,10,19,12,10,0),
end =newDateTime(2016,10,20,12,15,0)},// Spanning multiple weekdaysnew{
expectedMinutes =835,
start =newDateTime(2016,10,19,12,10,0),
end =newDateTime(2016,10,21,8,5,0)},// Spanning multiple weekdaysnew{
expectedMinutes =1375,
start =newDateTime(2016,10,18,12,10,0),
end =newDateTime(2016,10,21,8,5,0)},// Spanning from a Thursday to a Tuesday, 5 mins short of complete day.new{
expectedMinutes =1615,
start =newDateTime(2016,10,20,12,10,0),
end =newDateTime(2016,10,25,12,5,0)},// Spanning from a Thursday to a Tuesday, 5 mins beyond complete day.new{
expectedMinutes =1625,
start =newDateTime(2016,10,20,12,10,0),
end =newDateTime(2016,10,25,12,15,0)},// Spanning from a Friday to a Monday, 5 mins beyond complete day.new{
expectedMinutes =545,
start =newDateTime(2016,10,21,12,10,0),
end =newDateTime(2016,10,24,12,15,0)},// Spanning from a Friday to a Monday, 5 mins short complete day.new{
expectedMinutes =535,
start =newDateTime(2016,10,21,12,10,0),
end =newDateTime(2016,10,24,12,5,0)},// Spanning from a Saturday to a Monday, 5 mins short complete day.new{
expectedMinutes =245,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2016,10,24,12,5,0)},// Spanning from a Saturday to a Sunday, 5 mins beyond complete day.new{
expectedMinutes =0,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2016,10,23,12,15,0)},// Times within the same Saturday.new{
expectedMinutes =0,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2016,10,23,12,15,0)},// Spanning from a Saturday to the Sunday next week.new{
expectedMinutes =2700,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2016,10,30,12,15,0)},// Spanning a year.new{
expectedMinutes =143355,
start =newDateTime(2016,10,22,12,10,0),
end =newDateTime(2017,10,30,12,15,0)},// Spanning a year with 2 holidays.new{
expectedMinutes =142815,
start =newDateTime(2017,10,22,12,10,0),
end =newDateTime(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);}}
Cette solution évite les itérations, fonctionne à la fois pour les différences de jours de semaine + ve et -ve et comprend une suite de tests unitaires pour la régression par rapport à la méthode plus lente de comptage des jours de la semaine. J'ai également inclus une méthode concise pour ajouter des jours de la semaine fonctionne également de la même manière non itérative.
Les tests unitaires couvrent quelques milliers de combinaisons de dates afin de tester de manière exhaustive toutes les combinaisons de début / fin de semaine avec des plages de dates petites et grandes.
Important : Nous supposons que nous comptons les jours en excluant la date de début et en incluant la date de fin. Ceci est important lors du comptage des jours de la semaine car les jours de début / fin spécifiques que vous incluez / excluez affectent le résultat. Cela garantit également que la différence entre deux jours égaux est toujours égale à zéro et que nous n'incluons que les jours ouvrables complets, car vous souhaitez généralement que la réponse soit correcte à tout moment à la date de début actuelle (souvent aujourd'hui) et incluez la date de fin complète (par exemple une date d'échéance).
REMARQUE: Ce code nécessite un ajustement supplémentaire pour les jours fériés, mais conformément à l'hypothèse ci-dessus, ce code doit exclure les jours fériés à la date de début.
Ajouter des jours de la semaine:
privatestaticreadonlyint[,] _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};publicstaticDateTimeAddWeekdays(thisDateTime 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);}
Calculer la différence des jours de la semaine:
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(thisDateTime 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];}
J'ai trouvé que la plupart des autres solutions sur le débordement de pile étaient soit lentes (itératives), soit trop complexes et beaucoup étaient tout simplement incorrectes. La morale de l'histoire est ... Ne lui faites pas confiance à moins que vous ne l' ayez testé de manière exhaustive !!
L'idée est venue d'une solution SQL que j'ai trouvée sur le débordement de pile. Leur idée était solide mais malheureusement elle aussi avait un bug. Cela a fonctionné pour les valeurs + ve, mais leur mappage de table de recherche était incorrect pour les valeurs -ve.
Tony O'Hagan
4
Voici un code à cet effet, avec des vacances suédoises mais vous pouvez adapter les vacances à compter. Notez que j'ai ajouté une limite que vous voudrez peut-être supprimer, mais c'était pour un système basé sur le Web et je ne voulais pas que quiconque entre une date énorme pour monopoliser le processus
publicstaticintGetWorkdays(DateTimefrom,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));}// TrettondagenpublicstaticDateTimeGetEpiphanyDay(int year){returnnewDateTime(year,1,6);}// Första majpublicstaticDateTimeGetMayDay(int year){returnnewDateTime(year,5,1);}// JuldagenpublicstaticDateTimeGetSwedishNationalDay(int year){returnnewDateTime(year,6,6);}// JuldagenpublicstaticDateTimeGetNewYearsDay(int year){returnnewDateTime(year,1,1);}// JuldagenpublicstaticDateTimeGetChristmasDay(int year){returnnewDateTime(year,12,25);}// Annandag julpublicstaticDateTimeGetBoxingDay(int year){returnnewDateTime(year,12,26);}// LångfredagenpublicstaticDateTimeGetGoodFriday(int year){returnGetEasterDay(year).AddDays(-3);}// Kristi himmelsfärdsdagpublicstaticDateTimeGetAscensionDay(int year){returnGetEasterDay(year).AddDays(5*7+4);}// MidsommarpublicstaticDateTimeGetAllSaintsDay(int year){DateTime result =newDateTime(year,10,31);while(result.DayOfWeek!=DayOfWeek.Saturday){
result = result.AddDays(1);}return result;}// MidsommarpublicstaticDateTimeGetMidsummersDay(int year){DateTime result =newDateTime(year,6,20);while(result.DayOfWeek!=DayOfWeek.Saturday){
result = result.AddDays(1);}return result;}// PingstdagenpublicstaticDateTimeGetPentecostDay(int year){returnGetEasterDay(year).AddDays(7*7);}// Annandag påskpublicstaticDateTimeGetEasterMonday(int year){returnGetEasterDay(year).AddDays(1);}publicstaticDateTimeGetEasterDay(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 =newDateTime(y,3,1).AddDays(days-1);return result;}
la fonction issamedate est manquante mais est simplement publique static bool IsSameDay (DateTime date1, DateTime date2) {return date1.Date == date2.Date; }
Choco Smith
Vous pouvez utiliser une table de recherche de tableau int au lieu d'instancier de nouveaux objets Date.
TheRealChx101
3
Voici un exemple de code rapide. C'est une méthode de classe, donc ne fonctionnera qu'à l'intérieur de votre classe. Si vous le souhaitez static, remplacez la signature par private static(ou public static).
privateIEnumerable<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;}
Cette méthode crée une variable de boucle d, l'initialise au jour de début sd, puis incrémente d'un jour à chaque itération ( d = d.AddDays(1)).
Il renvoie les valeurs souhaitées en utilisant yield, ce qui crée un fichier iterator. Ce qui est cool avec les itérateurs, c'est qu'ils ne conservent pas toutes les valeurs de la IEnumerableen mémoire, n'appelant chacun que séquentiellement. Cela signifie que vous pouvez appeler cette méthode depuis la nuit des temps jusqu'à maintenant sans avoir à vous soucier de manquer de mémoire.
Cette méthode ne renvoie pas le nombre de jours ouvrés entre deux dates, elle renvoie les dates ouvrées entre deux dates. Le code que vous proposez est très propre et j'aime l'utilisation de yield, mais il ne répond pas à la question.
Martin
3
J'ai beaucoup cherché un algorithme, facile à digérer, pour calculer les jours ouvrables entre 2 dates, et aussi pour exclure les jours fériés, et finalement je décide de suivre cette approche:
publicstaticintNumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays){var dic =newDictionary<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();}
En gros, je voulais aller avec chaque date et évaluer mes conditions:
N'est pas samedi
N'est pas dimanche
N'est pas la fête nationale
mais aussi je voulais éviter d'itérer les dates.
En courant et en mesurant le temps qu'il faut pour évaluer 1 année complète, je vais au résultat suivant:
staticvoidMain(string[] args){var start =newDateTime(2017,1,1);var due =newDateTime(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}
Je pense qu'aucune des réponses ci-dessus n'est réellement correcte. Aucun d'eux ne résout tous les cas spéciaux tels que lorsque les dates commencent et se terminent au milieu d'un week-end, lorsque la date commence un vendredi et se termine le lundi prochain, etc. En plus de cela, ils arrondissent tous les calculs à l'ensemble jours, donc si la date de début est au milieu d'un samedi par exemple, cela soustrait une journée entière aux jours ouvrables, donnant de faux résultats ...
Quoi qu'il en soit, voici ma solution qui est assez efficace et simple et fonctionne pour tous les cas. L'astuce consiste simplement à trouver le lundi précédent pour les dates de début et de fin, puis de faire une petite compensation lorsque le début et la fin se produisent pendant le week-end:
publicdoubleWorkDays(DateTime startDate,DateTime endDate){double weekendDays;double days = endDate.Subtract(startDate).TotalDays;if(days<0)return0;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 daysdouble diffStart = startDate.Subtract(startMonday).TotalDays-5;double diffEnd = endDate.Subtract(endMonday).TotalDays-5;// compensate weekenddaysif(diffStart>0) weekendDays -= diffStart;if(diffEnd>0) weekendDays += diffEnd;return days - weekendDays;}
Cette méthode n'utilise aucune boucle et est en fait assez simple. Il étend la plage de dates à des semaines complètes puisque nous savons que chaque semaine compte 5 jours ouvrables. Il utilise ensuite une table de recherche pour trouver le nombre de jours ouvrables à soustraire du début et de la fin pour obtenir le bon résultat. J'ai développé le calcul pour aider à montrer ce qui se passe, mais le tout pourrait être condensé en une seule ligne si nécessaire.
Quoi qu'il en soit, cela fonctionne pour moi et j'ai donc pensé le publier ici au cas où cela pourrait aider les autres. Bon codage.
Calcul
t: Nombre total de jours entre les dates (1 si min = max)
a + b: jours supplémentaires nécessaires pour passer le total à des semaines complètes
k: 1,4 est le nombre de jours de la semaine par semaine, c'est-à-dire (t / 7) * 5
c: Nombre de jours de la semaine à soustraire du total
m: Une table de recherche utilisée pour trouver la valeur de "c" pour chaque jour de la semaine
Culture
Le code suppose une semaine de travail du lundi au vendredi. Pour les autres cultures, comme du dimanche au jeudi, vous devrez compenser les dates avant le calcul.
Méthode
publicintWeekdays(DateTime min,DateTime max){if(min.Date> max.Date)thrownewException("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;}
comment pouvez-vous obtenir K avec une valeur de 1,4?
toha
0
Je vais juste partager ma solution. Cela a fonctionné pour moi, peut-être que je ne remarque / ne sais pas qu'il y a un bug. J'ai commencé par avoir la première semaine incomplète s'il y en a. une semaine complète allait du dimanche au samedi, donc si le (int) _now.DayOfWeek n'était pas 0 (dimanche), la première semaine était incomplète.
Je soustrais juste 1 aux premières semaines pour le samedi de la première semaine, puis je l'ajoute au nouveau compte;
Ensuite, j'obtiens la dernière semaine incomplète, puis soustrayez 1 pour c'est dimanche puis ajoutez au nouveau décompte.
Enfin, le nombre de semaines complètes multiplié par 5 (jours de la semaine) a été ajouté au nouveau décompte.
J'avais du mal à trouver une version TSQL solide de ce code. Ci-dessous se trouve essentiellement une conversion du code C # ici avec l'ajout de la table Holiday qui devrait être utilisée pour pré-calculer les vacances.
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<=6
BEGIN
IF (@lastDayOfWeek>=7)--BothSaturday and Sunday are in the remaining time interval
BEGIN
SELECT @BusinessDays=@BusinessDays-2
END
ELSE IF @lastDayOfWeek>=6--OnlySaturdayisin the remaining time interval
BEGIN
SELECT @BusinessDays=@BusinessDays-1
END
END
ELSE IF @firstDayOfWeek<=7 AND @lastDayOfWeek>=7--OnlySundayisin 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
Voici une solution très simple à ce problème. Nous avons la date de début, la date de fin et la "boucle for" pour augmenter le jour et calculer pour voir si c'est un jour ouvrable ou un week-end en convertissant en chaîne DayOfWeek.
classProgram{staticvoidMain(string[] args){DateTime day =newDateTime();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);}}
Basé sur le commentaire marqué comme réponse et correctif recommandé, ainsi que -> Cette version veut convertir les jours en heures ouvrables ... Prend également en compte les heures le même jour.
/// <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,paramsDateTime[] 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;}
Je viens d'améliorer la réponse @Alexander et @Slauma pour prendre en charge une semaine de travail en tant que paramètre, pour les cas où le samedi est un jour ouvrable, ou même les cas où seuls quelques jours de la semaine sont considérés comme des jours ouvrés:
/// <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(thisDateTime fDay,DateTime lDay,DayOfWeek[]BusinessDaysOfWeek=null,DateTime[]Holidays=null){if(BusinessDaysOfWeek==null)BusinessDaysOfWeek=newDayOfWeek[]{DayOfWeek.Monday,DayOfWeek.Tuesday,DayOfWeek.Wednesday,DayOfWeek.Thursday,DayOfWeek.Friday};if(Holidays==null)Holidays=newDateTime[]{};
fDay = fDay.Date;
lDay = lDay.Date;if(fDay > lDay)thrownewArgumentException("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;}
Voici la fonction que nous pouvons utiliser pour calculer les jours ouvrables entre deux dates. Je n'utilise pas la liste des jours fériés car elle peut varier selon les pays / régions.
Si nous voulons quand même l'utiliser, nous pouvons prendre le troisième argument comme liste de vacances et avant d'augmenter le nombre, nous devons vérifier que la liste ne contient pas d
publicstaticintGetBussinessDaysBetweenTwoDates(DateTimeStartDate,DateTimeEndDate){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;}
Voici encore une autre idée - cette méthode permet de spécifier n'importe quelle semaine de travail et jours fériés.
L'idée ici est que nous trouvons le noyau de la plage de dates du premier premier jour ouvrable de la semaine au dernier jour de week-end de la semaine. Cela nous permet de calculer facilement les semaines entières ( sans itérer sur toutes les dates). Il ne reste plus qu'à ajouter les jours ouvrés qui précèdent le début et la fin de cette gamme de base.
publicstaticintCalculateWorkingDays(DateTime startDate,DateTime endDate,IList<DateTime> holidays,DayOfWeek firstDayOfWeek,DayOfWeek lastDayOfWeek){// Make sure the defined working days run contiguouslyif(lastDayOfWeek < firstDayOfWeek){thrownewException("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 =newList<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;}
Puisque je ne peux pas commenter. Il y a un autre problème avec la solution acceptée où les jours fériés sont soustraits même lorsqu'ils sont situés le week-end. En voyant comment les autres entrées sont vérifiées, il est normal que ce soit également le cas.
Le foreach devrait donc être:
// 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;}
Voici une approche si vous utilisez MVC. J'ai également calculé les jours fériés ou les jours de fête à exclure en le récupérant dans le calendrier des jours fériés dont vous aurez besoin pour en faire un.
foreach(DateTime day inEachDay(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++;}}
Voici une fonction d'aide que j'ai écrite pour cette tâche. il renvoie également le décompte des week-ends via le outparamètre. si vous le souhaitez, vous pouvez personnaliser les jours "week-end" dans le temps d'exécution pour les pays qui utilisent des jours de week-end différents ou pour inclure les jours fériés via le weekendDays[]paramètre facultatif:
publicstaticintGetNetworkDays(DateTime startDate,DateTime endDate,outint totalWeekenDays,DayOfWeek[] weekendDays =null){if(startDate >= endDate){thrownewException("start date can not be greater then or equel to end date");}DayOfWeek[] weekends =newDayOfWeek[]{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(thisDateTime startDate,DateTime endDate,paramsDateTime[] holidayDates){
endDate = endDate.Date;if(startDate > endDate)thrownewArgumentException("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
}
Je ne poste que ceci parce que malgré toutes les excellentes réponses qui ont été données, aucun des calculs mathématiques n'a de sens pour moi. C'est définitivement une méthode KISS qui devrait fonctionner et être assez maintenable. Certes, si vous calculez des fourchettes supérieures à 2-3 mois, ce ne sera pas le moyen le plus efficace. Nous déterminons simplement si c'est un samedi ou un dimanche ou si la date est une date de vacances donnée. Si ce n'est pas le cas, nous ajoutons un jour ouvrable. Si c'est le cas, tout va bien.
Je suis sûr que cela pourrait être encore plus simplifié avec LINQ, mais cette méthode est beaucoup plus facile à comprendre.
Encore une autre approche pour calculer les jours ouvrables, sans tenir compte des jours fériés, mais en tenant compte de l'heure de la journée en renvoyant une fraction de jours:
Réponses:
J'ai déjà eu une telle tâche et j'ai la solution. J'éviterais d'énumérer tous les jours entre les deux quand c'est évitable, ce qui est le cas ici. Je ne mentionne même pas la création d'un groupe d'instances DateTime, comme je l'ai vu dans l'une des réponses ci-dessus. C'est vraiment un gaspillage de puissance de traitement. Surtout dans la situation du monde réel, lorsque vous devez examiner des intervalles de temps de plusieurs mois. Voir mon code, avec des commentaires, ci-dessous.
Edit par Slauma, août 2011
Très bonne réponse! Il y a cependant un petit bug. Je prends la liberté d'éditer cette réponse puisque le répondeur est absent depuis 2009.
Le code ci-dessus suppose qu'il
DayOfWeek.Sunday
a la valeur7
qui n'est pas le cas. La valeur est en fait0
. Cela conduit à un calcul erroné si par exemplefirstDay
etlastDay
sont tous les deux le même dimanche. La méthode retourne1
dans ce cas, mais elle devrait l'être0
.Solution la plus simple pour ce bogue: Remplacez dans le code au-dessus des lignes où
firstDayOfWeek
etlastDayOfWeek
sont déclarés par ce qui suit:Maintenant, le résultat est:
la source
&& !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)
sinon il soustrait le même jour deux fois, si un jour férié tombe dans un week-end.D'accord. Je pense qu'il est temps d'afficher la bonne réponse:
Source primaire:
http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
PS Solutions publié ci-dessus me rend sic pour une raison quelconque.
la source
Je sais que cette question est déjà résolue, mais j'ai pensé que je pourrais fournir une réponse plus simple qui pourrait aider d'autres visiteurs à l'avenir.
Voici mon avis:
C'était ma soumission originale:
la source
to > from
. C'est peut-être le problème?Définissez une méthode d'extension sur DateTime comme ceci:
Ensuite, utilisez is dans une clause Where pour filtrer une liste plus large de dates:
la source
J'ai utilisé le code suivant pour prendre également en compte les jours fériés:
Et tests unitaires:
la source
Eh bien, cela a été battu à mort. :) Cependant, je vais toujours fournir une autre réponse car j'avais besoin de quelque chose d'un peu différent. Cette solution est différente en ce qu'elle renvoie un Business TimeSpan entre le début et la fin, et vous pouvez définir les heures d'ouverture de la journée et ajouter des jours fériés. Vous pouvez donc l'utiliser pour calculer si cela se produit dans une journée, sur plusieurs jours, pendant les week-ends et même les jours fériés. Et vous pouvez obtenir uniquement les jours ouvrables ou non en obtenant simplement ce dont vous avez besoin à partir de l'objet TimeSpan retourné. Et la façon dont il utilise les listes de jours, vous pouvez voir à quel point il serait très facile d'ajouter la liste des jours sans travail si ce n'est pas le samedi et le dimanche typiques. Et j'ai testé pendant un an, et ça me semble super rapide.
J'espère juste que le collage du code est exact. Mais je sais que ça marche.
Et voici le code de test: Notez qu'il vous suffit de mettre cette fonction dans une classe appelée DateHelper pour que le code de test fonctionne.
la source
Cette solution évite les itérations, fonctionne à la fois pour les différences de jours de semaine + ve et -ve et comprend une suite de tests unitaires pour la régression par rapport à la méthode plus lente de comptage des jours de la semaine. J'ai également inclus une méthode concise pour ajouter des jours de la semaine fonctionne également de la même manière non itérative.
Les tests unitaires couvrent quelques milliers de combinaisons de dates afin de tester de manière exhaustive toutes les combinaisons de début / fin de semaine avec des plages de dates petites et grandes.
Important : Nous supposons que nous comptons les jours en excluant la date de début et en incluant la date de fin. Ceci est important lors du comptage des jours de la semaine car les jours de début / fin spécifiques que vous incluez / excluez affectent le résultat. Cela garantit également que la différence entre deux jours égaux est toujours égale à zéro et que nous n'incluons que les jours ouvrables complets, car vous souhaitez généralement que la réponse soit correcte à tout moment à la date de début actuelle (souvent aujourd'hui) et incluez la date de fin complète (par exemple une date d'échéance).
REMARQUE: Ce code nécessite un ajustement supplémentaire pour les jours fériés, mais conformément à l'hypothèse ci-dessus, ce code doit exclure les jours fériés à la date de début.
Ajouter des jours de la semaine:
Calculer la différence des jours de la semaine:
J'ai trouvé que la plupart des autres solutions sur le débordement de pile étaient soit lentes (itératives), soit trop complexes et beaucoup étaient tout simplement incorrectes. La morale de l'histoire est ... Ne lui faites pas confiance à moins que vous ne l' ayez testé de manière exhaustive !!
Tests unitaires basés sur les tests NUnit Combinatorial et l' extension ShouldBe NUnit.
la source
Voici un code à cet effet, avec des vacances suédoises mais vous pouvez adapter les vacances à compter. Notez que j'ai ajouté une limite que vous voudrez peut-être supprimer, mais c'était pour un système basé sur le Web et je ne voulais pas que quiconque entre une date énorme pour monopoliser le processus
la source
Voici un exemple de code rapide. C'est une méthode de classe, donc ne fonctionnera qu'à l'intérieur de votre classe. Si vous le souhaitez
static
, remplacez la signature parprivate static
(oupublic static
).Cette méthode crée une variable de boucle
d
, l'initialise au jour de débutsd
, puis incrémente d'un jour à chaque itération (d = d.AddDays(1)
).Il renvoie les valeurs souhaitées en utilisant
yield
, ce qui crée un fichieriterator
. Ce qui est cool avec les itérateurs, c'est qu'ils ne conservent pas toutes les valeurs de laIEnumerable
en mémoire, n'appelant chacun que séquentiellement. Cela signifie que vous pouvez appeler cette méthode depuis la nuit des temps jusqu'à maintenant sans avoir à vous soucier de manquer de mémoire.la source
J'ai beaucoup cherché un algorithme, facile à digérer, pour calculer les jours ouvrables entre 2 dates, et aussi pour exclure les jours fériés, et finalement je décide de suivre cette approche:
En gros, je voulais aller avec chaque date et évaluer mes conditions:
mais aussi je voulais éviter d'itérer les dates.
En courant et en mesurant le temps qu'il faut pour évaluer 1 année complète, je vais au résultat suivant:
Edit: une nouvelle méthode plus simple:
la source
Je pense qu'aucune des réponses ci-dessus n'est réellement correcte. Aucun d'eux ne résout tous les cas spéciaux tels que lorsque les dates commencent et se terminent au milieu d'un week-end, lorsque la date commence un vendredi et se termine le lundi prochain, etc. En plus de cela, ils arrondissent tous les calculs à l'ensemble jours, donc si la date de début est au milieu d'un samedi par exemple, cela soustrait une journée entière aux jours ouvrables, donnant de faux résultats ...
Quoi qu'il en soit, voici ma solution qui est assez efficace et simple et fonctionne pour tous les cas. L'astuce consiste simplement à trouver le lundi précédent pour les dates de début et de fin, puis de faire une petite compensation lorsque le début et la fin se produisent pendant le week-end:
la source
la source
Fonctionne et sans boucles
Cette méthode n'utilise aucune boucle et est en fait assez simple. Il étend la plage de dates à des semaines complètes puisque nous savons que chaque semaine compte 5 jours ouvrables. Il utilise ensuite une table de recherche pour trouver le nombre de jours ouvrables à soustraire du début et de la fin pour obtenir le bon résultat. J'ai développé le calcul pour aider à montrer ce qui se passe, mais le tout pourrait être condensé en une seule ligne si nécessaire.
Quoi qu'il en soit, cela fonctionne pour moi et j'ai donc pensé le publier ici au cas où cela pourrait aider les autres. Bon codage.
Calcul
Culture
Le code suppose une semaine de travail du lundi au vendredi. Pour les autres cultures, comme du dimanche au jeudi, vous devrez compenser les dates avant le calcul.
Méthode
la source
Je vais juste partager ma solution. Cela a fonctionné pour moi, peut-être que je ne remarque / ne sais pas qu'il y a un bug. J'ai commencé par avoir la première semaine incomplète s'il y en a. une semaine complète allait du dimanche au samedi, donc si le (int) _now.DayOfWeek n'était pas 0 (dimanche), la première semaine était incomplète.
Je soustrais juste 1 aux premières semaines pour le samedi de la première semaine, puis je l'ajoute au nouveau compte;
Ensuite, j'obtiens la dernière semaine incomplète, puis soustrayez 1 pour c'est dimanche puis ajoutez au nouveau décompte.
Enfin, le nombre de semaines complètes multiplié par 5 (jours de la semaine) a été ajouté au nouveau décompte.
la source
J'avais du mal à trouver une version TSQL solide de ce code. Ci-dessous se trouve essentiellement une conversion du code C # ici avec l'ajout de la table Holiday qui devrait être utilisée pour pré-calculer les vacances.
la source
la source
Voici une solution très simple à ce problème. Nous avons la date de début, la date de fin et la "boucle for" pour augmenter le jour et calculer pour voir si c'est un jour ouvrable ou un week-end en convertissant en chaîne DayOfWeek.
la source
Basé sur le commentaire marqué comme réponse et correctif recommandé, ainsi que -> Cette version veut convertir les jours en heures ouvrables ... Prend également en compte les heures le même jour.
la source
Je viens d'améliorer la réponse @Alexander et @Slauma pour prendre en charge une semaine de travail en tant que paramètre, pour les cas où le samedi est un jour ouvrable, ou même les cas où seuls quelques jours de la semaine sont considérés comme des jours ouvrés:
la source
Voici la fonction que nous pouvons utiliser pour calculer les jours ouvrables entre deux dates. Je n'utilise pas la liste des jours fériés car elle peut varier selon les pays / régions.
Si nous voulons quand même l'utiliser, nous pouvons prendre le troisième argument comme liste de vacances et avant d'augmenter le nombre, nous devons vérifier que la liste ne contient pas d
la source
Je pense que cela pourrait être un moyen plus simple:
la source
Voici encore une autre idée - cette méthode permet de spécifier n'importe quelle semaine de travail et jours fériés.
L'idée ici est que nous trouvons le noyau de la plage de dates du premier premier jour ouvrable de la semaine au dernier jour de week-end de la semaine. Cela nous permet de calculer facilement les semaines entières ( sans itérer sur toutes les dates). Il ne reste plus qu'à ajouter les jours ouvrés qui précèdent le début et la fin de cette gamme de base.
la source
Puisque je ne peux pas commenter. Il y a un autre problème avec la solution acceptée où les jours fériés sont soustraits même lorsqu'ils sont situés le week-end. En voyant comment les autres entrées sont vérifiées, il est normal que ce soit également le cas.
Le foreach devrait donc être:
la source
Voici une approche si vous utilisez MVC. J'ai également calculé les jours fériés ou les jours de fête à exclure en le récupérant dans le calendrier des jours fériés dont vous aurez besoin pour en faire un.
Leavemodel est une liste ici
la source
Voici une fonction d'aide que j'ai écrite pour cette tâche.
il renvoie également le décompte des week-ends via le
out
paramètre.si vous le souhaitez, vous pouvez personnaliser les jours "week-end" dans le temps d'exécution pour les pays qui utilisent des jours de week-end différents ou pour inclure les jours fériés via le
weekendDays[]
paramètre facultatif:la source
J'ai trouvé la solution suivante
la source
Il vous suffit de parcourir chaque jour dans la plage horaire et de soustraire un jour du compteur si c'est un samedi ou un dimanche.
la source
Je ne poste que ceci parce que malgré toutes les excellentes réponses qui ont été données, aucun des calculs mathématiques n'a de sens pour moi. C'est définitivement une méthode KISS qui devrait fonctionner et être assez maintenable. Certes, si vous calculez des fourchettes supérieures à 2-3 mois, ce ne sera pas le moyen le plus efficace. Nous déterminons simplement si c'est un samedi ou un dimanche ou si la date est une date de vacances donnée. Si ce n'est pas le cas, nous ajoutons un jour ouvrable. Si c'est le cas, tout va bien.
Je suis sûr que cela pourrait être encore plus simplifié avec LINQ, mais cette méthode est beaucoup plus facile à comprendre.
la source
Encore une autre approche pour calculer les jours ouvrables, sans tenir compte des jours fériés, mais en tenant compte de l'heure de la journée en renvoyant une fraction de jours:
Vous pouvez le manipuler ici: https://rextester.com/ASHRS53997
la source
C'est une solution générique.
startdayvalue est le numéro du jour de la date de début.
weekendday_1 est le numéro de jour du week-end.
numéro du jour - MON - 1, TUE - 2, ... SAT - 6, SUN -7.
la différence est la différence entre deux dates.
Exemple: Date de début: 4 avril 2013, Date de fin: 14 avril 2013
Différence: 10, startdayvalue: 4, weekendday_1: 7 (si DIMANCHE est un week-end pour vous.)
Cela vous donnera le nombre de vacances.
No de jour ouvrable = (Différence + 1) - jour férié1
la source