In my current project at work, we have to call a third party API which requires, in the payload, a commencement date and a terminating date. It also requires us to send the difference between those two dates inclusively.
For example,
– 1 Sep 2019 to 20 Oct 2019 should be 1 month and 20 days.
– 10 Feb 2019 to 15 Mar 2019 should be 1 month and 30 days.
My colleague and I worked together on a function to calculate this. We figured that the calculation is pretty straightforward and similar to doing normal subtraction.
It is similar to normal subtraction in that if you don’t have enough days in the month of the date to subtract the days in the month of the commencement date, you “borrow” the number of days in the month prior to the terminating month and subtract one month from the terminating date. Repeat the same logic when subtracting the month part. If month in the terminating date is smaller than the month in the commencement date, then “borrow” 12 months and subtract one year from terminating date.
Let’s try with dates 3 Sep 2019 to 1 Apr 2021.
Since we want inclusive difference, we need to add 1 to the terminating date so it becomes 2 Apr 2021. We subtract the days portion first. Since 2 is less than 3, we need to borrow the number of days in the month prior to the terminating month. In this case, it will be 31 as there are 31 days in March. Hence the days difference is 31 + 2 – 3 = 30 days.
Now we move on to the months portion. Since we borrow 1 month when subtracting the days portion, we have to subtract 1 from the terminating month so it now becomes March. March – Sep or 3 – 9 will be negative so we need to borrow 12 months (1 year). Hence the months difference is 12 + 3 – 9 = 6 months.
Lastly, we subtract the years portion. Since we borrow 1 year when subtracting the months portion, we need to subtract 1 from the terminating year hence it becomes 2020. So the years difference will be 2020 – 2019 = 1 year.
So the inclusive difference between 3 Sep 2019 and 1 Apr 2021 is 1 year, 6 months and 30 days.
Here is the code for that function
public static (int Years, int Months, int Days) GetDurationsBetweenDates(DateTime start, DateTime end)
{
if (start > end)
throw new ArgumentException("Start date must not be greater than end date");
end = end.AddDays(1); // Add 1 day because we want the duration to be inclusive
var startDay = start.Day;
var endDay = end.Day;
var startMonth = start.Month;
var endMonth = end.Month;
var startYear = start.Year;
var endYear = end.Year;
int daysDuration;
int monthsDuration;
int yearsDuration;
if (endDay >= startDay)
{
daysDuration = endDay - startDay;
}
else
{
var tempMonth = endMonth - 1;
var tempYear = endYear;
if (tempMonth == 0)
{
tempMonth = 12;
tempYear--;
}
var daysInMonth = DateTime.DaysInMonth(tempYear, tempMonth);
daysDuration = endDay + daysInMonth - startDay;
endMonth--;
}
if (endMonth >= startMonth)
{
monthsDuration = endMonth - startMonth;
}
else
{
var monthsInYear = 12;
monthsDuration = endMonth + monthsInYear - startMonth;
endYear--;
}
yearsDuration = endYear - startYear;
return (yearsDuration, monthsDuration, daysDuration);
}
These are the unit tests for this function
[Test]
[TestCase("11 Sep 2019", "15 Nov 2019", 0, 2, 5)]
[TestCase("10 Feb 2019", "15 Mar 2019", 0, 1, 6)]
[TestCase("11 Sep 2019", "10 Nov 2019", 0, 2, 0)]
[TestCase("30 Dec 2019", "2 Jan 2020", 0, 0, 4)]
[TestCase("11 Sep 2019", "9 Nov 2019 ", 0, 1, 30)]
[TestCase("10 Feb 2020", "8 Mar 2020 ", 0, 0, 28)]
[TestCase("10 Feb 2020", "9 Mar 2020 ", 0, 1, 0)]
[TestCase("10 Feb 2020", "10 Mar 2020", 0, 1, 1)]
[TestCase("3 Sep 2019", "2 Apr 2021", 1, 7, 0)]
[TestCase("16 Nov 2018", "15 November 2019", 1, 0, 0)]
[TestCase("1 Nov 2018", "2 Nov 2018", 0, 0, 2)]
[TestCase("16 Nov 2018", "17 Dec 2023", 5, 1, 2)]
public void GetDurationsBetweenDates_Should_Return_Correct_Durations(string start, string end, int expectedYear, int expectedMonth, int expectedDays)
{
// Arrange
var startDate = DateTime.Parse(start);
var endDate = DateTime.Parse(end);
// Act
var (Years, Months, Days) = DateTimeHelper.GetDurationsBetweenDates(startDate, endDate);
// Assert
Years.Should().Be(expectedYear);
Months.Should().Be(expectedMonth);
Days.Should().Be(expectedDays);
}
[Test]
public void GetDurationsBetweenDates_With_StartDate_Greater_Than_EndDate_Should_Throw_Exception()
{
// Arrange
var startDate = DateTime.Now.AddDays(5);
var endDate = DateTime.Now.AddDays(-5);
// Act
Action action = () => DateTimeHelper.GetDurationsBetweenDates(startDate, endDate);
// Assert
action.Should().Throw<ArgumentException>().WithMessage("Start date must not be greater than end date");
}