根据计划查找下一个重复日期

时间:2014-08-02 23:47:58

标签: c# datetime math

让我们说我需要知道下一个预定日期是什么时候我知道时间表是基于2014年8月1日的开始日期,它应该每7天运行一次当前日期是8/10/2014。我应该回到2014年8月14日的日期。我最终希望每隔X小时,几天和几周使这段代码工作,现在我只需要测试几天。我有以下代码用于计算下一个运行时间,但我让它在一个日期工作,然后又失败了。仅供参考,我使用该选项指定当前日期以进行测试。我做错了什么?

public class ScheduleComputer
{
    public DateTime GetNextRunTime(ScheduleRequest request)
    {
        var daysSinceBase = ((int)((request.CurrentDate - request.BaseDate).TotalDays)) + 1;
        var partialIntervalsSinceBaseDate = daysSinceBase % request.Interval;
        var fullIntervalsSinceBaseDate = daysSinceBase / request.Interval;
        var daysToNextRun = 0;
        if (partialIntervalsSinceBaseDate > 0)
        {
            daysToNextRun = (request.Interval - partialIntervalsSinceBaseDate) + 1;
        }
        var nextRunDate = request.BaseDate.AddDays((fullIntervalsSinceBaseDate * request.Interval) + daysToNextRun - 1);
        return nextRunDate;
    }
}

public class ScheduleRequest
{
    private readonly DateTime _currentDate;

    public ScheduleRequest()
    {
        _currentDate = DateTime.Now;
    }

    public ScheduleRequest(DateTime currentDate)
    {
        _currentDate = currentDate;
    }

    public DateTime CurrentDate
    {
        get { return _currentDate; }
    }

    public DateTime BaseDate { get; set; }
    public Schedule Schedule { get; set; }
    public int Interval { get; set; }
}

public enum Schedule
{
    Hourly,
    Daily,
    Weekly
}

这是我的单元测试

[TestFixture]
public class ScheduleComputerTests
{
    private ScheduleComputer _scheduleComputer;

    [SetUp]
    public void SetUp()
    {
        _scheduleComputer = new ScheduleComputer();
    }

    [Test]
    public void ThisTestPassesAndItShould()
    {
        var scheduleRequest = new ScheduleRequest(currentDate: DateTime.Parse("8/14/2014"))
        {
            BaseDate = DateTime.Parse("8/1/2014"),
            Schedule = Schedule.Daily,
            Interval = 7
        };
        var result = _scheduleComputer.GetNextRunTime(scheduleRequest);
        Assert.AreEqual(DateTime.Parse("8/14/2014"), result);
    }

    [Test]
    public void ThisTestFailsAndItShouldNot()
    {
        var scheduleRequest = new ScheduleRequest(currentDate: DateTime.Parse("8/2/2014"))
        {
            BaseDate = DateTime.Parse("8/1/2014"),
            Schedule = Schedule.Daily,
            Interval = 7
        };
        var result = _scheduleComputer.GetNextRunTime(scheduleRequest);
        Assert.AreEqual(DateTime.Parse("8/7/2014"), result);
    }

仅供参考,我看过帖子here,但我似乎无法根据自己的需要量身定做。

---更新1 ---

这是我更新的代码。我知道我已经用变量使它变得冗长,所以我可以更好地理解逻辑(希望它不会对性能造成太大影响)。我还添加了处理不同时期(小时,天,周)的逻辑,并添加了扩展方法以使代码更清晰。但是,这段代码似乎可以在数小时和数天内完美运行,但几周之后就失败了。在某个地方,我没有正确地乘以或除以7。

public class ScheduleComputer
{
    public DateTime GetNextRunTime(ScheduleRequest request)
    {
        var timeBetwenCurrentAndBase = request.CurrentDate - request.BaseDate;
        var totalPeriodsBetwenCurrentAndBase = timeBetwenCurrentAndBase.TotalPeriods(request.Schedule);
        var fractionalIntervals = totalPeriodsBetwenCurrentAndBase % request.Interval;
        var partialIntervalsLeft = request.Interval - fractionalIntervals;
        if (request.Schedule != Schedule.Hourly) partialIntervalsLeft = partialIntervalsLeft - 1;
        var nextRunTime = request.CurrentDate.AddPeriods(partialIntervalsLeft, request.Schedule);
        return nextRunTime;
    }
}

public static class ScheduleComputerExtensions
{
    public static double TotalPeriods(this TimeSpan timeBetwenCurrentAndBase, Schedule schedule)
    {
        switch (schedule)
        {
            case Schedule.Hourly: return timeBetwenCurrentAndBase.TotalHours;
            case Schedule.Daily: return timeBetwenCurrentAndBase.TotalDays;
            case Schedule.Weekly: return timeBetwenCurrentAndBase.TotalDays * 7;
            default: throw new ApplicationException("Invalid Schedule Provided");
        }
    }

