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;
}
Related
I am struggling with a datetime problem where I am given a set of inputs and I need to find the EndDate by using those inputs. I am trying my best to solve this, but since I am running out of time, I landed up here. So someone who already might have faced this problem or someone with a solution could let me know one.
Problem Explanation:
The Concept is a Weekly schedule of a live class streaming application. The teacher has scheduled a weekly class from a specific start date.
Let's say, the start date is from 18th April, 2021 and the teacher selects 3 days per week(Monday, Tuesday & Wednesday) each with different class duration(By duration I mean that, We have the class start time of that day and the length of the class in hours and minutes).
Start Date: 18th April, 2021
Total days per week: 3
Days: Monday, Tuesday, Wednesday
Total duration per day: 3 hrs and 30 minutes
Total duration per week: 10 hours 30 minutes (Aggregated = Mon + Tue + Wed)
Max duration that the user cannot exceed: 50 hrs
Ok! Now we shall repeatedly add the TDPD(3 hrs and 30 minutes) to the start date until we reach 50 hrs and find which date it has landed upon(the end date).
What I have tried so far?
int totalWeeks = 0;
int totalHoursPerWeek = 3;
int totalHoursAdded = 0;
int maxHours = 50;
for(int i = totalHoursPerWeek; i <= maxHours; i += totalHoursPerWeek)
{
totalHoursAdded += i;
totalWeeks++;
}
Once the loop ends, I have the total week value.
DateTime endDate;
if(totalHoursAdded == maxHours)
{
//My problem is solved, as there is no remaining time pending
endDate = currentDate.AddDays(totalWeeks * 7);
}
else
{
// I have some pending hours
int pendingHours = maxHours - totalHoursAdded;
//How do I proceed with this pendingHours? how to add this to the
//specific days per week and find the end date? I am stuck here...
}
I am confident this can be done with some carefully thought-out math with dates, however below may be termed the lazy way out. This approach only needs the starting date, a list of days of the week that the class meets, i.e., Monday, Tuesday etc.…, the duration of the class in hours and minutes, and finally the max total hours and minutes that are required.
From my understanding, given the above info, we want to know, given that start date and the days of the week the class meets…
on what Date will the LAST class be that fulfills the max total hours.
To simplify this, one thing that will come in handy is knowing...
“how many classes are needed to fulfill the MAX requirement”.
In other words, if we know how many classes are needed to fulfill the max requirement, then this should make things easier.
Computing the total number of classes needed to fulfil the max requirement, can be found by dividing the max requirement by the class duration. If the division produces a remainder, then this would mean that one (1) additional class would be needed to fulfil the max requirement.
Therefore, if we have both the class duration and max duration variables as Timespan objects, then, we could divide the TimeSpan objects via their respective Tick properties and return how many classes are needed to fulfil the max requirement. This method may look something like…
private int GetTotalNumberOfClassesNeeded(TimeSpan classDuration, TimeSpan totalDuration) {
double td = totalDuration.Ticks / (double)classDuration.Ticks;
int totalClasses = (int)Math.Truncate(td); // <- get the whole portion
if (Math.Floor(td) != td) {
totalClasses++; // <- there is a fractional part - 1 more class needed
}
return totalClasses;
}
Next, we need to compare the DayOfWeek for a date with the DayOfWeek for the class. Therefore, is what we can do is create a List<DayOfWeek> … a list of DayOfWeek objects that the class is in session. We will use this list to check and see if a particular date’s day of week is IN that list. Therefore, in this example, the days of the week the class meets are a simple comma delimited string. Given this string, the code would parse out the days and return the proper list of DayOfWeek objects to compare with. This method may look something like…
private List<DayOfWeek> GetDaysOfWeekForClasses(string daysOfWeek) {
List<DayOfWeek> classesDOW = new List<DayOfWeek>();
string[] splitArray = daysOfWeek.Split(',');
DayOfWeek dow;
for (int i = 0; i < splitArray.Length; i++) {
switch (splitArray[i].Trim()) {
case "Monday":
dow = DayOfWeek.Monday;
break;
case "Tuesday":
dow = DayOfWeek.Tuesday;
break;
case "Wednesday":
dow = DayOfWeek.Wednesday;
break;
case "Thursday":
dow = DayOfWeek.Thursday;
break;
case "Friday":
dow = DayOfWeek.Friday;
break;
case "Saturday":
dow = DayOfWeek.Saturday;
break;
default:
dow = DayOfWeek.Sunday;
break;
}
classesDOW.Add(dow);
}
return classesDOW;
}
That is pretty much all we need. The general idea is this… we start by setting an int variable curClassCount to zero (0). In addition, we will create a DateTime object tempDate that is initialized with the starting date. Lastly, we will create a list of DateTime objects scheduledClasses which will get filled with the dates of the classes. We will start a while loop with the condition to continue as long as curClassCount is less than the total number of classes needed.
In each iteration of the loop a check is made to see if the tempDate’s DayOfWeek is one of the class’s DayOfWeek. … if it is, then we add that date to the scheduledClasses list and increment curClassCount. Finally increment tempDate by one (1) day, then start the loop over. Eventually, the curClassCount will equal the number of classes needed. This code may look something like…
while (curClassCount < totalNumberOfClassesNeeded) {
if (ClassDaysOfWeek.Contains(tempDate.DayOfWeek)) {
scheduledClasses.Add(tempDate.Date);
curClassCount++;
}
tempDate = tempDate.AddDays(1);
}
Putting all this together by droping a DateTimePicker, four (4) TextBoxes, a Button and a multi-line TextBox onto a new Winforms .Net Form may look something like…
Using the code below should complete the example.
private void Form1_Load(object sender, EventArgs e) {
dateTimePicker1.Value = new DateTime(2021, 4, 18);
TextBoxTotDaysPerWeek.Text = "3";
textBoxClassDays.Text = "Monday, Tuesday, Wednesday";
textBoxClassDuration.Text = "00:03:00:00";
textBoxMaxDuation.Text = "02:02:00:00";
}
private void btnCalculate_Click(object sender, EventArgs e) {
DateTime StartDate = dateTimePicker1.Value;
TimeSpan.TryParse(textBoxClassDuration.Text.Trim(), out TimeSpan ClassDuration);
TimeSpan.TryParse(textBoxMaxDuation.Text.Trim(), out TimeSpan MaxDuration);
int totalNumberOfClassesNeeded = GetTotalNumberOfClassesNeeded(ClassDuration, MaxDuration);
List<DayOfWeek> ClassDaysOfWeek = GetDaysOfWeekForClasses(textBoxClassDays.Text.Trim());
List<DateTime> scheduledClasses = new List<DateTime>();
int curClassCount = 0;
DateTime tempDate = StartDate.Date;
while (curClassCount < totalNumberOfClassesNeeded) {
if (ClassDaysOfWeek.Contains(tempDate.DayOfWeek)) {
scheduledClasses.Add(tempDate.Date);
curClassCount++;
}
tempDate = tempDate.AddDays(1);
}
txtBoxResults.Text = "";
txtBoxResults.Text = "Start Date: " + StartDate.ToShortDateString() +Environment.NewLine;
for (int i = 0; i < scheduledClasses.Count; i++) {
txtBoxResults.Text += "Class # " + (i + 1) + " of " + totalNumberOfClassesNeeded +
" Date: " + scheduledClasses[i].ToShortDateString() + Environment.NewLine;
}
}
A note, on the max duration… since the TimeSpan only allows hours < 23, we need to break 50 hours into 2 days and 2 hours. I hope this makes sense.
These are two options (using For and using While loop), should solve the problem:
using System;
namespace SO.DtProblem
{
class Program
{
static void Main(string[] args)
{
ClaculationOption1(); //Using For loop
ClaculationOption2(); //Using While loop
}
private static void ClaculationOption1()
{
var totalWeeks = 0;
var totalHoursPerWeek = 3;
var durationPerDay = 3;
var maxHours = 50;
var courseStartDate = new DateTime(2021, 4, 12); //Which is a Monday day
totalWeeks = maxHours / totalHoursPerWeek;
var expectedEndDate = courseStartDate.AddDays(totalWeeks * 7);
var pendingHours = maxHours % totalHoursPerWeek;
for (var day = 1; day <= 6; day++)
{
if (pendingHours > 0)
{
expectedEndDate = expectedEndDate.AddDays(1);
if ((expectedEndDate.AddDays(1).DayOfWeek == DayOfWeek.Monday)
|| (expectedEndDate.AddDays(1).DayOfWeek == DayOfWeek.Tuesday)
|| (expectedEndDate.AddDays(1).DayOfWeek == DayOfWeek.Wednesday))
{
if (pendingHours - durationPerDay >= 0)
{
pendingHours = pendingHours - durationPerDay;
}
else
{
pendingHours = 0;
break;
}
}
}
}
Console.Clear();
Console.WriteLine("Option 1 Results");
Console.WriteLine($"Course Start Date : {courseStartDate}");
Console.WriteLine($"Course Start Date Day Name: {courseStartDate.DayOfWeek}");
Console.WriteLine($"Expected End Date : {expectedEndDate}");
Console.WriteLine($"Expected End Date Day Name: {expectedEndDate.DayOfWeek}");
Console.WriteLine("===========================================================");
}
private static void ClaculationOption2()
{
var totalWeeks = 0;
var totalHoursPerWeek = 3;
var durationPerDay = 3;
var maxHours = 50;
var courseStartDate = new DateTime(2021, 4, 12); //Which is a Monday day
totalWeeks = maxHours / totalHoursPerWeek;
var expectedEndDate = courseStartDate.AddDays(totalWeeks * 7);
var pendingHours = maxHours % totalHoursPerWeek;
while (pendingHours > 0)
{
expectedEndDate = expectedEndDate.AddDays(1);
if ((expectedEndDate.AddDays(1).DayOfWeek == DayOfWeek.Monday)
|| (expectedEndDate.AddDays(1).DayOfWeek == DayOfWeek.Tuesday)
|| (expectedEndDate.AddDays(1).DayOfWeek == DayOfWeek.Wednesday))
{
if (pendingHours - durationPerDay >= 0)
{
pendingHours = pendingHours - durationPerDay;
}
else
{
pendingHours = 0;
break;
}
}
}
Console.WriteLine("Option 2 Results");
Console.WriteLine($"Course Start Date : {courseStartDate}");
Console.WriteLine($"Course Start Date Day Name: {courseStartDate.DayOfWeek}");
Console.WriteLine($"Expected End Date : {expectedEndDate}");
Console.WriteLine($"Expected End Date Day Name: {expectedEndDate.DayOfWeek}");
Console.ReadKey();
}
}
}
Finally I got my own solution working on the idea based on #John's answer. This answer is specially for the variable hours and minutes.
private static void CalculateClassDates()
{
DateTime courseStartDateTime = DateTime.Today;
int courseDurationInHours = 60;
int courseDurationInMinutes = 0;
TimeSpan courseMaxDuration = new TimeSpan(courseDurationInHours, courseDurationInMinutes, 0);
List<CourseScheduleDates> courseScheduleDates = new List<CourseScheduleDates>();
List<DaysOfWeek> daysOfWeeks = new List<DaysOfWeek>()
{
new DaysOfWeek(){ DayOfWeek = DayOfWeek.Monday, StartTime = DateTime.Today.AddHours(10).TimeOfDay , TotalHours = 1, TotalMinutes = 15},
new DaysOfWeek(){ DayOfWeek = DayOfWeek.Tuesday, StartTime = DateTime.Today.AddHours(10).TimeOfDay , TotalHours = 1, TotalMinutes = 15},
new DaysOfWeek(){ DayOfWeek = DayOfWeek.Wednesday, StartTime = DateTime.Today.AddHours(10).TimeOfDay , TotalHours = 1, TotalMinutes = 30}
};
int daysToAdd = 0;
TimeSpan singleDuration = new TimeSpan(daysOfWeeks[0].TotalHours, daysOfWeeks[0].TotalMinutes, 0);
TimeSpan additionallyAddedTime = TimeSpan.Zero;
List<DayOfWeek> days = daysOfWeeks.Select(x => x.DayOfWeek).ToList();
while (singleDuration.Ticks <= courseMaxDuration.Ticks)
{
if (days.Contains(courseStartDateTime.AddDays(daysToAdd).DayOfWeek))
{
var dayOfWeek = daysOfWeeks.Where(x => x.DayOfWeek == courseStartDateTime.AddDays(daysToAdd).DayOfWeek).First();
courseScheduleDates.Add(new CourseScheduleDates()
{
ScheduleDate = courseStartDateTime.AddDays(daysToAdd),
StartTime = dayOfWeek.StartTime,
TotalHours = dayOfWeek.TotalHours,
TotalMinutes = dayOfWeek.TotalMinutes
});
additionallyAddedTime = new TimeSpan(dayOfWeek.TotalHours, dayOfWeek.TotalMinutes, 0);
singleDuration = singleDuration.Add(additionallyAddedTime);
}
daysToAdd++;
}
singleDuration = singleDuration.Subtract(additionallyAddedTime);
if (singleDuration.Ticks != courseMaxDuration.Ticks)
{
var timeSpanToAdd = new TimeSpan(courseMaxDuration.Ticks - singleDuration.Ticks);
bool shouldContinue = true;
while (shouldContinue)
{
var currentDate = courseStartDateTime.AddDays(daysToAdd);
if (days.Contains(currentDate.DayOfWeek))
{
var dayOfWeek = daysOfWeeks.Where(x => x.DayOfWeek == courseStartDateTime.DayOfWeek).First();
courseScheduleDates.Add(new CourseScheduleDates()
{
ScheduleDate = courseStartDateTime.AddDays(daysToAdd),
StartTime = dayOfWeek.StartTime,
TotalHours = timeSpanToAdd.Hours,
TotalMinutes = timeSpanToAdd.Minutes
});
shouldContinue = false;
}
}
}
Console.WriteLine("Course Start Date " + courseStartDateTime.ToString("dd MMM, yyyy"));
int classCount = 1;
foreach (var item in courseScheduleDates)
{
DateTime dateTime = new DateTime(item.StartTime.Ticks);
Console.WriteLine("Class " + classCount + " will commence on " + item.ScheduleDate.ToString("dd MMM, yyyy") +
" " + dateTime.ToString("hh:mm tt") + " and will last for " + item.TotalHours + " hrs " + item.TotalMinutes + " mins");
classCount++;
}
Console.ReadLine();
}
And the Models
public class DaysOfWeek
{
public DayOfWeek DayOfWeek { get; set; }
public TimeSpan StartTime { get; set; }
public int TotalHours { get; set; }
public int TotalMinutes { get; set; }
}
public class CourseScheduleDates
{
public DateTime ScheduleDate { get; set; }
public TimeSpan StartTime { get; set; }
public int TotalHours { get; set; }
public int TotalMinutes { get; set; }
}
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;
}
i need a function that calculates if there is an upcoming anniversary for a person given a start date in the past and today's current date. I found this code below but looking at the below code and I have the feeling that there is a much simpler way to calculate this without required this for loop:
private const int ANNIVERSARY_ALERT = 10;
public virtual string UpcomingMilestone
{
get
{
var years = Years() + 2;
for (int year = 0; year < years; year++)
{
int days = year * 365;
int dayDiff = days - NumberOfDays;
if (dayDiff == 0)
{
return year + " year milestone";
}
if (dayDiff < ANNIVERSARY_ALERT && dayDiff > 0)
{
return year + " year milestone in " + dayDiff + " days";
}
}
return string.Empty;
}
}
public virtual int NumberOfDays
{
get
{
TimeSpan ts = DateTime.Today - StartDate.Value;
return (int)ts.TotalDays;
}
}
public virtual int Years()
{
TimeSpan span = DateTime.Now.Subtract(StartDate.Value);
return (int)(span.Days / 365.25); // leap years included
}
Can anyone suggest a way to calculate this above without having to do this loop? This is more for code maintainability versus any performance considerations.
Use the relevant parts of the startdate to compose a new DateTime:
//My birthday, feel free to put this date in your calendars!
var startDate = new DateTime(1976, 2, 29);
//Get the anniversary date for this year
DateTime nextAnniversary;
try
{
nextAnniversary = new DateTime(DateTime.Today.Year, startDate.Month, startDate.Day);
}
catch(ArgumentOutOfRangeException)
{
//DateTime conversion failed, try next day in the year
nextAnniversary = new DateTime(DateTime.Today.Year, startDate.AddDays(1).Month, startDate.AddDays(1).Day);
}
//Check if this year's anniversary has already happened
if(nextAnniversary < DateTime.Today) nextAnniversary = nextAnniversary.AddYears(1);
This should do it. You need to provide TimeSpan range that can be a day or a week, and date which you are matching anniversary to.
public bool Upcomming(DateTime date, TimeSpan range){
var newdate = new Date(DateTime.Now.Year, date.Month, date.Day);
return newdate - DateTime.Now < range;
}
int anniversaryYear = DateTime.Now.Year - StartDate.Year + 1;
DateTime nextAnniversary = StartDate.AddYears(anniversaryYear);
if (nextAnniversary == DateTime.Now)
{
return anniverasryYear + " year milestone";
}
if (DateTime.Now > nextAnniverasry)
{
anniverasryYear++;
nextAnniversary = StartDate.AddYears(anniversaryYear);
}
var daysTillNext = Math.Abs( (nextAnniversary - DateTime.Now).TotalDays );
return string.Format("{0} milestone in {1}", anniversaryYear, daysTillNext);
These methods should be sufficient for your purposes.
public DateTime GetNextAnniversaryDate(DateTime anniversary)
{
var today = DateTime.Today;
var year = anniversary.Month < today.Month ||
(anniversary.Month == today.Month && anniversary.Day < today.Day)
? today.Year + 1 : today.Year;
return anniversary.Month == 2 && anniversary.Day == 29 &&
!DateTime.IsLeapYear(year)
? new DateTime(year, 2, 28)
: new DateTime(year, anniversary.Month, anniversary.Day);
}
public int GetDaysUntilNextAnniversary(DateTime anniversary)
{
var nextDate = GetNextAnniversaryDate(anniversary);
return (int)(nextDate - DateTime.Today).TotalDays;
}
Note that we are specifically electing to celebrate leap-day anniversaries on Feb 28th when the anniversary year is not a leap year. You could change that to March 1 if desired.
Also note that this question assumes that the day of the person in question is the same day as the computer's local clock. This might not be true, for example if the local machine is in a time zone that is ahead of the user's time zone, then it might be one day off. If you want to take that into consideration, then you could use TimeZoneInfo and DateTime.UtcNow. Or you could use Noda Time. More about this on my blog.
Given a date (of type DateTime), how do I find the 3rd Friday in the month of that date?
I'm going to repeat my answer from here with one little addition.
The language-agnostic version:
To get the first particular day of the month, start with the first day of the month: yyyy-mm-01. Use whatever function is available to give a number corresponding to the day of the week; in C# this would be DateTime.DayOfWeek. Subtract that number from the day you are looking for; for example, if the first day of the month is Wednesday (3) and you're looking for Friday (5), subtract 3 from 5, leaving 2. If the answer is negative, add 7. Finally add that to the first of the month; for my example, the first Friday would be the 3rd.
To get the last Friday of the month, find the first Friday of the next month and subtract 7 days.
To get the 3rd Friday of the month, add 14 days to the first Friday.
I haven't tested this, but since the third Friday can't possibly occur before the 15th of the month, create a new DateTime, then just increment until you get to a Friday.
DateTime thirdFriday= new DateTime(yourDate.Year, yourDate.Month, 15);
while (thirdFriday.DayOfWeek != DayOfWeek.Friday)
{
thirdFriday = thirdFriday.AddDays(1);
}
I followed User:Mark Ransom's algorithm and wrote a generalized day finder. For example to get the 3rd friday of december 2013,
int thirdFriday = DayFinder.FindDay(2013, 12, DayOfWeek.Friday, 3);
And here is the function definition. It doesn't have any iterative loops, so its efficient.
public class DayFinder
{
//For example to find the day for 2nd Friday, February, 2016
//=>call FindDay(2016, 2, DayOfWeek.Friday, 2)
public static int FindDay(int year, int month, DayOfWeek Day, int occurance)
{
if (occurance <= 0 || occurance > 5)
throw new Exception("Occurance is invalid");
DateTime firstDayOfMonth = new DateTime(year, month, 1);
//Substract first day of the month with the required day of the week
var daysneeded = (int)Day - (int)firstDayOfMonth.DayOfWeek;
//if it is less than zero we need to get the next week day (add 7 days)
if (daysneeded < 0) daysneeded = daysneeded + 7;
//DayOfWeek is zero index based; multiply by the Occurance to get the day
var resultedDay = (daysneeded + 1) + (7 * (occurance - 1));
if (resultedDay > (firstDayOfMonth.AddMonths(1) - firstDayOfMonth).Days)
throw new Exception(String.Format("No {0} occurance(s) of {1} in the required month", occurance, Day.ToString()));
return resultedDay;
}
}
Probably best to abstract this to a method to do any date/day combination:
(Extension Method)
public static bool TryGetDayOfMonth(this DateTime instance,
DayOfWeek dayOfWeek,
int occurance,
out DateTime dateOfMonth)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
if (occurance <= 0 || occurance > 5)
{
throw new ArgumentOutOfRangeException("occurance", "Occurance must be greater than zero and less than 6.");
}
bool result;
dateOfMonth = new DateTime();
// Change to first day of the month
DateTime dayOfMonth = instance.AddDays(1 - instance.Day);
// Find first dayOfWeek of this month;
if (dayOfMonth.DayOfWeek > dayOfWeek)
{
dayOfMonth = dayOfMonth.AddDays(7 - (int)dayOfMonth.DayOfWeek + (int)dayOfWeek);
}
else
{
dayOfMonth = dayOfMonth.AddDays((int)dayOfWeek - (int)dayOfMonth.DayOfWeek);
}
// add 7 days per occurance
dayOfMonth = dayOfMonth.AddDays(7 * (occurance - 1));
// make sure this occurance is within the original month
result = dayOfMonth.Month == instance.Month;
if (result)
{
dateOfMonth = dayOfMonth;
}
return result;
}
Results:
DateTime myDate = new DateTime(2013, 1, 1)
DateTime dateOfMonth;
myDate.TryGetDayOfMonth(DayOfWeek.Sunday, 1, out dateOfMonth)
// returns: true; dateOfMonth = Sunday, 1/6/2013
myDate.TryGetDayOfMonth(DayOfWeek.Sunday, 4, out dateOfMonth)
// returns: true; dateOfMonth = Sunday, 1/27/2013
myDate.TryGetDayOfMonth(DayOfWeek.Sunday, 5, out dateOfMonth)
// returns: false;
myDate.TryGetDayOfMonth(DayOfWeek.Wednesday, 1, out dateOfMonth)
// returns: true; dateOfMonth = Wednesday, 1/2/2013
myDate.TryGetDayOfMonth(DayOfWeek.Wednesday, 4, out dateOfMonth)
// returns: true; dateOfMonth = Wednesday, 1/23/2013
myDate.TryGetDayOfMonth(DayOfWeek.Wednesday, 5, out dateOfMonth)
// returns: true; dateOfMonth = Wednesday, 1/30/2013
// etc
Old post, but I found remarkably few decent answers online for this surely quite common problem! Mark Ransom's answer should be the last word on this algorithm-wise, but here is a C# helper class (in this case I think clearer than extensions) for anyone who wants a quick answer to the common problems of "first day of week in month", "xth day of week in month" and "last day of week in month".
I modified it to return DateTime.MinValue if the Xth day of the week falls outside the provided month rather than wrapping to the next month, because that to me seems more useful.
I've thrown in a LINQPad-runnable example program too.
void Main()
{
DayOfWeek dow = DayOfWeek.Friday;
int y = 2014;
int m = 2;
String.Format("First {0}: {1}", new object[] { dow, DateHelper.FirstDayOfWeekInMonth(y, m, dow) }).Dump();
"".Dump();
String.Format("Last {0}: {1}", new object[] { dow, DateHelper.LastDayOfWeekInMonth(y, m, dow) }).Dump();
"".Dump();
for(int i = 1; i <= 6; i++)
String.Format("{0} #{1}: {2}", new object[] { dow, i, DateHelper.XthDayOfWeekInMonth(y, m, dow, i) }).Dump();
}
public class DateHelper
{
public static DateTime FirstDayOfWeekInMonth(int year, int month, DayOfWeek day)
{
DateTime res = new DateTime(year, month, 1);
int offset = -(res.DayOfWeek - day);
if (offset < 0)
offset += 7;
res = res.AddDays(offset);
return res;
}
public static DateTime LastDayOfWeekInMonth(int year, int month, DayOfWeek day)
{
DateTime dt = new DateTime(year, month, 1).AddMonths(1);
DateTime res = FirstDayOfWeekInMonth(dt.Year, dt.Month, day);
res = res.AddDays(-7);
return res;
}
public static DateTime XthDayOfWeekInMonth(int year, int month, DayOfWeek day, int x)
{
DateTime res = DateTime.MinValue;
if (x > 0)
{
res = FirstDayOfWeekInMonth(year, month, day);
if (x > 1)
res = res.AddDays((x - 1) * 7);
res = res.Year == year && res.Month == month ? res : DateTime.MinValue;
}
return res;
}
}
Prints:
First Friday: 07/02/2014 00:00:00
Last Friday: 28/02/2014 00:00:00
Friday #1: 07/02/2014 00:00:00
Friday #2: 14/02/2014 00:00:00
Friday #3: 21/02/2014 00:00:00
Friday #4: 28/02/2014 00:00:00
Friday #5: 01/01/0001 00:00:00
Friday #6: 01/01/0001 00:00:00
Slightly more optimized version:
DateTime Now = DateTime.Now;
DateTime TempDate = new DateTime(Now.Year, Now.Month, 1);
// find first friday
while (TempDate.DayOfWeek != DayOfWeek.Friday)
TempDate = TempDate.AddDays(1);
// add two weeks
TempDate = TempDate.AddDays(14);
This is a version that uses LINQ and functional programming style.
It works like this.
First, take all of the days of the month. Then select only the ones of the right day (Friday). Finally take the nth (3rd) entry and return.
// dt: The date to start from (usually DateTime.Now)
// n: The nth occurance (3rd)
// weekday: the day of the week to look for
public DateTime GetNthWeekdayOfMonth(DateTime dt, int n, DayOfWeek weekday)
{
var days = Enumerable.Range(1, DateTime.DaysInMonth(dt.Year, dt.Month)).Select(day => new DateTime(dt.Year, dt.Month, day));
var weekdays = from day in days
where day.DayOfWeek == weekday
orderby day.Day ascending
select day;
int index = n - 1;
if (index >= 0 && index < weekdays.Count())
return weekdays.ElementAt(index);
else
throw new InvalidOperationException("The specified day does not exist in this month!");
}
My reasoning goes like this
the 15th is the first possible "third Friday" (1,8,15)
therefore we're looking for the first Friday on or after the 15th
DayOfWeek is an enumeration starting with 0 for Sunday
Therefore you have to add an offet of 5-(int)baseDay.DayOfWeek to the 15th
Except that the above offset can be negative, which we fix by adding 7, then doing modulo 7.
In code:
public static DateTime GetThirdFriday(int year, int month)
{
DateTime baseDay = new DateTime(year, month, 15);
int thirdfriday = 15 + ((12 - (int)baseDay.DayOfWeek) % 7);
return new DateTime(year, month, thirdfriday);
}
Since there are only 7 possible results, you could also do this:
private readonly static int[] thirdfridays =
new int[] { 20, 19, 18, 17, 16, 15, 21 };
public static int GetThirdFriday(int year, int month)
{
DateTime baseDay = new DateTime(year, month, 15);
return thirdfridays[(int)baseDay.DayOfWeek];
}
I pass this the DateTime for the start of the month I am looking at.
private DateTime thirdSunday(DateTime timeFrom)
{
List<DateTime> days = new List<DateTime>();
DateTime testDate = timeFrom;
while (testDate < timeFrom.AddMonths(1))
{
if (testDate.DayOfWeek == DayOfWeek.Friday)
{
days.Add(testDate);
}
testDate = testDate.AddDays(1);
}
return days[2];
}
I know of no clean/built in way of doing this. But it's not too hard to code up:
DateTime now = DateTime.Now;
for (int i = 0; i < 7; ++i)
{
DateTime d = new DateTime(now.Year, now.Month, i+1);
if (d.DayOfWeek == DayOfWeek.Friday)
{
return d.AddDays(14);
}
}
public DateTime GetThirdThursday(DateTime now)
{
DateTime ThirdThursday;
now = DateTime.Now;
string wkday;
DateTime firstday = new DateTime(now.Year, now.Month, 1);
ThirdThursday = firstday.AddDays(15);
// ThirdThursday = now.AddDays((now.Day - 1) * -1).AddDays(14);
wkday = ThirdThursday.DayOfWeek.ToString();
while (wkday.CompareTo("Thursday") < 0)
{
ThirdThursday.AddDays(1);
}
return ThirdThursday;
}
Late to the game but here's my solution to add DateTime extension functionality which accounts for the January to December issue when subtracting occurrence values and accounts for if the occurrence is 5 - which in my case should return the last occurrence which would either be number 4 or 5 depending on where the day lands in the month:
public static DateTime NthOf(this DateTime CurDate, int Occurrence, DayOfWeek Day)
{
//Last day of month if 5 - return last day.
if (Occurrence == 5)
{
return LastDayOfMonth(CurDate, Day);
}
var fday = new DateTime(CurDate.Year, CurDate.Month, 1, CurDate.Hour, CurDate.Minute, CurDate.Second);
var firstoccurrence = fday.DayOfWeek == Day ? fday : fday.AddDays(Day - fday.DayOfWeek);
// CurDate = 2011.10.1 Occurance = 1, Day = Friday >> 2011.09.30 FIX.
if (firstoccurrence.Month < CurDate.Month)
{
Occurrence = Occurrence + 1;
} else if (firstoccurrence.Month == 12 && CurDate.Month == 1)
{
Occurrence = Occurrence + 1;
}
return firstoccurrence.AddDays(7 * (Occurrence - 1));
}
public static DateTime LastDayOfMonth(this DateTime CurDate, DayOfWeek Day)
{
DateTime EndOfMonth = new DateTime(CurDate.Year, CurDate.Month, 1).AddMonths(1).AddDays(-1);
while (EndOfMonth.DayOfWeek != Day)
{
EndOfMonth = EndOfMonth.AddDays(-1);
}
return EndOfMonth;
}
The you can call your method with something like this:
Console.WriteLine(DateTime.Now.NthOf(3, DayOfWeek.Friday).ToString());
This would return the third Friday of the current month and log it to the console as a string value. It extends from DateTime nicely and does not require any usage of a static helper class or any additional moving parts.
int numday = 0;
int dayofweek = 5; //friday
DateTime thirdfriday;
for (int i = 0; i < (date.AddMonths(1) - date).Days && numday <3; i++)
{
if ((int)date.AddDays(i).DayOfWeek == dayofweek)
{
numday++;
}
if (numday == 3)
{
thirdfriday = date.AddDays(i);
}
}
Sorry to jump in late on this... Might help someone else tho.
Begin rant: Loops, yuck. Too much code, yuck. Not Generic Enough, yuck.
Here's a simple function with a free overload.
public DateTime DateOfWeekOfMonth(int year, int month, DayOfWeek dayOfWeek, byte weekNumber)
{
DateTime tempDate = new DateTime(year, month, 1);
tempDate = tempDate.AddDays(-(tempDate.DayOfWeek - dayOfWeek));
return
tempDate.Day > (byte)DayOfWeek.Saturday
? tempDate.AddDays(7 * weekNumber)
: tempDate.AddDays(7 * (weekNumber - 1));
}
public DateTime DateOfWeekOfMonth(DateTime sender, DayOfWeek dayOfWeek, byte weekNumber)
{
return DateOfWeekOfMonth(sender.Year, sender.Month, dayOfWeek, weekNumber);
}
Your usage:
DateTime thirdFridayOfMonth = DateOfWeekOfMonth(DateTime.Now, DayOfWeek.Friday, 3);
Here's my algorithm:
Find the number of days until the upcoming Friday.
Initialize a counter and set it to 1. Subtract seven days from the date returned from [1], then compare the month from the date returned against the date returned from (1).
If the months are not equal, return the counter from [2].
If the months are equal, recurse into [2] and add 1 to the counter created in [2].
The counter will give you the nth Friday of the month for that date (or its upcoming Friday).
Following works great, no validation for occurrence is provided. You can find any nth day for the given date month either from start or last. Provide minus occurrence value if you are looking for from the last.
public static DateTime GetDayOfMonth(DateTime dateValue, DayOfWeek dayOfWeek, int occurance)
{
List<DateTime> dayOfWeekRanges = new List<DateTime>();
//move to the first of th month
DateTime startOfMonth = new DateTime(dateValue.Year, dateValue.Month, 1);
//move startOfMonth to the dayOfWeek requested
while (startOfMonth.DayOfWeek != dayOfWeek)
startOfMonth = startOfMonth.AddDays(1);
do
{
dayOfWeekRanges.Add(startOfMonth);
startOfMonth = startOfMonth.AddDays(7);
} while (startOfMonth.Month == dateValue.Month);
bool fromLast = occurance < 0;
if (fromLast)
occurance = occurance * -1;
if (fromLast)
return dayOfWeekRanges[dayOfWeekRanges.Count - occurance];
else
return dayOfWeekRanges[occurance - 1];
}
Here is my two cents...
An optimized solution without unnecessary loops or tests :
public static DateTime ThirdFridayOfMonth(DateTime dateTime)
{
int day = dateTime.Day;
return dateTime.AddDays(21 - day - ((int)dateTime.DayOfWeek + 37 - day) % 7);
}
I wrote extended version of #justcoding121's code that can get from the last day of the month. I don't know this algorithm is right, but it works so far.
public static int? GetNthDayOfWeekInMonth(int year, int month, DayOfWeek dow, int weekNumOfMonth)
{
if (weekNumOfMonth < -5 || weekNumOfMonth == 0 || weekNumOfMonth > 5)
throw new ArgumentOutOfRangeException("weekNumOfMonth", $"must be between 1~5 or -1~-5. ({weekNumOfMonth})");
int daysOfMonth = DateTime.DaysInMonth(year, month);
if (weekNumOfMonth > 0)
{
var firstDay = new DateTime(year, month, 1);
var firstDayOfTargetDOW = (int)dow - (int)firstDay.DayOfWeek;
if (firstDayOfTargetDOW < 0)
firstDayOfTargetDOW += 7;
var resultedDay = (firstDayOfTargetDOW + 1) + (7 * (weekNumOfMonth - 1));
if (resultedDay > daysOfMonth)
return null;
return resultedDay;
}
else
{
var lastDay = new DateTime(year, month, daysOfMonth);
var firstDayOfTargetDOW = (int)lastDay.DayOfWeek - (int)dow;
if (firstDayOfTargetDOW < 0)
firstDayOfTargetDOW += 7;
var resultedDay = firstDayOfTargetDOW + (7 * (Math.Abs(weekNumOfMonth) - 1));
if (resultedDay > daysOfMonth)
return null;
return (daysOfMonth - resultedDay);
}
}
usage
Assert.AreEqual(02, DateTimeHelper.GetNthDayOfWeekInMonth(2019, 11, DayOfWeek.Saturday, 1));
Assert.AreEqual(30, DateTimeHelper.GetNthDayOfWeekInMonth(2019, 11, DayOfWeek.Saturday, -1));
I know this post is old. I have this solution, trying to find a more clean code. #unclebob
public static DateTime FindTheNthDay(
int year, int month, DayOfWeek day, int occurrence)
{
var startDate = new DateTime(year, month, 1);
while(startDate.DayOfWeek != day)
{
startDate = startDate.AddDays(1);
}
var nDays = 7 * (occurrence - 1);
var result = startDate.AddDays(nDays);
return result;
}
> FindTheNthDay(2006, 11, DayOfWeek.Friday, 4)
[11/24/2006 12:00:00 AM]
> FindTheNthDay(2005, 11, DayOfWeek.Friday, 4)
[11/25/2005 12:00:00 AM]
> FindTheNthDay(2004, 11, DayOfWeek.Friday, 4)
[11/26/2004 12:00:00 AM]
> FindTheNthDay(2003, 11, DayOfWeek.Friday, 4)
[11/28/2003 12:00:00 AM]
> FindTheNthDay(1983, 11, DayOfWeek.Friday, 4)
[11/25/1983 12:00:00 AM]
> FindTheNthDay(1978, 11, DayOfWeek.Friday, 4)
[11/24/1978 12:00:00 AM]
> FindTheNthDay(1972, 11, DayOfWeek.Friday, 4)
[11/24/1972 12:00:00 AM]
public static bool IsThirdWednesday(DateTime inputDate)
{
DateTime firstDayOfMonth = new DateTime(inputDate.Year, inputDate.Month, 1);
DateTime firstDayOfNextMonth = firstDayOfMonth.AddMonths(1);
int wednesdayCount = 0;
while(firstDayOfMonth < firstDayOfNextMonth)
{
if (firstDayOfMonth.DayOfWeek == DayOfWeek.Wednesday)
wednesdayCount++;
if (wednesdayCount == 3)
{
if (inputDate == firstDayOfMonth)
return true;
else
return false;
}
firstDayOfMonth = firstDayOfMonth.AddDays(1);
}
return false;
}
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;
}