在我的项目中,我需要计算重复事件的日期。一开始我只有一个开始日期/时间以及该事件必须如何重复的信息:
Every Day
Every Week
Every 2 Weeks
Every 3 Weeks
Every Month
Every 2 Months
...
这样做的正确方法是什么?它应该在不同的时区和夏令时设置下正常工作。我想我应该将天/周/月添加到本地日期时间,然后将其转换为 UTC。但我对此不确定。如果我添加几天,这将是我们需要将时钟向前调整一小时的时间,会发生什么。在这种情况下,这个时间将不存在。
下面是我编写的代码,但我不确定它在每种情况下都能正常工作:
private static List<DateTime> FindOccurrences(DateTime localStart, Repeat repeat, int occurrences)
{
var result = new List<DateTime> { localStart };
switch (repeat)
{
case Repeat.Daily:
for (int i = 1; i <= occurrences; i++)
result.Add(localStart.AddDays(i));
break;
case Repeat.Every2Weeks:
for (int i = 1; i <= occurrences; i++)
result.Add(localStart.AddDays((7 * 2) * i));
break;
...
}
return result;
}
public List<Event> CreateRepeating(string timeZone, Repeat repeat, int repeatEnds, DateTime localStart, int eventDuration)
{
var events = new List<Event>();
var occurrences = FindOccurrences(localStart, repeat, repeatEnds);
foreach (var occurrence in occurrences)
{
var item = new Event();
item.Start = occurrence.ToUtcTime(timeZone);
item.End = occurrence.ToUtcTime(timeZone).AddMinutes(eventDuration);
events.Add(item);
}
return events;
}
PS: 所有日期都以 UTC 格式存储在数据库中。
安排或计算未来事件的日期,尤其是重复发生的事件,是一个非常复杂的主题。 我已经写过几次关于这个问题的文章,不过是从其他语言的角度来看的(参见:1、2、3、4)。
恐怕这个主题太宽泛,无法为您提供要运行的确切代码。 详细信息将非常具体于您的应用程序。 但这里有一些提示。
总体来说:
仅在单个事件实例发生的预计时刻使用 UTC。
存储本地时间的实际事件。 还存储时区 ID。
不要存储时区偏移。 应单独查找每个出现的情况。
dst 标签 wiki。
仔细考虑如何处理临近月底的日期。 并非所有月份都有相同的天数,并且日历数学很困难。 dt.AddMonths(1).AddMonths(1)
dt.AddMonths(2)
随时了解时区数据更新。 没有人可以预测未来,世界各国政府都喜欢改变事情!
时区标签 wiki
包含有关不同时区数据库及其更新方式的详细信息。考虑使用 Noda Time
和
请小心避免使用当地时区。 您不应拨打 DateTime.Now
ToLocalTime
ToUniversalTime
。 请记住,本地时区基于运行代码的计算机,这不应影响代码的行为。 阅读更多内容针对 DateTime.Now 的案例
。如果您执行所有这些操作只是为了开始预定的工作,那么您可能应该看看预先封装的解决方案,例如Quartz.NET。 它是免费、开源、功能强大的,并且涵盖了许多您可能没有想到的边缘情况。
public enum TimePeriod {
None = 0,
Day = 1,
// Just week, no "two weeks, three weeks etc."
Week = 2,
Month = 3
}
public static class Occurrencies {
// May be you want to convert it to method extension: "this DateTime from"
public static IEnumerable<DateTime> FindInterval(DateTime from,
TimePeriod period,
int count) {
while (true) {
switch (period) {
case TimePeriod.Day:
from = from.AddDays(count);
break;
case TimePeriod.Week:
from = from.AddDays(7 * count);
break;
case TimePeriod.Month:
from = from.AddMonths(count);
break;
}
yield return from;
}
}
}
// Get 5 2-week intervals
List<DateTime> intervals2Weeks = Occurrencies
.FindInterval(DateTime.Now, TimePeriod.Week, 2)
.Take(5)
.ToList();
// Get 11 3-month intervals
List<DateTime> intervals3Months = Occurrencies
.FindInterval(DateTime.Now, TimePeriod.Month, 3)
.Take(11)
.ToList();
为了分解它,我建议将其分为两部分:“日期”和“时间”。
“日期”部分与重复模式有关,例如:
“每周一”
“每月 15 日”
using DateRecurrenceR;
using DateRecurrenceR.Core;
var beginDate = DateOnly.MinValue;
var endDate = DateOnly.MaxValue;
var interval = new Interval(3);
var enumerator = Recurrence.Daily(beginDate, endDate, interval);
2。时间
NodaTime 和使用 UTC 的方法可以帮助避免时间计算方面的常见问题。