The system I'm working on is built and configured in such a way, where users cannot choose leap years when setting up a recurring payment. This results in all the date-math behind the scenes having to ignore leap years. (I didn't choose this, but this is how it was written)
I have to write a method that takes in a DateTime value, and adds days to the date, ignoring leap years, which essentially means ignoring Feb 29th and pretending it doesn't exist.
For example, If I'm adding 365 days to 1/1/2016, that should result in 1/1/2017, not 12/31/2016.
I'm using .NET, so I can make use of DateTime.IsLeapYear, and other helper methods.
This is a work in progress, and here is what I have so far. I started taking a simpler route, and I'm now realizing that it's going to require a more complex algorithm.
public static DateTime AddDaysToDateWithLeapYearConsideration(DateTime date, int daysToAdd)
{
// Nothing to do
if (daysToAdd == 0)
{
return date;
}
// NOTE: This is an invalid approach; using DateTime.AddDays will take leap years into account
DateTime dateWithAddedDays = date.AddDays(daysToAdd);
const int FEB_28_DAY_OF_YEAR = 59;
int daysToSubtractForLeapYearConsideration = 0;
// The year is a leap year, which is under the feb 28 day threshold, and we're adding enough days to push it over the feb 28 day threshold
// This will result in .NET taking into account the feb 29th (the leap year day), but we have to subtract that leap year day since the system doesn't take feb 29th into account
if (DateTime.IsLeapYear(date.Year) && date.DayOfYear < FEB_28_DAY_OF_YEAR && (date.DayOfYear + daysToAdd > FEB_28_DAY_OF_YEAR))
{
daysToSubtractForLeapYearConsideration++;
}
// The resulting date (after the days are added or subtracted) is a leap year, whose day is past the feburary 28 day threshold, and it's not the same year as the date (i.e. it spans across "n" years)
if (DateTime.IsLeapYear(dateWithAddedDays.Year) && dateWithAddedDays.DayOfYear > FEB_28_DAY_OF_YEAR && dateWithAddedDays.Year != date.Year)
{
daysToSubtractForLeapYearConsideration++;
}
// We determined if the original date should be leap year considered, as well as the resulting date/year with the days added. Now see if there are any years in between
// that we should consider
bool isThereAYearRangeThatWeNeedToEvaluateLeapYearsFor = Math.Abs(date.Year - dateWithAddedDays.Year) > 0;
if (isThereAYearRangeThatWeNeedToEvaluateLeapYearsFor)
{
for (int leapYearEvalIndex = Math.Min(date.Year, dateWithAddedDays.Year); leapYearEvalIndex <= Math.Max(date.Year, dateWithAddedDays.Year); leapYearEvalIndex++)
{
bool isYearPartOfTheYearsThatWeveAlreadyChecked = leapYearEvalIndex == date.Year || leapYearEvalIndex == dateWithAddedDays.Year;
if (!isYearPartOfTheYearsThatWeveAlreadyChecked && DateTime.IsLeapYear(leapYearEvalIndex))
{
daysToSubtractForLeapYearConsideration++;
}
}
}
DateTime dateResult = date.AddDays(daysToAdd - daysToSubtractForLeapYearConsideration);
// The system does not allow 2/29 days, hence all this crazy date math
if (dateResult.Month == 2 && dateResult.Day == 29)
{
dateResult = dateResult.AddDays(1);
}
return dateResult;
}
The logic has to take into account negative numbers as well (i.e. subtracting), which the above code fails on.
The above code by no means works, but I wanted to demonstrate that I'm trying to tackle the problem, and not simply asking without having tried anything.
Edit I've come up with an algorithm pretty close to David's approach. (I wrote it, and then came back to StackOverflow to check responses).
public static DateTime AddDaysToDateWithLeapYearConsideration(DateTime date, int daysToAdd)
{
// Nothing to do
if (daysToAdd == 0)
{
return date;
}
DateTime dateResult = date;
// Are we adding or subtracting
bool areWeAddingDays = daysToAdd > 0;
int daysToAccountForInRegardToLeapYearDates = 0,
absDaysToAdd = Math.Abs(daysToAdd);
for (int i = 1; i <= absDaysToAdd; i++)
{
dateResult = dateResult.AddDays(areWeAddingDays ? 1 : -1);
if (dateResult.Month == 2 && dateResult.Day == 29)
{
daysToAccountForInRegardToLeapYearDates++;
}
}
dateResult = dateResult.AddDays(areWeAddingDays ? daysToAccountForInRegardToLeapYearDates : -daysToAccountForInRegardToLeapYearDates);
return dateResult;
}
答案 0 :(得分:1)
Here is an extension method that works. Will also work if you're adding or subtracting enough days to span multiple leap years.
public static DateTime AddDaysWithoutLeapYear(this DateTime input, int days)
{
var output = input;
if (days != 0)
{
var increment = days > 0 ? 1 : -1; //this will be used to increment or decrement the date.
var daysAbs = Math.Abs(days); //get the absolute value of days to add
var daysAdded = 0; // save the number of days added here
while (daysAdded < daysAbs)
{
output = output.AddDays(increment);
if (!(output.Month == 2 && output.Day == 29)) //don't increment the days added if it is a leap year day
{
daysAdded++;
}
}
}
return output;
}
答案 1 :(得分:0)
可能需要更多测试,但不使用DateTime Add...
函数或过多循环,可能是自定义实现:
public static DateTime AddDaysToDateWithLeapYearConsideration(DateTime date, int daysToAdd)
{
int year = date.Year + daysToAdd / 365, month = date.Month - 1, dir = Math.Sign(daysToAdd);
daysToAdd = (daysToAdd % 365) + date.Day;
int[] months = {31,28,31,30,31,30,31,31,30,31,30,31};
while(daysToAdd > months[month] || daysToAdd < 0){
if(dir ==1) daysToAdd -= months[month];
month += dir;
if(month == 12 || month == -1){
year += dir;
month = dir == -1 ? 11 : 0;
}
if(dir ==-1) daysToAdd += months[month]; //for reverse direction, add previous month
}
return new DateTime(year, ++month,daysToAdd);
}