如何计算每月计划的计时器间隔?

How do I calculate timer intervals for monthly scheduling?

我正在构建一个 Windows 服务,我希望它以可配置的时间间隔处理一堆不同的后台任务。

我很难弄清楚如何计算计时器在开始某项任务之前需要等待的时间长度。

我针对任务存储了一个 StartDate 和一个间隔类型:每日、每周、每月等。我已经确定了每天,但不知道如何每月做...

规则是:

到目前为止我已经了解了。这是一个测试应用程序,它采用多个测试用例并尝试计算当前时间(根据测试用例)和任务的固定开始时间之间的小时数:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main() {

        // Test cases
        var times = new Dictionary<DateTime,TimeSpan> {
            { new DateTime(2016, 3, 1, 9, 0, 0), TimeSpan.FromHours(2016)},
            { new DateTime(2016, 3, 5, 3, 0, 0), TimeSpan.FromHours(1926)},
            { new DateTime(2016, 3, 5, 9, 0, 0), TimeSpan.FromHours(1920)},
            { new DateTime(2016, 3, 5, 15, 0, 0), TimeSpan.FromHours(1914)},
            { new DateTime(2016, 3, 24, 3, 0, 0), TimeSpan.FromHours(1470)},
            { new DateTime(2016, 3, 24, 9, 0, 0), TimeSpan.FromHours(1464)},
            { new DateTime(2016, 3, 24, 15, 0, 0), TimeSpan.FromHours(1458)},
            { new DateTime(2016, 4, 19, 9, 0, 0), TimeSpan.FromHours(840)},
            { new DateTime(2016, 4, 24, 3, 0, 0), TimeSpan.FromHours(726)},
            { new DateTime(2016, 4, 24, 9, 0, 0), TimeSpan.FromHours(720)},
            { new DateTime(2016, 4, 24, 15, 0, 0), TimeSpan.FromHours(714)},
            { new DateTime(2016, 4, 24, 21, 0, 0), TimeSpan.FromHours(708)},
            { new DateTime(2016, 5, 6, 3, 0, 0), TimeSpan.FromHours(438)},
            { new DateTime(2016, 5, 24, 3, 0, 0), TimeSpan.FromHours(6)},
            { new DateTime(2016, 5, 24, 9, 0, 0), TimeSpan.FromHours(0)},
            { new DateTime(2016, 5, 24, 15, 0, 0), TimeSpan.FromHours(738)},
            { new DateTime(2016, 5, 26, 3, 0, 0), TimeSpan.FromHours(702)},
            { new DateTime(2016, 5, 26, 9, 0, 0), TimeSpan.FromHours(696)},
            { new DateTime(2016, 5, 26, 15, 0, 0), TimeSpan.FromHours(690)},
            { new DateTime(2016, 6, 24, 3, 0, 0), TimeSpan.FromHours(6)},
            { new DateTime(2016, 6, 24, 9, 0, 0), TimeSpan.FromHours(0)},
            { new DateTime(2016, 6, 24, 15, 0, 0), TimeSpan.FromHours(714)},
            { new DateTime(2016, 7, 6, 3, 0, 0), TimeSpan.FromHours(438)},
            { new DateTime(2016, 7, 6, 9, 0, 0), TimeSpan.FromHours(432)},
            { new DateTime(2016, 7, 6, 15, 0, 0), TimeSpan.FromHours(426)},
            { new DateTime(2016, 7, 24, 3, 0, 0), TimeSpan.FromHours(6)},
            { new DateTime(2016, 7, 24, 9, 0, 0), TimeSpan.FromHours(0)},
            { new DateTime(2016, 7, 24, 15, 0, 0), TimeSpan.FromHours(738)},

        }; 


        var startTime = new DateTime(2016, 05, 24, 09, 00, 00);
        var last = times.First().Key;


        foreach (var time in times) {

            var now = time.Key;
            var expected = time.Value;

            var timer = startTime.TimeOfDay - now.TimeOfDay;

            if (now <= startTime)                   
                timer += TimeSpan.FromDays((startTime.Date - now.Date).TotalDays);
            else 
                timer += TimeSpan.FromDays((now.Date.AddMonths(1) - now.Date).TotalDays);                   

            if (last.Date != now.Date) Console.WriteLine();
            Console.WriteLine($"{now:yyyy-MM-dd HH:mm} -> {startTime:yyyy-MM-dd HH:mm} = {timer:dd\.hh}   {(timer != expected ? "EXPECTED " + expected.ToString("dd\.hh") : "CORRECT       ")}");

            last = now;

        }           
    }       
}

