How to get difference between two dates in Year/Month/Week/Day in an efficient way?
eg. difference between two dates is 1 Year, 2 Months, 3 Weeks, 4 Days.
Difference represents count of year(s), month(s), week(s) and day(s) between two dates.
How to get difference between two dates in Year/Month/Week/Day in an efficient way?
eg. difference between two dates is 1 Year, 2 Months, 3 Weeks, 4 Days.
Difference represents count of year(s), month(s), week(s) and day(s) between two dates.
This is actually quite tricky. A different total number of days can result in the same result. For example:
19th June 2008 to 19th June 2010 = 2 years, but also 365 * 2 days
19th June 2006 to 19th June 2008 = 2 years, but also 365 + 366 days due to leap years
You may well want to subtract years until you get to the point where you've got two dates which are less than a year apart. Then subtract months until you get to the point where you've got two dates which are less than a month apart.
Further confusion: subtracting (or adding) months is tricky when you might start with a date of "30th March" - what's a month earlier than that?
Even further confusion (may not be relevant): even a day isn't always 24 hours. Daylight saving anyone?
Even further confusion (almost certainly not relevant): even a minute isn't always 60 seconds. Leap seconds are highly confusing...
I don't have the time to work out the exact right way of doing this right now - this answer is mostly to raise the fact that it's not nearly as simple as it might sound.
EDIT: Unfortunately I'm not going to have enough time to answer this fully. I would suggest you start off by defining a struct representing a Period:
public struct Period
{
private readonly int days;
public int Days { get { return days; } }
private readonly int months;
public int Months { get { return months; } }
private readonly int years;
public int Years { get { return years; } }
public Period(int years, int months, int days)
{
this.years = years;
this.months = months;
this.days = days;
}
public Period WithDays(int newDays)
{
return new Period(years, months, newDays);
}
public Period WithMonths(int newMonths)
{
return new Period(years, newMonths, days);
}
public Period WithYears(int newYears)
{
return new Period(newYears, months, days);
}
public static DateTime operator +(DateTime date, Period period)
{
// TODO: Implement this!
}
public static Period Difference(DateTime first, DateTime second)
{
// TODO: Implement this!
}
}
I suggest you implement the + operator first, which should inform the Difference method - you should make sure that first + (Period.Difference(first, second)) == second for all first/second values.
Start with writing a whole slew of unit tests - initially "easy" cases, then move on to tricky ones involving leap years. I know the normal approach is to write one test at a time, but I'd personally brainstorm a bunch of them before you start any implementation work.
Allow yourself a day to implement this properly. It's tricky stuff.
Note that I've omitted weeks here - that value at least is easy, because it's always 7 days. So given a (positive) period, you'd have:
int years = period.Years;
int months = period.Months;
int weeks = period.Days / 7;
int daysWithinWeek = period.Days % 7;
(I suggest you avoid even thinking about negative periods - make sure everything is positive, all the time.)
Leap years and uneven months actually make this a non-trivial problem. I'm sure someone can come up with a more efficient way, but here's one option - approximate on the small side first and adjust up (untested):
public static void GetDifference(DateTime date1, DateTime date2, out int Years,
out int Months, out int Weeks, out int Days)
{
//assumes date2 is the bigger date for simplicity
//years
TimeSpan diff = date2 - date1;
Years = diff.Days / 366;
DateTime workingDate = date1.AddYears(Years);
while(workingDate.AddYears(1) <= date2)
{
workingDate = workingDate.AddYears(1);
Years++;
}
//months
diff = date2 - workingDate;
Months = diff.Days / 31;
workingDate = workingDate.AddMonths(Months);
while(workingDate.AddMonths(1) <= date2)
{
workingDate = workingDate.AddMonths(1);
Months++;
}
//weeks and days
diff = date2 - workingDate;
Weeks = diff.Days / 7; //weeks always have 7 days
Days = diff.Days % 7;
}
What about using the System.Data.Linq namespace and its SqlMethods.DateDiffMonth method?
For example, say:
DateTime starDT = {01-Jul-2009 12:00:00 AM}
DateTime endDT = {01-Nov-2009 12:00:00 AM}
Then:
int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(startDT, endDT);
==> 4
There are other DateDiff static methods in the SqlMethods class.
Subtract two DateTime instances to give you a TimeSpan which has a Days property. (E.g. in PowerShell):
PS > ([datetime]::today - [datetime]"2009-04-07") Days : 89 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 0 Ticks : 76896000000000 TotalDays : 89 TotalHours : 2136 TotalMinutes : 128160 TotalSeconds : 7689600 TotalMilliseconds : 7689600000
Converting days into years or weeks is relatively easy (days in a year could be 365, 365.25, ... depending on context). Months is much harder, because without a base date you don't know which month lengths apply.
Assuming you want to start with your base date, you can incrementally substract while counting first years (checking for leap years), then month lengths (indexing from startDate.Month), then weeks (remaining days divided by 7) and then days (remainder).
There are a lot of edge cases to consider, e.g. 2005-03-01 is one year from 2004-03-01, and from 2004-02-29 depending on what you mean by "Year".
If you subtract two instances of DateTime, that will return an instance of TimeSpan, which will represent the difference between the two dates.
DateTime dt1 = new DateTime(2009, 3, 14);
DateTime dt2 = new DateTime(2008, 3, 15);
int diffMonth = Math.Abs((dt2.Year - dt1.Year)*12 + dt1.Month - dt2.Month)
Use the Subtract method of the DateTime object which returns a TimeSpan...
DateTime dt1 = new DateTime(2009, 3, 14);
DateTime dt2 = new DateTime(2008, 3, 15);
TimeSpan ts = dt1.Subtract(dt2);
Double days = ts.TotalDays;
Double hours = ts.TotalHours;
Double years = ts.TotalDays / 365;
int day=0,month=0,year=0;
DateTime smallDate = Convert.ToDateTime(string.Format("{0}", "01.01.1900"));
DateTime bigDate = Convert.ToDateTime(string.Format("{0}", "05.06.2019"));
TimeSpan timeSpan = new TimeSpan();
//timeSpan is diff between bigDate and smallDate as days
timeSpan = bigDate - smallDate;
//year is totalDays / 365 as int
year = timeSpan.Days / 365;
//smallDate.AddYears(year) is closing the difference for year because we found the year variable
smallDate = smallDate.AddYears(year);
//again subtraction because we don't need the year now
timeSpan = bigDate - smallDate;
//month is totalDays / 30 as int
month = timeSpan.Days / 30;
//smallDate.AddMonths(month) is closing the difference for month because we found the month variable
smallDate = smallDate.AddMonths(month);
if (bigDate > smallDate)
{
timeSpan = bigDate - smallDate;
day = timeSpan.Days;
}
//else it is mean already day is 0
I'll show that there is in fact no perfectly consistent answer. To simplify, let's disregard years and weeks, just look at months and days in non-leap year.
What is the expected output for:
28/1 to 28/2?
-> Alice: "1 month" (1 month takes you to the same day of next month)
-> Brad: "1 month and 3 days" (3 days until end of Jan and the entire month of Feb)
Both legitimate answers, and this is already problem number one. But it gets worse. Let's reduce the date difference by 1 day and ask again:
28/1 to 27/2?
-> Alice: "30 days"
-> Brad: "Umm... 1 month and 2 days does not seem right!"
Brad has given up on his concept. But Alice is still happy about hers. Next, let's increase the date difference by 1 day from the initial question:
28/1 to 1/3?
-> Alice: "1 month and 1 day" (1 month to 28/2, plus 1 day)
Alice is really confident now.
29/1 to 1/3?
-> Alice: "Umm... still 1 month 1 day?"
30/1 to 1/3?
-> Alice: "Ok, I see... "
Neither Alice's nor Brad's concept stays consistent. You will have to accept one of the following two caveats for a timespan counter:
1 month to 1 month and 3 days from one day to the next1 month and 1 day for up to 4 days in a rowPersonally I'd prefer accepting the last one, if it means your counter keeps counting, but I guess it depends on the use case.
Once you accept caveat #3, adding the calculated timespan to DateTime.MinValue and taking the Year/Month/Day from where you land seems a reasonable approximation to me. Below code is adapted from this answer. You could roll in accounting for leap years as well, to have a few more days where the output meets the expectations.
public static string GetYearMonthDayDiff(DateTime start, DateTime end)
{
var span = (end - start).Duration(); // absolute value (allows switched start/end)
var diff = DateTime.MinValue + span;
int years = diff.Year - 1;
int months = diff.Month - 1;
int days = diff.Day - 1; // matter of definition if you start counting at 0 or 1
return $"{years}Y {months}M {days}D";
}
The code above then gives the following answers
28/1 to 27/2? -> 30 days
28/1 to 28/2? -> 1 month
28/1 to 1/3? -> 1 month and 1 day
29/1 to 1/3 -> 1 month
30/1 to 1/3 -> 30 days
Don't you love it, when it all doesn't quite add up :)?