I see this:
How would you add 13.245 years to 3/22/2023 5:25:00 AM?
...gives me an output of 4/23/2036 4:05:48 PM that I am not entirely confident in.
That's clearly not accurate. With a starting date in March and fractional year portion of 0.25
, you should expect to end up in June, not April.
So let's try this instead:
private static DateTime AddYears(DateTime startDate, double years)
{
startDate = startDate.AddYears((int)years);
double remainder = years - (int)years;
double yearSeconds = (new DateTime(startDate.Year + 1, 1, 1) - new DateTime(startDate.Year, 1, 1)).TotalSeconds;
return startDate.AddSeconds(yearSeconds * remainder);
}
Using the built-in date math functions helps a lot, and going down to the second has the advantage of accepting a double for the final addition, and allowing greater precision. Now we get a result date of 06/21/2036 5:25:00 PM
. This sounds more like it. We're not exactly 3 months later in the year (that would be 6/22/2036 5:25:00 AM
, so we "lost" 12 hours), but not all months are the same, so this looks reasonably accurate.
However, there's still potential for error because the remainder could put us into a new year which has a different length: possible change in the leap year or other things like the odd leap second. For example, say the starting date is 2023-12-31 23:23:59
, and the addition is 0.99
. The code above assumes the year length based on that initial 365 day year (0 whole years to add), but nearly all of the final fractional addition takes place the next year, which has 366 days. You'll end up nearly a whole day short of where you expect.
To get more accurate, we want to add the fractional part only up to the end of the year, and then recalculate whatever is left based on the new year.
private static DateTime AddYears(this DateTime startDate, double years)
{
startDate= startDate.AddYears((int)years);
double remainder = years - (int)years;
double yearSeconds = (new DateTime(startDate.Year + 1, 1, 1) - new DateTime(startDate.Year, 1, 1)).TotalSeconds;
var result = startDate.AddSeconds(yearSeconds * remainder);
if (result.Year == startDate.Year) return result;
// we crossed into a near year, so need to recalculate fractional portion from within the ending year
result = new DateTime(result.Year, 1, 1);
// how much of the partial year did we already use?
double usedFraction = (result - startDate).TotalSeconds;
usedFraction = (usedFraction/yearSeconds);
// how much of the partial year is still to add in the new year?
remainder = remainder - usedFraction;
//seconds in target year:
yearSeconds = (new DateTime(result.Year + 1, 1, 1) - result).TotalSeconds;
return result.AddSeconds(yearSeconds * remainder);
}
Knowing Jon Skeet was looking at this question, I wouldn't be surprised if Noda Time makes this easier. I'm sure there are other potentials for error, as well (at least fractional seconds around the end/start of the year boundary), but I feel like this would put you close enough for most purposes.