Adding two DateTime objects together

Saint picture Saint · Jan 21, 2015 · Viewed 12.9k times · Source

Is there any better way to add one DateTime object to another one, than this:

DateTime first = new DateTime(2000, 1, 1);
DateTime second = new DateTime(11, 2, 5, 10, 10, 11);

DateTime result = first.AddYears(second.Year);
DateTime result = first.AddMonths(second.Month);
...

and so on...

In this example I'd like to get DateTime(2011, 3, 6, 10, 10, 11)

EDIT

After a intensive brainstorm it seems to there's no different way, but to facilitate it can be boxed inside additional class and operator+ just like in JonSkeet's answer

Answer

Jon Skeet picture Jon Skeet · Jan 21, 2015

It doesn't make sense to add two DateTime values together. If you want to represent "11 years, 2 months, 5 days, 10 hours, 10 minutes and 11 seconds" then you should represent that. That's not the same as 0011-02-05T10:10:11. In particular, you'd never be able to add "2 months and 30 days" for example. Likewise you'd never be able to add just a single year, because you can't have 0 for month and day values within a date.

Now there's no BCL type to represent the idea of "11 years [...]" but you could create your own one reasonably easily. As an alternative, you could use my Noda Time project which has Period for precisely this purpose:

var localDateTime = new LocalDate(2000, 1, 10).AtMidnight();
var period = new PeriodBuilder {
    Years = 11, Months = 2, Days = 5,
    Hours = 10, Minutes = 10, Seconds = 11
}.Build();
var result = localDateTime + period;

Contrary to some other answers provided here, you cannot use TimeSpan for this purpose. TimeSpan doesn't have any concept of months and years, because they vary in length, whereas a TimeSpan represents a fixed number of ticks. (If your largest unit is days, then you're fine to use TimeSpan, but given your example, I assume you need months and years.)

If you don't want to use Noda Time, I'd recommend you fake up a Period-like class yourself. It's easy enough to do - for example:

// Untested and quickly hacked up. Lots more API you'd probably
// want, string conversions, properties etc.
public sealed class Period
{
    private readonly int years, months, days, hours, minutes, seconds;

    public Period(int years, int months, int days,
                  int hours, int minutes, int seconds)
    {
        this.years = years;
        this.months = months;
        this.days = days;
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
    }

    public static DateTime operator+(DateTime lhs, Period rhs)
    {
        // Note: order of operations is important here.
        // Consider January 1st + (1 month and 30 days)...
        // what do you want the result to be?
        return lhs.AddYears(rhs.years)
                  .AddMonths(rhs.months)
                  .AddDays(rhs.days)
                  .AddHours(rhs.hours)
                  .AddMinutes(rhs.minutes)
                  .AddSeconds(rhs.seconds);
    }
}

Usage:

DateTime first = new DateTime(2000, 1, 1);
Period second = new Period(11, 2, 5, 10, 10, 11);
DateTime result = first + second;

You need to be aware of how DateTime.Add will handle impossible situations - for example adding a month to January 31st will give you February 28th/29th depending on whether or not it's a leap year.

The simple approach I've listed here, going through intermediate values, has its downsides, because that truncation can happen twice (adding years and then adding months) when it needn't - for example, "February 29th + 1 year + 1 month" might logically be "March 29th" but it will actually end up as "March 28th" as the truncation to February 28th will happen before the month is added.

Trying to work out a "right" way of doing calendrical arithmetic is fiendishly difficult, particularly as in some cases people may disagree about what the "right" answer is. In the above code I've opted for simplicity and predictability - depending on your real requirements, you may need something more complex.