ModernUO/Projects/UOContent/Engines/Events/CommonRecurrencePatterns.cs

204 lines
7.3 KiB
C#

namespace Server.Engines.Events;
using System;
public class HourlyRecurrencePattern : IRecurrencePattern
{
public int IntervalHours { get; }
public HourlyRecurrencePattern(int intervalHours = 1) => IntervalHours = Math.Max(1, intervalHours);
public DateTime GetNextOccurrence(DateTime afterUtc, TimeOnly time, TimeZoneInfo timeZone)
{
var local = TimeZoneInfo.ConvertTimeFromUtc(afterUtc, timeZone);
return new DateTime(local.Year, local.Month, local.Day, local.Hour, time.Minute, 0)
.LocalToUtc(timeZone)
.AddHours(IntervalHours);
}
}
public class DailyRecurrencePattern : IRecurrencePattern
{
public int IntervalDays { get; }
public DailyRecurrencePattern(int intervalDays = 1) => IntervalDays = Math.Max(1, intervalDays);
public DateTime GetNextOccurrence(DateTime afterUtc, TimeOnly time, TimeZoneInfo timeZone)
{
var local = TimeZoneInfo.ConvertTimeFromUtc(afterUtc, timeZone);
return new DateTime(local.Year, local.Month, local.Day, time.Hour, time.Minute, 0)
.LocalToUtc(timeZone)
.AddDays(IntervalDays);
}
}
public class WeeklyRecurrencePattern : IRecurrencePattern
{
public int IntervalWeeks { get; }
public AllowedDays AllowedDays { get; }
public AllowedMonths AllowedMonths { get; }
public WeeklyRecurrencePattern(int intervalWeeks = 1, AllowedMonths allowedMonths = AllowedMonths.All, AllowedDays allowedDays = AllowedDays.None)
{
IntervalWeeks = Math.Max(1, intervalWeeks);
AllowedDays = allowedDays == AllowedDays.None ? AllowedDays.All : allowedDays;
AllowedMonths = allowedMonths == AllowedMonths.None ? AllowedMonths.All : allowedMonths;
}
public DateTime GetNextOccurrence(DateTime afterUtc, TimeOnly time, TimeZoneInfo timeZone)
{
var local = TimeZoneInfo.ConvertTimeFromUtc(afterUtc, timeZone);
var daysOfWeek = AllowedDays == AllowedDays.None ? local.DayOfWeek.ToDaysOfWeek() : AllowedDays;
var weekStart = local.Date.AddDays(-(int)local.DayOfWeek);
// No more days in this week, jump IntervalWeeks ahead
for (var week = 0; week <= 52; week++)
{
var nextWeekStart = weekStart.AddDays(7 * IntervalWeeks * week);
var weekEnd = nextWeekStart.AddDays(6);
var startMonth = (AllowedMonths)(1 << (nextWeekStart.Month - 1));
var endMonth = (AllowedMonths)(1 << (weekEnd.Month - 1));
// Skip the entire week if the start and end months are not in the allowed months
if ((AllowedMonths & (startMonth | endMonth)) == 0)
{
continue;
}
for (var i = 0; i < 7; i++)
{
var day = nextWeekStart.AddDays(i);
var currentMonth = (AllowedMonths)(1 << (day.Month - 1));
if ((AllowedMonths & currentMonth) == 0)
{
continue;
}
var dayOfWeekFlag = (AllowedDays)(1 << (int)day.DayOfWeek);
if ((daysOfWeek & dayOfWeekFlag) != 0)
{
var candidate = new DateTime(day.Year, day.Month, day.Day, time.Hour, time.Minute, 0);
if (candidate > local && !timeZone.IsInvalidTime(candidate))
{
return candidate.LocalToUtc(timeZone);
}
}
}
}
return DateTime.MaxValue;
}
}
public class MonthlyRecurrencePattern : IRecurrencePattern
{
public int DayOfMonth { get; }
public int IntervalMonths { get; }
public MonthlyRecurrencePattern(int dayOfMonth = -1, int intervalMonths = 1)
{
DayOfMonth = dayOfMonth != -1 ? Math.Clamp(dayOfMonth, 1, 31) : -1;
IntervalMonths = Math.Max(1, intervalMonths);
}
public DateTime GetNextOccurrence(DateTime afterUtc, TimeOnly time, TimeZoneInfo timeZone)
{
var local = TimeZoneInfo.ConvertTimeFromUtc(afterUtc, timeZone);
var year = local.Year;
var month = local.Month;
var day = DayOfMonth == -1 ? local.Day : DayOfMonth;
for (var i = 0; i < 100; i++)
{
var nextMonth = month + IntervalMonths * i;
var candidate = new DateTime(year, 1, 1)
.AddMonths(nextMonth - 1)
.AddDays(day - 1)
.Add(time.ToTimeSpan());
// Some months may not have that day of the month, if not, we skip to the next interval
if (candidate > local && candidate.Day == day && !timeZone.IsInvalidTime(candidate))
{
return candidate.LocalToUtc(timeZone);
}
}
return DateTime.MaxValue;
}
}
public class MonthlyOrdinalRecurrencePattern : IRecurrencePattern
{
public int IntervalMonths { get; }
public OrdinalDayOccurrence Ordinal { get; }
public DayOfWeek DayOfWeek { get; }
public MonthlyOrdinalRecurrencePattern(OrdinalDayOccurrence ordinal, DayOfWeek dayOfWeek, int intervalMonths = 1)
{
IntervalMonths = Math.Max(1, intervalMonths);
Ordinal = ordinal;
DayOfWeek = dayOfWeek;
}
public DateTime GetNextOccurrence(DateTime afterUtc, TimeOnly time, TimeZoneInfo timeZone)
{
var local = TimeZoneInfo.ConvertTimeFromUtc(afterUtc, timeZone);
var year = local.Year;
var month = local.Month;
for (var i = 0; i < 100; i++)
{
var nextMonth = month + IntervalMonths * i;
var candidateYearOffset = Math.DivRem(nextMonth - 1, 12, out var candidateMonthOffset);
var candidateYear = year + candidateYearOffset;
var candidateMonth = candidateMonthOffset + 1;
DateTime candidate;
if (Ordinal >= OrdinalDayOccurrence.First)
{
// Find the first day of the month
var firstOfMonth = new DateTime(candidateYear, candidateMonth, 1)
.Add(time.ToTimeSpan());
// Find the first occurrence of the desired day
var daysOffset = ((int)DayOfWeek - (int)firstOfMonth.DayOfWeek + 7) % 7;
if (daysOffset > 7)
{
daysOffset -= 7;
}
candidate = firstOfMonth.AddDays(daysOffset + 7 * (int)Ordinal);
// If candidate is not in the same month, skip
if (candidate.Month != candidateMonth)
{
continue;
}
}
else
{
// Find the last day of the month
var daysInMonth = DateTime.DaysInMonth(candidateYear, candidateMonth);
var lastOfMonth = new DateTime(candidateYear, candidateMonth, daysInMonth)
.Add(time.ToTimeSpan());
// Find the last occurrence of the desired day
var daysOffset = (int)lastOfMonth.DayOfWeek - (int)DayOfWeek + 7;
if (daysOffset >= 7)
{
daysOffset -= 7;
}
candidate = lastOfMonth.AddDays(-daysOffset);
}
if (candidate > local && !timeZone.IsInvalidTime(candidate))
{
return candidate.LocalToUtc(timeZone);
}
}
return DateTime.MaxValue;
}
}