How to compare current time between two different times in c# [duplicate] - c#

For my app I need to know if Now() is between two values.
The user can set a start- and an end-time so he will not disturbed by a notification (during the night for example).
So if have got two TimePickers (start- and end-time) that the user can set.
Lets say the user sets 22:00 for the StartTime and 07:00 for the EndTime (this would cover the night).
How can I check if the DateTime.Now is between the selected Start and End time?
EDIT:
I only want this to work with the Hour and minutes part. So if the user sets the Start and End time this should work for every night.

First you need to convert everything to the same units (we'll use TimeSpan), then you need to see whether the start-end times cross midnight, and finally do your comparison based on the results of that check:
// convert everything to TimeSpan
TimeSpan start = new TimeSpan(22, 0, 0);
TimeSpan end = new TimeSpan(07, 0, 0);
TimeSpan now = DateTime.Now.TimeOfDay;
// see if start comes before end
if (start < end)
return start <= now && now <= end;
// start is after end, so do the inverse comparison
return !(end < now && now < start);
Here's a function to do it for you:
bool TimeBetween(DateTime datetime, TimeSpan start, TimeSpan end)
{
// convert datetime to a TimeSpan
TimeSpan now = datetime.TimeOfDay;
// see if start comes before end
if (start < end)
return start <= now && now <= end;
// start is after end, so do the inverse comparison
return !(end < now && now < start);
}
You would call it like:
bool silenceAlarm = TimeBetween(DateTime.Now, StartTime.Value, EndTime.Value);

Since you are only gathering two times without dates, you need to figure out if the two times are from the same day or not. If you put the StartTime, EndTime, and Now into TimeSpans:
if (StartTime > EndTime)
{
// the range crosses midnight, do the comparisons independently
return (StartTime < Now) || (Now < EndTime);
}
else
{
// the range is on the same day, both comparisons must be true
return StartTime < Now && Now < EndTime;
}

public static bool isCurrenctDateBetween(DateTime fromDate, DateTime toDate)
{
DateTime curent = DateTime.Now.Date;
if (fromDate.CompareTo(toDate) >= 1)
{
MessageBox.Show("From Date shouldn't be grater than To Date", "DateRange",MessageBoxButton.OKCancel, MessageBoxImage.Warning);
}
int cd_fd = curent.CompareTo(fromDate);
int cd_td = curent.CompareTo(toDate);
if (cd_fd == 0 || cd_td == 0)
{
return true;
}
if (cd_fd >= 1 && cd_td <= -1)
{
return true;
}
return false;
}

DateTime nowDate = DateTime.Now;
// set these to today + time from time picker
DateTime startDate = new DateTime(nowDate.Year, nowDate.Month, nowDate.Day,
selectedStart.Hour, selectedStart.Minute, 0);
DateTime endDate = new DateTime(nowDate.Year, nowDate.Month, nowDate.Day,
selectedEnd.Hour, selectedEnd.Minute, 0);
bool isBetween = nowDate < endDate && nowDate > startDate;
Update 08-Jun-2016
Not sure why the downvote was appropriate as this is a working solution. The OP did ask specifically for DateTime, however I do recommend using TimeSpan instead as per the answer by #Gabe.
Here's a working function as per my answer:
public static bool TimeBetween(DateTime check, DateTime start, DateTime end, bool inclusive = true)
{
var from = new DateTime(check.Year, check.Month, check.Day,
start.Hour, start.Minute, start.Second, start.Millisecond);
var to = new DateTime(check.Year, check.Month, check.Day,
end.Hour, end.Minute, end.Second, end.Millisecond);
if (inclusive)
return from <= check && to >= check;
return from < check && to > check;
}
Here's a working fiddle: https://dotnetfiddle.net/vZCXqv.
Full code:
using System;
public class Program
{
public static void Main()
{
var start = new DateTime(1, 1, 1, 9, 0, 0);
var end = new DateTime(1, 1, 1, 17, 0, 0);
Console.WriteLine("{0} - Too early", TimeBetween(new DateTime(2014, 1, 1, 08, 59, 59, 999), start, end));
Console.WriteLine("{0} - On start time exclusive", TimeBetween(new DateTime(2014, 1, 1, 09, 00, 00, 000), start, end, false));
Console.WriteLine("{0} - On start time inclusive", TimeBetween(new DateTime(2014, 1, 1, 09, 00, 00, 000), start, end));
Console.WriteLine("{0} - After start time", TimeBetween(new DateTime(2014, 1, 1, 09, 00, 00, 001), start, end));
Console.WriteLine("{0} - Before end time", TimeBetween(new DateTime(2014, 1, 1, 16, 59, 59, 999), start, end));
Console.WriteLine("{0} - On end time inclusive", TimeBetween(new DateTime(2014, 1, 1, 17, 00, 00, 000), start, end));
Console.WriteLine("{0} - On end time exclusive", TimeBetween(new DateTime(2014, 1, 1, 17, 00, 00, 000), start, end, false));
Console.WriteLine("{0} - Too late", TimeBetween(new DateTime(2014, 1, 1, 17, 00, 00, 001), start, end));
}
public static bool TimeBetween(DateTime check, DateTime start, DateTime end, bool inclusive = true)
{
var from = new DateTime(check.Year, check.Month, check.Day, start.Hour, start.Minute, start.Second, start.Millisecond);
var to = new DateTime(check.Year, check.Month, check.Day, end.Hour, end.Minute, end.Second, end.Millisecond);
if (inclusive)
return from <= check && to >= check;
return from < check && to > check;
}
}

Just do straight comparison.
if(date > startdate && date < enddate)

Dupe of Find if current time falls in a time range
DateTime start = new DateTime(2009, 12, 9, 10, 0, 0));
DateTime end = new DateTime(2009, 12, 10, 12, 0, 0));
DateTime now = DateTime.Now;
if ((now > start) && (now < end))
{
//match found
}
Timespan, again, taken from dupe.
TimeSpan start = new TimeSpan(10, 0, 0);
TimeSpan end = new TimeSpan(12, 0, 0);
TimeSpan now = DateTime.Now.TimeOfDay;
if ((now > start) && (now < end))
{
//match found
}

I use this, so you can pass DateTime directly:
public static bool TimeBetween(DateTime time, DateTime startDateTime, DateTime endDateTime)
{
// get TimeSpan
TimeSpan start = new TimeSpan(startDateTime.Hour, startDateTime.Minute, 0);
TimeSpan end = new TimeSpan(endDateTime.Hour, endDateTime.Minute, 0);
// convert datetime to a TimeSpan
TimeSpan now = time.TimeOfDay;
// see if start comes before end
if (start < end)
return start <= now && now <= end;
// start is after end, so do the inverse comparison
return !(end < now && now < start);
}

Related

How to get interval between hours and put into list from start into finish?

For example, I will be given a time on hours with type DateTime hours like this
for the starter
my starttime is 00:00
endtime is 02:00
and every time 30 minutes I like to input the value into a List<DateTime>
so, how can I get the value to put into a list that is look like this?
00:00
00:30
01:00
01:30
02:00
My Code
DateTime starTime = new DateTime();
DateTime endTimes = new DateTime();
DateTime interval = new DateTime();
List<DateTime> intervals = new List<DateTime>();
starTime = DateTime.ParseExact(fulldate + "00:00",
"yyyy/MM/dd HH:mm",
CultureInfo.InvariantCulture);
endTimes = DateTime.ParseExact(fulldate + "02:00",
"yyyy/MM/dd HH:mm",
CultureInfo.InvariantCulture); ;
interval = starTime;
for (int i = 0; i < 24; i++)
{
interval.AddHours(0.5);
intervals.Add(interval);
if (interval.ToString("HH:mm") == endTimes.ToString("HH:mm"))
{
break;
}
}
Can anyone help me to solve this?
With some assumption (that end time is on the same day, that your end time is always something that can be devided by 30 mins, ...) this would work.
var start = new TimeSpan(0, 0, 0);
var end = new TimeSpan(2, 0, 0);
var current = start;
List<DateTime> values = new List<DateTime>();
var startDate = DateTime.Now.Date; // editited after #pinkflowydx33's comment
values.Add(startDate + start);
while (current < end)
{
current = current.Add(new TimeSpan(0, 30, 0));
values.Add(startDate + current);
}
foreach (var v in values)
{
Console.WriteLine(v);
}
i prepared that type of solution. - It's loop over number, which represent - times of valueToChange - in this specific case between 30 minutes - and add to the startDate - 30 minutes and also saving to list.
using System;
public class Program
{
public static void Main()
{
List<DateTime> intervals = new List<DateTime>();
var changeValue = 30;
var startDate = new DateTime(2010, 05, 12, 13, 00, 00);
var endDate = new DateTime(2010, 05, 12, 14, 00, 00);
var timeIntervals = System.Math.Abs(startDate.Subtract(endDate).TotalMinutes / changeValue);
for (int i = 0; i < timeIntervals; i++)
{
startDate.AddMinutes(30);
intervals.Add(startDate)
}
}
}
In this case the start and end date are divided by 30 minutes without rest - so if there will be 13:00 and 13:12 - it's doesn't add the value to List - cause the value doesn't > 30.

Find Count of Timespan overlap in Daterange