产生以下输出:

2016-03-01 09:00 -> 2016-05-24 09:00 = 84.00   CORRECT       

2016-03-05 03:00 -> 2016-05-24 09:00 = 80.06   CORRECT       
2016-03-05 09:00 -> 2016-05-24 09:00 = 80.00   CORRECT       
2016-03-05 15:00 -> 2016-05-24 09:00 = 79.18   CORRECT       

2016-03-24 03:00 -> 2016-05-24 09:00 = 61.06   CORRECT       
2016-03-24 09:00 -> 2016-05-24 09:00 = 61.00   CORRECT       
2016-03-24 15:00 -> 2016-05-24 09:00 = 60.18   CORRECT       

2016-04-19 09:00 -> 2016-05-24 09:00 = 35.00   CORRECT       

2016-04-24 03:00 -> 2016-05-24 09:00 = 30.06   CORRECT       
2016-04-24 09:00 -> 2016-05-24 09:00 = 30.00   CORRECT       
2016-04-24 15:00 -> 2016-05-24 09:00 = 29.18   CORRECT       
2016-04-24 21:00 -> 2016-05-24 09:00 = 29.12   CORRECT       

2016-05-06 03:00 -> 2016-05-24 09:00 = 18.06   CORRECT       

2016-05-24 03:00 -> 2016-05-24 09:00 = 00.06   CORRECT       
2016-05-24 09:00 -> 2016-05-24 09:00 = 00.00   CORRECT       
2016-05-24 15:00 -> 2016-05-24 09:00 = 30.18   CORRECT       

2016-05-26 03:00 -> 2016-05-24 09:00 = 31.06   EXPECTED 29.06
2016-05-26 09:00 -> 2016-05-24 09:00 = 31.00   EXPECTED 29.00
2016-05-26 15:00 -> 2016-05-24 09:00 = 30.18   EXPECTED 28.18

2016-06-24 03:00 -> 2016-05-24 09:00 = 30.06   EXPECTED 00.06
2016-06-24 09:00 -> 2016-05-24 09:00 = 30.00   EXPECTED 00.00
2016-06-24 15:00 -> 2016-05-24 09:00 = 29.18   CORRECT       

2016-07-06 03:00 -> 2016-05-24 09:00 = 31.06   EXPECTED 18.06
2016-07-06 09:00 -> 2016-05-24 09:00 = 31.00   EXPECTED 18.00
2016-07-06 15:00 -> 2016-05-24 09:00 = 30.18   EXPECTED 17.18

2016-07-24 03:00 -> 2016-05-24 09:00 = 31.06   EXPECTED 00.06
2016-07-24 09:00 -> 2016-05-24 09:00 = 31.00   EXPECTED 00.00
2016-07-24 15:00 -> 2016-05-24 09:00 = 30.18   CORRECT

如您所见,最后有点不对劲。我需要进行哪些计算才能正确计算小时数?

试试这个代码:

foreach (var time in times)
{
    var now = time.Key;
    var expected = time.Value;                
    TimeSpan timer;
    if (now <= startTime) {
         // no need to do anything here - just substract
         timer = startTime - now;
    } 
    else {
         // normalize start time to current month
         var normalized = new DateTime(now.Year, now.Month, startTime.Day, startTime.Hour, startTime.Minute, startTime.Second);
         if (normalized >= now) {
               // normalized date is later in the same month - substract
               timer = normalized - now;
         }
         else {
               // normalized date is before current - move to next month
               timer = normalized.AddMonths(1) - now;
         }
    }                

if (last.Date != now.Date) Console.WriteLine();
    Console.WriteLine($"{now:yyyy-MM-dd HH:mm} -> {startTime:yyyy-MM-dd HH:mm} = {timer:dd\.hh}   {(timer != expected ? "EXPECTED " + expected.ToString("dd\.hh") : "CORRECT       ")}");
    last = now;
}