【问题标题】:Date algorithm - Add days without considering leap years日期算法 - 添加天数而不考虑闰年
【发布时间】:2016-07-11 21:17:20
【问题描述】:

我正在开发的系统是这样构建和配置的,用户在设置定期付款时无​​法选择闰年。这导致幕后的所有日期数学都必须忽略闰年。 (我没选这个,不过是这样写的)

我必须编写一个接受 DateTime 值的方法,并在日期上添加天数,忽略闰年,这实际上意味着忽略 2 月 29 日并假装它不存在。

例如,如果我将 365 天添加到 2016 年 1 月 1 日,则结果应该是 2017 年 1 月 1 日,而不是 2016 年 12 月 31 日。

我使用的是 .NET,所以我可以使用 DateTime.IsLeapYear 和其他辅助方法。

这是一项正在进行的工作,这就是我目前所拥有的。我开始采用更简单的路线,现在我意识到这将需要更复杂的算法。

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;
}

逻辑还必须考虑负数(即减法),上面的代码失败了。

上面的代码根本不起作用,但我想证明我正在努力解决这个问题,而不是简单地询问而没有尝试过任何事情。

编辑 我提出了一个非常接近大卫方法的算法。 (我写了它,然后回到 StackOverflow 来检查响应)。

        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;
    }

【问题讨论】:

  • 你可以做int increment = 1; if(daysToAdd &lt; 0) { increment = -1; daysToAdd = -daysToAdd; } for(int i =0; i &lt; daysToAdd; i++){ date.AddDays(increment); if(date.Month == 2 and date.Day == 29) date.AddDays(increment); } return date;
  • 志同道合的人

标签: c# .net algorithm


【解决方案1】:

这是一个有效的扩展方法。如果您要增加或减少足够的天数以跨越多个闰年,也可以使用。

    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;
    }

【讨论】:

  • 将此标记为答案。对于其他读者,请查看我的问题以了解我的实现,它的行为与 David 的非常相似。
【解决方案2】:

可能需要更多测试,但不使用 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);
}

【讨论】:

    猜你喜欢
    • 2017-01-13
    • 2021-10-17
    • 1970-01-01
    • 1970-01-01
    • 2015-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-15
    相关资源
    最近更新 更多