using System.Text.RegularExpressions;
public static void Main()
Console.WriteLine("Hello World");
var IterationType = ScheduleIterationType.Weekly;
var IterationKind = ScheduleIterationKind.Local;
var dateTime = DateTime.Now;
string windowStart = "16T15:50:00+0200";
string windowEnd = "16T16:00:00+0200";
var periodicSchedule = new PeriodicSchedule(IterationType, IterationKind, dateTime);
var start = string.IsNullOrEmpty(windowStart) ? periodicSchedule.GetNthPeriodStart(0) : new PartialDateTime(windowStart, IterationType).CompleteWith(dateTime);
var end = string.IsNullOrEmpty(windowEnd) ? periodicSchedule.GetNthPeriodStart(1) : new PartialDateTime(windowEnd, IterationType).CompleteWith(dateTime);
Console.WriteLine(start <= dateTime && dateTime < end);
Console.WriteLine(periodicSchedule.GetNthPeriodStart(0));
public enum ScheduleIterationType
public enum ScheduleIterationKind
public class PeriodicSchedule
private ScheduleIterationType IterationType;
private ScheduleIterationKind IterationKind;
private DateTime StartTime;
public PeriodicSchedule(ScheduleIterationType iterationType, ScheduleIterationKind iterationKind, DateTime startTime)
IterationType = iterationType;
IterationKind = iterationKind;
public PeriodicSchedule(ScheduleIterationType iterationType, DateTime startTime)
IterationType = iterationType;
IterationKind = ScheduleIterationKind.Local;
public DateTime GetNthPeriodStart(int nth)
var frenchTimeZone = TimeZoneInfo.FindSystemTimeZoneById("CET");
var referenceDateTime = TimeZoneInfo.ConvertTime(StartTime, frenchTimeZone);
case ScheduleIterationType.Yearly:
return new DateTimeOffset(referenceDateTime.Year, 1, 1, 0, 0, 0, frenchTimeZone.BaseUtcOffset).AddYears(nth).UtcDateTime;
case ScheduleIterationType.Monthly:
return new DateTimeOffset(referenceDateTime.Year, referenceDateTime.Month, 1, 0, 0, 0, frenchTimeZone.BaseUtcOffset).AddMonths(nth).UtcDateTime;
case ScheduleIterationType.Weekly:
var periodStartDate = new DateTimeOffset(referenceDateTime.Year, referenceDateTime.Month, referenceDateTime.Day, 0, 0, 0, frenchTimeZone.BaseUtcOffset);
var dayOfTheWeek = ((int)periodStartDate.DayOfWeek + 6) % 7;
periodStartDate = periodStartDate.AddDays(-dayOfTheWeek + 7 * nth);
return periodStartDate.UtcDateTime;
case ScheduleIterationType.Daily:
return new DateTimeOffset(referenceDateTime.Year, referenceDateTime.Month, referenceDateTime.Day, 0, 0, 0, frenchTimeZone.BaseUtcOffset).AddDays(nth).UtcDateTime;
case ScheduleIterationType.Hourly:
return new DateTimeOffset(referenceDateTime.Year, referenceDateTime.Month, referenceDateTime.Day, referenceDateTime.Hour, 0, 0, frenchTimeZone.BaseUtcOffset).AddHours(nth).UtcDateTime;
case ScheduleIterationType.Minutely:
return new DateTimeOffset(referenceDateTime.Year, referenceDateTime.Month, referenceDateTime.Day, referenceDateTime.Hour, referenceDateTime.Minute, 0, frenchTimeZone.BaseUtcOffset).AddMinutes(nth).UtcDateTime;
return referenceDateTime;
public class PartialDateTime
private static string pattern = @"((\d\d\d\d)-)?((\d\d)-)?((\d\d)T)?((\d\d):)?((\d\d):)?(\d\d)(\.(\d{3,6}))?(Z|\+(\d\d)(\d\d))";
private static Regex regex = new Regex(pattern);
private int timezoneHour;
private int timezoneMinute;
private string definition;
private ScheduleIterationType iterationType;
public PartialDateTime(string definition, ScheduleIterationType iterationType)
this.definition = definition;
this.iterationType = iterationType;
var match = regex.Match(definition);
if (match.Success == false)
throw new ArgumentException($"Definition {definition} is not a valid Partial DateTime");
int.TryParse(match.Groups[2].Value, out year);
int.TryParse(match.Groups[4].Value, out month);
int.TryParse(match.Groups[6].Value, out day);
int.TryParse(match.Groups[8].Value, out hour);
int.TryParse(match.Groups[10].Value, out minute);
int.TryParse(match.Groups[11].Value, out second);
int.TryParse(match.Groups[15].Value, out timezoneHour);
int.TryParse(match.Groups[16].Value, out timezoneMinute);
public DateTime CompleteWith(DateTime dateTime)
var utcTime = dateTime.ToUniversalTime();
case ScheduleIterationType.None:
case ScheduleIterationType.Yearly:
return new DateTime(utcTime.Year, month, day, hour, minute, second, DateTimeKind.Utc).AddHours(-timezoneHour).AddMinutes(-timezoneMinute);
case ScheduleIterationType.Monthly:
return new DateTime(utcTime.Year, utcTime.Month, day, hour, minute, second, DateTimeKind.Utc).AddHours(-timezoneHour).AddMinutes(-timezoneMinute);
case ScheduleIterationType.Weekly:
return new DateTime(utcTime.Year, utcTime.Month, day, hour, minute, second, DateTimeKind.Utc).AddHours(-timezoneHour).AddMinutes(-timezoneMinute);
case ScheduleIterationType.Daily:
return new DateTime(utcTime.Year, utcTime.Month, utcTime.Day, hour, minute, second, DateTimeKind.Utc).AddHours(-timezoneHour).AddMinutes(-timezoneMinute);
case ScheduleIterationType.Hourly:
return new DateTime(utcTime.Year, utcTime.Month, utcTime.Day, utcTime.Hour, minute, second, DateTimeKind.Utc).AddMinutes(-timezoneMinute);
case ScheduleIterationType.Minutely:
return new DateTime(utcTime.Year, utcTime.Month, utcTime.Day, utcTime.Hour, utcTime.Minute, second, DateTimeKind.Utc);
public static bool IsInside(string start, string end, ScheduleIterationType iterationType, DateTime dateTime)
var partialStartTime = new PartialDateTime(start, iterationType);
var partialEndTime = new PartialDateTime(end, iterationType);
return IsInside(partialStartTime, partialEndTime, dateTime);
public static bool IsInside(PartialDateTime partialStartTime, PartialDateTime partialEndTime, DateTime dateTime)
var startTime = partialStartTime.CompleteWith(dateTime);
var endTime = partialEndTime.CompleteWith(dateTime);
return startTime <= dateTime && dateTime < endTime;