Let's say we have a Date range like: 2019-05-26 07:12:00 to 2019-11-26 19:15:00
And we got Two timespans: 07:00 and 08:00 for calculation of breakfast.
Now I had to calculate how many time this time overlap occurred.
this is what I wrote so far but I know I miss some times in start & end of periods:
var breakfast = 0;
for (DateTime date = StartDate; date.Date <= EndDate; date = date.AddDays(1))
{
if (date.TimeOfDay >= new TimeSpan(0, 07, 00, 00) && date.TimeOfDay <= new TimeSpan(0, 08, 00, 00))
{
breakfast++;
}
}
///////
var lunch = 0;
for (DateTime date = StartDate; date.Date <= EndDate; date = date.AddDays(1))
{
if (date.TimeOfDay >= new TimeSpan(0, 13, 00, 00) && date.TimeOfDay <= new TimeSpan(0, 14, 00, 00))
{
lunch++;
}
}
Any help will be appreciated.
Now, you say that you have a date range. StartDate and EndDate. Let's say they have values as follows:
StartDate = new DateTime(2019,05,26,07,12); // "2019-05-26 07:12"
EndDate = new DateTime(2019,11,26,19,15); // "2019-11-26 19:15"
Now when you write:
for (DateTime date = StartDate; date.Date <= EndDate; date = date.AddDays(1))
each time this loop runs, you will get date.TimeOfDay as 07:12 for each iteration. This means that if the time component of StartDate does not fall between 07:00 and 08:00 range, then it is not going to fall for any other day in the loop as well.
Therefore, we can simplify the whole counting process in the following way:
var breakfast = 0;
if (StartDate.TimeOfDay >= new TimeSpan(0, 07, 00, 00) && StartDate.TimeOfDay <= new TimeSpan(0, 08, 00, 00))
{
breakfast++;
breakfast += EndDate.Date.Subtract(StartDate.Date.AddDays(1)).Days;
if(EndDate.TimeOfDay >= new TimeSpan(0, 07, 00, 00))
breakfast++;
}

How Can I Only Work Hours DateTime and Time Addition

My code below.I want to do user select 10.06.2015 09:00 - 12.06.2015 13:00 after I will show 2 days 2 hours.
But I want to do Working days and Working Hours beetween 09:00 - 18:00 well users when you 10.06.2015 09:00 - 12.06.2015 13:00 I want to show only 2,5 days.
How can I do?
DateTime t1 = dateTimePicker1.Value.Date;
DateTime t2 = dateTimePicker2.Value.Date;
string s1 = textBox9.Text;
string s2 = textBox10.Text;
DateTime dt1 = t1.AddMinutes(DateTime.Parse(s1).TimeOfDay.TotalMinutes);
DateTime dt2 = t2.AddMinutes(DateTime.Parse(s2).TimeOfDay.TotalMinutes);
var fark = dt2 - dt1;
label1.Text =
String.Format("{0}{1}{2}",
fark.Days > 0 ? string.Format("{0} gün", fark.Days) : "",
fark.Hours > 0 ? string.Format("{0} saat ", fark.Hours) : "",
fark.Minutes > 0 ? string.Format("{0} dakika ", fark.Minutes) : "").Trim();
Well you can assume that any days in the range, except the first and last are full working days. So you need (AllDaysInRange -2) + HoursInfirstDay + HoursInLastDay.
TimeSpan ts = t2 - t1;
ts.Days = ts.Days - 2; //Allow for the 2 end days
int Day1Hours = t1.Hours - 9;//This removes any hours between 00.00 and 09.00
if (day1Hours > 9) //Then the user was working past 18.00
ts.Days = ts.Days+1
else
ts.Hours = ts.Hours + day1Hours;
int Day2Hours = t2.Hours - 9;//This removes any hours between 00.00 and 09.00
if (day2Hours > 9) //Then the user was working past 18.00
ts.Days = ts.Days+1
else
ts.Hours = ts.Hours + day2Hours;
If you can get this to work (I have written it from memory), then I'd wrap the code to convert the hours of the end days into a method rather than repeating it.
According to DateTime Picker In WinForm How To Pick Time? post, you can change your DateTimePicker, to function with times as well.
To limit the range which the users can select, you can modify your ValueChanged event or write your own validation for it.
Probably the simplest is:
private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
{
if (dateTimePicker1.Value.Hour < 10) // the 10 is just a random number, you can change it to your own limit
dateTimePicker1.Value = this.dateTimePicker1.Value.AddHours(10 - dateTimePicker1.Value.Hour);
}
To calculate your 2,5 day according to the working hours, i would write a function to handle this responsibility like:
private int TimeInWorkingHours(DateTime start, DateTime end, int firstHour, int lastHour)
{
int days = Math.Min(end.Subtract(start).Days - 2, 0) ;
int hoursInADay = lastHour - firstHour;
int result = days * hoursInADay;
result += start.Hour - firstHour;
result += lastHour - end.Hour;
return result;
}
This way you call TimeInWorkingHours(...) function with your start date and end date, also providing your first and last working hours.
First you calculate the worked days, than add the border hours. This way you get your worked hours which you can then divide by the work hours to get your working days.
try this
private void GetProperOfficeHours(ref DateTime date)
{
int minHour = 9, maxHour = 17;
if (date.Hour < minHour) //if earlier than office hours - start from 9am
{
date = date + new TimeSpan(9, 0, 0);
}
else if (date.Hour > maxHour) //if later than office hours - go to next day 9am
{
date = date.AddDays(1) + new TimeSpan(9, 0, 0);
}
}
Then ...
//assuming firstDate & lastDate have date and time
int[] weekendDays = new int[2] { 0, 6 }; // Sunday and Saturday
GetProperOfficeHours(ref firstDate);
GetProperOfficeHours(ref lastDate);
while (weekendDays.Contains((int)firstDate.DayOfWeek))
{
//get next date
firstDate = firstDate.AddDays(1);
}
while (weekendDays.Contains((int)lastDate.DayOfWeek))
{
//get prev date
lastDate = lastDate.AddDays(-1);
}
double hourDiff = Math.Abs(firstDate.Hour - lastDate.Hour) / 8.0; //8 office hours
double dayDifference = 0;
while (firstDate.Date <= lastDate.Date) //Loop and skip weekends
{
if (!weekendDays.Contains((int)firstDate.DayOfWeek)) //can also check for holidays here
dayDifference++;
firstDate = firstDate.AddDays(1);
}
dayDifference = dayDifference + hourDiff;
May need some tweaking, hope you find it helpful.
Try this
bool IsWorkingDay(DateTime dt)
{
int year = dt.Year;
Dictionary<DateTime, object> holidays = new Dictionary<DateTime, object>();
holidays.Add(new DateTime(year, 1, 1), null);
holidays.Add(new DateTime(year, 1, 6), null);
holidays.Add(new DateTime(year, 4, 25), null);
holidays.Add(new DateTime(year, 5, 1), null);
holidays.Add(new DateTime(year, 6, 2), null);
holidays.Add(new DateTime(year, 8, 15), null);
holidays.Add(new DateTime(year, 11, 1), null);
holidays.Add(new DateTime(year, 12, 8), null);
holidays.Add(new DateTime(year, 12, 25), null);
holidays.Add(new DateTime(year, 12, 26), null);
DateTime easterMonday = EasterSunday(year).AddDays(1);
if (!holidays.ContainsKey(easterMonday))
holidays.Add(easterMonday, null);
if (!holidays.ContainsKey(dt.Date))
if (dt.DayOfWeek > DayOfWeek.Sunday && dt.DayOfWeek < DayOfWeek.Saturday)
return true;
return false;
}
string WorkingTime(DateTime dt1, DateTime dt2)
{
// Adjust begin datetime
if (IsWorkingDay(dt1))
{
if (dt1.TimeOfDay < TimeSpan.FromHours(9))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0);
else if (dt1.TimeOfDay > TimeSpan.FromHours(13) && dt1.TimeOfDay < TimeSpan.FromHours(14))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 14, 0, 0);
else if (dt1.TimeOfDay > TimeSpan.FromHours(18))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0).AddDays(1);
}
else
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0).AddDays(1);
// Adjust end datetime
if (IsWorkingDay(dt2))
{
if (dt2.TimeOfDay < TimeSpan.FromHours(9))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0).AddDays(-1);
else if (dt2.TimeOfDay > TimeSpan.FromHours(18))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0);
else if (dt2.TimeOfDay > TimeSpan.FromHours(13) && dt2.TimeOfDay < TimeSpan.FromHours(14))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 13, 0, 0);
}
else
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0).AddDays(-1);
double days = 0;
double hours = 0;
double minutes = 0;
if (dt2 > dt1)
{
// Move dt1 forward to reach dt2 day chacking for working days
while (dt1.DayOfYear < dt2.DayOfYear)
{
if (IsWorkingDay(dt1))
days++;
dt1 = dt1.AddDays(1);
}
// Now get the worked hours as if were on the same day in the same manner
TimeSpan sdwt = dt2 - dt1;
if (dt1.TimeOfDay < TimeSpan.FromHours(13) && dt2.TimeOfDay > TimeSpan.FromHours(14))
sdwt -= TimeSpan.FromHours(1);
if (sdwt == TimeSpan.FromHours(8))
days++;
else
{
hours = sdwt.Hours;
minutes = sdwt.Minutes;
}
}
// There is a pause in between so adjust if the interval include it
var totalminutes = (days * 8 * 60 + hours * 60 + minutes);
string res = String.Format("{0} days {1} hours {2} minutes",
days,
hours,
minutes);
string totRes = String.Format("{0} days {1} hours {2} minutes",
totalminutes / 8 / 60,
totalminutes / 8,
totalminutes);
return res + "\r\n" + totRes;
}

