Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

In my project I need to calculate dates for repeating events. At the beginning I just have a start date/time and the information how this event must repeat:

Every Day
Every Week
Every 2 Weeks
Every 3 Weeks
Every Month
Every 2 Months
...

What is the right way to do this? It should work correctly with different time zones and day saving time settings. I think I should just add days/weeks/month to the local DateTime and then convert it to UTC. But I'm not sure about this. What happens if I add a few days and this will be the time when we need to adjust our clocks forward one hour. In this case this time will not exists.

Below is the code I wrote, but I'm not sure that it works correctly in every case:

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: All dates are stored in UTC format in the database.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
864 views
Welcome To Ask or Share your Answers For Others

1 Answer

Scheduling or calculating the dates of future events, especially recurring events, is a very complex subject. I've written about this a few times, though from the perspective of other languages (See: 1, 2, 3, 4).

I'm afraid this is too broad of a subject to give you the exact code to run. The details will be very specific to your application. But here are some tips.

In general:

  • Use UTC only for the projected moment in time that a single instance of the event is to occur.

  • Store the actual event in local time. Store the time zone id also.

  • Do not store the time zone offset. That should be looked up for each occurrence individually.

  • Project upcoming occurrence(s) of the event as UTC so you know how when to perform an action based on the event (or whatever makes sense for your scenario).

  • Decide what to do for daylight saving time, when an occurrence falls into a spring-forward gap, or a fall-back overlap. Your needs may vary, but a common strategy is to jump ahead of the spring gap, and choose the first occurrence in the fall. If you're not sure what I mean, refer to the dst tag wiki.

  • Think carefully about how to handle dates near the end of a month. Not all months have the same number of days, and calendar math is difficult. dt.AddMonths(1).AddMonths(1) is not necessarily the same as dt.AddMonths(2).

  • Stay on top of time zone data updates. Nobody can predict the future, and the governments of the world like to change things!

  • You need to retain the original local-time values of the schedule, so that you can re-project the UTC values of the occurrences. You should do this either periodically, or whenever you apply a time zone update (if you're tracking them manually). The timezone tag wiki has details about the different time zone databases and how they are updated.

  • Consider using Noda Time and IANA/TZDB time zones. They are much more suited for this type of work than the built in types and time zones Microsoft provides.

  • Be careful to avoid using the local time zone. You should have no calls to DateTime.Now, ToLocalTime, or ToUniversalTime. Remember, the local time zone is based on the machine where the code is running, and that should not impact the behavior of your code. Read more in The Case Against DateTime.Now.

  • If you are doing all of this to just kick off a scheduled job, you should probably take a look at a pre-canned solution, such as Quartz.NET. It is free, open source, highly functional, and covers a lot of edge cases you may not have thought about.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...