SystemEvents.TimeChanged 会告诉您时钟是否已被用户更改。它不会在时钟的每个滴答声中定期触发。所以它对调度事件没有用,除了你可能会在它发生时重新计算计时器。 (我认为如果系统与时间服务器同步,它也会触发,但我对此并不乐观。)
如果您尝试按照 Hans 的建议计算挂墙时间的差异,请小心。你不能只使用DateTime。观察:
// With time zone set for US Pacific time, there should only be 23 hours
// between these two points
DateTime a = new DateTime(2013, 03, 10, 0, 0, 0, DateTimeKind.Local);
DateTime b = new DateTime(2013, 03, 11, 0, 0, 0, DateTimeKind.Local);
TimeSpan t = b - a;
Debug.WriteLine(t.TotalHours); // 24
即使指定了本地种类,也不会考虑夏令时。
如果要采用这种方法,则必须使用DateTimeOffset 类型。
DateTimeOffset a = new DateTimeOffset(2013, 03, 10, 0, 0, 0, TimeSpan.FromHours(-8));
DateTimeOffset b = new DateTimeOffset(2013, 03, 11, 0, 0, 0, TimeSpan.FromHours(-7));
TimeSpan t = b - a;
Debug.WriteLine(t.TotalHours); // 23
您可能需要通过以下方式收集您的输入:
DateTime today = DateTime.Today; // today at midnight
DateTime tomorrow = today.AddDays(1); // tomorrow at midnight
TimeZoneInfo tz = TimeZoneInfo.Local;
DateTimeOffset a = new DateTimeOffset(today, tz.GetUtcOffset(today));
DateTimeOffset b = new DateTimeOffset(tomorrow, tz.GetUtcOffset(tomorrow));
TimeSpan t = b - a;
Debug.WriteLine(t.TotalHours); // 23, 24, or 25 depending on DST
但是 - 设置一个计时器运行这么长时间可能不是一个好主意。不仅时钟可能由用户更改或系统时间同步,而且应用程序或系统可能会关闭或重新启动。此外,如果您有很多此类任务,最终可能会因为闲置而消耗大量资源。
一个想法是保留下一次触发事件的列表。在您的应用程序中,您将触发一个短暂的轮询计时器(例如每分钟一次左右),并将当前时间与列表中的值进行比较,以了解您是否真的需要做任何事情。
另一个想法是稍微改变一下。代替短时间的轮询,您将保持您的列表排序,并设置一个延迟时间,直到下一个事件,并具有一些最大延迟(可能是一个小时)。同样,当计时器触发时,您会查看是否有任何事情要做,或者是否需要设置另一个计时器延迟。您必须在您的应用程序启动时运行它,并且任何时候安排新事件。
使用这两种方法中的任何一种,您都应该使用DateTimeOffset 或等效的UTC DateTime 进行调度。否则,您可能会在 DST 的错误时间触发 - 甚至在 DST 回退过渡期间触发两次。
如果所有这些听起来都太复杂,那么您可以尝试使用预构建的解决方案,例如 Quartz.net。特别是阅读this section of their FAQ。
关于您的第一个和第三个要点,我完全同意 - 但它永远不会发生。即使它确实发生了,我们仍然必须考虑它确实发生的所有多年历史。如果你还没有看过this video,你应该看看。