Datediff without Weekends [duplicate]

In C#, how can I calculate the number of business (or weekdays) days between two dates?
I've had such a task before and I've got the solution.
I would avoid enumerating all days in between when it's avoidable, which is the case here. I don't even mention creating a bunch of DateTime instances, as I saw in one of the answers above. This is really waste of processing power. Especially in the real world situation, when you have to examine time intervals of several months.
See my code, with comments, below.
/// <summary>
/// Calculates number of business days, taking into account:
/// - weekends (Saturdays and Sundays)
/// - bank holidays in the middle of the week
/// </summary>
/// <param name="firstDay">First day in the time interval</param>
/// <param name="lastDay">Last day in the time interval</param>
/// <param name="bankHolidays">List of bank holidays excluding weekends</param>
/// <returns>Number of business days during the 'span'</returns>
public static int BusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeks
if (businessDays > fullWeekCount*7)
{
// we are here to find out if there is a 1-day or 2-days weekend
// in the time interval remaining after subtracting the complete weeks
int firstDayOfWeek = (int) firstDay.DayOfWeek;
int lastDayOfWeek = (int) lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
return businessDays;
}
Edit by Slauma, August 2011
Great answer! There is little bug though. I take the freedom to edit this answer since the answerer is absent since 2009.
The code above assumes that DayOfWeek.Sunday has the value 7 which is not the case. The value is actually 0. It leads to a wrong calculation if for example firstDay and lastDay are both the same Sunday. The method returns 1 in this case but it should be 0.
Easiest fix for this bug: Replace in the code above the lines where firstDayOfWeek and lastDayOfWeek are declared by the following:
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)lastDay.DayOfWeek;
Now the result is:
Friday to Friday -> 1
Saturday to Saturday -> 0
Sunday to Sunday -> 0
Friday to Saturday -> 1
Friday to Sunday -> 1
Friday to Monday -> 2
Saturday to Monday -> 1
Sunday to Monday -> 1
Monday to Monday -> 1
Ok. I think it's time to post the right answer:
public static double GetBusinessDays(DateTime startD, DateTime endD)
{
double calcBusinessDays =
1 + ((endD - startD).TotalDays * 5 -
(startD.DayOfWeek - endD.DayOfWeek) * 2) / 7;
if (endD.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--;
if (startD.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--;
return calcBusinessDays;
}
Original Source:
http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/
I know this question is already solved, but I thought I could provide a more straightforward-looking answer that may help other visitors in the future.
Here's my take at it:
public int GetWorkingDays(DateTime from, DateTime to)
{
var dayDifference = (int)to.Subtract(from).TotalDays;
return Enumerable
.Range(1, dayDifference)
.Select(x => from.AddDays(x))
.Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
}
This was my original submission:
public int GetWorkingDays(DateTime from, DateTime to)
{
var totalDays = 0;
for (var date = from; date < to; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday)
totalDays++;
}
return totalDays;
}
Define an Extension Method on DateTime like so:
public static class DateTimeExtensions
{
public static bool IsWorkingDay(this DateTime date)
{
return date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday;
}
}
Then, use is within a Where clause to filter a broader list of 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() ;
I used the following code to also take in to account bank holidays:
public class WorkingDays
{
public List<DateTime> GetHolidays()
{
var client = new WebClient();
var json = client.DownloadString("https://www.gov.uk/bank-holidays.json");
var js = new JavaScriptSerializer();
var holidays = js.Deserialize <Dictionary<string, Holidays>>(json);
return holidays["england-and-wales"].events.Select(d => d.date).ToList();
}
public int GetWorkingDays(DateTime from, DateTime to)
{
var totalDays = 0;
var holidays = GetHolidays();
for (var date = from.AddDays(1); date <= to; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday
&& date.DayOfWeek != DayOfWeek.Sunday
&& !holidays.Contains(date))
totalDays++;
}
return totalDays;
}
}
public class Holidays
{
public string division { get; set; }
public List<Event> events { get; set; }
}
public class Event
{
public DateTime date { get; set; }
public string notes { get; set; }
public string title { get; set; }
}
And Unit Tests:
[TestClass]
public class WorkingDays
{
[TestMethod]
public void SameDayIsZero()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 12);
Assert.AreEqual(0, service.GetWorkingDays(from, from));
}
[TestMethod]
public void CalculateDaysInWorkingWeek()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 12);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(4, service.GetWorkingDays(from, to), "Mon - Fri = 4");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Mon - Tues = 1");
}
[TestMethod]
public void NotIncludeWeekends()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 9);
var to = new DateTime(2013, 8, 16);
Assert.AreEqual(5, service.GetWorkingDays(from, to), "Fri - Fri = 5");
Assert.AreEqual(2, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Fri - Tues = 2");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 12)), "Fri - Mon = 1");
}
[TestMethod]
public void AccountForHolidays()
{
var service = new WorkingDays();
var from = new DateTime(2013, 8, 23);
Assert.AreEqual(0, service.GetWorkingDays(from, new DateTime(2013, 8, 26)), "Fri - Mon = 0");
Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 27)), "Fri - Tues = 1");
}
}
I searched a lot for a, easy to digest, algorithm to calculate the working days between 2 dates, and also to exclude the national holidays, and finally I decide to go with this approach:
public static int NumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays)
{
var dic = new Dictionary<DateTime, DayOfWeek>();
var totalDays = (due - start).Days;
for (int i = 0; i < totalDays + 1; i++)
{
if (!holidays.Any(x => x == start.AddDays(i)))
dic.Add(start.AddDays(i), start.AddDays(i).DayOfWeek);
}
return dic.Where(x => x.Value != DayOfWeek.Saturday && x.Value != DayOfWeek.Sunday).Count();
}
Basically I wanted to go with each date and evaluate my conditions:
Is not Saturday
Is not Sunday
Is not national holiday
but also I wanted to avoid iterating dates.
By running and measuring the time need it to evaluate 1 full year, I go the following result:
static void Main(string[] args)
{
var start = new DateTime(2017, 1, 1);
var due = new DateTime(2017, 12, 31);
var sw = Stopwatch.StartNew();
var days = NumberOfWorkingDaysBetween2Dates(start, due,NationalHolidays());
sw.Stop();
Console.WriteLine($"Total working days = {days} --- time: {sw.Elapsed}");
Console.ReadLine();
// result is:
// Total working days = 249-- - time: 00:00:00.0269087
}
Edit: a new method more simple:
public static int ToBusinessWorkingDays(this DateTime start, DateTime due, DateTime[] holidays)
{
return Enumerable.Range(0, (due - start).Days)
.Select(a => start.AddDays(a))
.Where(a => a.DayOfWeek != DayOfWeek.Sunday)
.Where(a => a.DayOfWeek != DayOfWeek.Saturday)
.Count(a => !holidays.Any(x => x == a));
}
This solution avoids iteration, works for both +ve and -ve weekday differences and includes a unit test suite to regression against the slower method of counting weekdays. I've also include a concise method to add weekdays also works in the same non-iterative way.
Unit tests cover a few thousand date combinations in order to exhaustively test all start/end weekday combinations with both small and large date ranges.
Important: We make the assumption that we are counting days by excluding the start date and including the end date. This is important when counting weekdays as the specific start/end days that you include/exclude affect the result. This also ensures that the difference between two equal days is always zero and that we only include full working days as typically you want the answer to be correct for any time on the current start date (often today) and include the full end date (e.g. a due date).
NOTE: This code needs an additional adjustment for holidays but in keeping with the above assumption, this code must exclude holidays on the start date.
Add weekdays:
private static readonly int[,] _addOffset =
{
// 0 1 2 3 4
{0, 1, 2, 3, 4}, // Su 0
{0, 1, 2, 3, 4}, // M 1
{0, 1, 2, 3, 6}, // Tu 2
{0, 1, 4, 5, 6}, // W 3
{0, 1, 4, 5, 6}, // Th 4
{0, 3, 4, 5, 6}, // F 5
{0, 2, 3, 4, 5}, // Sa 6
};
public static DateTime AddWeekdays(this DateTime date, int weekdays)
{
int extraDays = weekdays % 5;
int addDays = weekdays >= 0
? (weekdays / 5) * 7 + _addOffset[(int)date.DayOfWeek, extraDays]
: (weekdays / 5) * 7 - _addOffset[6 - (int)date.DayOfWeek, -extraDays];
return date.AddDays(addDays);
}
Compute weekday difference:
static readonly int[,] _diffOffset =
{
// Su M Tu W Th F Sa
{0, 1, 2, 3, 4, 5, 5}, // Su
{4, 0, 1, 2, 3, 4, 4}, // M
{3, 4, 0, 1, 2, 3, 3}, // Tu
{2, 3, 4, 0, 1, 2, 2}, // W
{1, 2, 3, 4, 0, 1, 1}, // Th
{0, 1, 2, 3, 4, 0, 0}, // F
{0, 1, 2, 3, 4, 5, 0}, // Sa
};
public static int GetWeekdaysDiff(this DateTime dtStart, DateTime dtEnd)
{
int daysDiff = (int)(dtEnd - dtStart).TotalDays;
return daysDiff >= 0
? 5 * (daysDiff / 7) + _diffOffset[(int) dtStart.DayOfWeek, (int) dtEnd.DayOfWeek]
: 5 * (daysDiff / 7) - _diffOffset[6 - (int) dtStart.DayOfWeek, 6 - (int) dtEnd.DayOfWeek];
}
I found that most other solutions on stack overflow were either slow (iterative) or overly complex and many were just plain incorrect. Moral of the story is ... Don't trust it unless you've exhaustively tested it!!
Unit tests based on NUnit Combinatorial testing and ShouldBe NUnit extension.
[TestFixture]
public class DateTimeExtensionsTests
{
/// <summary>
/// Exclude start date, Include end date
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <returns></returns>
private IEnumerable<DateTime> GetDateRange(DateTime dtStart, DateTime dtEnd)
{
Console.WriteLine(#"dtStart={0:yy-MMM-dd ddd}, dtEnd={1:yy-MMM-dd ddd}", dtStart, dtEnd);
TimeSpan diff = dtEnd - dtStart;
Console.WriteLine(diff);
if (dtStart <= dtEnd)
{
for (DateTime dt = dtStart.AddDays(1); dt <= dtEnd; dt = dt.AddDays(1))
{
Console.WriteLine(#"dt={0:yy-MMM-dd ddd}", dt);
yield return dt;
}
}
else
{
for (DateTime dt = dtStart.AddDays(-1); dt >= dtEnd; dt = dt.AddDays(-1))
{
Console.WriteLine(#"dt={0:yy-MMM-dd ddd}", dt);
yield return dt;
}
}
}
[Test, Combinatorial]
public void TestGetWeekdaysDiff(
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int startDay,
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int endDay,
[Values(7)]
int startMonth,
[Values(7)]
int endMonth)
{
// Arrange
DateTime dtStart = new DateTime(2016, startMonth, startDay);
DateTime dtEnd = new DateTime(2016, endMonth, endDay);
int nDays = GetDateRange(dtStart, dtEnd)
.Count(dt => dt.DayOfWeek != DayOfWeek.Saturday && dt.DayOfWeek != DayOfWeek.Sunday);
if (dtEnd < dtStart) nDays = -nDays;
Console.WriteLine(#"countBusDays={0}", nDays);
// Act / Assert
dtStart.GetWeekdaysDiff(dtEnd).ShouldBe(nDays);
}
[Test, Combinatorial]
public void TestAddWeekdays(
[Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int startDay,
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
int weekdays)
{
DateTime dtStart = new DateTime(2016, 7, startDay);
DateTime dtEnd1 = dtStart.AddWeekdays(weekdays); // ADD
dtStart.GetWeekdaysDiff(dtEnd1).ShouldBe(weekdays);
DateTime dtEnd2 = dtStart.AddWeekdays(-weekdays); // SUBTRACT
dtStart.GetWeekdaysDiff(dtEnd2).ShouldBe(-weekdays);
}
}
Well this has been beaten to death. :) However I'm still going to provide another answer because I needed something a bit different. This solution is different in that it returns a Business TimeSpan between the start and end, and you can set the business hours of the day, and add holidays. So you can use it to calculate if it happens within a day, across days, over weekends, and even holidays. And you can get just the business days or not by just getting what you need from the returned TimeSpan object. And the way it uses lists of days, you can see how very easy it would be to add the list of non-work days if it's not the typical Sat and Sun.
And I tested for a year, and it seems super fast.
I just hope the pasting of the code is accurate. But I know it works.
public static TimeSpan GetBusinessTimespanBetween(
DateTime start, DateTime end,
TimeSpan workdayStartTime, TimeSpan workdayEndTime,
List<DateTime> holidays = null)
{
if (end < start)
throw new ArgumentException("start datetime must be before end datetime.");
// Just create an empty list for easier coding.
if (holidays == null) holidays = new List<DateTime>();
if (holidays.Where(x => x.TimeOfDay.Ticks > 0).Any())
throw new ArgumentException("holidays can not have a TimeOfDay, only the Date.");
var nonWorkDays = new List<DayOfWeek>() { DayOfWeek.Saturday, DayOfWeek.Sunday };
var startTime = start.TimeOfDay;
// If the start time is before the starting hours, set it to the starting hour.
if (startTime < workdayStartTime) startTime = workdayStartTime;
var timeBeforeEndOfWorkDay = workdayEndTime - startTime;
// If it's after the end of the day, then this time lapse doesn't count.
if (timeBeforeEndOfWorkDay.TotalSeconds < 0) timeBeforeEndOfWorkDay = new TimeSpan();
// If start is during a non work day, it doesn't count.
if (nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay = new TimeSpan();
else if (holidays.Contains(start.Date)) timeBeforeEndOfWorkDay = new TimeSpan();
var endTime = end.TimeOfDay;
// If the end time is after the ending hours, set it to the ending hour.
if (endTime > workdayEndTime) endTime = workdayEndTime;
var timeAfterStartOfWorkDay = endTime - workdayStartTime;
// If it's before the start of the day, then this time lapse doesn't count.
if (timeAfterStartOfWorkDay.TotalSeconds < 0) timeAfterStartOfWorkDay = new TimeSpan();
// If end is during a non work day, it doesn't count.
if (nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay = new TimeSpan();
else if (holidays.Contains(end.Date)) timeAfterStartOfWorkDay = new TimeSpan();
// Easy scenario if the times are during the day day.
if (start.Date.CompareTo(end.Date) == 0)
{
if (nonWorkDays.Contains(start.DayOfWeek)) return new TimeSpan();
else if (holidays.Contains(start.Date)) return new TimeSpan();
return endTime - startTime;
}
else
{
var timeBetween = end - start;
var daysBetween = (int)Math.Floor(timeBetween.TotalDays);
var dailyWorkSeconds = (int)Math.Floor((workdayEndTime - workdayStartTime).TotalSeconds);
var businessDaysBetween = 0;
// Now the fun begins with calculating the actual Business days.
if (daysBetween > 0)
{
var nextStartDay = start.AddDays(1).Date;
var dayBeforeEnd = end.AddDays(-1).Date;
for (DateTime d = nextStartDay; d <= dayBeforeEnd; d = d.AddDays(1))
{
if (nonWorkDays.Contains(d.DayOfWeek)) continue;
else if (holidays.Contains(d.Date)) continue;
businessDaysBetween++;
}
}
var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween;
var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay;
output = output + new TimeSpan(0, 0, dailyWorkSecondsToAdd);
return output;
}
}
And here is test code: Note that you just have to put this function in a class called DateHelper for the test code to work.
[TestMethod]
public void TestGetBusinessTimespanBetween()
{
var workdayStart = new TimeSpan(8, 0, 0);
var workdayEnd = new TimeSpan(17, 0, 0);
var holidays = new List<DateTime>()
{
new DateTime(2018, 1, 15), // a Monday
new DateTime(2018, 2, 15) // a Thursday
};
var testdata = new[]
{
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 9, 50, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 9, 50, 0),
end = new DateTime(2016, 10, 19, 10, 0, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 7, 50, 0),
end = new DateTime(2016, 10, 19, 8, 5, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 16, 55, 0),
end = new DateTime(2016, 10, 19, 17, 5, 0)
},
new
{
expectedMinutes = 15,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 10,
start = new DateTime(2016, 10, 19, 16, 50, 0),
end = new DateTime(2016, 10, 20, 7, 55, 0)
},
new
{
expectedMinutes = 5,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 8, 5, 0)
},
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 19, 17, 10, 0),
end = new DateTime(2016, 10, 20, 7, 5, 0)
},
new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 20, 12, 15, 0)
},
// Spanning multiple weekdays
new
{
expectedMinutes = 835,
start = new DateTime(2016, 10, 19, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning multiple weekdays
new
{
expectedMinutes = 1375,
start = new DateTime(2016, 10, 18, 12, 10, 0),
end = new DateTime(2016, 10, 21, 8, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins short of complete day.
new
{
expectedMinutes = 1615,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 5, 0)
},
// Spanning from a Thursday to a Tuesday, 5 mins beyond complete day.
new
{
expectedMinutes = 1625,
start = new DateTime(2016, 10, 20, 12, 10, 0),
end = new DateTime(2016, 10, 25, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins beyond complete day.
new
{
expectedMinutes = 545,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 15, 0)
},
// Spanning from a Friday to a Monday, 5 mins short complete day.
new
{
expectedMinutes = 535,
start = new DateTime(2016, 10, 21, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Monday, 5 mins short complete day.
new
{
expectedMinutes = 245,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 24, 12, 5, 0)
},
// Spanning from a Saturday to a Sunday, 5 mins beyond complete day.
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Times within the same Saturday.
new
{
expectedMinutes = 0,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 23, 12, 15, 0)
},
// Spanning from a Saturday to the Sunday next week.
new
{
expectedMinutes = 2700,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2016, 10, 30, 12, 15, 0)
},
// Spanning a year.
new
{
expectedMinutes = 143355,
start = new DateTime(2016, 10, 22, 12, 10, 0),
end = new DateTime(2017, 10, 30, 12, 15, 0)
},
// Spanning a year with 2 holidays.
new
{
expectedMinutes = 142815,
start = new DateTime(2017, 10, 22, 12, 10, 0),
end = new DateTime(2018, 10, 30, 12, 15, 0)
},
};
foreach (var item in testdata)
{
Assert.AreEqual(item.expectedMinutes,
DateHelper.GetBusinessTimespanBetween(
item.start, item.end,
workdayStart, workdayEnd,
holidays)
.TotalMinutes);
}
}
Here's some code for that purpose, with swedish holidays but you can adapt what holidays to count. Note that I added a limit you might want to remove, but it was for a web-based system and I didnt want anyone to enter some huge date to hog the process
public static int GetWorkdays(DateTime from ,DateTime to)
{
int limit = 9999;
int counter = 0;
DateTime current = from;
int result = 0;
if (from > to)
{
DateTime temp = from;
from = to;
to = temp;
}
if (from >= to)
{
return 0;
}
while (current <= to && counter < limit)
{
if (IsSwedishWorkday(current))
{
result++;
}
current = current.AddDays(1);
counter++;
}
return result;
}
public static bool IsSwedishWorkday(DateTime date)
{
return (!IsSwedishHoliday(date) && date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday);
}
public static bool IsSwedishHoliday(DateTime date)
{
return (
IsSameDay(GetEpiphanyDay(date.Year), date) ||
IsSameDay(GetMayDay(date.Year), date) ||
IsSameDay(GetSwedishNationalDay(date.Year), date) ||
IsSameDay(GetChristmasDay(date.Year), date) ||
IsSameDay(GetBoxingDay(date.Year), date) ||
IsSameDay(GetGoodFriday(date.Year), date) ||
IsSameDay(GetAscensionDay(date.Year), date) ||
IsSameDay(GetAllSaintsDay(date.Year), date) ||
IsSameDay(GetMidsummersDay(date.Year), date) ||
IsSameDay(GetPentecostDay(date.Year), date) ||
IsSameDay(GetEasterMonday(date.Year), date) ||
IsSameDay(GetNewYearsDay(date.Year), date) ||
IsSameDay(GetEasterDay(date.Year), date)
);
}
// Trettondagen
public static DateTime GetEpiphanyDay(int year)
{
return new DateTime(year, 1, 6);
}
// Första maj
public static DateTime GetMayDay(int year)
{
return new DateTime(year,5,1);
}
// Juldagen
public static DateTime GetSwedishNationalDay(int year)
{
return new DateTime(year, 6, 6);
}
// Juldagen
public static DateTime GetNewYearsDay(int year)
{
return new DateTime(year,1,1);
}
// Juldagen
public static DateTime GetChristmasDay(int year)
{
return new DateTime(year,12,25);
}
// Annandag jul
public static DateTime GetBoxingDay(int year)
{
return new DateTime(year, 12, 26);
}
// Långfredagen
public static DateTime GetGoodFriday(int year)
{
return GetEasterDay(year).AddDays(-3);
}
// Kristi himmelsfärdsdag
public static DateTime GetAscensionDay(int year)
{
return GetEasterDay(year).AddDays(5*7+4);
}
// Midsommar
public static DateTime GetAllSaintsDay(int year)
{
DateTime result = new DateTime(year,10,31);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Midsommar
public static DateTime GetMidsummersDay(int year)
{
DateTime result = new DateTime(year, 6, 20);
while (result.DayOfWeek != DayOfWeek.Saturday)
{
result = result.AddDays(1);
}
return result;
}
// Pingstdagen
public static DateTime GetPentecostDay(int year)
{
return GetEasterDay(year).AddDays(7 * 7);
}
// Annandag påsk
public static DateTime GetEasterMonday(int year)
{
return GetEasterDay(year).AddDays(1);
}
public static DateTime GetEasterDay(int y)
{
double c;
double n;
double k;
double i;
double j;
double l;
double m;
double d;
c = System.Math.Floor(y / 100.0);
n = y - 19 * System.Math.Floor(y / 19.0);
k = System.Math.Floor((c - 17) / 25.0);
i = c - System.Math.Floor(c / 4) - System.Math.Floor((c - k) / 3) + 19 * n + 15;
i = i - 30 * System.Math.Floor(i / 30);
i = i - System.Math.Floor(i / 28) * (1 - System.Math.Floor(i / 28) * System.Math.Floor(29 / (i + 1)) * System.Math.Floor((21 - n) / 11));
j = y + System.Math.Floor(y / 4.0) + i + 2 - c + System.Math.Floor(c / 4);
j = j - 7 * System.Math.Floor(j / 7);
l = i - j;
m = 3 + System.Math.Floor((l + 40) / 44);// month
d = l + 28 - 31 * System.Math.Floor(m / 4);// day
double days = ((m == 3) ? d : d + 31);
DateTime result = new DateTime(y, 3, 1).AddDays(days-1);
return result;
}
Works and without loops
This method doesn't use any loops and is actually quite simple. It expands the date range to full weeks since we know that each week has 5 business days. It then uses a lookup table to find the number of business days to subtract from the start and end to get the right result. I've expanded out the calculation to help show what's going on, but the whole thing could be condensed into a single line if needed.
Anyway, this works for me and so I thought I'd post it here in case it might help others. Happy coding.
Calculation
t : Total number of days between dates (1 if min = max)
a + b : Extra days needed to expand total to full weeks
k : 1.4 is number of weekdays per week, i.e., (t / 7) * 5
c : Number of weekdays to subtract from the total
m : A lookup table used to find the value of "c" for each day of the week
Culture
Code assumes a Monday to Friday work week. For other cultures, such as Sunday to Thursday, you'll need to offset the dates prior to calculation.
Method
public int Weekdays(DateTime min, DateTime max)
{
if (min.Date > max.Date) throw new Exception("Invalid date span");
var t = (max.AddDays(1).Date - min.Date).TotalDays;
var a = (int) min.DayOfWeek;
var b = 6 - (int) max.DayOfWeek;
var k = 1.4;
var m = new int[]{0, 0, 1, 2, 3, 4, 5};
var c = m[a] + m[b];
return (int)((t + a + b) / k) - c;
}
Here's a quick sample code. It's a class method, so will only work inside of your class. If you want it to be static, change the signature to private static (or public static).
private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed)
{
for (var d = sd; d <= ed; d = d.AddDays(1))
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
yield return d;
}
This method creates a loop variable d, initializes it to the start day, sd, then increments by one day each iteration (d = d.AddDays(1)).
It returns the desired values using yield, which creates an iterator. The cool thing about iterators is that they don't hold all of the values of the IEnumerable in memory, only calling each one sequentially. This means that you can call this method from the dawn of time to now without having to worry about running out of memory.
This method returns the number of business days between two dates:
Here I use the DayOfWeek enum for checking weekends.
private static int BusinessDaysLeft(DateTime first, DateTime last)
{
var count = 0;
while (first.Date != last.Date)
{
if(first.DayOfWeek != DayOfWeek.Saturday && first.DayOfWeek != DayOfWeek.Sunday)
count++;
first = first.AddDays(1);
}
return count;
}
I think none of the above answers are actually correct. None of them solves all the special cases such as when the dates starts and ends on the middle of a weekend, when the date starts on a Friday and ends on next Monday, etc. On top of that, they all round the calculations to whole days, so if the start date is in the middle of a saturday for example, it will substract a whole day from the working days, giving wrong results...
Anyway, here is my solution that is quite efficient and simple and works for all cases. The trick is just to find the previous Monday for start and end dates, and then do a small compensation when start and end happens during the weekend:
public double WorkDays(DateTime startDate, DateTime endDate){
double weekendDays;
double days = endDate.Subtract(startDate).TotalDays;
if(days<0) return 0;
DateTime startMonday = startDate.AddDays(DayOfWeek.Monday - startDate.DayOfWeek).Date;
DateTime endMonday = endDate.AddDays(DayOfWeek.Monday - endDate.DayOfWeek).Date;
weekendDays = ((endMonday.Subtract(startMonday).TotalDays) / 7) * 2;
// compute fractionary part of weekend days
double diffStart = startDate.Subtract(startMonday).TotalDays - 5;
double diffEnd = endDate.Subtract(endMonday).TotalDays - 5;
// compensate weekenddays
if(diffStart>0) weekendDays -= diffStart;
if(diffEnd>0) weekendDays += diffEnd;
return days - weekendDays;
}
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DateTime start = new DateTime(2014, 1, 1);
DateTime stop = new DateTime(2014, 12, 31);
int totalWorkingDays = GetNumberOfWorkingDays(start, stop);
Console.WriteLine("There are {0} working days.", totalWorkingDays);
}
private static int GetNumberOfWorkingDays(DateTime start, DateTime stop)
{
TimeSpan interval = stop - start;
int totalWeek = interval.Days / 7;
int totalWorkingDays = 5 * totalWeek;
int remainingDays = interval.Days % 7;
for (int i = 0; i <= remainingDays; i++)
{
DayOfWeek test = (DayOfWeek)(((int)start.DayOfWeek + i) % 7);
if (test >= DayOfWeek.Monday && test <= DayOfWeek.Friday)
totalWorkingDays++;
}
return totalWorkingDays;
}
}
}
Here is the function which we can use to calculate business days between two date. I'm not using holiday list as it can vary accross country/region.
If we want to use it anyway we can take third argument as list of holiday and before incrementing count we should check that list does not contains d
public static int GetBussinessDaysBetweenTwoDates(DateTime StartDate, DateTime EndDate)
{
if (StartDate > EndDate)
return -1;
int bd = 0;
for (DateTime d = StartDate; d < EndDate; d = d.AddDays(1))
{
if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
bd++;
}
return bd;
}
By using marinjw library,
And solution provided by Alec Pojidaev i created below, this will skip public (you need to specify country) holidays:
Please Note that if you use actual DateTime and have hours present then you have to add .Date and best to reformat FromDate and ToDate as FromDate = FromDate.Date and ToDate = ToDate.Date
public static int GetProcessingTime(this DateTime? FromDate, DateTime? ToDate)
{
if (FromDate == null) return 0;
if (ToDate == null) return 0;
List<DateTime> Holidays = new List<DateTime>();
int startyear = FromDate.Value.Year;
int endyear = ToDate.Value.Year;
//loop years to extract all holidays
for(int i = startyear; i <= endyear; i++)
{
IList<DateTime> Temp = new PolandPublicHoliday().PublicHolidays(i);
Holidays.AddRange(Temp.ToList());
}
//exclude holidays outside of range
Holidays = Holidays.Where(x => x >= FromDate && x <= ToDate).ToList();
//exclude holidays that are set to be on sunday/saturday
Holidays = Holidays.Where(x => x.DayOfWeek != DayOfWeek.Sunday && x.DayOfWeek != DayOfWeek.Saturday).ToList();
//calculate date difference without sundays and saturdays Value need to be added as I am using nullable DateTime
double calcBusinessDays = 1 + ((FromDate.Value - ToDate.Value).TotalDays * 5 - (FromDate.Value.DayOfWeek - ToDate.Value.DayOfWeek) * 2) / 7;
if (FromDate.Value.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--;
if (ToDate.Value.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--;
//remove left holidays
if (Holidays!=null)
calcBusinessDays -= Holidays.Count;
return (int)calcBusinessDays;
}
I'll just share my solution. It worked for me, maybe I just don't notice/know that theres a bug.
I started by getting the first incomplete week if there's any.
a complete week was from sunday for saturday, so if the (int)_now.DayOfWeek was not 0(Sunday), the first week was incomplete.
I just subtract 1 to first weeks count for the first week's saturday then add it to new count;
Then I get the last incomplete week, then subtract 1 for it's sunday then add to new count.
Then finally, the number of complete weeks multiply by 5(weekdays) was added to new count.
public int RemoveNonWorkingDays(int numberOfDays){
int workingDays = 0;
int firstWeek = 7 - (int)_now.DayOfWeek;
if(firstWeek < 7){
if(firstWeek > numberOfDays)
return numberOfDays;
workingDays += firstWeek-1;
numberOfDays -= firstWeek;
}
int lastWeek = numberOfDays % 7;
if(lastWeek > 0){
numberOfDays -= lastWeek;
workingDays += lastWeek - 1;
}
workingDays += (numberOfDays/7)*5;
return workingDays;
}
I was having trouble finding a solid TSQL version of this code. Below is essentially a conversion of the C# code here with addition of the Holiday table which should be used to pre-calculate holidays.
CREATE TABLE dbo.Holiday
(
HolidayDt DATE NOT NULL,
Name NVARCHAR(50) NOT NULL,
IsWeekday BIT NOT NULL,
CONSTRAINT PK_Holiday PRIMARY KEY (HolidayDt)
)
GO
CREATE INDEX IDX_Holiday ON Holiday (HolidayDt, IsWeekday)
GO
CREATE function dbo.GetBusinessDays
(
#FirstDay datetime,
#LastDay datetime
)
RETURNS INT
AS
BEGIN
DECLARE #BusinessDays INT, #FullWeekCount INT
SELECT #FirstDay = CONVERT(DATETIME,CONVERT(DATE,#FirstDay))
, #LastDay = CONVERT(DATETIME,CONVERT(DATE,#LastDay))
IF #FirstDay > #LastDay
RETURN NULL;
SELECT #BusinessDays = DATEDIFF(DAY, #FirstDay, #LastDay) + 1
SELECT #FullWeekCount = #BusinessDays / 7;
-- find out if there are weekends during the time exceedng the full weeks
IF #BusinessDays > (#FullWeekCount * 7)
BEGIN
-- we are here to find out if there is a 1-day or 2-days weekend
-- in the time interval remaining after subtracting the complete weeks
DECLARE #firstDayOfWeek INT, #lastDayOfWeek INT;
SELECT #firstDayOfWeek = DATEPART(DW, #FirstDay), #lastDayOfWeek = DATEPART(DW, #LastDay);
IF #lastDayOfWeek < #firstDayOfWeek
SELECT #lastDayOfWeek = #lastDayOfWeek + 7;
IF #firstDayOfWeek <= 6
BEGIN
IF (#lastDayOfWeek >= 7) --Both Saturday and Sunday are in the remaining time interval
BEGIN
SELECT #BusinessDays = #BusinessDays - 2
END
ELSE IF #lastDayOfWeek>=6 --Only Saturday is in the remaining time interval
BEGIN
SELECT #BusinessDays = #BusinessDays - 1
END
END
ELSE IF #firstDayOfWeek <= 7 AND #lastDayOfWeek >=7 -- Only Sunday is in the remaining time interval
BEGIN
SELECT #BusinessDays = #BusinessDays - 1
END
END
-- subtract the weekends during the full weeks in the interval
DECLARE #Holidays INT;
SELECT #Holidays = COUNT(*)
FROM Holiday
WHERE HolidayDt BETWEEN #FirstDay AND #LastDay
AND IsWeekday = CAST(1 AS BIT)
SELECT #BusinessDays = #BusinessDays - (#FullWeekCount + #FullWeekCount) -- - #Holidays
RETURN #BusinessDays
END
int BusinessDayDifference(DateTime Date1, DateTime Date2)
{
int Sign = 1;
if (Date2 > Date1)
{
Sign = -1;
DateTime TempDate = Date1;
Date1 = Date2;
Date2 = TempDate;
}
int BusDayDiff = (int)(Date1.Date - Date2.Date).TotalDays;
if (Date1.DayOfWeek == DayOfWeek.Saturday)
BusDayDiff -= 1;
if (Date2.DayOfWeek == DayOfWeek.Sunday)
BusDayDiff -= 1;
int Week1 = GetWeekNum(Date1);
int Week2 = GetWeekNum(Date2);
int WeekDiff = Week1 - Week2;
BusDayDiff -= WeekDiff * 2;
foreach (DateTime Holiday in Holidays)
if (Date1 >= Holiday && Date2 <= Holiday)
BusDayDiff--;
BusDayDiff *= Sign;
return BusDayDiff;
}
private int GetWeekNum(DateTime Date)
{
return (int)(Date.AddDays(-(int)Date.DayOfWeek).Ticks / TimeSpan.TicksPerDay / 7);
}
Here is one very simple solution for this problem. We have starting date, end date and "for loop" for encreasing the day and calculating to see if it's a workday or a weekend by converting to string DayOfWeek.
class Program
{
static void Main(string[] args)
{
DateTime day = new DateTime();
Console.Write("Inser your end date (example: 01/30/2015): ");
DateTime endDate = DateTime.Parse(Console.ReadLine());
int numberOfDays = 0;
for (day = DateTime.Now.Date; day.Date < endDate.Date; day = day.Date.AddDays(1))
{
string dayToString = Convert.ToString(day.DayOfWeek);
if (dayToString != "Saturday" && dayToString != "Sunday") numberOfDays++;
}
Console.WriteLine("Number of working days (not including local holidays) between two dates is "+numberOfDays);
}
}
Based on the comment marked as answer and patch recommended , as well as -> This version wants to convert the Days to Business-Hours ... Considers Same day hours as well.
/// <summary>
/// Calculates number of business days, taking into account:
/// - weekends (Saturdays and Sundays)
/// - bank holidays in the middle of the week
/// </summary>
/// <param name="firstDay">First day in the time interval</param>
/// <param name="lastDay">Last day in the time interval</param>
/// <param name="bankHolidays">List of bank holidays excluding weekends</param>
/// <returns>Number of business hours during the 'span'</returns>
public static int BusinessHoursUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
var original_firstDay = firstDay;
var original_lastDay = lastDay;
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
return -1; //// throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeks
if (businessDays > fullWeekCount * 7)
{
// we are here to find out if there is a 1-day or 2-days weekend
// in the time interval remaining after subtracting the complete weeks
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
if (bankHolidays != null && bankHolidays.Any())
{
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
}
int total_business_hours = 0;
if (firstDay.Date == lastDay.Date)
{//If on the same day, go granular with Hours from the Orginial_*Day values
total_business_hours = (int)(original_lastDay - original_firstDay).TotalHours;
}
else
{//Convert Business-Days to TotalHours
total_business_hours = (int)(firstDay.AddDays(businessDays).AddHours(firstDay.Hour) - firstDay).TotalHours;
}
return total_business_hours;
}
I just improved #Alexander and #Slauma answer to support a business week as a parameter, for cases where saturday is a business day, or even cases where there is just a couple of days of the week that are considered business days:
/// <summary>
/// Calculate the number of business days between two dates, considering:
/// - Days of the week that are not considered business days.
/// - Holidays between these two dates.
/// </summary>
/// <param name="fDay">First day of the desired 'span'.</param>
/// <param name="lDay">Last day of the desired 'span'.</param>
/// <param name="BusinessDaysOfWeek">Days of the week that are considered to be business days, if NULL considers monday, tuesday, wednesday, thursday and friday as business days of the week.</param>
/// <param name="Holidays">Holidays, if NULL, considers no holiday.</param>
/// <returns>Number of business days during the 'span'</returns>
public static int BusinessDaysUntil(this DateTime fDay, DateTime lDay, DayOfWeek[] BusinessDaysOfWeek = null, DateTime[] Holidays = null)
{
if (BusinessDaysOfWeek == null)
BusinessDaysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday };
if (Holidays == null)
Holidays = new DateTime[] { };
fDay = fDay.Date;
lDay = lDay.Date;
if (fDay > lDay)
throw new ArgumentException("Incorrect last day " + lDay);
int bDays = (lDay - fDay).Days + 1;
int fullWeekCount = bDays / 7;
int fullWeekCountMult = 7 - WeekDays.Length;
// Find out if there are weekends during the time exceedng the full weeks
if (bDays > (fullWeekCount * 7))
{
int fDayOfWeek = (int)fDay.DayOfWeek;
int lDayOfWeek = (int)lDay.DayOfWeek;
if (fDayOfWeek > lDayOfWeek)
lDayOfWeek += 7;
// If they are the same, we already covered it right before the Holiday subtraction
if (lDayOfWeek != fDayOfWeek)
{
// Here we need to see if any of the days between are considered business days
for (int i = fDayOfWeek; i <= lDayOfWeek; i++)
if (!WeekDays.Contains((DayOfWeek)(i > 6 ? i - 7 : i)))
bDays -= 1;
}
}
// Subtract the days that are not in WeekDays[] during the full weeks in the interval
bDays -= (fullWeekCount * fullWeekCountMult);
// Subtract the number of bank holidays during the time interval
bDays = bDays - Holidays.Select(x => x.Date).Count(x => fDay <= x && x <= lDay);
return bDays;
}
I believe this could be a simpler way:
public int BusinessDaysUntil(DateTime start, DateTime end, params DateTime[] bankHolidays)
{
int tld = (int)((end - start).TotalDays) + 1; //including end day
int not_buss_day = 2 * (tld / 7); //Saturday and Sunday
int rest = tld % 7; //rest.
if (rest > 0)
{
int tmp = (int)start.DayOfWeek - 1 + rest;
if (tmp == 6 || start.DayOfWeek == DayOfWeek.Sunday) not_buss_day++; else if (tmp > 6) not_buss_day += 2;
}
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (!(bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) && (start <= bh && bh <= end))
{
not_buss_day++;
}
}
return tld - not_buss_day;
}
Here's yet another idea - this method allows to specify any working week and holidays.
The idea here is that we find the core of the date range from the first first working day of the week to the last weekend day of the week. This enables us to calculate the whole weeks easily (without iterating over all of the dates). All we need to do then is to add the working days that fall before the start and end of this core range.
public static int CalculateWorkingDays(
DateTime startDate,
DateTime endDate,
IList<DateTime> holidays,
DayOfWeek firstDayOfWeek,
DayOfWeek lastDayOfWeek)
{
// Make sure the defined working days run contiguously
if (lastDayOfWeek < firstDayOfWeek)
{
throw new Exception("Last day of week cannot fall before first day of week!");
}
// Create a list of the days of the week that make-up the weekend by working back
// from the firstDayOfWeek and forward from lastDayOfWeek to get the start and end
// the weekend
var weekendStart = lastDayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : lastDayOfWeek + 1;
var weekendEnd = firstDayOfWeek == DayOfWeek.Sunday ? DayOfWeek.Saturday : firstDayOfWeek - 1;
var weekendDays = new List<DayOfWeek>();
var w = weekendStart;
do {
weekendDays.Add(w);
if (w == weekendEnd) break;
w = (w == DayOfWeek.Saturday) ? DayOfWeek.Sunday : w + 1;
} while (true);
// Force simple dates - no time
startDate = startDate.Date;
endDate = endDate.Date;
// Ensure a progessive date range
if (endDate < startDate)
{
var t = startDate;
startDate = endDate;
endDate = t;
}
// setup some working variables and constants
const int daysInWeek = 7; // yeah - really!
var actualStartDate = startDate; // this will end up on startOfWeek boundary
var actualEndDate = endDate; // this will end up on weekendEnd boundary
int workingDaysInWeek = daysInWeek - weekendDays.Count;
int workingDays = 0; // the result we are trying to find
int leadingDays = 0; // the number of working days leading up to the firstDayOfWeek boundary
int trailingDays = 0; // the number of working days counting back to the weekendEnd boundary
// Calculate leading working days
// if we aren't on the firstDayOfWeek we need to step forward to the nearest
if (startDate.DayOfWeek != firstDayOfWeek)
{
var d = startDate;
do {
if (d.DayOfWeek == firstDayOfWeek || d >= endDate)
{
actualStartDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
leadingDays++;
}
d = d.AddDays(1);
} while(true);
}
// Calculate trailing working days
// if we aren't on the weekendEnd we step back to the nearest
if (endDate >= actualStartDate && endDate.DayOfWeek != weekendEnd)
{
var d = endDate;
do {
if (d.DayOfWeek == weekendEnd || d < actualStartDate)
{
actualEndDate = d;
break;
}
if (!weekendDays.Contains(d.DayOfWeek))
{
trailingDays++;
}
d = d.AddDays(-1);
} while(true);
}
// Calculate the inclusive number of days between the actualStartDate and the actualEndDate
var coreDays = (actualEndDate - actualStartDate).Days + 1;
var noWeeks = coreDays / daysInWeek;
// add together leading, core and trailing days
workingDays += noWeeks * workingDaysInWeek;
workingDays += leadingDays;
workingDays += trailingDays;
// Finally remove any holidays that fall within the range.
if (holidays != null)
{
workingDays -= holidays.Count(h => h >= startDate && (h <= endDate));
}
return workingDays;
}
Since I can't comment. There is one more issue with the accepted solution where bank holidays are subtracted even when they are situated in the weekend. Seeing how other input is checked, it is only fitting that this is as well.
The foreach should therefore be:
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
// Do not subtract bank holidays when they fall in the weekend to avoid double subtraction
if (bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday)
continue;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
Here is an approach if you are using MVC.
I have also calculated national holidays or any festive days to be excluded by fetching it from holidayscalendar which you will need to make one.
foreach (DateTime day in EachDay(model))
{
bool key = false;
foreach (LeaveModel ln in holidaycalendar)
{
if (day.Date == ln.Date && day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday)
{
key = true; break;
}
}
if (day.DayOfWeek == DayOfWeek.Saturday || day.DayOfWeek == DayOfWeek.Sunday)
{
key = true;
}
if (key != true)
{
leavecount++;
}
}
Leavemodel is a list here
Here is an helper function I wrote for that task. it also returns the count of weekends via the out parameter. if you wish you can customize the "weekend" days in run time for countries that use different weekend days or to include holidays trough the weekendDays[] optional parameter :
public static int GetNetworkDays(DateTime startDate, DateTime endDate,out int totalWeekenDays, DayOfWeek[] weekendDays = null)
{
if (startDate >= endDate)
{
throw new Exception("start date can not be greater then or equel to end date");
}
DayOfWeek[] weekends = new DayOfWeek[] { DayOfWeek.Sunday, DayOfWeek.Saturday };
if (weekendDays != null)
{
weekends = weekendDays;
}
var totaldays = (endDate - startDate).TotalDays + 1; // add one to include the first day to
var counter = 0;
var workdaysCounter = 0;
var weekendsCounter = 0;
for (int i = 0; i < totaldays; i++)
{
if (weekends.Contains(startDate.AddDays(counter).DayOfWeek))
{
weekendsCounter++;
}
else
{
workdaysCounter++;
}
counter++;
}
totalWeekenDays = weekendsCounter;
return workdaysCounter;
}
I came up with the following solution
var dateStart = new DateTime(2019,01,10);
var dateEnd = new DateTime(2019,01,31);
var timeBetween = (dateEnd - dateStart).TotalDays + 1;
int numberOf7DayWeeks = (int)(timeBetween / 7);
int numberOfWeekendDays = numberOf7DayWeeks * 2;
int workingDays =(int)( timeBetween - numberOfWeekendDays);
if(dateStart.DayOfWeek == DayOfWeek.Saturday || dateEnd.DayOfWeek == DayOfWeek.Sunday){
workingDays -=2;
}
if(dateStart.DayOfWeek == DayOfWeek.Sunday || dateEnd.DayOfWeek == DayOfWeek.Saturday){
workingDays -=1;
}
You just have to iterate through each day in the time range and subtract a day from the counter if its a Saturday or a Sunday.
private float SubtractWeekend(DateTime start, DateTime end) {
float totaldays = (end.Date - start.Date).Days;
var iterationVal = totalDays;
for (int i = 0; i <= iterationVal; i++) {
int dayVal = (int)start.Date.AddDays(i).DayOfWeek;
if(dayVal == 6 || dayVal == 0) {
// saturday or sunday
totalDays--;
}
}
return totalDays;
}
public static int CalculateBusinessDaysInRange(this DateTime startDate, DateTime endDate, params DateTime[] holidayDates)
{
endDate = endDate.Date;
if(startDate > endDate)
throw new ArgumentException("The end date can not be before the start date!", nameof(endDate));
int accumulator = 0;
DateTime itterator = startDate.Date;
do
{
if(itterator.DayOfWeek != DayOfWeek.Saturday && itterator.DayOfWeek != DayOfWeek.Sunday && !holidayDates.Any(hol => hol.Date == itterator))
{ accumulator++; }
}
while((itterator = itterator.AddDays(1)).Date <= endDate);
return accumulator
}
I'm only posting this because despite all of the excellent answers that have been given, none of the math made sense to me. This is definitely a KISS method that should work and be fairly maintainable. Granted if you are calculating ranges that are greater than 2-3 months this will not be the most effective way. We simply determine if it is a Saturday or Sunday or the date is a given holiday date. If it's not we add a business day. If it is then everything is fine.
I'm sure this could be even more so simplified with LINQ, but this way is much easier to understand.

How to check if a DateTime range is within another 3 month DateTime range

Hi I have a Start Date and End Date per record in a db.
I need to check to see where the time period falls in a 2 year period broken into two lots of quarters then display what quarters each record falls into.
Quarter 1 includes June 09, Jul 09, Aug 09
Quarter 2 includes Sept 09, Oct 09, Nov 09
Quarter 3 includes Dec 09, Jan 10, Feb 10
Quarter 4 includes Mar 10, Apr 10, May 10
Quaretr 5 includes Jun 10, Jul 10...
e.g. 01/10/09 - 01/06/10 would fall into quarters 2, 3, 4 & 5
I am very new to .NET so any examples would be much appreciated.
This should work for you also.
class Range
{
public DateTime Begin { get; private set; }
public DateTime End { get; private set; }
public Range(DateTime begin, DateTime end)
{
Begin = begin;
End = end;
}
public bool Contains(Range range)
{
return range.Begin >= Begin && range.End <= End;
}
}
and then to use it
List<Range> ranges = new List<Range>();
ranges.Add(new Range(DateTime.Now, DateTime.Now.AddMonths(3)));
ranges.Add(new Range(DateTime.Now.AddMonths(3), DateTime.Now.AddMonths(6)));
Range test = new Range(DateTime.Now.AddMonths(1), DateTime.Now.AddMonths(2));
var hits = ranges.Where(range => range.Contains(test));
MessageBox.Show(hits.Count().ToString());
You would call IntervalInQuarters as follows:
IntervalInQuarters(new DateTime(2007, 10, 10), new DateTime(2009, 10, 11));
The function returns a list of quarter start dates. Note that the range of quarters searched is defined within the function itself. Please edit as appropriate for your situation. They key point is to make sure the interval/quarter intersection logic is right.
private List<DateTime> IntervalInQuarters(DateTime myStartDate, DateTime myEndDate)
{
DateTime quarterStart = new DateTime(2006, 06, 01);
DateTime nextQuarterStart = new DateTime(2006, 09, 01);
DateTime finalDate = new DateTime(2011, 01, 01);
List<DateTime> foundQuarters = new List<DateTime>();
while (quarterStart < finalDate)
{
// quarter intersects interval if:
// its start/end date is within our interval
// our start/end date is within quarter interval
DateTime quarterEnd = nextQuarterStart.AddDays(-1);
if (DateInInterval(myStartDate, quarterStart, quarterEnd) ||
DateInInterval(myEndDate, quarterStart, quarterEnd) ||
DateInInterval(quarterStart, myStartDate, myEndDate) ||
DateInInterval(quarterEnd, myStartDate, myEndDate))
{
foundQuarters.Add(quarterStart);
}
quarterStart = nextQuarterStart;
nextQuarterStart = nextQuarterStart.AddMonths(3);
}
return foundQuarters;
}
private bool DateInInterval(DateTime myDate, DateTime intStart, DateTime intEnd)
{
return ((intStart <= myDate) && (myDate <= intEnd));
}
private void Form1_Load(object sender, EventArgs e)
{
DateTime[,] ranges = new DateTime[3,2];
//Range 1 - Jan to March
ranges[0, 0] = new DateTime(2010, 1, 1);
ranges[0, 1] = new DateTime(2010, 3, 1);
//Range 2 - April to July
ranges[1, 0] = new DateTime(2010, 4, 1);
ranges[1, 1] = new DateTime(2010, 7, 1);
//Range 3 - March to June
ranges[2, 0] = new DateTime(2010, 3, 1);
ranges[2, 1] = new DateTime(2010, 6, 1);
DateTime checkDate = new DateTime(2010, 4, 1);
string validRanges = string.Empty;
for (int i = 0; i < ranges.GetLength(0); i++)
{
if (DateWithin(ranges[i,0], ranges[i,1], checkDate))
{
validRanges += i.ToString() + " ";
}
}
MessageBox.Show(validRanges);
}
private bool DateWithin(DateTime dateStart, DateTime dateEnd, DateTime checkDate)
{
if (checkDate.CompareTo(dateStart) < 0 || checkDate.CompareTo(dateEnd) > 0)
{
return false;
}
return true;
}
You may have to take a look at: http://msdn.microsoft.com/en-us/library/03ybds8y(v=VS.100).aspx
This may start you up
FindQuarter(DateTime startDate, DateTime endDate) // 01-10-09, 01-06-10
{
startDateQuarter = GetQuarter(startDate.Month); // 2
endDateQuarter = GetQuarter(endDate.Month); // 1
endDateQuarter += (endDate.Year - startDate.Year) * 4; // 5
// fill up startDateQuarter to endDateQuarter into a list
// and return it // 2,3,4,5
}
GetQuarter(int month) // 6
{
int quarter;
// check the month value and accordingly assign one of the basic quarters
// using if-else construct ie, if(month>=6 && month<=8){ quarter = 1 };
return quarter; // 1
}
Instead of GetQuarter() method, you can also use a dictionary to store your month to quarter mappings
Dictionary<int, int> quarter = new Dictionary<int, int>();
quarter.Add(1,1); //of the format Add(month,quarter)
quarter.Add(2,1);
...
Now instead of GetQuarter(someDate.Month); you can use quarter[someDate.Month];
If you want to compare two dates you should find out the first day of the quarter corresponds every of this dates, then you can compare this two dates:
using System;
namespace DataTime {
class Program {
static int GetQuarter (DateTime dt) {
int Month = dt.Month; // from 1 to 12
return Month / 3 + 1;
}
static DateTime GetQuarterFirstDay (DateTime dt) {
int monthsOfTheFirstDayOfQuarter = (GetQuarter (dt) - 1) * 3 + 1;
return new DateTime(dt.Year, monthsOfTheFirstDayOfQuarter, 1);
// it can be changed to
// return new DateTime(dt.Year, (dt.Month/3)*3 + 1, 1);
}
static void Main (string[] args) {
DateTime dt1 = new DateTime (2009, 6, 9),
dt2 = new DateTime (2009, 7, 9),
dt3 = new DateTime (2009, 8, 9),
dt4 = new DateTime (2009, 8, 9);
Console.WriteLine ("dt1={0}", dt1.AddMonths (1));
Console.WriteLine ("dt2={0}", dt2.AddMonths (1));
Console.WriteLine ("dt3={0}", dt3.AddMonths (1));
DateTime startDate = DateTime.Now,
endDate1 = startDate.AddMonths(24).AddDays(1),
endDate2 = startDate.AddMonths(24).AddDays(-1),
endDate3 = startDate.AddMonths(28);
Console.WriteLine ("Now we have={0}", startDate);
Console.WriteLine ("endDate1={0}", endDate1);
Console.WriteLine ("endDate2={0}", endDate2);
Console.WriteLine ("endDate3={0}", endDate3);
Console.WriteLine ("GetQuarterFirstDay(startDate)={0}", GetQuarterFirstDay (startDate));
Console.WriteLine ("GetQuarterFirstDay(endDate1)={0}", GetQuarterFirstDay (endDate1));
Console.WriteLine ("GetQuarterFirstDay(endDate2)={0}", GetQuarterFirstDay (endDate2));
Console.WriteLine ("GetQuarterFirstDay(endDate3)={0}", GetQuarterFirstDay (endDate3));
if (DateTime.Compare (GetQuarterFirstDay (endDate2), GetQuarterFirstDay (startDate).AddMonths (24)) > 0)
Console.WriteLine ("> 2 Yeas");
else
Console.WriteLine ("<= 2 Yeas");
if (DateTime.Compare (GetQuarterFirstDay (endDate3), GetQuarterFirstDay (startDate).AddMonths (24)) > 0)
Console.WriteLine ("> 2 Yeas");
else
Console.WriteLine ("<= 2 Yeas");
}
}
}
produce
dt1=09.07.2009 00:00:00
dt2=09.08.2009 00:00:00
dt3=09.09.2009 00:00:00
Now we have=22.04.2010 11:21:45
endDate1=23.04.2012 11:21:45
endDate2=21.04.2012 11:21:45
endDate3=22.08.2012 11:21:45
GetQuarterFirstDay(startDate)=01.04.2010 00:00:00
GetQuarterFirstDay(endDate1)=01.04.2012 00:00:00
GetQuarterFirstDay(endDate2)=01.04.2012 00:00:00
GetQuarterFirstDay(endDate3)=01.07.2012 00:00:00
<= 2 Yeas
> 2 Yeas
EDITED: I fixed an error from the first version. Now it should works correct.

Categories

Resources