    public static DateTime AddPeriods(this DateTime dateTime, double partialIntervalsLeft, Schedule schedule)
    {
        switch (schedule)
        {
            case Schedule.Hourly: return dateTime.AddHours(partialIntervalsLeft);
            case Schedule.Daily: return dateTime.AddDays(partialIntervalsLeft);
            case Schedule.Weekly: return dateTime.AddDays(partialIntervalsLeft * 7);
            default: throw new ApplicationException("Invalid Schedule Provided");
        }
    }
}

1 个答案:

答案 0 :(得分:2)

尝试用此

替换GetNextRunTime
public DateTime GetNextRunTime(ScheduleRequest request)
{
    double days = (request.Interval - ((request.CurrentDate - request.BaseDate).TotalDays % request.Interval));
    return request.CurrentDate.AddDays(days-1);
}

这应该给你正确的日期。

编辑:让我们分解它,希望能帮助你找出逻辑。

diff = (request.CurrentDate - request.BaseDate).TotalDays这将为您提供BaseDate和CurrentDate之间的天数。请注意,天数不包括BaseDate的日期。所以8/7/14和8/1/14之间的差异是6天。

daysSinceLast = diff % request.Interval这会给你自上次间隔点击以来经过的天数,所以如果最后一个间隔在8/1/14点击,现在是8/7/14那么结果将是6%7 = 6;自上次计划的间隔(不包括最后一个间隔日期)以来已过去6天。这是计算中最重要的部分;它保留了天数,无论在该时间间隔内有多少天,所以例如,如果自BaseDate以来已过去100天且间隔为7:100%7 = 2这意味着自上次以来已过去2天间隔触发,无需实际知道触发的最后日期。您只需要BaseDate和CurrentDate。您可以使用此逻辑查找上次触发间隔的日期,只需从CurrentDate中减去天数。

daysUntil = request.Interval - daysSinceLast这将为您提供下一个计划间隔之前的天数。 7 - 6 =下一个预定时间间隔的第1天

此方案中的1天不正确且结果永远不会正确,因为TimeSpan差异的计算不包括BaseDate的日期,因此您需要从daysUntil nextDate = request.CurrentDate.AddDays(daysUntil - 1)中减去1

将剩余天数(基准日期减去1)添加到当前日期会为您提供所需的值。这有帮助吗?

<强>更新 根据您的测试,我发现问题出在我们的两个目的上。我的计算是不正确的,当你需要除以7时,你乘以7。无论哪种方式,结果仍然是错误的。试试这个。

  • 完全删除您的扩展程序
  • 使用以下代码修改您的GetNextRunTime
  • 使用以下代码
  • 修改ScheduleRequest和Schedule class / enum

<强> GetNextRunTime

public DateTime GetNextRunTime(ScheduleRequest request)
{
    double diffMillis = (request.CurrentDate - request.BaseDate).TotalMilliseconds;
    double modMillis = (diffMillis % request.IntervalMillis);
    double timeLeft = (request.IntervalMillis - modMillis);

    ulong adjust = (request.Schedule == Schedule.Daily) ? (ulong)Schedule.Daily : 0;

    return request.CurrentDate.AddMilliseconds(timeLeft - adjust);
}

<强> ScheduleRequest

public class ScheduleRequest
{
    private readonly DateTime _currentDate;

    public ScheduleRequest()
    {
        _currentDate = DateTime.Now;
    }

    public ScheduleRequest(DateTime currentDate)
    {
        _currentDate = currentDate;
    }

    public DateTime CurrentDate
    {
        get { return _currentDate; }
    }

    public DateTime BaseDate { get; set; }

    public Schedule Schedule { get; set; }

    public double IntervalMillis { get { return (double)this.Schedule * this.Interval; } }

    public int Interval { get; set; }
}

<强>定时

public enum Schedule : ulong
{
    Hourly = 3600000,
    Daily = 86400000,
    Weekly = 604800000
}

这应适用于所有日期,间隔和时间表。编辑:更正调整值