C# 中计算重复日期的正确方法

问题描述 投票:0回答:3

在我的项目中,我需要计算重复事件的日期。一开始我只有一个开始日期/时间以及该事件必须如何重复的信息:

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 格式存储在数据库中。

c# datetime timezone
3个回答
15
投票

安排或计算未来事件的日期,尤其是重复发生的事件,是一个非常复杂的主题。 我已经写过几次关于这个问题的文章,不过是从其他语言的角度来看的(参见:1234)。

恐怕这个主题太宽泛,无法为您提供要运行的确切代码。 详细信息将非常具体于您的应用程序。 但这里有一些提示。

总体来说:

  • 仅在单个事件实例发生的预计时刻使用 UTC。

  • 存储本地时间的实际事件。 还存储时区 ID。

  • 不要存储时区偏移。 应单独查找每个出现的情况。

  • 将即将发生的事件预测为 UTC,以便您知道何时根据事件(或任何对您的场景有意义的内容)执行操作。

  • 决定在夏令时期间,当某个事件陷入向前跳动间隙或向后重叠时该怎么做。 您的需求可能会有所不同,但常见的策略是“跳过”春季间隙,并选择“第一个”发生在秋季。 如果您不确定我的意思,请参阅

    dst 标签 wiki 仔细考虑如何处理临近月底的日期。 并非所有月份都有相同的天数,并且日历数学很困难。 dt.AddMonths(1).AddMonths(1)

    不一定与
  • dt.AddMonths(2)
  • 相同。

    随时了解时区数据更新。  没有人可以预测未来,世界各国政府都喜欢改变事情!

  • 您需要保留计划的原始当地时间值,以便您可以重新投影事件的 UTC 值。 您应该定期执行此操作,或者在应用时区更新时执行此操作(如果您手动跟踪它们)。

    时区标签 wiki

    包含有关不同时区数据库及其更新方式的详细信息。
  • 考虑使用 Noda Time

  • IANA/TZDB
  • 时区。 它们比 Microsoft 提供的内置类型和时区更适合此类工作。

    请小心避免使用当地时区。 您不应拨打 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; } } }

7
投票
用途:

// 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();
    

为了分解它,我建议将其分为两部分:“日期”和“时间”。

0
投票

“日期”部分与重复模式有关,例如:

“每周一”

“每月 15 日”

    “每个第三个星期三。”
  • 您可以使用自定义逻辑来管理这些模式,或者,如果您正在寻找更简单的方法来处理它,您可能需要查看像
  • DateRecurrenceR
  • (
  • github
) 这样的库。它可以帮助根据不同的模式生成重复日期,使逻辑更容易处理。

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。时间

“时间”部分是关于处理时区、UTC 和当地时间。这可能会变得棘手,因为重复发生的事件可能跨越时区,而夏令时的变化可能会把事情搞砸。
为此,我建议您查看@matt-johnson-pint 的一些建议。他使用 

NodaTime 和使用 UTC 的方法可以帮助避免时间计算方面的常见问题。

© www.soinside.com 2019 - 2024. All rights reserved.