need some help. I need to count regular working days for a given date period, for example, in our country, we have 5 regular working days monday to friday, then in code i need to exclude saturdays and sundays when I use it on my computations.
I need an algorithm something like this in C#:
int GetRegularWorkingDays(DateTime startDate, DateTime endDate)
{
int nonWorkingDays = ((endDate - startDate) % 7) * 2;
return (endDate - startDate) - nonWorkingDays;
}
I know my draft is way way off :(. Thanks in advance. =)
PS: Guys please up-vote the best/fastest/most efficient answer below. Thanks =)
Check out this example on Code Project that uses a very efficient way that doesn't involve any looping ;)
It uses this alogrithm:
Calculate the number of time span in terms of weeks. Call it, W.
Deduct the first week from the number of weeks. W= W-1
Multiply the number of weeks with the number of working days per
week. Call it, D.
Find out the holidays during the specified time span. Call it, H.
Calculate the days in the first week. Call it, SD.
Calculate the days in the last week. Call it, ED.
Sum up all the days. BD = D + SD + ED - H.
One-liner!
int workingDays = Enumerable.Range(0, Convert.ToInt32(endDate.Subtract(startDate).TotalDays)).Select(i=>new [] { DayOfWeek.Saturday, DayOfWeek.Sunday }.Contains(startDate.AddDays(i).DayOfWeek) ? 0 : 1).Sum();
Or more efficient:
DayOfWeek currDay = startDate.DayOfWeek;
int nonWorkingDays = 0;
foreach(var i in Enumerable.Range(0, Convert.ToInt32(endDate.Subtract(startDate).TotalDays)))
{
if(currDay == DayOfWeek.Sunday || currDay == DayOfWeek.Saturday)
nonWorkingDays++;
if((int)++currDay > 6) currDay = (DayOfWeek)0;
}
I wrote a type extender to allow me to add (or subtract) weekdays to a given date. Maybe this will help you. Works great, so please vote for my post if this helped you.
/// <summary>
/// Adds weekdays to date
/// </summary>
/// <param name="value">DateTime to add to</param>
/// <param name="weekdays">Number of weekdays to add</param>
/// <returns>DateTime</returns>
public static DateTime AddWeekdays(this DateTime value, int weekdays)
{
int direction = Math.Sign(weekdays);
int initialDayOfWeek = Convert.ToInt32(value.DayOfWeek);
//---------------------------------------------------------------------------
// if the day is a weekend, shift to the next weekday before calculating
if ((value.DayOfWeek == DayOfWeek.Sunday && direction < 0)
|| (value.DayOfWeek == DayOfWeek.Saturday && direction > 0))
{
value = value.AddDays(direction * 2);
weekdays += (direction * -1); // adjust days to add by one
}
else if ((value.DayOfWeek == DayOfWeek.Sunday && direction > 0)
|| (value.DayOfWeek == DayOfWeek.Saturday && direction < 0))
{
value = value.AddDays(direction);
weekdays += (direction * -1); // adjust days to add by one
}
//---------------------------------------------------------------------------
int weeksBase = Math.Abs(weekdays / 5);
int addDays = Math.Abs(weekdays % 5);
int totalDays = (weeksBase * 7) + addDays;
DateTime result = value.AddDays(totalDays * direction);
//---------------------------------------------------------------------------
// if the result is a weekend, shift to the next weekday
if ((result.DayOfWeek == DayOfWeek.Sunday && direction > 0)
|| (result.DayOfWeek == DayOfWeek.Saturday && direction < 0))
{
result = result.AddDays(direction);
}
else if ((result.DayOfWeek == DayOfWeek.Sunday && direction < 0)
|| (result.DayOfWeek == DayOfWeek.Saturday && direction > 0))
{
result = result.AddDays(direction * 2);
}
//---------------------------------------------------------------------------
return result;
}
Not very fast, but this will do the trick:
int GetRegularWorkingDays(DateTime start, DateTime end)
{
return (
from day in Range(start, end)
where day.DayOfWeek != DayOfWeek.Saturday
where day.DayOfWeek != DayOfWeek.Sunday
select day).Count();
}
IEnumerable<DateTime> Range(DateTime start, DateTime end)
{
while (start <= end)
{
yield return start;
start = start.AddDays(1);
}
}
You could try a simple method of just counting the days. If this is usually done for periods of time like months and not years and isn't called repeatedly then this will not be a performance hit to just walk it.
int GetWorkingDays(DateTime startDate, DateTime endDate)
{
int count = 0;
for (DateTime currentDate = startDate; currentDate < endDate; currentDate = currentDate.AddDays(1))
{
if (currentDate.DayOfWeek == DayOfWeek.Sunday || currentDate.DayOfWeek == DayOfWeek.Saturday)
{
continue;
}
count++;
}
return count;
}
You could do it with a time line helper class - this class also allows for other intervals:
public class TimeLine
{
public static IEnumerable<DateTime> CreateTimeLine(DateTime start, TimeSpan interval) {
return TimeLine.CreateTimeLine(start, interval, DateTime.MaxValue);
}
public static IEnumerable<DateTime> CreateTimeLine(DateTime start, TimeSpan interval, DateTime end) {
var currentVal = start;
var endVal = end.Subtract(interval);
do {
currentVal = currentVal.Add(interval);
yield return currentVal;
} while (currentVal <= endVal);
}
}
To solve your problem you can do the following:
var workingDays = TimeLine.CreateTimeLine(DateTime.Now.Date, TimeSpan.FromDays(1), DateTime.Now.Date.AddDays(30))
.Where(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
var noOfWorkingDays = workingDays.Count();
This time line class can be used for any continuous time line of any interval.
Simple method to get work days:
int GetRegularWorkingDays(DateTime startDate, DateTime endDate)
{
int total = 0;
if (startDate < endDate)
{
var days = endDate - startDate;
for( ; startDate < endDate; startDate = startDate.AddDays(1) )
{
switch(startDate.DayOfWeek)
{
case DayOfWeek.Saturday :
case DayOfWeek.Sunday :
break;
default:
total++;
break;
}
}
}
return total;
}
int count = 0;
switch (dateTimePicker2.Value.DayOfWeek.ToString())
{
case "Saturday": count--; break;
case "Sunday": count--; break;
default:break;
}
switch (dateTimePicker3.Value.DayOfWeek.ToString())
{
case "Saturday": count--; break;
case "Sunday": count--; break;
default:break;
}
if (count == -2)
count = -1;
int weeks = t.Days / 7;
int daycount =count+ t.Days - (2 * weeks)+1;
label7.Text = "No of Days : " + daycount.ToString();
Related
I have two fields startdate and enddate. I need to calculate how many weekends in between two date and time fields and show the result in minutes.
For example start date is 01/11/2019 00:00:00 and end date as 03/11/2019 11:00:00. Below code is returning the difference in minutes correctly as 2100 minutes but when I keep the dates as02/11/2019 08:00 and 03/11/2019 00:00 I am getting the result as 1440 but my expected result is 960 minutes.
I understand that's because I am adding 1440 in code so how to correct this?
public double CountOfWeekEnds(DateTime startDate, DateTime endDate)
{
double weekEndCount = 0;
if (startDate > endDate)
{
DateTime temp = startDate;
startDate = endDate;
endDate = temp;
}
TimeSpan diff = endDate - startDate;
int days = diff.Days;
for (var i = 0; i <= days; i++)
{
var testDate = startDate.AddDays(i);
if (testDate.DayOfWeek == DayOfWeek.Saturday || testDate.DayOfWeek == DayOfWeek.Sunday)
{
if (testDate.Date < endDate.Date)
{
weekEndCount += 1440; // 24h * 60 min
}
else
{
var todayStart = new DateTime(testDate.Year, testDate.Month, testDate.Day, 0, 0, 0);
var difference = (endDate - todayStart).TotalMinutes;
weekEndCount += difference;
}
}
}
return weekEndCount;
}
OK, i simplified what i said a little down to:
DateTime start = new DateTime(2019,11,1,0,0,0);
DateTime end = new DateTime(2019, 11, 3, 11, 0, 0);
TimeSpan diff = end - start;
Console.WriteLine(diff.TotalDays);
int total = 0;
for (int i = 0; i<Math.Ceiling(diff.TotalDays); i++)
{
DateTime test = start.AddDays(i);
Console.WriteLine(test.DayOfWeek);
if (test.DayOfWeek == DayOfWeek.Saturday || test.DayOfWeek == DayOfWeek.Sunday)
{
if (test.Date==start.Date)
{
Console.WriteLine("start");
total += (23 - start.Hour) * 60 + (60 - start.Minute);
}
else if (test.Date==end.Date)
{
Console.WriteLine("end");
total += end.Hour * 60 + end.Minute;
}
else
{
total += 24 * 60;
}
}
Console.WriteLine(test + " total " + total);
}
Console.WriteLine("done");
Console.WriteLine(total);
which counts all saturdays and sundays and allows for start and ends to be partials
(and can someone send a keyboard with actual keys this membrain lark is hampering typings)
Trying to remain as much of the original code as possible, only three minor changes have to be made:
1. Use the actual dates to calculate diff:
TimeSpan diff = endDate.Date - startDate.Date; instead of TimeSpan diff = endDate - startDate;
This is because later in the upcoming for-loop you are trying to evaluate each date in order to say if is a saturday or sunday. Otherwise, you are evaluating if the date 24 (, 48, …) hours after your starting time stamp is a saturday or sunday.
2. Use testDate instead of todayStart in order to calculate difference
difference = (endDate - testDate).TotalMinutes;
instead of
var todayStart = new DateTime(testDate.Year, testDate.Month, testDate.Day, 0, 0, 0);
var difference = (endDate - todayStart).TotalMinutes;
This is because testDate does contain the hours and minutes to calculate the difference in minutes. Otherwise you are just ignoring the day time of the starting day. Note that this correction can lead to a negative difference value if the startDate day time is later than the endDate day time.
3. do not add a whole day if there is only one day to examine in total
That means that if startDate.Date == endDate.Date, you should just calculate the difference between the dates.
if (testDate.Date < endDate.Date && startDate.Date != endDate.Date)
This has to be done because of the code logic: a full day is added for every new day other than the final day and for the final day ~24hours are added or substracted to the final value depending on the day times of the startDate and endDate.
The complete corrected code:
public static double CountOfWeekEnds(DateTime startDate, DateTime endDate)
{
double weekEndCount = 0;
if (startDate > endDate)
{
DateTime temp = startDate;
startDate = endDate;
endDate = temp;
}
TimeSpan diff = endDate.Date - startDate.Date; //instead of endDate - startDate
int days = diff.Days;
for (var i = 0; i <= days; i++)
{
var testDate = startDate.AddDays(i);
//Console.WriteLine(testDate);
if (testDate.DayOfWeek == DayOfWeek.Saturday || testDate.DayOfWeek == DayOfWeek.Sunday) //only weekends count
{
if (testDate.Date < endDate.Date && startDate.Date != endDate.Date) { // added startDate.Date != endDate.Date
weekEndCount += 1440; // 24h * 60 min
//Console.WriteLine("************************add 1440 ");
}
else
{
double difference;
difference = (endDate - testDate).TotalMinutes; //instead of endDate - todayStart
//Console.WriteLine("************************add " + difference);
weekEndCount += difference;
}
}
}
//return days;
return weekEndCount;
}
You need to have a look at this condition:
if (testDate.Date < endDate.Date)
It means that "as long as the ticks of testDate is less than the ticks of endDate".
This condition will be true for all conditions that makes your variable "days" positive.
I think you need to extend this, condition e.g.
if ((endDate - todayStart).TotalMinutes > 1440 )
This way it will check whether it is AT LEAST 24 hours earlier. If it isn't it should go forth with your "else" condition and take the used fraction of the start day into consideration.
Here is a (somewhat) simple solution. Please note that the code could (and probably should) be refactored if it was to be production code. But I tried to optimize it for understandability, since it was your first post...
public static int CalculateWeekendMinutes(DateTime start, DateTime end)
{
int weekendMinutes = 0;
// First and last day will be handled seperately in the end
var firstFullDay = start.AddDays(1).Date;
var lastFullDay = end.AddDays(-1).Date;
TimeSpan limitedSpan = lastFullDay - firstFullDay;
int spanLengthDays = (int)limitedSpan.TotalDays;
var dateIterator = firstFullDay;
// Looping over the limited span allows us to analyse all the full days
while (dateIterator <= lastFullDay)
{
if (dateIterator.DayOfWeek == DayOfWeek.Saturday || dateIterator.DayOfWeek == DayOfWeek.Sunday)
{
weekendMinutes += (24 * 60);
}
dateIterator = dateIterator.AddDays(1);
}
// Finally we can calculate the partial days and add that to our total
weekendMinutes += CalculateMinutesOnFirstDay(start);
weekendMinutes += CalculateMinutesOnLastDay(end);
return weekendMinutes;
}
// Helps us calculate the minutes of the first day in the span
private static int CalculateMinutesOnFirstDay(DateTime date)
{
if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
{
// We want to know how many minutes there are UNTIL the next midnight
int minutes = (int)(date.Date.AddDays(1) - date).TotalMinutes;
return minutes;
}
else
{
return 0;
}
}
// Helps us calculate the minutes of the last day in the span
private static int CalculateMinutesOnLastDay(DateTime date)
{
if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
{
// We want to know how many minutes there are SINCE the last midnight
int minutes = (int)(date - date.Date).TotalMinutes;
return minutes;
}
else
{
return 0;
}
}
I am using the following code to skip the sat and sunday to get only the working days for a particular number of hours, actually what is my task:
I have a project and specific time to complete that, now i just want to show the date when the project will be complete.:
My code is:
DateTime date = DateTime.Now;
Int32 d = 1;
Int32 result = 260 / 8;
for (d = 0; d <= result; d++)
{
if (date.DayOfWeek.ToString() == "Saturday" || date.DayOfWeek.ToString() == "Sunday")
{
d = d - 1;
date = date.AddDays(1);
}
else
{
date = date.AddDays(1);
}
}
lbldate.Text = date.ToString();
Its working fine upto 30 days when days are 32 then date should come is :: 25th nov 2013
but it is coming 26th nov 2013.
please tell me where i am making mistake.
Maybe there's something more elegant, but it works and doesn't need a loop:
public static DateTime GetDateIn(int numWorkingHours)
{
int numDays = numWorkingHours / 8;
DateTime date = DateTime.Now;
// normalize to monday
if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
date = date.AddDays(date.DayOfWeek == DayOfWeek.Sunday ? 1 : 2);
int weeks = numDays / 5;
int remainder = numDays % 5;
date = date.AddDays(weeks * 7 + remainder);
// normalize to monday
if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
date = date.AddDays(date.DayOfWeek == DayOfWeek.Sunday ? 1 : 2);
return date;
}
Demo
A working solution would be:
DateTime endDate = DateTime.Now;
double remainingAmountOfWorkingDays = Math.Round(260d / 8d);
while (remainingAmountOfWorkingDays > 0)
{
endDate = endDate.AddDays(1);
if (endDate.DayOfWeek == DayOfWeek.Saturday || endDate.DayOfWeek == DayOfWeek.Sunday)
continue;
Console.WriteLine(remainingAmountOfWorkingDays +" "+endDate.ToString("dddd dd.MM.yyyy"));
remainingAmountOfWorkingDays--;
}
Instead of subtracting from the d variable
use the following Code in case it's a Holiday Sat or Sun
date.Subtract(new TimeSpan(1, 0, 0, 0)); // where 1 is day count you need to subtract
How can I get the date of next Tuesday?
In PHP, it's as simple as strtotime('next tuesday');.
How can I achieve something similar in .NET
As I've mentioned in the comments, there are various things you could mean by "next Tuesday", but this code gives you "the next Tuesday to occur, or today if it's already Tuesday":
DateTime today = DateTime.Today;
// The (... + 7) % 7 ensures we end up with a value in the range [0, 6]
int daysUntilTuesday = ((int) DayOfWeek.Tuesday - (int) today.DayOfWeek + 7) % 7;
DateTime nextTuesday = today.AddDays(daysUntilTuesday);
If you want to give "a week's time" if it's already Tuesday, you can use:
// This finds the next Monday (or today if it's Monday) and then adds a day... so the
// result is in the range [1-7]
int daysUntilTuesday = (((int) DayOfWeek.Monday - (int) today.DayOfWeek + 7) % 7) + 1;
... or you could use the original formula, but from tomorrow:
DateTime tomorrow = DateTime.Today.AddDays(1);
// The (... + 7) % 7 ensures we end up with a value in the range [0, 6]
int daysUntilTuesday = ((int) DayOfWeek.Tuesday - (int) tomorrow.DayOfWeek + 7) % 7;
DateTime nextTuesday = tomorrow.AddDays(daysUntilTuesday);
EDIT: Just to make this nice and versatile:
public static DateTime GetNextWeekday(DateTime start, DayOfWeek day)
{
// The (... + 7) % 7 ensures we end up with a value in the range [0, 6]
int daysToAdd = ((int) day - (int) start.DayOfWeek + 7) % 7;
return start.AddDays(daysToAdd);
}
So to get the value for "today or in the next 6 days":
DateTime nextTuesday = GetNextWeekday(DateTime.Today, DayOfWeek.Tuesday);
To get the value for "the next Tuesday excluding today":
DateTime nextTuesday = GetNextWeekday(DateTime.Today.AddDays(1), DayOfWeek.Tuesday);
This should do the trick:
static DateTime GetNextWeekday(DayOfWeek day)
{
DateTime result = DateTime.Now.AddDays(1);
while( result.DayOfWeek != day )
result = result.AddDays(1);
return result;
}
There are less verbose and more clever/elegant solutions to this problem, but the following C# function works really well for a number of situations.
/// <summary>
/// Find the closest weekday to the given date
/// </summary>
/// <param name="includeStartDate">if the supplied date is on the specified day of the week, return that date or continue to the next date</param>
/// <param name="searchForward">search forward or backward from the supplied date. if a null parameter is given, the closest weekday (ie in either direction) is returned</param>
public static DateTime ClosestWeekDay(this DateTime date, DayOfWeek weekday, bool includeStartDate = true, bool? searchForward=true)
{
if (!searchForward.HasValue && !includeStartDate)
{
throw new ArgumentException("if searching in both directions, start date must be a valid result");
}
var day = date.DayOfWeek;
int add = ((int)weekday - (int)day);
if (searchForward.HasValue)
{
if (add < 0 && searchForward.Value)
{
add += 7;
}
else if (add > 0 && !searchForward.Value)
{
add -= 7;
}
else if (add == 0 && !includeStartDate)
{
add = searchForward.Value ? 7 : -7;
}
}
else if (add < -3)
{
add += 7;
}
else if (add > 3)
{
add -= 7;
}
return date.AddDays(add);
}
#Jon Skeet good answer.
For previous Day:
private DateTime GetPrevWeekday(DateTime start, DayOfWeek day) {
// The (... - 7) % 7 ensures we end up with a value in the range [0, 6]
int daysToRemove = ((int) day - (int) start.DayOfWeek - 7) % 7;
return start.AddDays(daysToRemove);
}
Thanks!!
DateTime nextTuesday = DateTime.Today.AddDays(((int)DateTime.Today.DayOfWeek - (int)DayOfWeek.Tuesday) + 7);
Very simple sample to include or exclude current date, you specify the date and the day the week you are interested in.
public static class DateTimeExtensions
{
/// <summary>
/// Gets the next date.
/// </summary>
/// <param name="date">The date to inspected.</param>
/// <param name="dayOfWeek">The day of week you want to get.</param>
/// <param name="exclDate">if set to <c>true</c> the current date will be excluded and include next occurrence.</param>
/// <returns></returns>
public static DateTime GetNextDate(this DateTime date, DayOfWeek dayOfWeek, bool exclDate = true)
{
//note: first we need to check if the date wants to move back by date - Today, + diff might move it forward or backwards to Today
//eg: date - Today = 0 - 1 = -1, so have to move it forward
var diff = dayOfWeek - date.DayOfWeek;
var ddiff = date.Date.Subtract(DateTime.Today).Days + diff;
//note: ddiff < 0 : date calculates to past, so move forward, even if the date is really old, it will just move 7 days from date passed in
//note: ddiff >= (exclDate ? 6 : 7) && diff < 0 : date is into the future, so calculated future weekday, based on date
if (ddiff < 0 || ddiff >= (exclDate ? 6 : 7) && diff < 0)
diff += 7;
//note: now we can get safe values between 0 - 6, especially if past dates is being used
diff = diff % 7;
//note: if diff is 0 and we are excluding the date passed, we will add 7 days, eg: 1 week
diff += diff == 0 & exclDate ? 7 : 0;
return date.AddDays(diff);
}
}
some test cases
[TestMethod]
public void TestNextDate()
{
var date = new DateTime(2013, 7, 15);
var start = date;
//testing same month - forwardOnly
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Tuesday)); //16
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Wednesday)); //17
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Thursday)); //18
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Friday)); //19
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Saturday)); //20
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Sunday)); //21
Assert.AreEqual(start.AddDays(1), date.GetNextDate(DayOfWeek.Monday)); //22
//testing same month - include date
Assert.AreEqual(start = date, date.GetNextDate(DayOfWeek.Monday, false)); //15
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Tuesday, false)); //16
Assert.AreEqual(start.AddDays(1), date.GetNextDate(DayOfWeek.Wednesday, false)); //17
//testing month change - forwardOnly
date = new DateTime(2013, 7, 29);
start = date;
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Tuesday)); //30
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Wednesday)); //31
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Thursday)); //2013/09/01-month increased
Assert.AreEqual(start.AddDays(1), date.GetNextDate(DayOfWeek.Friday)); //02
//testing year change
date = new DateTime(2013, 12, 30);
start = date;
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Tuesday)); //31
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Wednesday)); //2014/01/01 - year increased
Assert.AreEqual(start = start.AddDays(1), date.GetNextDate(DayOfWeek.Thursday)); //02
}
DateTime nexttuesday=DateTime.Today.AddDays(1);
while(nexttuesday.DayOfWeek!=DayOfWeek.Tuesday)
nexttuesday = nexttuesday.AddDays(1);
Now in oneliner flavor - in case you need to pass it as parameter into some mechanism.
DateTime.Now.AddDays(((int)yourDate.DayOfWeek - (int)DateTime.Now.DayOfWeek + 7) % 7).Day
In this specific case:
DateTime.Now.AddDays(((int)DayOfWeek.Tuesday - (int)DateTime.Now.DayOfWeek + 7) % 7).Day
It could be an extension also, it all depends
public static class DateTimeExtensions
{
public static IEnumerable<DateTime> Next(this DateTime date, DayOfWeek day)
{
// This loop feels expensive and useless, but the point is IEnumerable
while(true)
{
if (date.DayOfWeek == day)
{
yield return date;
}
date = date.AddDays(1);
}
}
}
Usage
var today = DateTime.Today;
foreach(var monday in today.Next(DayOfWeek.Monday))
{
Console.WriteLine(monday);
Console.ReadKey();
}
I want to get next day from this time include today
also I want it to be at 12:00 AM
public static DateTime GetNextWeekday(DateTime start, DayOfWeek day , bool includetoday = false) {
if (includetoday && start.DayOfWeek == day) return start.Date;
int daysToAdd = ((int)day - (int)start.DayOfWeek + 7) % 7;
return start.Date.AddDays(daysToAdd);
}
Objective C Version:
+(NSInteger) daysUntilNextWeekday: (NSDate*)startDate withTargetWeekday: (NSInteger) targetWeekday
{
NSInteger startWeekday = [[NSCalendar currentCalendar] component:NSCalendarUnitWeekday fromDate:startDate];
return (targetWeekday - startWeekday + 7) % 7;
}
I have trouble doing this. I'm creating a method that add working days on a specific date.
for example, I want to add 3 working days to sept 15, 2010 (Wednesday), the method would return sept 20 (Monday next week). it disregards saturday and sunday because its non-working day..
Something like this in C#:
DateTime AddWorkingDays(DateTime specificDate, int workingDaysToAdd)
{
return specificDate + (workingDaysToAdd - (all saturdays and sundays))
}
I don't consider special holidays on the computations, i just literally want to add days except saturday and sundays.. Thanks in advance! =)
If you don't need to consider holidays, I would suggest you do something like this:
public static DateTime AddWorkingDays(DateTime specificDate,
int workingDaysToAdd)
{
int completeWeeks = workingDaysToAdd / 5;
DateTime date = specificDate.AddDays(completeWeeks * 7);
workingDaysToAdd = workingDaysToAdd % 5;
for (int i = 0; i < workingDaysToAdd; i++)
{
date = date.AddDays(1);
while (!IsWeekDay(date))
{
date = date.AddDays(1);
}
}
return date;
}
private static bool IsWeekDay(DateTime date)
{
DayOfWeek day = date.DayOfWeek;
return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
}
It's inefficient, but easy to understand. For an efficient version, you'd work out the number of complete weeks to add as before, but then have a mapping from any "current day of week" and "working days left to add" to "number of actual days to add". Then you could just work out the total number of days to add, and do it in one call.
EDIT: In terms of the level of inefficiency... it's really not very bad. It'll only perform manual "is this a weekend" checks for up to 4 days, which isn't too bad. In particular, despite igor's (current at the time of posting) claims, it's rather faster than his approach, flawed benchmarks notwithstanding ;)
Note that it may not handle negative inputs yet - I haven't checked.
One of the reasons behind the approach I'm using is that it doesn't rely on either me or the code reader knowing what the values in the DayOfWeek enum are. I don't care whether it's 0-6, 1-7, Monday-Sunday, Saturday-Friday... or even if there are completely bizarre values. I only compare for equality, which makes the code more "obviously correct".
A cool way (i think) is put that in a extension method, like:
public static class DateTimeExtensions
{
public static DateTime AddWorkingDays(this DateTime self, int days)
{
self = self.AddDays(days);
while (self.DayOfWeek == DayOfWeek.Saturday || self.DayOfWeek == DayOfWeek.Sunday)
{
self = self.AddDays(1);
}
return self;
}
}
so your final code will look like:
specificDate.AddWorkingDays(3);
Here's what you need :
Updated :
public static DateTime AddWeekdays(DateTime start, int days)
{
int remainder = days % 5;
int weekendDays = (days / 5) * 2;
DateTime end = start.AddDays(remainder);
if (start.DayOfWeek == DayOfWeek.Saturday && days > 0)
{
// fix for saturday.
end = end.AddDays(-1);
}
if (end.DayOfWeek == DayOfWeek.Saturday && days > 0)
{
// add two days for landing on saturday
end = end.AddDays(2);
}
else if (end.DayOfWeek < start.DayOfWeek)
{
// add two days for rounding the weekend
end = end.AddDays(2);
}
// add the remaining days
return end.AddDays(days + weekendDays - remainder);
}
int foundWorkingDays = 0;
while (foundWorkingDays < workingDaysToAdd)
{
specificDate= specificDate.AddDays(1);
if(specificDate.DayOfWeek != DayOfWeek.Sunday && specificDate.DayOfWeek != DayOfWeek.Saturday)
foundWorkingDays++;
}
return specificDate;
ADDED:
class Program
{
public static DateTime AddWorkingDays(DateTime specificDate,
int workingDaysToAdd)
{
int completeWeeks = workingDaysToAdd / 5;
DateTime date = specificDate.AddDays(completeWeeks * 7);
workingDaysToAdd = workingDaysToAdd % 5;
for (int i = 0; i < workingDaysToAdd; i++)
{
date = date.AddDays(1);
while (!IsWeekDay(date))
{
date = date.AddDays(1);
}
}
return date;
}
private static bool IsWeekDay(DateTime date)
{
DayOfWeek day = date.DayOfWeek;
return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
}
public static DateTime MyAddWorkingDays(DateTime specificDate,
int workingDaysToAdd)
{
int foundWorkingDays = 0;
while (foundWorkingDays < workingDaysToAdd)
{
specificDate = specificDate.AddDays(1);
if (specificDate.DayOfWeek != DayOfWeek.Sunday && specificDate.DayOfWeek != DayOfWeek.Saturday)
foundWorkingDays++;
}
return specificDate;
}
static void Main(string[] args)
{
DateTime specificDate = DateTime.Now;
Stopwatch globalTimer = Stopwatch.StartNew();
Console.WriteLine(AddWorkingDays(specificDate, 300)); // 100000 :)
globalTimer.Stop();
Console.WriteLine(globalTimer.ElapsedMilliseconds);
globalTimer = Stopwatch.StartNew();
Console.WriteLine(MyAddWorkingDays(specificDate, 300)); // 100000 :)
globalTimer.Stop();
Console.WriteLine(globalTimer.ElapsedMilliseconds);
Console.ReadLine();
}
}
Is an old post but somebody could be interested in an extension that handles also negative days. (I've reworked #Jon answer)
public static DateTime AddWeekDays(this DateTime start, int days)
{
int direction = Math.Sign(days);
int completeWeeks = days / 5;
int remaining = days % 5;
DateTime end = start.AddDays(completeWeeks * 7);
for (int i = 0; i < remaining * direction; i++)
{
end = end.AddDays(direction * 1);
while (!IsWeekDay(end))
{
end = end.AddDays(direction * 1);
}
}
return end;
}
private static bool IsWeekDay(DateTime date)
{
DayOfWeek day = date.DayOfWeek;
return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
}
This seems to me the cleanest way:
public static DateTime AddWorkingDays(DateTime date, int daysToAdd)
{
while (daysToAdd > 0)
{
date = date.AddDays(1);
if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday) daysToAdd -= 1;
}
return date;
}
How can I get the number of weekdays between two given dates without just iterating through the dates between and counting the weekdays?
Seems fairly straightforward but I can't seem to find a conclusive correct answer that abides by the following:
The total should be inclusive, so GetNumberOfWeekdays(new DateTime(2009,11,30), new DateTime(2009,12,4)) should equal 5, that's Monday to Friday.
Should allow for leap days
does NOT just iterate through all the dates between whilst counting the weekdays.
I've found a similar question with an answer that comes close but is not correct
O(1) solution:
// Count days from d0 to d1 inclusive, excluding weekends
public static int countWeekDays(DateTime d0, DateTime d1)
{
int ndays = 1 + Convert.ToInt32((d1 - d0).TotalDays);
int nsaturdays = (ndays + Convert.ToInt32(d0.DayOfWeek)) / 7;
return ndays - 2 * nsaturdays
- (d0.DayOfWeek == DayOfWeek.Sunday ? 1 : 0)
+ (d1.DayOfWeek == DayOfWeek.Saturday ? 1 : 0);
}
Examples for January 2014:
January 2014
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 1)); // 1
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 2)); // 2
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 3)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 4)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 5)); // 3
countWeekDays(new DateTime(2014, 1, 1), new DateTime(2014, 1, 6)); // 4
N.B. The DateTime inputs should be at around the same time of the day. If you are creating DateTime objects based solely on year, month, and day as in the examples above, then you should be fine. As a counter example, 12:01am on Jan 1 to 11:59pm Jan 2 spans only 2 days, but the above function will count 3 if you use those times.
From this link:
public static int Weekdays(DateTime dtmStart, DateTime dtmEnd)
{
// This function includes the start and end date in the count if they fall on a weekday
int dowStart = ((int)dtmStart.DayOfWeek == 0 ? 7 : (int)dtmStart.DayOfWeek);
int dowEnd = ((int)dtmEnd.DayOfWeek == 0 ? 7 : (int)dtmEnd.DayOfWeek);
TimeSpan tSpan = dtmEnd - dtmStart;
if (dowStart <= dowEnd)
{
return (((tSpan.Days / 7) * 5) + Math.Max((Math.Min((dowEnd + 1), 6) - dowStart), 0));
}
return (((tSpan.Days / 7) * 5) + Math.Min((dowEnd + 6) - Math.Min(dowStart, 6), 5));
}
[1]: http://www.eggheadcafe.com/community/aspnet/2/44982/how-to-calculate-num-of-w.aspx
Tests (each test returns 5):
int ndays = Weekdays(new DateTime(2009, 11, 30), new DateTime(2009, 12, 4));
System.Console.WriteLine(ndays);
// leap year test
ndays = Weekdays(new DateTime(2000, 2,27), new DateTime(2000, 3, 5));
System.Console.WriteLine(ndays);
// non leap year test
ndays = Weekdays(new DateTime(2007, 2, 25), new DateTime(2007, 3, 4));
System.Console.WriteLine(ndays);
eFloh's answer had an extra day if the last day was a Saturday or Sunday. This would fix it.
public static int Weekdays(DateTime dtmStart, DateTime dtmEnd)
{
if (dtmStart > dtmEnd)
{
DateTime temp = dtmStart;
dtmStart = dtmEnd;
dtmEnd = temp;
}
/* Move border dates to the monday of the first full week and sunday of the last week */
DateTime startMonday = dtmStart;
int startDays = 1;
while (startMonday.DayOfWeek != DayOfWeek.Monday)
{
if (startMonday.DayOfWeek != DayOfWeek.Saturday && startMonday.DayOfWeek != DayOfWeek.Sunday)
{
startDays++;
}
startMonday = startMonday.AddDays(1);
}
DateTime endSunday = dtmEnd;
int endDays = 0;
while (endSunday.DayOfWeek != DayOfWeek.Sunday)
{
if (endSunday.DayOfWeek != DayOfWeek.Saturday && endSunday.DayOfWeek != DayOfWeek.Sunday)
{
endDays++;
}
endSunday = endSunday.AddDays(1);
}
int weekDays;
/* calculate weeks between full week border dates and fix the offset created by moving the border dates */
weekDays = (Math.Max(0, (int)Math.Ceiling((endSunday - startMonday).TotalDays + 1)) / 7 * 5) + startDays - endDays;
if (dtmEnd.DayOfWeek == DayOfWeek.Saturday || dtmEnd.DayOfWeek == DayOfWeek.Sunday)
{
weekDays -= 1;
}
return weekDays;
}
This should do better than the solution by dcp:
/// <summary>
/// Count Weekdays between two dates
/// </summary>
/// <param name="dtmStart">first date</param>
/// <param name="dtmEnd">second date</param>
/// <returns>weekdays between the two dates, including the start and end day</returns>
internal static int getWeekdaysBetween(DateTime dtmStart, DateTime dtmEnd)
{
if (dtmStart > dtmEnd)
{
DateTime temp = dtmStart;
dtmStart = dtmEnd;
dtmEnd = temp;
}
/* Move border dates to the monday of the first full week and sunday of the last week */
DateTime startMonday = dtmStart;
int startDays = 1;
while (startMonday.DayOfWeek != DayOfWeek.Monday)
{
if (startMonday.DayOfWeek != DayOfWeek.Saturday && startMonday.DayOfWeek != DayOfWeek.Sunday)
{
startDays++;
}
startMonday = startMonday.AddDays(1);
}
DateTime endSunday = dtmEnd;
int endDays = 0;
while (endSunday.DayOfWeek != DayOfWeek.Sunday)
{
if (endSunday.DayOfWeek != DayOfWeek.Saturday && endSunday.DayOfWeek != DayOfWeek.Sunday)
{
endDays++;
}
endSunday = endSunday.AddDays(1);
}
int weekDays;
/* calculate weeks between full week border dates and fix the offset created by moving the border dates */
weekDays = (Math.Max(0, (int)Math.Ceiling((endSunday - startMonday).TotalDays + 1)) / 7 * 5) + startDays - endDays;
return weekDays;
}
I needed positive / negatives (not absolute values) so here's how I solved it:
public static int WeekdayDifference(DateTime StartDate, DateTime EndDate)
{
DateTime thisDate = StartDate;
int weekDays = 0;
while (thisDate != EndDate)
{
if (thisDate.DayOfWeek != DayOfWeek.Saturday && thisDate.DayOfWeek != DayOfWeek.Sunday) { weekDays++; }
if (EndDate > StartDate) { thisDate = thisDate.AddDays(1); } else { thisDate = thisDate.AddDays(-1); }
}
/* Determine if value is positive or negative */
if (EndDate > StartDate) {
return weekDays;
}
else
{
return weekDays * -1;
}
}
public static int GetWeekDays(DateTime startDay, DateTime endDate, bool countEndDate = true)
{
var daysBetween = (int)(endDate - startDay).TotalDays;
daysBetween = countEndDate ? daysBetween += 1 : daysBetween;
return Enumerable.Range(0, daysBetween).Count(d => !startDay.AddDays(d).DayOfWeek.In(DayOfWeek.Saturday, DayOfWeek.Sunday));
}
public static bool In<T>(this T source, params T[] list)
{
if (null == source)
{
throw new ArgumentNullException("source");
}
return list.Contains(source);
}
public static List<DateTime> Weekdays(DateTime startDate, DateTime endDate)
{
if (startDate > endDate)
{
Swap(ref startDate, ref endDate);
}
List<DateTime> days = new List<DateTime>();
var ts = endDate - startDate;
for (int i = 0; i < ts.TotalDays; i++)
{
var cur = startDate.AddDays(i);
if (cur.DayOfWeek != DayOfWeek.Saturday && cur.DayOfWeek != DayOfWeek.Sunday)
days.Add(cur);
//if (startingDate.AddDays(i).DayOfWeek != DayOfWeek.Saturday || startingDate.AddDays(i).DayOfWeek != DayOfWeek.Sunday)
//yield return startingDate.AddDays(i);
}
return days;
}
And swap dates
private static void Swap(ref DateTime startDate, ref DateTime endDate)
{
object a = startDate;
startDate = endDate;
endDate = (DateTime)a;
}
Utility functions to get a range of dates:
public IEnumerable<DateTime> GetDates(DateTime begin, int count)
{
var first = new DateTime(begin.Year, begin.Month, begin.Day);
var maxYield = Math.Abs(count);
for (int i = 0; i < maxYield; i++)
{
if(count < 0)
yield return first - TimeSpan.FromDays(i);
else
yield return first + TimeSpan.FromDays(i);
}
yield break;
}
public IEnumerable<DateTime> GetDates(DateTime begin, DateTime end)
{
var days = (int)Math.Ceiling((end - begin).TotalDays);
return GetDates(begin, days);
}
LINQPad demo code:
var begin = DateTime.Now;
var end = begin + TimeSpan.FromDays(14);
var dates = GetDates(begin, end);
var weekdays = dates.Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
var mondays = dates.Count(x => x.DayOfWeek == DayOfWeek.Monday);
var firstThursday = dates
.OrderBy(d => d)
.FirstOrDefault(d => d.DayOfWeek == DayOfWeek.Thursday);
dates.Dump("Dates in Range");
weekdays.Dump("Count of Weekdays");
mondays.Dump("Count of Mondays");
firstThursday.Dump("First Thursday");
var dates = new List<DateTime>();
for (var dt = YourStartDate; dt <= YourEndDate; dt = dt.AddDays(1))
{
if (dt.DayOfWeek != DayOfWeek.Sunday && dt.DayOfWeek != DayOfWeek.Saturday)
{ dates.Add(dt); }
}
in this code you could have a list that all buisness days between two dates.
if you want the count of these dates you could get dates.Count as an integer.
or if you want to get the each day , you could join the list to a string.
Here the function, that calculates count of DayOfWeek betwean two dates. Be carefully it calculates it inclusively (includes beginning day and ending day in calculation):
private int GetWeekdayCount(DayOfWeek dayOfWeek, DateTime begin, DateTime end)
{
if (begin < end)
{
var timeSpan = end.Subtract(begin);
var fullDays = timeSpan.Days;
var count = fullDays / 7; // количество дней равно как минимум количеству полных недель попавших в диапазон
var remain = fullDays % 7; // остаток от деления
// и вычислим попал ли еще один день в те куски недели, что остаются от полной
if (remain > 0)
{
var dowBegin = (int)begin.DayOfWeek;
var dowEnd = (int)end.DayOfWeek;
var dowDay = (int)dayOfWeek;
if (dowBegin < dowEnd)
{
// когда день недели начала меньше дня недели конца, например:
// начало конец
// \/ \/
// -- -- -- -- --
// Вс Пн Вт Ср Чт Пт Сб
if (dowDay >= dowBegin && dowDay <= dowEnd)
count++;
}
else
{
// когда день недели начала больше дня недели конца, например:
// конец начало
// \/ \/
// -- -- -- --
// Вс Пн Вт Ср Чт Пт Сб
if (dowDay <= dowEnd || dowDay >= dowBegin)
count++;
}
}
else if (begin.DayOfWeek == dayOfWeek)
count++;
return count;
}
return 0;
}
Here is another one simple analog of previous function:
private int GetWeekdayCountStupid(DayOfWeek dayOfWeek, DateTime begin, DateTime end)
{
if (begin < end)
{
var count = 0;
var day = begin;
while (day <= end)
{
if (day.DayOfWeek == dayOfWeek)
count++;
day = day.AddDays(1);
}
return count;
}
return 0;
}
And tests for both functions:
[TestMethod()]
public void TestWeekdayCount()
{
var init = new DateTime(2000, 01, 01);
for (int day = 0; day < 7; day++)
{
var dayOfWeek = (DayOfWeek)day;
for (int shift = 0; shift < 8; shift++)
{
for (int i = 0; i < 365; i++)
{
var begin = init.AddDays(shift);
var end = init.AddDays(shift + i);
var count1 = GetWeekdayCount(dayOfWeek, begin, end);
var count2 = GetWeekdayCountStupid(dayOfWeek, begin, end);
Assert.AreEqual(count1, count2);
}
}
}
}
This is an old question but I figured I would share a more flexible answer which allows to removes any day of the week.
I tried to keep the code clean and easy to read while remaining efficient by only looping through 6 days max.
So for the OP you can use it like this:
myDate.DaysUntill(DateTime.UtcNow, new List<DayOfWeek> { DayOfWeek.Saturday, DayOfWeek.Sunday });
/// <summary>
/// For better accuracy make sure <paramref name="startDate"/> and <paramref name="endDate"/> have the same time zone.
/// This is only really useful if we use <paramref name="daysOfWeekToExclude"/> - otherwise the computation is trivial.
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <param name="daysOfWeekToExclude"></param>
/// <returns></returns>
public static int DaysUntill(this DateTime startDate, DateTime endDate, IEnumerable<DayOfWeek> daysOfWeekToExclude = null)
{
if (startDate >= endDate) return 0;
daysOfWeekToExclude = daysOfWeekToExclude?.Distinct() ?? new List<DayOfWeek>();
int nbOfWeeks = (endDate - startDate).Days / 7;
int nbOfExtraDays = (endDate - startDate).Days % 7;
int result = nbOfWeeks * (7 - daysOfWeekToExclude.Count());
for (int i = 0; i < nbOfExtraDays; i++)
{
if (!daysOfWeekToExclude.Contains(startDate.AddDays(i + 1).DayOfWeek)) result++;
}
return result;
}