Calculate the number of weekdays between two dates in C# - c#

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;
}

Related

How to get the total of days and subtract affected weekends between two datetimepickers [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.

Get exact days and months between two dates c# [duplicate]

How to calculate the difference in months between two dates in C#?
Is there is equivalent of VB's DateDiff() method in C#. I need to find difference in months between two dates that are years apart. The documentation says that I can use TimeSpan like:
TimeSpan ts = date1 - date2;
but this gives me data in Days. I don't want to divide this number by 30 because not every month is 30 days and since the two operand values are quite apart from each other, I am afraid dividing by 30 might give me a wrong value.
Any suggestions?
Assuming the day of the month is irrelevant (i.e. the diff between 2011.1.1 and 2010.12.31 is 1), with date1 > date2 giving a positive value and date2 > date1 a negative value
((date1.Year - date2.Year) * 12) + date1.Month - date2.Month
Or, assuming you want an approximate number of 'average months' between the two dates, the following should work for all but very huge date differences.
date1.Subtract(date2).Days / (365.25 / 12)
Note, if you were to use the latter solution then your unit tests should state the widest date range which your application is designed to work with and validate the results of the calculation accordingly.
Update (with thanks to Gary)
If using the 'average months' method, a slightly more accurate number to use for the 'average number of days per year' is 365.2425.
Here is a comprehensive solution to return a DateTimeSpan, similar to a TimeSpan, except that it includes all the date components in addition to the time components.
Usage:
void Main()
{
DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
var dateSpan = DateTimeSpan.CompareDates(compareTo, now);
Console.WriteLine("Years: " + dateSpan.Years);
Console.WriteLine("Months: " + dateSpan.Months);
Console.WriteLine("Days: " + dateSpan.Days);
Console.WriteLine("Hours: " + dateSpan.Hours);
Console.WriteLine("Minutes: " + dateSpan.Minutes);
Console.WriteLine("Seconds: " + dateSpan.Seconds);
Console.WriteLine("Milliseconds: " + dateSpan.Milliseconds);
}
Outputs:
Years: 1
Months: 5
Days: 27
Hours: 1
Minutes: 36
Seconds: 50
Milliseconds: 0
For convenience, I've lumped the logic into the DateTimeSpan struct, but you may move the method CompareDates wherever you see fit. Also note, it doesn't matter which date comes before the other.
public struct DateTimeSpan
{
public int Years { get; }
public int Months { get; }
public int Days { get; }
public int Hours { get; }
public int Minutes { get; }
public int Seconds { get; }
public int Milliseconds { get; }
public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
{
Years = years;
Months = months;
Days = days;
Hours = hours;
Minutes = minutes;
Seconds = seconds;
Milliseconds = milliseconds;
}
enum Phase { Years, Months, Days, Done }
public static DateTimeSpan CompareDates(DateTime date1, DateTime date2)
{
if (date2 < date1)
{
var sub = date1;
date1 = date2;
date2 = sub;
}
DateTime current = date1;
int years = 0;
int months = 0;
int days = 0;
Phase phase = Phase.Years;
DateTimeSpan span = new DateTimeSpan();
int officialDay = current.Day;
while (phase != Phase.Done)
{
switch (phase)
{
case Phase.Years:
if (current.AddYears(years + 1) > date2)
{
phase = Phase.Months;
current = current.AddYears(years);
}
else
{
years++;
}
break;
case Phase.Months:
if (current.AddMonths(months + 1) > date2)
{
phase = Phase.Days;
current = current.AddMonths(months);
if (current.Day < officialDay && officialDay <= DateTime.DaysInMonth(current.Year, current.Month))
current = current.AddDays(officialDay - current.Day);
}
else
{
months++;
}
break;
case Phase.Days:
if (current.AddDays(days + 1) > date2)
{
current = current.AddDays(days);
var timespan = date2 - current;
span = new DateTimeSpan(years, months, days, timespan.Hours, timespan.Minutes, timespan.Seconds, timespan.Milliseconds);
phase = Phase.Done;
}
else
{
days++;
}
break;
}
}
return span;
}
}
You could do
if ( date1.AddMonths(x) > date2 )
If you want the exact number of full months, always positive (2000-01-15, 2000-02-14 returns 0), considering a full month is when you reach the same day the next month (something like the age calculation)
public static int GetMonthsBetween(DateTime from, DateTime to)
{
if (from > to) return GetMonthsBetween(to, from);
var monthDiff = Math.Abs((to.Year * 12 + (to.Month - 1)) - (from.Year * 12 + (from.Month - 1)));
if (from.AddMonths(monthDiff) > to || to.Day < from.Day)
{
return monthDiff - 1;
}
else
{
return monthDiff;
}
}
Edit reason: the old code was not correct in some cases like :
new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },
Test cases I used to test the function:
var tests = new[]
{
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 1), Result = 0 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 2), Result = 0 },
new { From = new DateTime(1900, 1, 2), To = new DateTime(1900, 1, 1), Result = 0 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 2, 1), Result = 1 },
new { From = new DateTime(1900, 2, 1), To = new DateTime(1900, 1, 1), Result = 1 },
new { From = new DateTime(1900, 1, 31), To = new DateTime(1900, 2, 1), Result = 0 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 9, 30), Result = 0 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 10, 1), Result = 1 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1901, 1, 1), Result = 12 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1911, 1, 1), Result = 132 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },
};
I checked the usage of this method in VB.NET via MSDN and it seems that it has a lot of usages. There is no such a built-in method in C#. (Even it's not a good idea) you can call VB's in C#.
Add Microsoft.VisualBasic.dll to
your project as a reference
use
Microsoft.VisualBasic.DateAndTime.DateDiff
in your code
Use Noda Time:
LocalDate start = new LocalDate(2013, 1, 5);
LocalDate end = new LocalDate(2014, 6, 1);
Period period = Period.Between(start, end, PeriodUnits.Months);
Console.WriteLine(period.Months); // 16
(example source)
To get difference in months (both start and end inclusive), irrespective of dates:
DateTime start = new DateTime(2013, 1, 1);
DateTime end = new DateTime(2014, 2, 1);
var diffMonths = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
I just needed something simple to cater for e.g. employment dates where only the month/year is entered, so wanted distinct years and months worked in. This is what I use, here for usefullness only
public static YearsMonths YearMonthDiff(DateTime startDate, DateTime endDate) {
int monthDiff = ((endDate.Year * 12) + endDate.Month) - ((startDate.Year * 12) + startDate.Month) + 1;
int years = (int)Math.Floor((decimal) (monthDiff / 12));
int months = monthDiff % 12;
return new YearsMonths {
TotalMonths = monthDiff,
Years = years,
Months = months
};
}
.NET Fiddle
You can use Noda Time https://nodatime.org/
LocalDate start = new LocalDate(2010, 1, 5);
LocalDate end = new LocalDate(2012, 6, 1);
Period period = Period.Between(start, end, PeriodUnits.Months);
Console.WriteLine(period.Months);
You can use the DateDiff class of the Time Period Library for .NET:
// ----------------------------------------------------------------------
public void DateDiffSample()
{
DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 );
DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 );
DateDiff dateDiff = new DateDiff( date1, date2 );
// differences
Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months );
// > DateDiff.Months: 16
// elapsed
Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths );
// > DateDiff.ElapsedMonths: 4
// description
Console.WriteLine( "DateDiff.GetDescription(6): {0}", dateDiff.GetDescription( 6 ) );
// > DateDiff.GetDescription(6): 1 Year 4 Months 12 Days 12 Hours 41 Mins 29 Secs
} // DateDiffSample
Here is my contribution to get difference in Months that I've found to be accurate:
namespace System
{
public static class DateTimeExtensions
{
public static Int32 DiffMonths( this DateTime start, DateTime end )
{
Int32 months = 0;
DateTime tmp = start;
while ( tmp < end )
{
months++;
tmp = tmp.AddMonths( 1 );
}
return months;
}
}
}
Usage:
Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) );
You can create another method called DiffYears and apply exactly the same logic as above and AddYears instead of AddMonths in the while loop.
This worked for what I needed it for. The day of month didn't matter in my case because it always happens to be the last day of the month.
public static int MonthDiff(DateTime d1, DateTime d2){
int retVal = 0;
if (d1.Month<d2.Month)
{
retVal = (d1.Month + 12) - d2.Month;
retVal += ((d1.Year - 1) - d2.Year)*12;
}
else
{
retVal = d1.Month - d2.Month;
retVal += (d1.Year - d2.Year)*12;
}
//// Calculate the number of years represented and multiply by 12
//// Substract the month number from the total
//// Substract the difference of the second month and 12 from the total
//retVal = (d1.Year - d2.Year) * 12;
//retVal = retVal - d1.Month;
//retVal = retVal - (12 - d2.Month);
return retVal;
}
There's 3 cases: same year, previous year and other years.
If the day of the month does not matter...
public int GetTotalNumberOfMonths(DateTime start, DateTime end)
{
// work with dates in the right order
if (start > end)
{
var swapper = start;
start = end;
end = swapper;
}
switch (end.Year - start.Year)
{
case 0: // Same year
return end.Month - start.Month;
case 1: // last year
return (12 - start.Month) + end.Month;
default:
return 12 * (3 - (end.Year - start.Year)) + (12 - start.Month) + end.Month;
}
}
The most precise way is this that return difference in months by fraction :
private double ReturnDiffereceBetweenTwoDatesInMonths(DateTime startDateTime, DateTime endDateTime)
{
double result = 0;
double days = 0;
DateTime currentDateTime = startDateTime;
while (endDateTime > currentDateTime.AddMonths(1))
{
result ++;
currentDateTime = currentDateTime.AddMonths(1);
}
if (endDateTime > currentDateTime)
{
days = endDateTime.Subtract(currentDateTime).TotalDays;
}
return result + days/endDateTime.GetMonthDays;
}
My understanding of the total months difference between 2 dates has an integral and a fractional part (the date matters).
The integral part is the full months difference.
The fractional part, for me, is the difference of the % of the day (to the full days of month) between the starting and ending months.
public static class DateTimeExtensions
{
public static double TotalMonthsDifference(this DateTime from, DateTime to)
{
//Compute full months difference between dates
var fullMonthsDiff = (to.Year - from.Year)*12 + to.Month - from.Month;
//Compute difference between the % of day to full days of each month
var fractionMonthsDiff = ((double)(to.Day-1) / (DateTime.DaysInMonth(to.Year, to.Month)-1)) -
((double)(from.Day-1)/ (DateTime.DaysInMonth(from.Year, from.Month)-1));
return fullMonthsDiff + fractionMonthsDiff;
}
}
With this extension, those are the results:
2/29/2000 TotalMonthsDifference 2/28/2001 => 12
2/28/2000 TotalMonthsDifference 2/28/2001 => 12.035714285714286
01/01/2000 TotalMonthsDifference 01/16/2000 => 0.5
01/31/2000 TotalMonthsDifference 01/01/2000 => -1.0
01/31/2000 TotalMonthsDifference 02/29/2000 => 1.0
01/31/2000 TotalMonthsDifference 02/28/2000 => 0.9642857142857143
01/31/2001 TotalMonthsDifference 02/28/2001 => 1.0
Here is a simple solution that works at least for me. It's probably not the fastest though because it uses the cool DateTime's AddMonth feature in a loop:
public static int GetMonthsDiff(DateTime start, DateTime end)
{
if (start > end)
return GetMonthsDiff(end, start);
int months = 0;
do
{
start = start.AddMonths(1);
if (start > end)
return months;
months++;
}
while (true);
}
This simple static function calculates the fraction of months between two Datetimes, e.g.
1.1. to 31.1. = 1.0
1.4. to 15.4. = 0.5
16.4. to 30.4. = 0.5
1.3. to 1.4. = 1 + 1/30
The function assumes that the first date is smaller than the second date. To deal with negative time intervals one can modify the function easily by introducing a sign and a variable swap at the beginning.
public static double GetDeltaMonths(DateTime t0, DateTime t1)
{
DateTime t = t0;
double months = 0;
while(t<=t1)
{
int daysInMonth = DateTime.DaysInMonth(t.Year, t.Month);
DateTime endOfMonth = new DateTime(t.Year, t.Month, daysInMonth);
int cutDay = endOfMonth <= t1 ? daysInMonth : t1.Day;
months += (cutDay - t.Day + 1) / (double) daysInMonth;
t = new DateTime(t.Year, t.Month, 1).AddMonths(1);
}
return Math.Round(months,2);
}
one line solution
For first, check if both dates are in the current year, if not get months of whole years and then add months from the start and end year.
DateTime dateFrom = new DateTime(2019, 2, 1);
DateTime dateTo = new DateTime(2021, 5, 25);
With the first month
var monthCount = dateFrom.Year != dateTo.Year ? ((dateTo.Year - dateFrom.Year - 1) * 12) + (13 - dateFrom.Month + dateTo.Month) : dateTo.Month - dateFrom.Month + 1;
result = 28
Without first month
monthCount = dateFrom.Year != dateTo.Year ? ((dateTo.Year - dateFrom.Year - 1) * 12) + (12 - dateFrom.Month + dateTo.Month) : dateTo.Month - dateFrom.Month;
result = 27
Public Class ClassDateOperation
Private prop_DifferenceInDay As Integer
Private prop_DifferenceInMonth As Integer
Private prop_DifferenceInYear As Integer
Public Function DayMonthYearFromTwoDate(ByVal DateStart As Date, ByVal DateEnd As Date) As ClassDateOperation
Dim differenceInDay As Integer
Dim differenceInMonth As Integer
Dim differenceInYear As Integer
Dim myDate As Date
DateEnd = DateEnd.AddDays(1)
differenceInYear = DateEnd.Year - DateStart.Year
If DateStart.Month <= DateEnd.Month Then
differenceInMonth = DateEnd.Month - DateStart.Month
Else
differenceInYear -= 1
differenceInMonth = (12 - DateStart.Month) + DateEnd.Month
End If
If DateStart.Day <= DateEnd.Day Then
differenceInDay = DateEnd.Day - DateStart.Day
Else
myDate = CDate("01/" & DateStart.AddMonths(1).Month & "/" & DateStart.Year).AddDays(-1)
If differenceInMonth <> 0 Then
differenceInMonth -= 1
Else
differenceInMonth = 11
differenceInYear -= 1
End If
differenceInDay = myDate.Day - DateStart.Day + DateEnd.Day
End If
prop_DifferenceInDay = differenceInDay
prop_DifferenceInMonth = differenceInMonth
prop_DifferenceInYear = differenceInYear
Return Me
End Function
Public ReadOnly Property DifferenceInDay() As Integer
Get
Return prop_DifferenceInDay
End Get
End Property
Public ReadOnly Property DifferenceInMonth As Integer
Get
Return prop_DifferenceInMonth
End Get
End Property
Public ReadOnly Property DifferenceInYear As Integer
Get
Return prop_DifferenceInYear
End Get
End Property
End Class
This is from my own library, will return the difference of months between two dates.
public static int MonthDiff(DateTime d1, DateTime d2)
{
int retVal = 0;
// Calculate the number of years represented and multiply by 12
// Substract the month number from the total
// Substract the difference of the second month and 12 from the total
retVal = (d1.Year - d2.Year) * 12;
retVal = retVal - d1.Month;
retVal = retVal - (12 - d2.Month);
return retVal;
}
You can have a function something like this.
For Example, from 2012/12/27 to 2012/12/29 becomes 3 days. Likewise, from 2012/12/15 to 2013/01/15 becomes 2 months, because up to 2013/01/14 it's 1 month. from 15th it's 2nd month started.
You can remove the "=" in the second if condition, if you do not want to include both days in the calculation. i.e, from 2012/12/15 to 2013/01/15 is 1 month.
public int GetMonths(DateTime startDate, DateTime endDate)
{
if (startDate > endDate)
{
throw new Exception("Start Date is greater than the End Date");
}
int months = ((endDate.Year * 12) + endDate.Month) - ((startDate.Year * 12) + startDate.Month);
if (endDate.Day >= startDate.Day)
{
months++;
}
return months;
}
you can use the following extension:
Code
public static class Ext
{
#region Public Methods
public static int GetAge(this DateTime #this)
{
var today = DateTime.Today;
return ((((today.Year - #this.Year) * 100) + (today.Month - #this.Month)) * 100 + today.Day - #this.Day) / 10000;
}
public static int DiffMonths(this DateTime #from, DateTime #to)
{
return (((((#to.Year - #from.Year) * 12) + (#to.Month - #from.Month)) * 100 + #to.Day - #from.Day) / 100);
}
public static int DiffYears(this DateTime #from, DateTime #to)
{
return ((((#to.Year - #from.Year) * 100) + (#to.Month - #from.Month)) * 100 + #to.Day - #from.Day) / 10000;
}
#endregion Public Methods
}
Implementation !
int Age;
int years;
int Months;
//Replace your own date
var d1 = new DateTime(2000, 10, 22);
var d2 = new DateTime(2003, 10, 20);
//Age
Age = d1.GetAge();
Age = d2.GetAge();
//positive
years = d1.DiffYears(d2);
Months = d1.DiffMonths(d2);
//negative
years = d2.DiffYears(d1);
Months = d2.DiffMonths(d1);
//Or
Months = Ext.DiffMonths(d1, d2);
years = Ext.DiffYears(d1, d2);
Here's a much more concise solution using VB.Net DateDiff for Year, Month, Day only. You can load the DateDiff library in C# as well.
date1 must be <= date2
VB.NET
Dim date1 = Now.AddDays(-2000)
Dim date2 = Now
Dim diffYears = DateDiff(DateInterval.Year, date1, date2) - If(date1.DayOfYear > date2.DayOfYear, 1, 0)
Dim diffMonths = DateDiff(DateInterval.Month, date1, date2) - diffYears * 12 - If(date1.Day > date2.Day, 1, 0)
Dim diffDays = If(date2.Day >= date1.Day, date2.Day - date1.Day, date2.Day + (Date.DaysInMonth(date1.Year, date1.Month) - date1.Day))
C#
DateTime date1 = Now.AddDays(-2000);
DateTime date2 = Now;
int diffYears = DateDiff(DateInterval.Year, date1, date2) - date1.DayOfYear > date2.DayOfYear ? 1 : 0;
int diffMonths = DateDiff(DateInterval.Month, date1, date2) - diffYears * 12 - date1.Day > date2.Day ? 1 : 0;
int diffDays = date2.Day >= date1.Day ? date2.Day - date1.Day : date2.Day + (System.DateTime.DaysInMonth(date1.Year, date1.Month) - date1.Day);
This is in response to Kirk Woll's answer. I don't have enough reputation points to reply to a comment yet...
I liked Kirk's solution and was going to shamelessly rip it off and use it in my code, but when I looked through it I realized it's way too complicated. Unnecessary switching and looping, and a public constructor that is pointless to use.
Here's my rewrite:
public class DateTimeSpan {
private DateTime _date1;
private DateTime _date2;
private int _years;
private int _months;
private int _days;
private int _hours;
private int _minutes;
private int _seconds;
private int _milliseconds;
public int Years { get { return _years; } }
public int Months { get { return _months; } }
public int Days { get { return _days; } }
public int Hours { get { return _hours; } }
public int Minutes { get { return _minutes; } }
public int Seconds { get { return _seconds; } }
public int Milliseconds { get { return _milliseconds; } }
public DateTimeSpan(DateTime date1, DateTime date2) {
_date1 = (date1 > date2) ? date1 : date2;
_date2 = (date2 < date1) ? date2 : date1;
_years = _date1.Year - _date2.Year;
_months = (_years * 12) + _date1.Month - _date2.Month;
TimeSpan t = (_date2 - _date1);
_days = t.Days;
_hours = t.Hours;
_minutes = t.Minutes;
_seconds = t.Seconds;
_milliseconds = t.Milliseconds;
}
public static DateTimeSpan CompareDates(DateTime date1, DateTime date2) {
return new DateTimeSpan(date1, date2);
}
}
Usage1, pretty much the same:
void Main()
{
DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
var dateSpan = new DateTimeSpan(compareTo, now);
Console.WriteLine("Years: " + dateSpan.Years);
Console.WriteLine("Months: " + dateSpan.Months);
Console.WriteLine("Days: " + dateSpan.Days);
Console.WriteLine("Hours: " + dateSpan.Hours);
Console.WriteLine("Minutes: " + dateSpan.Minutes);
Console.WriteLine("Seconds: " + dateSpan.Seconds);
Console.WriteLine("Milliseconds: " + dateSpan.Milliseconds);
}
Usage2, similar:
void Main()
{
DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
Console.WriteLine("Years: " + DateTimeSpan.CompareDates(compareTo, now).Years);
Console.WriteLine("Months: " + DateTimeSpan.CompareDates(compareTo, now).Months);
Console.WriteLine("Days: " + DateTimeSpan.CompareDates(compareTo, now).Days);
Console.WriteLine("Hours: " + DateTimeSpan.CompareDates(compareTo, now).Hours);
Console.WriteLine("Minutes: " + DateTimeSpan.CompareDates(compareTo, now).Minutes);
Console.WriteLine("Seconds: " + DateTimeSpan.CompareDates(compareTo, now).Seconds);
Console.WriteLine("Milliseconds: " + DateTimeSpan.CompareDates(compareTo, now).Milliseconds);
}
In my case it is required to calculate the complete month from the start date to the day prior to this day in the next month or from start to end of month.
Ex: from 1/1/2018 to 31/1/2018 is a complete month
Ex2: from 5/1/2018 to 4/2/2018 is a complete month
so based on this here is my solution:
public static DateTime GetMonthEnd(DateTime StartDate, int MonthsCount = 1)
{
return StartDate.AddMonths(MonthsCount).AddDays(-1);
}
public static Tuple<int, int> CalcPeriod(DateTime StartDate, DateTime EndDate)
{
int MonthsCount = 0;
Tuple<int, int> Period;
while (true)
{
if (GetMonthEnd(StartDate) > EndDate)
break;
else
{
MonthsCount += 1;
StartDate = StartDate.AddMonths(1);
}
}
int RemainingDays = (EndDate - StartDate).Days + 1;
Period = new Tuple<int, int>(MonthsCount, RemainingDays);
return Period;
}
Usage:
Tuple<int, int> Period = CalcPeriod(FromDate, ToDate);
Note: in my case it was required to calculate the remaining days after the complete months so if it's not your case you could ignore the days result or even you could change the method return from tuple to integer.
public static int PayableMonthsInDuration(DateTime StartDate, DateTime EndDate)
{
int sy = StartDate.Year; int sm = StartDate.Month; int count = 0;
do
{
count++;if ((sy == EndDate.Year) && (sm >= EndDate.Month)) { break; }
sm++;if (sm == 13) { sm = 1; sy++; }
} while ((EndDate.Year >= sy) || (EndDate.Month >= sm));
return (count);
}
This solution is for Rental/subscription calculation, where difference doesn't means to be subtraction, it's meant to be the span in within those two dates.
I wrote a function to accomplish this, because the others ways weren't working for me.
public string getEndDate (DateTime startDate,decimal monthCount)
{
int y = startDate.Year;
int m = startDate.Month;
for (decimal i = monthCount; i > 1; i--)
{
m++;
if (m == 12)
{ y++;
m = 1;
}
}
return string.Format("{0}-{1}-{2}", y.ToString(), m.ToString(), startDate.Day.ToString());
}
There are not a lot of clear answers on this because you are always assuming things.
This solution calculates between two dates the months between assuming you want to save the day of month for comparison, (meaning that the day of the month is considered in the calculation)
Example, if you have a date of 30 Jan 2012, 29 Feb 2012 will not be a month but 01 March 2013 will.
It's been tested pretty thoroughly, probably will clean it up later as we use it, but here:
private static int TotalMonthDifference(DateTime dtThis, DateTime dtOther)
{
int intReturn = 0;
bool sameMonth = false;
if (dtOther.Date < dtThis.Date) //used for an error catch in program, returns -1
intReturn--;
int dayOfMonth = dtThis.Day; //captures the month of day for when it adds a month and doesn't have that many days
int daysinMonth = 0; //used to caputre how many days are in the month
while (dtOther.Date > dtThis.Date) //while Other date is still under the other
{
dtThis = dtThis.AddMonths(1); //as we loop, we just keep adding a month for testing
daysinMonth = DateTime.DaysInMonth(dtThis.Year, dtThis.Month); //grabs the days in the current tested month
if (dtThis.Day != dayOfMonth) //Example 30 Jan 2013 will go to 28 Feb when a month is added, so when it goes to march it will be 28th and not 30th
{
if (daysinMonth < dayOfMonth) // uses day in month max if can't set back to day of month
dtThis.AddDays(daysinMonth - dtThis.Day);
else
dtThis.AddDays(dayOfMonth - dtThis.Day);
}
if (((dtOther.Year == dtThis.Year) && (dtOther.Month == dtThis.Month))) //If the loop puts it in the same month and year
{
if (dtOther.Day >= dayOfMonth) //check to see if it is the same day or later to add one to month
intReturn++;
sameMonth = true; //sets this to cancel out of the normal counting of month
}
if ((!sameMonth)&&(dtOther.Date > dtThis.Date))//so as long as it didn't reach the same month (or if i started in the same month, one month ahead, add a month)
intReturn++;
}
return intReturn; //return month
}
Based on the excellent DateTimeSpan work done above, I've normalized the code a bit; this seems to work pretty well:
public class DateTimeSpan
{
private DateTimeSpan() { }
private DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
{
Years = years;
Months = months;
Days = days;
Hours = hours;
Minutes = minutes;
Seconds = seconds;
Milliseconds = milliseconds;
}
public int Years { get; private set; } = 0;
public int Months { get; private set; } = 0;
public int Days { get; private set; } = 0;
public int Hours { get; private set; } = 0;
public int Minutes { get; private set; } = 0;
public int Seconds { get; private set; } = 0;
public int Milliseconds { get; private set; } = 0;
public static DateTimeSpan CompareDates(DateTime StartDate, DateTime EndDate)
{
if (StartDate.Equals(EndDate)) return new DateTimeSpan();
DateTimeSpan R = new DateTimeSpan();
bool Later;
if (Later = StartDate > EndDate)
{
DateTime D = StartDate;
StartDate = EndDate;
EndDate = D;
}
// Calculate Date Stuff
for (DateTime D = StartDate.AddYears(1); D < EndDate; D = D.AddYears(1), R.Years++) ;
if (R.Years > 0) StartDate = StartDate.AddYears(R.Years);
for (DateTime D = StartDate.AddMonths(1); D < EndDate; D = D.AddMonths(1), R.Months++) ;
if (R.Months > 0) StartDate = StartDate.AddMonths(R.Months);
for (DateTime D = StartDate.AddDays(1); D < EndDate; D = D.AddDays(1), R.Days++) ;
if (R.Days > 0) StartDate = StartDate.AddDays(R.Days);
// Calculate Time Stuff
TimeSpan T1 = EndDate - StartDate;
R.Hours = T1.Hours;
R.Minutes = T1.Minutes;
R.Seconds = T1.Seconds;
R.Milliseconds = T1.Milliseconds;
// Return answer. Negate values if the Start Date was later than the End Date
if (Later)
return new DateTimeSpan(-R.Years, -R.Months, -R.Days, -R.Hours, -R.Minutes, -R.Seconds, -R.Milliseconds);
return R;
}
}
Insane method that counts all days, so super precise
helper class :
public class DaysInMonth
{
public int Days { get; set; }
public int Month { get; set; }
public int Year { get; set; }
public bool Full { get; set; }
}
function:
public static List<DaysInMonth> MonthsDelta(DateTime start, DateTime end)
{
var dates = Enumerable.Range(0, 1 + end.Subtract(start).Days)
.Select(offset => start.AddDays(offset))
.ToArray();
DateTime? prev = null;
int days = 0;
List < DaysInMonth > list = new List<DaysInMonth>();
foreach (DateTime date in dates)
{
if (prev != null)
{
if(date.Month!=prev.GetValueOrDefault().Month)
{
DaysInMonth daysInMonth = new DaysInMonth();
daysInMonth.Days = days;
daysInMonth.Month = prev.GetValueOrDefault().Month;
daysInMonth.Year = prev.GetValueOrDefault().Year;
daysInMonth.Full = DateTime.DaysInMonth(daysInMonth.Year, daysInMonth.Month) == daysInMonth.Days;
list.Add(daysInMonth);
days = 0;
}
}
days++;
prev = date;
}
//------------------ add last
if (days > 0)
{
DaysInMonth daysInMonth = new DaysInMonth();
daysInMonth.Days = days;
daysInMonth.Month = prev.GetValueOrDefault().Month;
daysInMonth.Year = prev.GetValueOrDefault().Year;
daysInMonth.Full = DateTime.DaysInMonth(daysInMonth.Year, daysInMonth.Month) == daysInMonth.Days;
list.Add(daysInMonth);
}
return list;
}

Compute the DateTime of an upcoming weekday

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;
}

Difference in months between two dates

How to calculate the difference in months between two dates in C#?
Is there is equivalent of VB's DateDiff() method in C#. I need to find difference in months between two dates that are years apart. The documentation says that I can use TimeSpan like:
TimeSpan ts = date1 - date2;
but this gives me data in Days. I don't want to divide this number by 30 because not every month is 30 days and since the two operand values are quite apart from each other, I am afraid dividing by 30 might give me a wrong value.
Any suggestions?
Assuming the day of the month is irrelevant (i.e. the diff between 2011.1.1 and 2010.12.31 is 1), with date1 > date2 giving a positive value and date2 > date1 a negative value
((date1.Year - date2.Year) * 12) + date1.Month - date2.Month
Or, assuming you want an approximate number of 'average months' between the two dates, the following should work for all but very huge date differences.
date1.Subtract(date2).Days / (365.25 / 12)
Note, if you were to use the latter solution then your unit tests should state the widest date range which your application is designed to work with and validate the results of the calculation accordingly.
Update (with thanks to Gary)
If using the 'average months' method, a slightly more accurate number to use for the 'average number of days per year' is 365.2425.
Here is a comprehensive solution to return a DateTimeSpan, similar to a TimeSpan, except that it includes all the date components in addition to the time components.
Usage:
void Main()
{
DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
var dateSpan = DateTimeSpan.CompareDates(compareTo, now);
Console.WriteLine("Years: " + dateSpan.Years);
Console.WriteLine("Months: " + dateSpan.Months);
Console.WriteLine("Days: " + dateSpan.Days);
Console.WriteLine("Hours: " + dateSpan.Hours);
Console.WriteLine("Minutes: " + dateSpan.Minutes);
Console.WriteLine("Seconds: " + dateSpan.Seconds);
Console.WriteLine("Milliseconds: " + dateSpan.Milliseconds);
}
Outputs:
Years: 1
Months: 5
Days: 27
Hours: 1
Minutes: 36
Seconds: 50
Milliseconds: 0
For convenience, I've lumped the logic into the DateTimeSpan struct, but you may move the method CompareDates wherever you see fit. Also note, it doesn't matter which date comes before the other.
public struct DateTimeSpan
{
public int Years { get; }
public int Months { get; }
public int Days { get; }
public int Hours { get; }
public int Minutes { get; }
public int Seconds { get; }
public int Milliseconds { get; }
public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
{
Years = years;
Months = months;
Days = days;
Hours = hours;
Minutes = minutes;
Seconds = seconds;
Milliseconds = milliseconds;
}
enum Phase { Years, Months, Days, Done }
public static DateTimeSpan CompareDates(DateTime date1, DateTime date2)
{
if (date2 < date1)
{
var sub = date1;
date1 = date2;
date2 = sub;
}
DateTime current = date1;
int years = 0;
int months = 0;
int days = 0;
Phase phase = Phase.Years;
DateTimeSpan span = new DateTimeSpan();
int officialDay = current.Day;
while (phase != Phase.Done)
{
switch (phase)
{
case Phase.Years:
if (current.AddYears(years + 1) > date2)
{
phase = Phase.Months;
current = current.AddYears(years);
}
else
{
years++;
}
break;
case Phase.Months:
if (current.AddMonths(months + 1) > date2)
{
phase = Phase.Days;
current = current.AddMonths(months);
if (current.Day < officialDay && officialDay <= DateTime.DaysInMonth(current.Year, current.Month))
current = current.AddDays(officialDay - current.Day);
}
else
{
months++;
}
break;
case Phase.Days:
if (current.AddDays(days + 1) > date2)
{
current = current.AddDays(days);
var timespan = date2 - current;
span = new DateTimeSpan(years, months, days, timespan.Hours, timespan.Minutes, timespan.Seconds, timespan.Milliseconds);
phase = Phase.Done;
}
else
{
days++;
}
break;
}
}
return span;
}
}
You could do
if ( date1.AddMonths(x) > date2 )
If you want the exact number of full months, always positive (2000-01-15, 2000-02-14 returns 0), considering a full month is when you reach the same day the next month (something like the age calculation)
public static int GetMonthsBetween(DateTime from, DateTime to)
{
if (from > to) return GetMonthsBetween(to, from);
var monthDiff = Math.Abs((to.Year * 12 + (to.Month - 1)) - (from.Year * 12 + (from.Month - 1)));
if (from.AddMonths(monthDiff) > to || to.Day < from.Day)
{
return monthDiff - 1;
}
else
{
return monthDiff;
}
}
Edit reason: the old code was not correct in some cases like :
new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },
Test cases I used to test the function:
var tests = new[]
{
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 1), Result = 0 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 2), Result = 0 },
new { From = new DateTime(1900, 1, 2), To = new DateTime(1900, 1, 1), Result = 0 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 2, 1), Result = 1 },
new { From = new DateTime(1900, 2, 1), To = new DateTime(1900, 1, 1), Result = 1 },
new { From = new DateTime(1900, 1, 31), To = new DateTime(1900, 2, 1), Result = 0 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 9, 30), Result = 0 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 10, 1), Result = 1 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1901, 1, 1), Result = 12 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1911, 1, 1), Result = 132 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },
};
I checked the usage of this method in VB.NET via MSDN and it seems that it has a lot of usages. There is no such a built-in method in C#. (Even it's not a good idea) you can call VB's in C#.
Add Microsoft.VisualBasic.dll to
your project as a reference
use
Microsoft.VisualBasic.DateAndTime.DateDiff
in your code
Use Noda Time:
LocalDate start = new LocalDate(2013, 1, 5);
LocalDate end = new LocalDate(2014, 6, 1);
Period period = Period.Between(start, end, PeriodUnits.Months);
Console.WriteLine(period.Months); // 16
(example source)
To get difference in months (both start and end inclusive), irrespective of dates:
DateTime start = new DateTime(2013, 1, 1);
DateTime end = new DateTime(2014, 2, 1);
var diffMonths = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
I just needed something simple to cater for e.g. employment dates where only the month/year is entered, so wanted distinct years and months worked in. This is what I use, here for usefullness only
public static YearsMonths YearMonthDiff(DateTime startDate, DateTime endDate) {
int monthDiff = ((endDate.Year * 12) + endDate.Month) - ((startDate.Year * 12) + startDate.Month) + 1;
int years = (int)Math.Floor((decimal) (monthDiff / 12));
int months = monthDiff % 12;
return new YearsMonths {
TotalMonths = monthDiff,
Years = years,
Months = months
};
}
.NET Fiddle
You can use Noda Time https://nodatime.org/
LocalDate start = new LocalDate(2010, 1, 5);
LocalDate end = new LocalDate(2012, 6, 1);
Period period = Period.Between(start, end, PeriodUnits.Months);
Console.WriteLine(period.Months);
You can use the DateDiff class of the Time Period Library for .NET:
// ----------------------------------------------------------------------
public void DateDiffSample()
{
DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 );
DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 );
DateDiff dateDiff = new DateDiff( date1, date2 );
// differences
Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months );
// > DateDiff.Months: 16
// elapsed
Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths );
// > DateDiff.ElapsedMonths: 4
// description
Console.WriteLine( "DateDiff.GetDescription(6): {0}", dateDiff.GetDescription( 6 ) );
// > DateDiff.GetDescription(6): 1 Year 4 Months 12 Days 12 Hours 41 Mins 29 Secs
} // DateDiffSample
Here is my contribution to get difference in Months that I've found to be accurate:
namespace System
{
public static class DateTimeExtensions
{
public static Int32 DiffMonths( this DateTime start, DateTime end )
{
Int32 months = 0;
DateTime tmp = start;
while ( tmp < end )
{
months++;
tmp = tmp.AddMonths( 1 );
}
return months;
}
}
}
Usage:
Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) );
You can create another method called DiffYears and apply exactly the same logic as above and AddYears instead of AddMonths in the while loop.
This worked for what I needed it for. The day of month didn't matter in my case because it always happens to be the last day of the month.
public static int MonthDiff(DateTime d1, DateTime d2){
int retVal = 0;
if (d1.Month<d2.Month)
{
retVal = (d1.Month + 12) - d2.Month;
retVal += ((d1.Year - 1) - d2.Year)*12;
}
else
{
retVal = d1.Month - d2.Month;
retVal += (d1.Year - d2.Year)*12;
}
//// Calculate the number of years represented and multiply by 12
//// Substract the month number from the total
//// Substract the difference of the second month and 12 from the total
//retVal = (d1.Year - d2.Year) * 12;
//retVal = retVal - d1.Month;
//retVal = retVal - (12 - d2.Month);
return retVal;
}
There's 3 cases: same year, previous year and other years.
If the day of the month does not matter...
public int GetTotalNumberOfMonths(DateTime start, DateTime end)
{
// work with dates in the right order
if (start > end)
{
var swapper = start;
start = end;
end = swapper;
}
switch (end.Year - start.Year)
{
case 0: // Same year
return end.Month - start.Month;
case 1: // last year
return (12 - start.Month) + end.Month;
default:
return 12 * (3 - (end.Year - start.Year)) + (12 - start.Month) + end.Month;
}
}
The most precise way is this that return difference in months by fraction :
private double ReturnDiffereceBetweenTwoDatesInMonths(DateTime startDateTime, DateTime endDateTime)
{
double result = 0;
double days = 0;
DateTime currentDateTime = startDateTime;
while (endDateTime > currentDateTime.AddMonths(1))
{
result ++;
currentDateTime = currentDateTime.AddMonths(1);
}
if (endDateTime > currentDateTime)
{
days = endDateTime.Subtract(currentDateTime).TotalDays;
}
return result + days/endDateTime.GetMonthDays;
}
My understanding of the total months difference between 2 dates has an integral and a fractional part (the date matters).
The integral part is the full months difference.
The fractional part, for me, is the difference of the % of the day (to the full days of month) between the starting and ending months.
public static class DateTimeExtensions
{
public static double TotalMonthsDifference(this DateTime from, DateTime to)
{
//Compute full months difference between dates
var fullMonthsDiff = (to.Year - from.Year)*12 + to.Month - from.Month;
//Compute difference between the % of day to full days of each month
var fractionMonthsDiff = ((double)(to.Day-1) / (DateTime.DaysInMonth(to.Year, to.Month)-1)) -
((double)(from.Day-1)/ (DateTime.DaysInMonth(from.Year, from.Month)-1));
return fullMonthsDiff + fractionMonthsDiff;
}
}
With this extension, those are the results:
2/29/2000 TotalMonthsDifference 2/28/2001 => 12
2/28/2000 TotalMonthsDifference 2/28/2001 => 12.035714285714286
01/01/2000 TotalMonthsDifference 01/16/2000 => 0.5
01/31/2000 TotalMonthsDifference 01/01/2000 => -1.0
01/31/2000 TotalMonthsDifference 02/29/2000 => 1.0
01/31/2000 TotalMonthsDifference 02/28/2000 => 0.9642857142857143
01/31/2001 TotalMonthsDifference 02/28/2001 => 1.0
Here is a simple solution that works at least for me. It's probably not the fastest though because it uses the cool DateTime's AddMonth feature in a loop:
public static int GetMonthsDiff(DateTime start, DateTime end)
{
if (start > end)
return GetMonthsDiff(end, start);
int months = 0;
do
{
start = start.AddMonths(1);
if (start > end)
return months;
months++;
}
while (true);
}
This simple static function calculates the fraction of months between two Datetimes, e.g.
1.1. to 31.1. = 1.0
1.4. to 15.4. = 0.5
16.4. to 30.4. = 0.5
1.3. to 1.4. = 1 + 1/30
The function assumes that the first date is smaller than the second date. To deal with negative time intervals one can modify the function easily by introducing a sign and a variable swap at the beginning.
public static double GetDeltaMonths(DateTime t0, DateTime t1)
{
DateTime t = t0;
double months = 0;
while(t<=t1)
{
int daysInMonth = DateTime.DaysInMonth(t.Year, t.Month);
DateTime endOfMonth = new DateTime(t.Year, t.Month, daysInMonth);
int cutDay = endOfMonth <= t1 ? daysInMonth : t1.Day;
months += (cutDay - t.Day + 1) / (double) daysInMonth;
t = new DateTime(t.Year, t.Month, 1).AddMonths(1);
}
return Math.Round(months,2);
}
one line solution
For first, check if both dates are in the current year, if not get months of whole years and then add months from the start and end year.
DateTime dateFrom = new DateTime(2019, 2, 1);
DateTime dateTo = new DateTime(2021, 5, 25);
With the first month
var monthCount = dateFrom.Year != dateTo.Year ? ((dateTo.Year - dateFrom.Year - 1) * 12) + (13 - dateFrom.Month + dateTo.Month) : dateTo.Month - dateFrom.Month + 1;
result = 28
Without first month
monthCount = dateFrom.Year != dateTo.Year ? ((dateTo.Year - dateFrom.Year - 1) * 12) + (12 - dateFrom.Month + dateTo.Month) : dateTo.Month - dateFrom.Month;
result = 27
Public Class ClassDateOperation
Private prop_DifferenceInDay As Integer
Private prop_DifferenceInMonth As Integer
Private prop_DifferenceInYear As Integer
Public Function DayMonthYearFromTwoDate(ByVal DateStart As Date, ByVal DateEnd As Date) As ClassDateOperation
Dim differenceInDay As Integer
Dim differenceInMonth As Integer
Dim differenceInYear As Integer
Dim myDate As Date
DateEnd = DateEnd.AddDays(1)
differenceInYear = DateEnd.Year - DateStart.Year
If DateStart.Month <= DateEnd.Month Then
differenceInMonth = DateEnd.Month - DateStart.Month
Else
differenceInYear -= 1
differenceInMonth = (12 - DateStart.Month) + DateEnd.Month
End If
If DateStart.Day <= DateEnd.Day Then
differenceInDay = DateEnd.Day - DateStart.Day
Else
myDate = CDate("01/" & DateStart.AddMonths(1).Month & "/" & DateStart.Year).AddDays(-1)
If differenceInMonth <> 0 Then
differenceInMonth -= 1
Else
differenceInMonth = 11
differenceInYear -= 1
End If
differenceInDay = myDate.Day - DateStart.Day + DateEnd.Day
End If
prop_DifferenceInDay = differenceInDay
prop_DifferenceInMonth = differenceInMonth
prop_DifferenceInYear = differenceInYear
Return Me
End Function
Public ReadOnly Property DifferenceInDay() As Integer
Get
Return prop_DifferenceInDay
End Get
End Property
Public ReadOnly Property DifferenceInMonth As Integer
Get
Return prop_DifferenceInMonth
End Get
End Property
Public ReadOnly Property DifferenceInYear As Integer
Get
Return prop_DifferenceInYear
End Get
End Property
End Class
This is from my own library, will return the difference of months between two dates.
public static int MonthDiff(DateTime d1, DateTime d2)
{
int retVal = 0;
// Calculate the number of years represented and multiply by 12
// Substract the month number from the total
// Substract the difference of the second month and 12 from the total
retVal = (d1.Year - d2.Year) * 12;
retVal = retVal - d1.Month;
retVal = retVal - (12 - d2.Month);
return retVal;
}
You can have a function something like this.
For Example, from 2012/12/27 to 2012/12/29 becomes 3 days. Likewise, from 2012/12/15 to 2013/01/15 becomes 2 months, because up to 2013/01/14 it's 1 month. from 15th it's 2nd month started.
You can remove the "=" in the second if condition, if you do not want to include both days in the calculation. i.e, from 2012/12/15 to 2013/01/15 is 1 month.
public int GetMonths(DateTime startDate, DateTime endDate)
{
if (startDate > endDate)
{
throw new Exception("Start Date is greater than the End Date");
}
int months = ((endDate.Year * 12) + endDate.Month) - ((startDate.Year * 12) + startDate.Month);
if (endDate.Day >= startDate.Day)
{
months++;
}
return months;
}
you can use the following extension:
Code
public static class Ext
{
#region Public Methods
public static int GetAge(this DateTime #this)
{
var today = DateTime.Today;
return ((((today.Year - #this.Year) * 100) + (today.Month - #this.Month)) * 100 + today.Day - #this.Day) / 10000;
}
public static int DiffMonths(this DateTime #from, DateTime #to)
{
return (((((#to.Year - #from.Year) * 12) + (#to.Month - #from.Month)) * 100 + #to.Day - #from.Day) / 100);
}
public static int DiffYears(this DateTime #from, DateTime #to)
{
return ((((#to.Year - #from.Year) * 100) + (#to.Month - #from.Month)) * 100 + #to.Day - #from.Day) / 10000;
}
#endregion Public Methods
}
Implementation !
int Age;
int years;
int Months;
//Replace your own date
var d1 = new DateTime(2000, 10, 22);
var d2 = new DateTime(2003, 10, 20);
//Age
Age = d1.GetAge();
Age = d2.GetAge();
//positive
years = d1.DiffYears(d2);
Months = d1.DiffMonths(d2);
//negative
years = d2.DiffYears(d1);
Months = d2.DiffMonths(d1);
//Or
Months = Ext.DiffMonths(d1, d2);
years = Ext.DiffYears(d1, d2);
Here's a much more concise solution using VB.Net DateDiff for Year, Month, Day only. You can load the DateDiff library in C# as well.
date1 must be <= date2
VB.NET
Dim date1 = Now.AddDays(-2000)
Dim date2 = Now
Dim diffYears = DateDiff(DateInterval.Year, date1, date2) - If(date1.DayOfYear > date2.DayOfYear, 1, 0)
Dim diffMonths = DateDiff(DateInterval.Month, date1, date2) - diffYears * 12 - If(date1.Day > date2.Day, 1, 0)
Dim diffDays = If(date2.Day >= date1.Day, date2.Day - date1.Day, date2.Day + (Date.DaysInMonth(date1.Year, date1.Month) - date1.Day))
C#
DateTime date1 = Now.AddDays(-2000);
DateTime date2 = Now;
int diffYears = DateDiff(DateInterval.Year, date1, date2) - date1.DayOfYear > date2.DayOfYear ? 1 : 0;
int diffMonths = DateDiff(DateInterval.Month, date1, date2) - diffYears * 12 - date1.Day > date2.Day ? 1 : 0;
int diffDays = date2.Day >= date1.Day ? date2.Day - date1.Day : date2.Day + (System.DateTime.DaysInMonth(date1.Year, date1.Month) - date1.Day);
This is in response to Kirk Woll's answer. I don't have enough reputation points to reply to a comment yet...
I liked Kirk's solution and was going to shamelessly rip it off and use it in my code, but when I looked through it I realized it's way too complicated. Unnecessary switching and looping, and a public constructor that is pointless to use.
Here's my rewrite:
public class DateTimeSpan {
private DateTime _date1;
private DateTime _date2;
private int _years;
private int _months;
private int _days;
private int _hours;
private int _minutes;
private int _seconds;
private int _milliseconds;
public int Years { get { return _years; } }
public int Months { get { return _months; } }
public int Days { get { return _days; } }
public int Hours { get { return _hours; } }
public int Minutes { get { return _minutes; } }
public int Seconds { get { return _seconds; } }
public int Milliseconds { get { return _milliseconds; } }
public DateTimeSpan(DateTime date1, DateTime date2) {
_date1 = (date1 > date2) ? date1 : date2;
_date2 = (date2 < date1) ? date2 : date1;
_years = _date1.Year - _date2.Year;
_months = (_years * 12) + _date1.Month - _date2.Month;
TimeSpan t = (_date2 - _date1);
_days = t.Days;
_hours = t.Hours;
_minutes = t.Minutes;
_seconds = t.Seconds;
_milliseconds = t.Milliseconds;
}
public static DateTimeSpan CompareDates(DateTime date1, DateTime date2) {
return new DateTimeSpan(date1, date2);
}
}
Usage1, pretty much the same:
void Main()
{
DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
var dateSpan = new DateTimeSpan(compareTo, now);
Console.WriteLine("Years: " + dateSpan.Years);
Console.WriteLine("Months: " + dateSpan.Months);
Console.WriteLine("Days: " + dateSpan.Days);
Console.WriteLine("Hours: " + dateSpan.Hours);
Console.WriteLine("Minutes: " + dateSpan.Minutes);
Console.WriteLine("Seconds: " + dateSpan.Seconds);
Console.WriteLine("Milliseconds: " + dateSpan.Milliseconds);
}
Usage2, similar:
void Main()
{
DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
Console.WriteLine("Years: " + DateTimeSpan.CompareDates(compareTo, now).Years);
Console.WriteLine("Months: " + DateTimeSpan.CompareDates(compareTo, now).Months);
Console.WriteLine("Days: " + DateTimeSpan.CompareDates(compareTo, now).Days);
Console.WriteLine("Hours: " + DateTimeSpan.CompareDates(compareTo, now).Hours);
Console.WriteLine("Minutes: " + DateTimeSpan.CompareDates(compareTo, now).Minutes);
Console.WriteLine("Seconds: " + DateTimeSpan.CompareDates(compareTo, now).Seconds);
Console.WriteLine("Milliseconds: " + DateTimeSpan.CompareDates(compareTo, now).Milliseconds);
}
In my case it is required to calculate the complete month from the start date to the day prior to this day in the next month or from start to end of month.
Ex: from 1/1/2018 to 31/1/2018 is a complete month
Ex2: from 5/1/2018 to 4/2/2018 is a complete month
so based on this here is my solution:
public static DateTime GetMonthEnd(DateTime StartDate, int MonthsCount = 1)
{
return StartDate.AddMonths(MonthsCount).AddDays(-1);
}
public static Tuple<int, int> CalcPeriod(DateTime StartDate, DateTime EndDate)
{
int MonthsCount = 0;
Tuple<int, int> Period;
while (true)
{
if (GetMonthEnd(StartDate) > EndDate)
break;
else
{
MonthsCount += 1;
StartDate = StartDate.AddMonths(1);
}
}
int RemainingDays = (EndDate - StartDate).Days + 1;
Period = new Tuple<int, int>(MonthsCount, RemainingDays);
return Period;
}
Usage:
Tuple<int, int> Period = CalcPeriod(FromDate, ToDate);
Note: in my case it was required to calculate the remaining days after the complete months so if it's not your case you could ignore the days result or even you could change the method return from tuple to integer.
public static int PayableMonthsInDuration(DateTime StartDate, DateTime EndDate)
{
int sy = StartDate.Year; int sm = StartDate.Month; int count = 0;
do
{
count++;if ((sy == EndDate.Year) && (sm >= EndDate.Month)) { break; }
sm++;if (sm == 13) { sm = 1; sy++; }
} while ((EndDate.Year >= sy) || (EndDate.Month >= sm));
return (count);
}
This solution is for Rental/subscription calculation, where difference doesn't means to be subtraction, it's meant to be the span in within those two dates.
I wrote a function to accomplish this, because the others ways weren't working for me.
public string getEndDate (DateTime startDate,decimal monthCount)
{
int y = startDate.Year;
int m = startDate.Month;
for (decimal i = monthCount; i > 1; i--)
{
m++;
if (m == 12)
{ y++;
m = 1;
}
}
return string.Format("{0}-{1}-{2}", y.ToString(), m.ToString(), startDate.Day.ToString());
}
There are not a lot of clear answers on this because you are always assuming things.
This solution calculates between two dates the months between assuming you want to save the day of month for comparison, (meaning that the day of the month is considered in the calculation)
Example, if you have a date of 30 Jan 2012, 29 Feb 2012 will not be a month but 01 March 2013 will.
It's been tested pretty thoroughly, probably will clean it up later as we use it, but here:
private static int TotalMonthDifference(DateTime dtThis, DateTime dtOther)
{
int intReturn = 0;
bool sameMonth = false;
if (dtOther.Date < dtThis.Date) //used for an error catch in program, returns -1
intReturn--;
int dayOfMonth = dtThis.Day; //captures the month of day for when it adds a month and doesn't have that many days
int daysinMonth = 0; //used to caputre how many days are in the month
while (dtOther.Date > dtThis.Date) //while Other date is still under the other
{
dtThis = dtThis.AddMonths(1); //as we loop, we just keep adding a month for testing
daysinMonth = DateTime.DaysInMonth(dtThis.Year, dtThis.Month); //grabs the days in the current tested month
if (dtThis.Day != dayOfMonth) //Example 30 Jan 2013 will go to 28 Feb when a month is added, so when it goes to march it will be 28th and not 30th
{
if (daysinMonth < dayOfMonth) // uses day in month max if can't set back to day of month
dtThis.AddDays(daysinMonth - dtThis.Day);
else
dtThis.AddDays(dayOfMonth - dtThis.Day);
}
if (((dtOther.Year == dtThis.Year) && (dtOther.Month == dtThis.Month))) //If the loop puts it in the same month and year
{
if (dtOther.Day >= dayOfMonth) //check to see if it is the same day or later to add one to month
intReturn++;
sameMonth = true; //sets this to cancel out of the normal counting of month
}
if ((!sameMonth)&&(dtOther.Date > dtThis.Date))//so as long as it didn't reach the same month (or if i started in the same month, one month ahead, add a month)
intReturn++;
}
return intReturn; //return month
}
Based on the excellent DateTimeSpan work done above, I've normalized the code a bit; this seems to work pretty well:
public class DateTimeSpan
{
private DateTimeSpan() { }
private DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
{
Years = years;
Months = months;
Days = days;
Hours = hours;
Minutes = minutes;
Seconds = seconds;
Milliseconds = milliseconds;
}
public int Years { get; private set; } = 0;
public int Months { get; private set; } = 0;
public int Days { get; private set; } = 0;
public int Hours { get; private set; } = 0;
public int Minutes { get; private set; } = 0;
public int Seconds { get; private set; } = 0;
public int Milliseconds { get; private set; } = 0;
public static DateTimeSpan CompareDates(DateTime StartDate, DateTime EndDate)
{
if (StartDate.Equals(EndDate)) return new DateTimeSpan();
DateTimeSpan R = new DateTimeSpan();
bool Later;
if (Later = StartDate > EndDate)
{
DateTime D = StartDate;
StartDate = EndDate;
EndDate = D;
}
// Calculate Date Stuff
for (DateTime D = StartDate.AddYears(1); D < EndDate; D = D.AddYears(1), R.Years++) ;
if (R.Years > 0) StartDate = StartDate.AddYears(R.Years);
for (DateTime D = StartDate.AddMonths(1); D < EndDate; D = D.AddMonths(1), R.Months++) ;
if (R.Months > 0) StartDate = StartDate.AddMonths(R.Months);
for (DateTime D = StartDate.AddDays(1); D < EndDate; D = D.AddDays(1), R.Days++) ;
if (R.Days > 0) StartDate = StartDate.AddDays(R.Days);
// Calculate Time Stuff
TimeSpan T1 = EndDate - StartDate;
R.Hours = T1.Hours;
R.Minutes = T1.Minutes;
R.Seconds = T1.Seconds;
R.Milliseconds = T1.Milliseconds;
// Return answer. Negate values if the Start Date was later than the End Date
if (Later)
return new DateTimeSpan(-R.Years, -R.Months, -R.Days, -R.Hours, -R.Minutes, -R.Seconds, -R.Milliseconds);
return R;
}
}
Insane method that counts all days, so super precise
helper class :
public class DaysInMonth
{
public int Days { get; set; }
public int Month { get; set; }
public int Year { get; set; }
public bool Full { get; set; }
}
function:
public static List<DaysInMonth> MonthsDelta(DateTime start, DateTime end)
{
var dates = Enumerable.Range(0, 1 + end.Subtract(start).Days)
.Select(offset => start.AddDays(offset))
.ToArray();
DateTime? prev = null;
int days = 0;
List < DaysInMonth > list = new List<DaysInMonth>();
foreach (DateTime date in dates)
{
if (prev != null)
{
if(date.Month!=prev.GetValueOrDefault().Month)
{
DaysInMonth daysInMonth = new DaysInMonth();
daysInMonth.Days = days;
daysInMonth.Month = prev.GetValueOrDefault().Month;
daysInMonth.Year = prev.GetValueOrDefault().Year;
daysInMonth.Full = DateTime.DaysInMonth(daysInMonth.Year, daysInMonth.Month) == daysInMonth.Days;
list.Add(daysInMonth);
days = 0;
}
}
days++;
prev = date;
}
//------------------ add last
if (days > 0)
{
DaysInMonth daysInMonth = new DaysInMonth();
daysInMonth.Days = days;
daysInMonth.Month = prev.GetValueOrDefault().Month;
daysInMonth.Year = prev.GetValueOrDefault().Year;
daysInMonth.Full = DateTime.DaysInMonth(daysInMonth.Year, daysInMonth.Month) == daysInMonth.Days;
list.Add(daysInMonth);
}
return list;
}

Counting regular working days in a given period of time

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();

Categories