The correct way to calculate recurring dates in C #

In my project, I need to calculate the dates of repeated events. First I have a start date / time and information on how this event should be repeated:

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 daytime settings. I think I should just add the days / weeks / month to the local DateTime and then convert it to UTC. But I'm not so sure about that. What happens if I add a few days, and it will be a time when we need to set our clocks forward one hour. In this case, this time will not be.

Below is the code I wrote, but I'm not sure if it works correctly in each 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.

+6
source share
2 answers

Planning or calculating dates for future events, especially recurring events, is a very complex matter. I wrote about this several times, although from the point of view of other languages โ€‹โ€‹(see 1 , 2 , 3 , 4 ).

I am afraid this is too broad a question to give you the exact code to run. Details will be very specific to your application. But here are some tips.

Generally:

  • Use UTC only for the predicted point in time when one instance of the event should occur.

  • Store the actual event in local time. Save the time zone identifier.

  • Do not save the time zone offset. This must be sought for each event individually.

  • The upcoming project event in UTC so that you know how to perform an action based on the event (or something else makes sense for your scenario).

  • Decide what to do to switch to daylight saving time when the event falls into the spring-transition gap or overlap overlap. Your needs may vary, but the general strategy is to move to the spring space and select the first event in the fall. If you don't know what I mean, check out the dty tag wiki .

  • Consider processing dates closer to the end of the month. Not all months have the same number of days, and calendar math is difficult. dt.AddMonths(1).AddMonths(1) does not necessarily match dt.AddMonths(2) .

  • Stay on top of timezone data updates. No one can predict the future, and world governments like to change things!

  • You need to keep the original local time of the schedule so that you can re-project the UTC values โ€‹โ€‹of the occurrence. You must do this either periodically or whenever you apply a time zone update (if you track them manually). The time zone wiki contains information about various time zone databases and how they are updated.

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

  • Be careful to avoid the local time zone. You should not have calls to DateTime.Now , ToLocalTime or ToUniversalTime . Remember that the local time zone is based on the machine where the code is running, and this should not affect the behavior of your code. More details in the Case with DateTime.Now .

  • If you do all this just to start a scheduled task, you should probably take a look at a pre-finished solution, such as Quartz.NET . It is free, open source, highly functional, and covers a lot of regional affairs, which you might not have thought of.

+8
source

Partial answer. I would prefer to change the implementation to

  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; } } } 

Using:

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

Source: https://habr.com/ru/post/976721/


All Articles