Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Cron Job v1.14.0
CronJob.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; using Cronos; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("CronJob")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+5344fc06c65d0325fa4d6cd728ed9e0b26aef454")] [assembly: AssemblyProduct("CronJob")] [assembly: AssemblyTitle("CronJob")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CronJob { public class CronData { [DefaultValue("UTC")] public string timezone = "UTC"; [DefaultValue(10f)] public float interval = 10f; public List<CronEntryData> jobs = new List<CronEntryData>(); public List<CronEntryData> zone = new List<CronEntryData>(); public List<CronEntryData> join = new List<CronEntryData>(); [DefaultValue(true)] public bool logJobs = true; [DefaultValue(true)] public bool logZone = true; [DefaultValue(true)] public bool logJoin = true; [DefaultValue(true)] public bool logSkipped = true; [DefaultValue("true")] public string discordConnector = "true"; } public class CronEntryData { [DefaultValue("")] public string command = ""; [DefaultValue(null)] public string[]? commands; [DefaultValue("")] public string schedule = ""; [DefaultValue(null)] public float? inactive; [DefaultValue(null)] public float? chance; [DefaultValue(false)] public bool avoidPlayers; [DefaultValue(false)] public bool useGameTime; [DefaultValue(null)] public bool? log; [DefaultValue("")] public string biomes = ""; [DefaultValue("")] public string locations = ""; [DefaultValue("")] public string objects = ""; [DefaultValue("")] public string bannedObjects = ""; [DefaultValue("")] public string globalKeys = ""; [DefaultValue("")] public string bannedGlobalKeys = ""; } public class CronGeneralJob : CronBaseJob { public string Schedule = data.schedule; public bool UseGameTime = data.useGameTime; public CronGeneralJob(CronEntryData data) : base(data) { } } public class CronZoneJob : CronBaseJob { public string Schedule = data.schedule; public bool AvoidPlayers = data.avoidPlayers; public Biome Biomes = Parse.ToBiomes(data.biomes); public HashSet<string> Locations = Parse.ToSet(data.locations); public HashSet<int> Objects = Parse.ToHashSet(data.objects); public HashSet<int> BannedObjects = Parse.ToHashSet(data.bannedObjects); public CronZoneJob(CronEntryData data) : base(data) { }//IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) } public class CronJoinJob : CronBaseJob { public CronJoinJob(CronEntryData data) : base(data) { } } public abstract class CronBaseJob { public string[] Commands = data.commands ?? new string[1] { data.command }; public float? Chance = data.chance; public bool? Log = data.log; public string[] GlobalKeys = Parse.Split(data.globalKeys); public string[] BannedGlobalKeys = Parse.Split(data.bannedGlobalKeys); protected CronBaseJob(CronEntryData data) { } } [BepInPlugin("cron_job", "Cron Job", "1.14")] public class CronJob : BaseUnityPlugin { public const string GUID = "cron_job"; public const string NAME = "Cron Job"; public const string VERSION = "1.14"; private static ManualLogSource? Logs; private float timer; public static ManualLogSource Log => Logs; public void Awake() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) Logs = ((BaseUnityPlugin)this).Logger; new Harmony("cron_job").PatchAll(); } public void Start() { DiscordHook.Init(); CronManager.SetupWatcher(); } public void LateUpdate() { if (Object.op_Implicit((Object)(object)ZNet.instance)) { timer -= Time.deltaTime; if (timer <= 0f) { timer = CronManager.Interval; CronManager.Execute(); TrackManager.Track(); } } } } [HarmonyPatch] public class CronManager { public static string FileName = "cron.yaml"; public static string FilePath = Path.Combine(Paths.ConfigPath, FileName); public static List<CronGeneralJob> Jobs = new List<CronGeneralJob>(); public static List<CronZoneJob> ZoneJobs = new List<CronZoneJob>(); public static List<CronJoinJob> JoinJobs = new List<CronJoinJob>(); public static float Interval = 10f; public static bool LogJobs = true; public static bool LogZone = true; public static bool LogJoin = true; public static bool LogSkipped = true; public static string DiscordConnector = "CronJob"; public static DateTime Previous = DateTime.UtcNow; public static DateTime PreviousGameTime = new DateTime(Year2000, DateTimeKind.Utc); public static TimeZoneInfo TimeZone = TimeZoneInfo.Utc; private static readonly Random random = new Random(); private static readonly long Year2000 = new DateTime(2000, 1, 1).Ticks; private static readonly HashSet<ZNetPeer> HandledPeers = new HashSet<ZNetPeer>(); private static DateTime? ParseCron(string value, DateTime? next = null) { CronFormat format = ((value.Split(new char[1] { ' ' }).Length == 6) ? CronFormat.IncludeSeconds : CronFormat.Standard); return CronExpression.Parse(value, format).GetNextOccurrence(next ?? DateTime.UtcNow, TimeZone); } private static bool Roll(float? chance) { if (!chance.HasValue || chance >= 1f || chance == 0f) { return true; } return random.NextDouble() < (double?)chance; } public static void Execute() { DateTime utcNow = DateTime.UtcNow; DateTime dateTime = new DateTime(Year2000 + (long)(ZNet.instance.GetTimeSeconds() / (double)EnvMan.instance.m_dayLengthSec * 864000000000.0), DateTimeKind.Utc); foreach (CronGeneralJob job in Jobs) { if ((job.GlobalKeys.Length != 0 && !HasEveryGlobalKey(job.GlobalKeys)) || (job.BannedGlobalKeys.Length != 0 && HasAnyGlobalKey(job.BannedGlobalKeys))) { continue; } DateTime dateTime2 = (job.UseGameTime ? dateTime : utcNow); DateTime dateTime3 = (job.UseGameTime ? PreviousGameTime : Previous); if (!(dateTime2 < dateTime3)) { DateTime value = dateTime2; DateTime? dateTime4 = ParseCron(job.Schedule, dateTime3); if (!(value < dateTime4)) { RunJob(job.Commands, job.Chance, job.Log); } } } Previous = utcNow; PreviousGameTime = dateTime; } private static void RunJob(string[] commands, float? chance, bool? log) { if (Roll(chance)) { string[] array = commands; foreach (string text in array) { ((Terminal)Console.instance).TryRunCommand(text, false, false); if (log ?? LogJobs) { Log("Executing: " + text); } } } else { if (!LogSkipped) { return; } string[] array = commands; foreach (string text2 in array) { if (log ?? LogJobs) { Log("Skipped: " + text2); } } } } private static void Log(string message) { CronJob.Log.LogInfo((object)message); if (DiscordConnector != "") { DiscordHook.SendMessage(DiscordConnector, message); } } public static bool Execute(Vector2s zone, bool hasPlayer, DateTime? previous) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) List<CronZoneJob> list = ZoneJobs.Where((CronZoneJob cronZoneJob) => !cronZoneJob.AvoidPlayers || !hasPlayer).ToList(); if (list.Count == 0) { return false; } DateTime utcNow = DateTime.UtcNow; ZoneSystem instance = ZoneSystem.instance; ZDOMan instance2 = ZDOMan.instance; WorldGenerator instance3 = WorldGenerator.instance; foreach (CronZoneJob cron in list) { DateTime value = utcNow; DateTime? dateTime = ParseCron(cron.Schedule, previous); if (value < dateTime) { continue; } Vector3 pos = ZoneSystem.GetZonePos(zone); if (((int)cron.Biomes != 0 && (instance3.GetBiome(pos.x, pos.y, 0.02f, false) & cron.Biomes) == 0 && (instance3.GetBiome(pos.x + 32f, pos.y + 32f, 0.02f, false) & cron.Biomes) == 0 && (instance3.GetBiome(pos.x + 32f, pos.y - 32f, 0.02f, false) & cron.Biomes) == 0 && (instance3.GetBiome(pos.x - 32f, pos.y + 32f, 0.02f, false) & cron.Biomes) == 0 && (instance3.GetBiome(pos.x - 32f, pos.y + 32f, 0.02f, false) & cron.Biomes) == 0) || (cron.Locations.Count > 0 && (!instance.m_locationInstances.TryGetValue(zone, out var value2) || !cron.Locations.Contains(value2.m_location?.m_prefabName ?? "")))) { continue; } if (cron.Objects.Count > 0) { uint sector = ZoneSystem.SectorToIndex(zone).Sector; if (sector < 0 || sector >= instance2.m_objectsBySector.Length) { continue; } List<ZDO> list2 = instance2.m_objectsBySector[sector]; if (list2 == null || list2.All((ZDO zdo) => !cron.Objects.Contains(zdo.m_prefab))) { continue; } } if (cron.BannedObjects.Count > 0) { uint sector2 = ZoneSystem.SectorToIndex(zone).Sector; if (sector2 < 0 || sector2 >= instance2.m_objectsBySector.Length) { continue; } List<ZDO> list3 = instance2.m_objectsBySector[sector2]; if (list3 == null || list3.Any((ZDO zdo) => cron.BannedObjects.Contains(zdo.m_prefab))) { continue; } } RunJob(cron.Commands.Select((string cmd) => cmd.Replace("<i>", zone.x.ToString()).Replace("<j>", zone.y.ToString()).Replace("<x>", pos.x.ToString("F2", CultureInfo.InvariantCulture)) .Replace("<y>", pos.y.ToString("F2", CultureInfo.InvariantCulture)) .Replace("<z>", pos.z.ToString("F2", CultureInfo.InvariantCulture))).ToArray(), cron.Chance, cron.Log); } return true; } public static bool HasAnyGlobalKey(IEnumerable<string> keys) { return keys.Any(ZoneSystem.instance.m_globalKeys.Contains); } public static bool HasEveryGlobalKey(IEnumerable<string> keys) { return keys.All(ZoneSystem.instance.m_globalKeys.Contains); } [HarmonyPatch(typeof(ZNet), "RPC_CharacterID")] [HarmonyPostfix] private static void AddPeer(ZNet __instance, ZRpc rpc, ZDOID characterID) { if (!__instance.IsServer() || ((ZDOID)(ref characterID)).IsNone()) { return; } ZNetPeer peer = __instance.GetPeer(rpc); if (HandledPeers.Contains(peer)) { return; } HandledPeers.Add(peer); foreach (CronJoinJob joinJob in JoinJobs) { RunJob(joinJob.Commands.Select((string cmd) => cmd.Replace("<name>", peer.m_playerName).Replace("<pname>", peer.m_playerName).Replace("<first>", peer.m_playerName.Split(new char[1] { ' ' })[0]) .Replace("<id>", ((ZDOID)(ref peer.m_characterID)).UserID.ToString()) .Replace("<pid>", peer.m_rpc.GetSocket().GetHostName()) .Replace("<pchar>", ((ZDOID)(ref peer.m_characterID)).UserID.ToString()) .Replace("<x>", peer.m_refPos.x.ToString("F2", CultureInfo.InvariantCulture)) .Replace("<y>", peer.m_refPos.y.ToString("F2", CultureInfo.InvariantCulture)) .Replace("<z>", peer.m_refPos.z.ToString("F2", CultureInfo.InvariantCulture))).ToArray(), joinJob.Chance, joinJob.Log); } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPostfix] private static void Disconnect(ZNetPeer peer) { HandledPeers.Remove(peer); } [HarmonyPatch(typeof(Chat), "Awake")] [HarmonyPostfix] private static void ChatAwake() { if (File.Exists(FilePath)) { FromFile(); return; } string contents = Data.Serializer().Serialize((object)new CronData()); File.WriteAllText(FilePath, contents); } private static bool ParseTimeZone(string timezone) { timezone = timezone.ToLower(); foreach (TimeZoneInfo systemTimeZone in TimeZoneInfo.GetSystemTimeZones()) { if (systemTimeZone.Id.ToLower() == timezone || systemTimeZone.DisplayName.ToLower() == timezone) { TimeZone = systemTimeZone; return true; } } foreach (TimeZoneInfo systemTimeZone2 in TimeZoneInfo.GetSystemTimeZones()) { if (systemTimeZone2.Id.ToLower().Contains(timezone) || systemTimeZone2.DisplayName.ToLower().Contains(timezone)) { TimeZone = systemTimeZone2; return true; } } return false; } public static void FromFile() { if (!File.Exists(FilePath)) { File.WriteAllText(FilePath, Data.Serializer().Serialize((object)new CronData())); } try { CronData cronData = Data.Read<CronData>(FilePath); Interval = cronData.interval; if (ParseTimeZone(cronData.timezone)) { CronJob.Log.LogInfo((object)("Selected time zone " + TimeZone.Id + " / " + TimeZone.DisplayName + ".")); } else { CronJob.Log.LogWarning((object)("Time zone " + cronData.timezone + " not found, using UTC. Possible time zones are:")); foreach (TimeZoneInfo systemTimeZone in TimeZoneInfo.GetSystemTimeZones()) { CronJob.Log.LogWarning((object)(systemTimeZone.Id + " / " + systemTimeZone.DisplayName)); } } LogJobs = cronData.logJobs; LogZone = cronData.logZone; LogJoin = cronData.logJoin; LogSkipped = cronData.logSkipped; cronData.join.ForEach(ReplaceParameters); cronData.zone.ForEach(ReplaceParameters); cronData.jobs.ForEach(ReplaceParameters); cronData.zone.ForEach(VerifyParameterExists); cronData.zone.ForEach(VerifyInactiveIsNull); cronData.jobs.ForEach(VerifyInactiveIsNull); cronData.jobs = SkipWithoutSchedule(cronData.jobs); cronData.zone = SkipWithoutSchedule(cronData.zone); DiscordConnector = cronData.discordConnector; if (DiscordConnector == "true") { DiscordConnector = "cronjob"; } else if (DiscordConnector == "false") { DiscordConnector = ""; } Jobs = cronData.jobs.Select((CronEntryData s) => new CronGeneralJob(s)).ToList(); CronJob.Log.LogInfo((object)$"Reloading {Jobs.Count} cron jobs."); ZoneJobs = cronData.zone.Select((CronEntryData s) => new CronZoneJob(s)).ToList(); CronJob.Log.LogInfo((object)$"Reloading {ZoneJobs.Count} zone cron jobs."); JoinJobs = cronData.join.Select((CronEntryData s) => new CronJoinJob(s)).ToList(); CronJob.Log.LogInfo((object)$"Reloading {JoinJobs.Count} join jobs."); } catch (Exception ex) { CronJob.Log.LogError((object)ex.StackTrace); } } private static void ReplaceParameters(CronEntryData entry) { if (entry.command.Contains("$$")) { CronJob.Log.LogWarning((object)("$$ is deprecated, use <> instead. Command: " + entry.command)); entry.command = entry.command.Replace("$$i", "<i>").Replace("$$I", "<i>").Replace("$$j", "<j>") .Replace("$$J", "<j>") .Replace("$$x", "<x>") .Replace("$$X", "<x>") .Replace("$$y", "<y>") .Replace("$$Y", "<y>") .Replace("$$z", "<z>") .Replace("$$Z", "<>>") .Replace("$$id", "<id>") .Replace("$$ID", "<id>") .Replace("$$name", "<name>") .Replace("$$NAME", "<name>") .Replace("$$first", "<first>") .Replace("$$FIRST", "<first>"); } } private static void VerifyParameterExists(CronEntryData entry) { if (!entry.command.Contains("<") && !entry.command.Contains(">")) { CronJob.Log.LogWarning((object)("Command " + entry.command + " does not contain parameters, should this be general job instead?")); } } private static void VerifyInactiveIsNull(CronEntryData entry) { if (entry.inactive.HasValue) { CronJob.Log.LogWarning((object)("Inactive time is deprecated and can be removed. Command: " + entry.command)); } } private static List<CronEntryData> SkipWithoutSchedule(List<CronEntryData> entries) { return entries.Where(delegate(CronEntryData entry) { int num; if (entry.schedule != null) { num = ((entry.schedule != "") ? 1 : 0); if (num != 0) { goto IL_003d; } } else { num = 0; } CronJob.Log.LogWarning((object)("Command " + entry.command + " does not have a schedule, skipping.")); goto IL_003d; IL_003d: return (byte)num != 0; }).ToList(); } public static void SetupWatcher() { Data.SetupWatcher(FileName, FromFile); } } public class Data : MonoBehaviour { public static bool SkipWatch; public static void SetupWatcher(string pattern, Action action) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, pattern); fileSystemWatcher.Created += delegate { skippableAction(); }; fileSystemWatcher.Changed += delegate { skippableAction(); }; fileSystemWatcher.Renamed += delegate { skippableAction(); }; fileSystemWatcher.Deleted += delegate { skippableAction(); }; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; fileSystemWatcher.EnableRaisingEvents = true; void skippableAction() { if (SkipWatch) { SkipWatch = false; } else { action(); } } } public static IDeserializer Deserializer() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown return ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); } public static ISerializer Serializer() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown return ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).WithIndentedSequences().ConfigureDefaultValuesHandling((DefaultValuesHandling)1) .DisableAliases() .Build(); } public static T Deserialize<T>(string raw) where T : new() { return Deserializer().Deserialize<T>(raw); } public static string Serialize<T>(T data) where T : new() { return Serializer().Serialize((object)(T)((data != null) ? ((object)data) : ((object)new T()))); } public static T Read<T>(string file) where T : new() { return Deserialize<T>(File.ReadAllText(file)); } } public static class DiscordHook { public const string GUID = "nwesterhausen.DiscordConnector"; private static Assembly? assembly; private static MethodInfo? sendMessage; private static Type? eventType; private static Dictionary<string, object> messageTypes = new Dictionary<string, object>(); public static void SendMessage(string type, string message) { if (sendMessage == null || eventType == null) { return; } if (!messageTypes.ContainsKey(type)) { try { object value = Enum.Parse(eventType, type, ignoreCase: true); messageTypes[type] = value; } catch { CronJob.Log.LogError((object)("Invalid message type " + type)); return; } } sendMessage.Invoke(null, new object[2] { messageTypes[type], message }); } public static void Init() { if (!Chainloader.PluginInfos.TryGetValue("nwesterhausen.DiscordConnector", out var value)) { return; } assembly = ((object)value.Instance).GetType().Assembly; eventType = assembly.GetType("DiscordConnector.Webhook+Event"); if (eventType == null) { CronJob.Log.LogError((object)"Failed to get DiscordConnector.WebHook+Event"); return; } Type type = assembly.GetType("DiscordConnector.DiscordApi"); if (type == null) { CronJob.Log.LogError((object)"Failed to get DiscordConnector.DiscordApi"); return; } sendMessage = AccessTools.Method(type, "SendMessage", new Type[2] { eventType, typeof(string) }, (Type[])null); if (sendMessage == null) { CronJob.Log.LogError((object)"Failed to get DiscordConnector.DiscordApi.SendMessage"); } else { CronJob.Log.LogInfo((object)"DiscordConnector initialized"); } } } public class Parse { public static string[] Split(string arg, bool removeEmpty = true, char split = ',') { return (from s in arg.Split(new char[1] { split }) select s.Trim() into s where !removeEmpty || s != "" select s).ToArray(); } public static HashSet<int> ToHashSet(string arg) { HashSet<int> hashSet = new HashSet<int>(); foreach (int item in from s in Split(arg) select StringExtensionMethods.GetStableHashCode(s)) { hashSet.Add(item); } return hashSet; } public static HashSet<string> ToSet(string arg) { HashSet<string> hashSet = new HashSet<string>(); string[] array = Split(arg); foreach (string item in array) { hashSet.Add(item); } return hashSet; } public static Biome ToBiomes(string biomeStr) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) Biome val = (Biome)0; string[] array = Split(biomeStr); foreach (string text in array) { if (Enum.TryParse<Biome>(text, ignoreCase: true, out Biome result)) { val |= result; continue; } if (int.TryParse(text, out var result2)) { val = (Biome)(val + result2); continue; } throw new InvalidOperationException("Invalid biome " + text + "."); } return val; } } [HarmonyPatch] public class TrackManager { public static string FileNameJob = "cron_last.yaml"; public static string FileNameZone = "cron_track.yaml"; public static string FilePathJob = Path.Combine(Paths.ConfigPath, FileNameJob); public static string FilePathZone = Path.Combine(Paths.ConfigPath, FileNameZone); public static Dictionary<Vector2s, DateTime> ZoneTimestamps = new Dictionary<Vector2s, DateTime>(); private static readonly HashSet<Vector2s> Zones = new HashSet<Vector2s>(); private static readonly HashSet<Vector2s> PlayerZones = new HashSet<Vector2s>(); [HarmonyPatch(typeof(Chat), "Awake")] [HarmonyPostfix] public static void ChatAwake() { if (File.Exists(FilePathJob)) { Dictionary<string, long> dictionary = Data.Read<Dictionary<string, long>>(FilePathJob); if (dictionary.TryGetValue("world", out var value)) { CronManager.Previous = new DateTime(value, DateTimeKind.Utc); } if (dictionary.TryGetValue("game", out var value2)) { CronManager.PreviousGameTime = new DateTime(value2, DateTimeKind.Utc); } } if (File.Exists(FilePathZone)) { ZoneTrackFromFile(); } } [HarmonyPatch(typeof(ZNet), "SaveWorldThread")] [HarmonyPrefix] public static void OnSave() { Dictionary<string, long> dictionary = new Dictionary<string, long> { { "world", CronManager.Previous.Ticks + 10000000 }, { "game", CronManager.PreviousGameTime.Ticks + 10000000 } }; string contents = Data.Serializer().Serialize((object)dictionary); File.WriteAllText(FilePathJob, contents); if (ZoneTimestamps.Count == 0) { if (File.Exists(FilePathZone)) { File.Delete(FilePathZone); } return; } Dictionary<string, long> dictionary2 = ZoneTimestamps.ToDictionary((KeyValuePair<Vector2s, DateTime> kvp) => $"{kvp.Key.x},{kvp.Key.y}", (KeyValuePair<Vector2s, DateTime> kvp) => kvp.Value.Ticks); contents = Data.Serializer().Serialize((object)dictionary2); File.WriteAllText(FilePathZone, contents); } public static void ZoneTrackFromFile() { ZoneTimestamps = new Dictionary<Vector2s, DateTime>(); if (!File.Exists(FilePathZone)) { return; } try { ZoneTimestamps = ((IEnumerable<KeyValuePair<string, long>>)Data.Read<Dictionary<string, long>>(FilePathZone)).ToDictionary((Func<KeyValuePair<string, long>, Vector2s>)delegate(KeyValuePair<string, long> kvp) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) string[] array = kvp.Key.Split(new char[1] { ',' }); return new Vector2s(int.Parse(array[0]), int.Parse(array[1])); }, (Func<KeyValuePair<string, long>, DateTime>)((KeyValuePair<string, long> kvp) => new DateTime(kvp.Value, DateTimeKind.Utc))); CronJob.Log.LogInfo((object)$"Reloading {ZoneTimestamps.Count} zone last runs."); } catch (Exception ex) { CronJob.Log.LogError((object)ex.StackTrace); } } public static void Track() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) if (CronManager.ZoneJobs.Count == 0) { return; } HashSet<Vector2s> playerZones = GetPlayerZones(); Zones.Clear(); if (!ZNet.instance.IsDedicated()) { TrackPeer(Zones, ZNet.instance.GetReferencePosition()); } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { TrackPeer(Zones, peer.GetRefPos()); } foreach (Vector2s zone in Zones) { Poke(zone, playerZones.Contains(zone)); } } private static void Poke(Vector2s zone, bool hasPlayer) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) DateTime? previous = (ZoneTimestamps.ContainsKey(zone) ? new DateTime?(ZoneTimestamps[zone]) : ((DateTime?)null)); if (CronManager.Execute(zone, hasPlayer, previous)) { ZoneTimestamps[zone] = DateTime.UtcNow; } } private static void TrackPeer(HashSet<Vector2s> zones, Vector3 pos) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) ZoneSystem instance = ZoneSystem.instance; Vector2s zone = ZoneSystem.GetZone(pos); int num = 3; Vector2s val = default(Vector2s); for (int i = zone.y - num; i <= zone.y + num; i++) { for (int j = zone.x - num; j <= zone.x + num; j++) { ((Vector2s)(ref val))..ctor(j, i); if (instance.IsZoneGenerated(val)) { zones.Add(val); } } } } private static HashSet<Vector2s> GetPlayerZones() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) PlayerZones.Clear(); if (!ZNet.instance.IsDedicated()) { PlayerZones.Add(ZoneSystem.GetZone(ZNet.instance.GetReferencePosition())); } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { PlayerZones.Add(ZoneSystem.GetZone(peer.GetRefPos())); } return PlayerZones; } } } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] internal sealed class SetsRequiredMembersAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method, Inherited = false)] internal sealed class DoesNotReturnAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] internal sealed class MaybeNullWhenAttribute : Attribute { public bool ReturnValue { get; } public MaybeNullWhenAttribute(bool returnValue) { ReturnValue = returnValue; } } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] internal sealed class NotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public NotNullWhenAttribute(bool returnValue) { ReturnValue = returnValue; } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] internal sealed class NotNullIfNotNullAttribute : Attribute { public string ParameterName { get; } public NotNullIfNotNullAttribute(string parameterName) { ParameterName = parameterName; } } } namespace System.Runtime.CompilerServices { [EditorBrowsable(EditorBrowsableState.Never)] internal static class IsExternalInit { } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] internal sealed class RequiredMemberAttribute : Attribute { } [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] internal sealed class CompilerFeatureRequiredAttribute : Attribute { public const string RefStructs = "RefStructs"; public const string RequiredMembers = "RequiredMembers"; public string FeatureName { get; } public bool IsOptional { get; init; } public CompilerFeatureRequiredAttribute(string featureName) { FeatureName = featureName; } } } namespace Cronos { internal static class CalendarHelper { private const int DaysPerWeekCount = 7; private const long TicksPerMillisecond = 10000L; private const long TicksPerSecond = 10000000L; private const long TicksPerMinute = 600000000L; private const long TicksPerHour = 36000000000L; private const long TicksPerDay = 864000000000L; private const int DaysPerYear = 365; private const int DaysPer4Years = 1461; private const int DaysPer100Years = 36524; private const int DaysPer400Years = 146097; private static readonly int[] DaysToMonth365 = new int[13] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; private static readonly int[] DaysToMonth366 = new int[13] { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }; private static readonly int[] DaysInMonth = new int[13] { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; public static bool IsGreaterThan(int year1, int month1, int day1, int year2, int month2, int day2) { if (year1 != year2) { return year1 > year2; } if (month1 != month2) { return month1 > month2; } if (day2 != day1) { return day1 > day2; } return false; } public static long DateTimeToTicks(int year, int month, int day, int hour, int minute, int second) { int[] array = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365); checked { int num = year - 1; return (num * 365 + unchecked(num / 4) - unchecked(num / 100) + unchecked(num / 400) + array[month - 1] + day - 1) * 864000000000L + (unchecked((long)hour) * 3600L + unchecked((long)minute) * 60L + second) * 10000000; } } public static void FillDateTimeParts(long ticks, out int second, out int minute, out int hour, out int day, out int month, out int year) { checked { second = (int)unchecked(ticks / 10000000 % 60); if (unchecked(ticks % 10000000) != 0L) { second++; } minute = (int)unchecked(ticks / 600000000 % 60); hour = (int)unchecked(ticks / 36000000000L % 24); int num = (int)unchecked(ticks / 864000000000L); int num2 = unchecked(num / 146097); num -= num2 * 146097; int num3 = unchecked(num / 36524); if (num3 == 4) { num3 = 3; } num -= num3 * 36524; int num4 = unchecked(num / 1461); num -= num4 * 1461; int num5 = unchecked(num / 365); if (num5 == 4) { num5 = 3; } year = num2 * 400 + num3 * 100 + num4 * 4 + num5 + 1; num -= num5 * 365; int[] array = ((num5 == 3 && (num4 != 24 || num3 == 3)) ? DaysToMonth366 : DaysToMonth365); month = (num >> 5) + 1; while (num >= array[month]) { month++; } day = num - array[month - 1] + 1; } } public static DayOfWeek GetDayOfWeek(int year, int month, int day) { int[] array = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365); int num = checked(year - 1); return (DayOfWeek)(checked((int)(unchecked(checked((num * 365 + unchecked(num / 4) - unchecked(num / 100) + unchecked(num / 400) + array[month - 1] + day - 1) * 864000000000L) / 864000000000L) + 1)) % 7); } public static int GetDaysInMonth(int year, int month) { if (month != 2 || year % 4 != 0) { return DaysInMonth[month]; } if (year % 100 == 0 && year % 400 != 0) { return 28; } return 29; } public static int MoveToNearestWeekDay(int year, int month, int day) { DayOfWeek dayOfWeek = GetDayOfWeek(year, month, day); if (dayOfWeek != DayOfWeek.Saturday && dayOfWeek != DayOfWeek.Sunday) { return day; } checked { if (dayOfWeek != DayOfWeek.Sunday) { if (day != CronField.DaysOfMonth.First) { return day - 1; } return day + 2; } if (day != GetDaysInMonth(year, month)) { return day + 1; } return day - 2; } } public static bool IsNthDayOfWeek(int day, int n) { checked { if (day - 7 * n < CronField.DaysOfMonth.First) { return day - 7 * (n - 1) >= CronField.DaysOfMonth.First; } return false; } } public static bool IsLastDayOfWeek(int year, int month, int day) { return checked(day + 7) > GetDaysInMonth(year, month); } } internal sealed class CronExpression : IEquatable<CronExpression> { private const long NotFound = -1L; public static readonly CronExpression Yearly = Parse("0 0 1 1 *", CronFormat.Standard); public static readonly CronExpression Weekly = Parse("0 0 * * 0", CronFormat.Standard); public static readonly CronExpression Monthly = Parse("0 0 1 * *", CronFormat.Standard); public static readonly CronExpression Daily = Parse("0 0 * * *", CronFormat.Standard); public static readonly CronExpression Hourly = Parse("0 * * * *", CronFormat.Standard); public static readonly CronExpression EveryMinute = Parse("* * * * *", CronFormat.Standard); public static readonly CronExpression EverySecond = Parse("* * * * * *", CronFormat.IncludeSeconds); private static readonly TimeZoneInfo UtcTimeZone = TimeZoneInfo.Utc; private static readonly int[] DeBruijnPositions = new int[64] { 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 }; private readonly ulong _second; private readonly ulong _minute; private readonly uint _hour; private readonly uint _dayOfMonth; private readonly ushort _month; private readonly byte _dayOfWeek; private readonly byte _nthDayOfWeek; private readonly byte _lastMonthOffset; private readonly CronExpressionFlag _flags; public static CronExpression YearlyWithJitter(int jitterSeed) { return Parse("H H H H H *", CronFormat.IncludeSeconds, jitterSeed); } public static CronExpression WeeklyWithJitter(int jitterSeed) { return Parse("H H H * * H", CronFormat.IncludeSeconds, jitterSeed); } public static CronExpression MonthlyWithJitter(int jitterSeed) { return Parse("H H H H * *", CronFormat.IncludeSeconds, jitterSeed); } public static CronExpression DailyWithJitter(int jitterSeed) { return Parse("H H H * * *", CronFormat.IncludeSeconds, jitterSeed); } public static CronExpression HourlyWithJitter(int jitterSeed) { return Parse("H H * * * *", CronFormat.IncludeSeconds, jitterSeed); } public static CronExpression EveryMinuteWithJitter(int jitterSeed) { return Parse("H * * * * *", CronFormat.IncludeSeconds, jitterSeed); } internal CronExpression(ulong second, ulong minute, uint hour, uint dayOfMonth, ushort month, byte dayOfWeek, byte nthDayOfWeek, byte lastMonthOffset, CronExpressionFlag flags) { _second = second; _minute = minute; _hour = hour; _dayOfMonth = dayOfMonth; _month = month; _dayOfWeek = dayOfWeek; _nthDayOfWeek = nthDayOfWeek; _lastMonthOffset = lastMonthOffset; _flags = flags; } public static CronExpression Parse(string expression) { return Parse(expression, CronFormat.Standard); } public static CronExpression Parse(string expression, int jitterSeed) { return Parse(expression, CronFormat.Standard, jitterSeed); } public static CronExpression Parse(string expression, CronFormat format) { if (expression == null) { throw new ArgumentNullException("expression"); } return CronParser.Parse(expression, format); } public static CronExpression Parse(string expression, CronFormat format, int jitterSeed) { if (expression == null) { throw new ArgumentNullException("expression"); } return CronParser.Parse(expression, format, jitterSeed); } public static bool TryParse(string expression, [MaybeNullWhen(false)] out CronExpression cronExpression) { return TryParse(expression, CronFormat.Standard, out cronExpression); } public static bool TryParse(string expression, int jitterSeed, [MaybeNullWhen(false)] out CronExpression cronExpression) { return TryParse(expression, CronFormat.Standard, jitterSeed, out cronExpression); } public static bool TryParse(string expression, CronFormat format, [MaybeNullWhen(false)] out CronExpression cronExpression) { if (expression == null) { throw new ArgumentNullException("expression"); } try { cronExpression = Parse(expression, format); return true; } catch (CronFormatException) { cronExpression = null; return false; } catch (MissingSeedException) { cronExpression = null; return false; } } public static bool TryParse(string expression, CronFormat format, int jitterSeed, [MaybeNullWhen(false)] out CronExpression cronExpression) { if (expression == null) { throw new ArgumentNullException("expression"); } try { cronExpression = Parse(expression, format, jitterSeed); return true; } catch (CronFormatException) { cronExpression = null; return false; } catch (MissingSeedException) { cronExpression = null; return false; } } public DateTime? GetNextOccurrence(DateTime fromUtc, bool inclusive = false) { if (fromUtc.Kind != DateTimeKind.Utc) { ThrowWrongDateTimeKindException("fromUtc"); } long num = FindOccurrence(fromUtc.Ticks, inclusive); if (num == -1) { return null; } return new DateTime(num, DateTimeKind.Utc); } public DateTime? GetPreviousOccurrence(DateTime fromUtc, bool inclusive = false) { if (fromUtc.Kind != DateTimeKind.Utc) { ThrowWrongDateTimeKindException("fromUtc"); } long num = FindPreviousOccurrence(fromUtc.Ticks, inclusive); if (num == -1) { return null; } return new DateTime(num, DateTimeKind.Utc); } public DateTime? GetNextOccurrence(DateTime fromUtc, TimeZoneInfo zone, bool inclusive = false) { if (fromUtc.Kind != DateTimeKind.Utc) { ThrowWrongDateTimeKindException("fromUtc"); } if (zone == null) { ThrowArgumentNullException("zone"); } if (zone == UtcTimeZone) { long num = FindOccurrence(fromUtc.Ticks, inclusive); if (num == -1) { return null; } return new DateTime(num, DateTimeKind.Utc); } DateTimeOffset fromUtc2 = new DateTimeOffset(fromUtc); return GetOccurrenceConsideringTimeZone(fromUtc2, zone, inclusive)?.UtcDateTime; } public DateTime? GetPreviousOccurrence(DateTime fromUtc, TimeZoneInfo zone, bool inclusive = false) { if (fromUtc.Kind != DateTimeKind.Utc) { ThrowWrongDateTimeKindException("fromUtc"); } if (zone == null) { ThrowArgumentNullException("zone"); } if (zone == UtcTimeZone) { long num = FindPreviousOccurrence(fromUtc.Ticks, inclusive); if (num == -1) { return null; } return new DateTime(num, DateTimeKind.Utc); } DateTimeOffset fromUtc2 = new DateTimeOffset(fromUtc); return GetPreviousOccurrenceConsideringTimeZone(fromUtc2, zone, inclusive)?.UtcDateTime; } public DateTimeOffset? GetNextOccurrence(DateTimeOffset from, TimeZoneInfo zone, bool inclusive = false) { if (zone == null) { ThrowArgumentNullException("zone"); } if (zone == UtcTimeZone) { long num = FindOccurrence(from.UtcTicks, inclusive); if (num == -1) { return null; } return new DateTimeOffset(num, TimeSpan.Zero); } return GetOccurrenceConsideringTimeZone(from, zone, inclusive); } public DateTimeOffset? GetPreviousOccurrence(DateTimeOffset from, TimeZoneInfo zone, bool inclusive = false) { if (zone == null) { ThrowArgumentNullException("zone"); } if (zone == UtcTimeZone) { long num = FindPreviousOccurrence(from.UtcTicks, inclusive); if (num == -1) { return null; } return new DateTimeOffset(num, TimeSpan.Zero); } return GetPreviousOccurrenceConsideringTimeZone(from, zone, inclusive); } public IEnumerable<DateTime> GetOccurrences(DateTime fromUtc, DateTime toUtc, bool fromInclusive = true, bool toInclusive = false) { if (fromUtc > toUtc) { ThrowFromShouldBeLessThanToException("fromUtc", "toUtc"); } DateTime? occurrence = GetNextOccurrence(fromUtc, fromInclusive); while (occurrence < toUtc || (occurrence == toUtc && toInclusive)) { yield return occurrence.Value; occurrence = GetNextOccurrence(occurrence.Value); } } public IEnumerable<DateTime> GetOccurrences(DateTime fromUtc, DateTime toUtc, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false) { if (fromUtc > toUtc) { ThrowFromShouldBeLessThanToException("fromUtc", "toUtc"); } DateTime? occurrence = GetNextOccurrence(fromUtc, zone, fromInclusive); while (occurrence < toUtc || (occurrence == toUtc && toInclusive)) { yield return occurrence.Value; occurrence = GetNextOccurrence(occurrence.Value, zone); } } public IEnumerable<DateTimeOffset> GetOccurrences(DateTimeOffset from, DateTimeOffset to, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false) { if (from > to) { ThrowFromShouldBeLessThanToException("from", "to"); } DateTimeOffset? occurrence = GetNextOccurrence(from, zone, fromInclusive); while (occurrence < to || (occurrence == to && toInclusive)) { yield return occurrence.Value; occurrence = GetNextOccurrence(occurrence.Value, zone); } } public IEnumerable<DateTime> GetOccurrencesDescending(DateTime fromUtc, DateTime toUtc, bool fromInclusive = true, bool toInclusive = false) { if (fromUtc < toUtc) { ThrowFromShouldBeGreaterThanToException("fromUtc", "toUtc"); } DateTime? occurrence = GetPreviousOccurrence(fromUtc, fromInclusive); while (occurrence > toUtc || (occurrence == toUtc && toInclusive)) { yield return occurrence.Value; occurrence = GetPreviousOccurrence(occurrence.Value); } } public IEnumerable<DateTime> GetOccurrencesDescending(DateTime fromUtc, DateTime toUtc, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false) { if (fromUtc < toUtc) { ThrowFromShouldBeGreaterThanToException("fromUtc", "toUtc"); } DateTime? occurrence = GetPreviousOccurrence(fromUtc, zone, fromInclusive); while (occurrence > toUtc || (occurrence == toUtc && toInclusive)) { yield return occurrence.Value; occurrence = GetPreviousOccurrence(occurrence.Value, zone); } } public IEnumerable<DateTimeOffset> GetOccurrencesDescending(DateTimeOffset from, DateTimeOffset to, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false) { if (from < to) { ThrowFromShouldBeGreaterThanToException("from", "to"); } DateTimeOffset? occurrence = GetPreviousOccurrence(from, zone, fromInclusive); while (occurrence > to || (occurrence == to && toInclusive)) { yield return occurrence.Value; occurrence = GetPreviousOccurrence(occurrence.Value, zone); } } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); if (_second != 1) { AppendFieldValue(stringBuilder, CronField.Seconds, _second).Append(' '); } AppendFieldValue(stringBuilder, CronField.Minutes, _minute).Append(' '); AppendFieldValue(stringBuilder, CronField.Hours, _hour).Append(' '); AppendDayOfMonth(stringBuilder, _dayOfMonth).Append(' '); AppendFieldValue(stringBuilder, CronField.Months, _month).Append(' '); AppendDayOfWeek(stringBuilder, _dayOfWeek); return stringBuilder.ToString(); } public bool Equals(CronExpression? other) { if ((object)other == null) { return false; } if (_second == other._second && _minute == other._minute && _hour == other._hour && _dayOfMonth == other._dayOfMonth && _month == other._month && _dayOfWeek == other._dayOfWeek && _nthDayOfWeek == other._nthDayOfWeek && _lastMonthOffset == other._lastMonthOffset) { return _flags == other._flags; } return false; } public override bool Equals(object? obj) { return Equals(obj as CronExpression); } public override int GetHashCode() { ulong second = _second; int num = second.GetHashCode() * 397; second = _minute; int num2 = (num ^ second.GetHashCode()) * 397; uint hour = _hour; int num3 = (num2 ^ hour.GetHashCode()) * 397; hour = _dayOfMonth; int num4 = (num3 ^ hour.GetHashCode()) * 397; ushort month = _month; int num5 = (num4 ^ month.GetHashCode()) * 397; byte dayOfWeek = _dayOfWeek; int num6 = (num5 ^ dayOfWeek.GetHashCode()) * 397; dayOfWeek = _nthDayOfWeek; int num7 = (num6 ^ dayOfWeek.GetHashCode()) * 397; dayOfWeek = _lastMonthOffset; return ((num7 ^ dayOfWeek.GetHashCode()) * 397) ^ (int)_flags; } public static bool operator ==(CronExpression? left, CronExpression? right) { return object.Equals(left, right); } public static bool operator !=(CronExpression? left, CronExpression? right) { return !object.Equals(left, right); } private DateTimeOffset? GetOccurrenceConsideringTimeZone(DateTimeOffset fromUtc, TimeZoneInfo zone, bool inclusive) { if (!DateTimeHelper.IsRound(fromUtc)) { fromUtc = DateTimeHelper.FloorToSeconds(fromUtc); inclusive = false; } DateTimeOffset dateTimeOffset = TimeZoneInfo.ConvertTime(fromUtc, zone); DateTime dateTime = dateTimeOffset.DateTime; if (TimeZoneHelper.IsAmbiguousTime(zone, dateTime)) { TimeSpan offset = dateTimeOffset.Offset; TimeSpan utcOffset = zone.GetUtcOffset(dateTime); if (utcOffset != offset) { TimeSpan daylightOffset = TimeZoneHelper.GetDaylightOffset(zone, dateTime); DateTime dateTime2 = TimeZoneHelper.GetDaylightTimeEnd(zone, dateTime, daylightOffset).DateTime; long num = FindOccurrence(dateTime.Ticks, dateTime2.Ticks, inclusive); if (num != -1) { return new DateTimeOffset(num, daylightOffset); } dateTime = TimeZoneHelper.GetStandardTimeStart(zone, dateTime, daylightOffset).DateTime; inclusive = true; } DateTime dateTime3 = TimeZoneHelper.GetAmbiguousIntervalEnd(zone, dateTime).DateTime; if (HasFlag(CronExpressionFlag.Interval)) { long num2 = FindOccurrence(dateTime.Ticks, checked(dateTime3.Ticks - 1), inclusive); if (num2 != -1) { return new DateTimeOffset(num2, utcOffset); } } dateTime = dateTime3; inclusive = true; } long num3 = FindOccurrence(dateTime.Ticks, inclusive); if (num3 == -1) { return null; } DateTime dateTime4 = new DateTime(num3, DateTimeKind.Unspecified); if (zone.IsInvalidTime(dateTime4)) { return TimeZoneHelper.GetDaylightTimeStart(zone, dateTime4); } if (TimeZoneHelper.IsAmbiguousTime(zone, dateTime4)) { TimeSpan daylightOffset2 = TimeZoneHelper.GetDaylightOffset(zone, dateTime4); return new DateTimeOffset(dateTime4, daylightOffset2); } return new DateTimeOffset(dateTime4, zone.GetUtcOffset(dateTime4)); } private DateTimeOffset? GetPreviousOccurrenceConsideringTimeZone(DateTimeOffset fromUtc, TimeZoneInfo zone, bool inclusive) { if (!DateTimeHelper.IsRound(fromUtc)) { fromUtc = DateTimeHelper.FloorToSeconds(fromUtc); } DateTimeOffset dateTimeOffset = TimeZoneInfo.ConvertTime(fromUtc, zone); DateTime dateTime = dateTimeOffset.DateTime; if (TimeZoneHelper.IsAmbiguousTime(zone, dateTime)) { TimeSpan offset = dateTimeOffset.Offset; TimeSpan utcOffset = zone.GetUtcOffset(dateTime); TimeSpan daylightOffset = TimeZoneHelper.GetDaylightOffset(zone, dateTime); DateTime dateTime2 = TimeZoneHelper.GetStandardTimeStart(zone, dateTime, daylightOffset).DateTime; if (utcOffset == offset) { if (HasFlag(CronExpressionFlag.Interval)) { long num = FindPreviousOccurrence(dateTime.Ticks, dateTime2.Ticks, inclusive); if (num != -1) { return new DateTimeOffset(num, utcOffset); } } long num2 = FindPreviousOccurrence(TimeZoneHelper.GetDaylightTimeEnd(zone, dateTime, daylightOffset).DateTime.Ticks, dateTime2.Ticks, startInclusive: true); if (num2 != -1) { return new DateTimeOffset(num2, daylightOffset); } dateTime = dateTime2.AddTicks(-1L); inclusive = true; } else { long num3 = FindPreviousOccurrence(dateTime.Ticks, dateTime2.Ticks, inclusive); if (num3 != -1) { return new DateTimeOffset(num3, daylightOffset); } dateTime = dateTime2.AddTicks(-1L); inclusive = true; } } long num4 = FindPreviousOccurrence(dateTime.Ticks, inclusive); if (num4 == -1) { return null; } DateTime dateTime3 = new DateTime(num4, DateTimeKind.Unspecified); if (zone.IsInvalidTime(dateTime3)) { DateTimeOffset standardTimeEnd = TimeZoneHelper.GetStandardTimeEnd(zone, dateTime3); if (standardTimeEnd.Date < dateTime3.Date) { DateTimeOffset daylightTimeStart = TimeZoneHelper.GetDaylightTimeStart(zone, dateTime3); return new DateTimeOffset(dateTime3, daylightTimeStart.Offset); } return standardTimeEnd; } if (TimeZoneHelper.IsAmbiguousTime(zone, dateTime3)) { TimeSpan daylightOffset2 = TimeZoneHelper.GetDaylightOffset(zone, dateTime3); if (HasFlag(CronExpressionFlag.Interval)) { return new DateTimeOffset(dateTime3, zone.GetUtcOffset(dateTime3)); } return new DateTimeOffset(dateTime3, daylightOffset2); } return new DateTimeOffset(dateTime3, zone.GetUtcOffset(dateTime3)); } private long FindOccurrence(long startTimeTicks, long endTimeTicks, bool startInclusive) { long num = FindOccurrence(startTimeTicks, startInclusive); if (num == -1 || num > endTimeTicks) { return -1L; } return num; } private long FindPreviousOccurrence(long startTimeTicks, long endTimeTicks, bool startInclusive) { long num = FindPreviousOccurrence(startTimeTicks, startInclusive); if (num == -1 || num < endTimeTicks) { return -1L; } return num; } private long FindOccurrence(long ticks, bool startInclusive) { int minute; int hour; int day; int month; int year; int firstSet; int fieldValue; int fieldValue2; int fieldValue3; int fieldValue4; int fieldValue5; int num; checked { if (!startInclusive) { ticks++; } CalendarHelper.FillDateTimeParts(ticks, out var second, out minute, out hour, out day, out month, out year); firstSet = GetFirstSet(_dayOfMonth); fieldValue = second; fieldValue2 = minute; fieldValue3 = hour; fieldValue4 = day; fieldValue5 = month; num = year; if (!GetBit(_second, fieldValue) && !Move(_second, ref fieldValue)) { fieldValue2++; } if (!GetBit(_minute, fieldValue2) && !Move(_minute, ref fieldValue2)) { fieldValue3++; } if (!GetBit(_hour, fieldValue3) && !Move(_hour, ref fieldValue3)) { fieldValue4++; } if (HasFlag(CronExpressionFlag.NearestWeekday)) { fieldValue4 = CronField.DaysOfMonth.First; } if ((GetBit(_dayOfMonth, fieldValue4) || Move(_dayOfMonth, ref fieldValue4)) && GetBit(_month, fieldValue5)) { goto IL_00f6; } goto IL_01bc; } IL_01bc: if (!Move(_month, ref fieldValue5)) { num = checked(num + 1); int num2 = num; DateTime maxValue = DateTime.MaxValue; if (num2 > maxValue.Year) { return -1L; } } fieldValue4 = firstSet; goto IL_00f6; IL_00f6: while (fieldValue4 <= GetLastDayOfMonth(num, fieldValue5)) { if (HasFlag(CronExpressionFlag.DayOfMonthLast)) { fieldValue4 = GetLastDayOfMonth(num, fieldValue5); } int num3 = fieldValue4; if (HasFlag(CronExpressionFlag.NearestWeekday)) { fieldValue4 = CalendarHelper.MoveToNearestWeekDay(num, fieldValue5, fieldValue4); } if (IsDayOfWeekMatch(num, fieldValue5, fieldValue4)) { if (!CalendarHelper.IsGreaterThan(num, fieldValue5, fieldValue4, year, month, day)) { if (fieldValue3 <= hour) { if (fieldValue2 > minute) { goto IL_017d; } goto IL_018a; } } else { fieldValue3 = GetFirstSet(_hour); } fieldValue2 = GetFirstSet(_minute); goto IL_017d; } goto IL_01a5; IL_01a5: fieldValue4 = num3; if (!Move(_dayOfMonth, ref fieldValue4)) { break; } continue; IL_018a: long num4 = CalendarHelper.DateTimeToTicks(num, fieldValue5, fieldValue4, fieldValue3, fieldValue2, fieldValue); if (num4 >= ticks) { return num4; } goto IL_01a5; IL_017d: fieldValue = GetFirstSet(_second); goto IL_018a; } goto IL_01bc; } private long FindPreviousOccurrence(long ticks, bool startInclusive) { int minute; int hour; int day; int month; int year; int num2; int fieldValue; int fieldValue2; int fieldValue3; int day2; int fieldValue4; int num3; checked { if (!startInclusive) { long num = ticks; DateTime minValue = DateTime.MinValue; if (num == minValue.Ticks) { return -1L; } ticks--; } CalendarHelper.FillDateTimeParts(ticks, out var second, out minute, out hour, out day, out month, out year); if (unchecked(ticks % 10000000) != 0L) { second--; } num2 = ((HasFlag(CronExpressionFlag.DayOfMonthLast) || HasFlag(CronExpressionFlag.NearestWeekday)) ? CronField.DaysOfMonth.Last : GetLastSet(_dayOfMonth)); fieldValue = second; fieldValue2 = minute; fieldValue3 = hour; day2 = day; fieldValue4 = month; num3 = year; if (!GetBit(_second, fieldValue) && !MoveBack(_second, ref fieldValue)) { fieldValue2--; } if (fieldValue2 < CronField.Minutes.First || (!GetBit(_minute, fieldValue2) && !MoveBack(_minute, ref fieldValue2))) { fieldValue3--; } if (fieldValue3 < CronField.Hours.First || (!GetBit(_hour, fieldValue3) && !MoveBack(_hour, ref fieldValue3))) { day2--; } if (GetBit(_month, fieldValue4)) { goto IL_011a; } goto IL_019e; } IL_019e: if (!MoveBack(_month, ref fieldValue4)) { num3 = checked(num3 - 1); int num4 = num3; DateTime minValue = DateTime.MinValue; if (num4 < minValue.Year) { return -1L; } } day2 = num2; goto IL_011a; IL_011a: int day3; int actualDay; while (TryGetPreviousDay(num3, fieldValue4, day2, out day3, out actualDay)) { if (!CalendarHelper.IsGreaterThan(year, month, day, num3, fieldValue4, actualDay)) { if (fieldValue3 >= hour) { if (fieldValue2 < minute) { goto IL_0165; } goto IL_0172; } } else { fieldValue3 = GetLastSet(_hour); } fieldValue2 = GetLastSet(_minute); goto IL_0165; IL_0172: long num5 = CalendarHelper.DateTimeToTicks(num3, fieldValue4, actualDay, fieldValue3, fieldValue2, fieldValue); if (num5 <= ticks) { return num5; } day2 = day3; if (!MoveBackDay(ref day2)) { break; } continue; IL_0165: fieldValue = GetLastSet(_second); goto IL_0172; } goto IL_019e; } private static bool Move(ulong fieldBits, ref int fieldValue) { checked { if (fieldBits >> ++fieldValue == 0L) { fieldValue = GetFirstSet(fieldBits); return false; } fieldValue += GetFirstSet(fieldBits >> fieldValue); return true; } } private static bool MoveBack(ulong fieldBits, ref int fieldValue) { ulong bitsAtOrBelow = GetBitsAtOrBelow(fieldBits, checked(fieldValue - 1)); if (bitsAtOrBelow == 0L) { fieldValue = GetLastSet(fieldBits); return false; } fieldValue = GetLastSet(bitsAtOrBelow); return true; } private bool TryGetPreviousDay(int year, int month, int dayLimit, out int day, out int actualDay) { int daysInMonth = CalendarHelper.GetDaysInMonth(year, month); int num = ((dayLimit > daysInMonth) ? daysInMonth : dayLimit); if (HasFlag(CronExpressionFlag.NearestWeekday)) { day = (HasFlag(CronExpressionFlag.DayOfMonthLast) ? GetLastDayOfMonth(year, month) : GetFirstSet(_dayOfMonth)); actualDay = CalendarHelper.MoveToNearestWeekDay(year, month, day); if (actualDay <= num) { return IsDayOfWeekMatch(year, month, actualDay); } return false; } if (HasFlag(CronExpressionFlag.DayOfMonthLast)) { day = GetLastDayOfMonth(year, month); actualDay = day; if (actualDay <= num) { return IsDayOfWeekMatch(year, month, actualDay); } return false; } if (_dayOfMonth == CronField.DaysOfMonth.AllBits && !HasFlag(CronExpressionFlag.DayOfWeekLast) && !HasFlag(CronExpressionFlag.NthDayOfWeek)) { day = num; actualDay = day; if (_dayOfWeek == CronField.DaysOfWeek.AllBits) { return true; } int dayOfWeek = (int)CalendarHelper.GetDayOfWeek(year, month, day); checked { for (int i = 0; i < 7 && day - i >= CronField.DaysOfMonth.First; i++) { int num2 = dayOfWeek - i; if (num2 < 0) { num2 += 7; } if (((_dayOfWeek >> num2) & 1) != 0) { day -= i; actualDay = day; return true; } } actualDay = 0; return false; } } day = num; if (!GetBit(_dayOfMonth, day) && !MoveBackDay(ref day)) { actualDay = 0; return false; } while (true) { if (day > daysInMonth) { if (!MoveBackDay(ref day)) { actualDay = 0; return false; } continue; } actualDay = day; if (IsDayOfWeekMatch(year, month, actualDay)) { return true; } if (!MoveBackDay(ref day)) { break; } } actualDay = 0; return false; } private bool MoveBackDay(ref int day) { if (HasFlag(CronExpressionFlag.NearestWeekday) || HasFlag(CronExpressionFlag.DayOfMonthLast)) { return false; } checked { if (_dayOfMonth == CronField.DaysOfMonth.AllBits) { if (day <= CronField.DaysOfMonth.First) { day = CronField.DaysOfMonth.Last; return false; } day--; return true; } return MoveBack(_dayOfMonth, ref day); } } private bool IsDayMatch(int year, int month, int day) { bool flag; if (!HasFlag(CronExpressionFlag.NearestWeekday)) { flag = ((!HasFlag(CronExpressionFlag.DayOfMonthLast)) ? GetBit(_dayOfMonth, day) : (GetLastDayOfMonth(year, month) == day)); } else { flag = false; int daysInMonth = CalendarHelper.GetDaysInMonth(year, month); if (HasFlag(CronExpressionFlag.DayOfMonthLast)) { int lastDayOfMonth = GetLastDayOfMonth(year, month); flag = CalendarHelper.MoveToNearestWeekDay(year, month, lastDayOfMonth) == day; } else { for (int i = CronField.DaysOfMonth.First; i <= daysInMonth; i = checked(i + 1)) { if (GetBit(_dayOfMonth, i) && CalendarHelper.MoveToNearestWeekDay(year, month, i) == day) { flag = true; break; } } } } if (flag) { return IsDayOfWeekMatch(year, month, day); } return false; } private int GetLastDayOfMonth(int year, int month) { return checked(CalendarHelper.GetDaysInMonth(year, month) - _lastMonthOffset); } private bool IsDayOfWeekMatch(int year, int month, int day) { if ((HasFlag(CronExpressionFlag.DayOfWeekLast) && !CalendarHelper.IsLastDayOfWeek(year, month, day)) || (HasFlag(CronExpressionFlag.NthDayOfWeek) && !CalendarHelper.IsNthDayOfWeek(day, _nthDayOfWeek))) { return false; } if (_dayOfWeek == CronField.DaysOfWeek.AllBits) { return true; } DayOfWeek dayOfWeek = CalendarHelper.GetDayOfWeek(year, month, day); return ((_dayOfWeek >> (int)dayOfWeek) & 1) != 0; } private static int GetFirstSet(ulong value) { ulong num = (value & (0L - value)) * 157587932685088877L >> 58; return DeBruijnPositions[num]; } private static int GetLastSet(ulong value) { int num = 0; while ((value >>= 1) != 0L) { num = checked(num + 1); } return num; } private static int GetLastSetWithin(ulong fieldBits, int maxValue) { return GetLastSet(GetBitsAtOrBelow(fieldBits, maxValue)); } private static ulong GetBitsAtOrBelow(ulong fieldBits, int maxValue) { if (maxValue < 0) { return 0uL; } if (maxValue >= 63) { return fieldBits; } checked { return fieldBits & (unchecked((ulong)(1L << checked(maxValue + 1))) - 1uL); } } private bool HasFlag(CronExpressionFlag value) { return (_flags & value) != 0; } private static StringBuilder AppendFieldValue(StringBuilder expressionBuilder, CronField field, ulong fieldValue) { if (field.AllBits == fieldValue) { return expressionBuilder.Append('*'); } if (field == CronField.DaysOfWeek) { fieldValue &= (uint)(~(1 << field.Last)); } int firstSet = GetFirstSet(fieldValue); while (true) { expressionBuilder.Append(firstSet); if (fieldValue >> (firstSet = checked(firstSet + 1)) == 0L) { break; } expressionBuilder.Append(','); firstSet = GetFirstSet(fieldValue >> firstSet << firstSet); } return expressionBuilder; } private StringBuilder AppendDayOfMonth(StringBuilder expressionBuilder, uint domValue) { if (HasFlag(CronExpressionFlag.DayOfMonthLast)) { expressionBuilder.Append('L'); if (_lastMonthOffset != 0) { expressionBuilder.Append(string.Format(CultureInfo.InvariantCulture, "-{0}", new object[1] { _lastMonthOffset })); } } else { AppendFieldValue(expressionBuilder, CronField.DaysOfMonth, domValue); } if (HasFlag(CronExpressionFlag.NearestWeekday)) { expressionBuilder.Append('W'); } return expressionBuilder; } private void AppendDayOfWeek(StringBuilder expressionBuilder, uint dowValue) { AppendFieldValue(expressionBuilder, CronField.DaysOfWeek, dowValue); if (HasFlag(CronExpressionFlag.DayOfWeekLast)) { expressionBuilder.Append('L'); } else if (HasFlag(CronExpressionFlag.NthDayOfWeek)) { expressionBuilder.Append(string.Format(CultureInfo.InvariantCulture, "#{0}", new object[1] { _nthDayOfWeek })); } } [MethodImpl(MethodImplOptions.NoInlining)] [DoesNotReturn] private static void ThrowFromShouldBeLessThanToException(string fromName, string toName) { throw new ArgumentException("The value of the " + fromName + " argument should be less than the value of the " + toName + " argument.", fromName); } [MethodImpl(MethodImplOptions.NoInlining)] [DoesNotReturn] private static void ThrowFromShouldBeGreaterThanToException(string fromName, string toName) { throw new ArgumentException("The value of the " + fromName + " argument should be greater than or equal to the value of the " + toName + " argument.", fromName); } [MethodImpl(MethodImplOptions.NoInlining)] [DoesNotReturn] private static void ThrowWrongDateTimeKindException(string paramName) { throw new ArgumentException("The supplied DateTime must have the Kind property set to Utc", paramName); } [MethodImpl(MethodImplOptions.NoInlining)] [DoesNotReturn] private static void ThrowArgumentNullException(string paramName) { throw new ArgumentNullException(paramName); } private static bool GetBit(ulong value, int index) { return (value & (ulong)(1L << index)) != 0; } } [Flags] internal enum CronExpressionFlag : byte { DayOfMonthLast = 1, DayOfWeekLast = 2, Interval = 4, NearestWeekday = 8, NthDayOfWeek = 0x10 } internal sealed class CronField { private static readonly string[] MonthNames; private static readonly string[] DayOfWeekNames; private static readonly int[] MonthNamesArray; private static readonly int[] DayOfWeekNamesArray; public static readonly CronField DaysOfWeek; public static readonly CronField Months; public static readonly CronField DaysOfMonth; public static readonly CronField Hours; public static readonly CronField Minutes; public static readonly CronField Seconds; public const int LastCommonDayOfMonth = 28; public readonly string Name; public readonly int First; public readonly int Last; public readonly int[]? Names; public readonly bool CanDefineInterval; public readonly ulong AllBits; static CronField() { MonthNames = new string[13] { string.Empty, "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; DayOfWeekNames = new string[8] { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN" }; MonthNamesArray = new int[MonthNames.Length]; DayOfWeekNamesArray = new int[DayOfWeekNames.Length]; DaysOfWeek = new CronField("Days of week", 0, 7, DayOfWeekNamesArray, canDefineInterval: false); Months = new CronField("Months", 1, 12, MonthNamesArray, canDefineInterval: false); DaysOfMonth = new CronField("Days of month", 1, 31, null, canDefineInterval: false); Hours = new CronField("Hours", 0, 23, null, canDefineInterval: true); Minutes = new CronField("Minutes", 0, 59, null, canDefineInterval: true); Seconds = new CronField("Seconds", 0, 59, null, canDefineInterval: true); for (int i = 1; i < MonthNames.Length; i = checked(i + 1)) { string text = MonthNames[i].ToUpperInvariant(); int num = (int)(text[0] | ((uint)text[1] << 8) | ((uint)text[2] << 16)); MonthNamesArray[i] = num; } for (int j = 0; j < DayOfWeekNames.Length; j = checked(j + 1)) { string text2 = DayOfWeekNames[j].ToUpperInvariant(); int num2 = (int)(text2[0] | ((uint)text2[1] << 8) | ((uint)text2[2] << 16)); DayOfWeekNamesArray[j] = num2; } } private CronField(string name, int first, int last, int[]? names, bool canDefineInterval) { Name = name; First = first; Last = last; Names = names; CanDefineInterval = canDefineInterval; for (int i = First; i <= Last; i = checked(i + 1)) { AllBits |= (ulong)(1L << i); } } public override string ToString() { return Name; } } [Flags] internal enum CronFormat { Standard = 0, IncludeSeconds = 1 } [Serializable] internal class CronFormatException : FormatException { internal const string BaseMessage = "The given cron expression has an invalid format."; public CronFormatException() : this("The given cron expression has an invalid format.") { } public CronFormatException(string message) : base(message) { } public CronFormatException(string message, Exception innerException) : base(message, innerException) { } protected CronFormatException(SerializationInfo info, StreamingContext context) : base(info, context) { } } internal static class CronParser { private const int MinNthDayOfWeek = 1; private const int MaxNthDayOfWeek = 5; private const int SundayBits = 129; public unsafe static CronExpression Parse(string expression, CronFormat format, int? jitterSeed = null) { checked { fixed (char* ptr = expression) { char* pointer = ptr; SkipWhiteSpaces(ref pointer); if (Accept(ref pointer, '@')) { CronExpression cronExpression = ParseMacro(ref pointer, jitterSeed); SkipWhiteSpaces(ref pointer); if ((object)cronExpression == null || !IsEndOfString(*pointer)) { ThrowFormatException("Macro: Unexpected character '{0}' on position {1}.", *pointer, pointer - ptr); } return cronExpression; } ulong value = 0uL; byte nthWeekDay = 0; byte lastDayOffset = 0; CronExpressionFlag flags = (CronExpressionFlag)0; Random rng = ((!jitterSeed.HasValue) ? null : new Random(jitterSeed.Value)); if (format == CronFormat.IncludeSeconds) { value = ParseField(CronField.Seconds, ref pointer, ref flags, rng); ParseWhiteSpace(CronField.Seconds, ref pointer); } else { SetBit(ref value, CronField.Seconds.First); } ulong minute = ParseField(CronField.Minutes, ref pointer, ref flags, rng); ParseWhiteSpace(CronField.Minutes, ref pointer); uint hour = (uint)ParseField(CronField.Hours, ref pointer, ref flags, rng); ParseWhiteSpace(CronField.Hours, ref pointer); uint dayOfMonth = (uint)ParseDayOfMonth(ref pointer, ref flags, ref lastDayOffset, rng); ParseWhiteSpace(CronField.DaysOfMonth, ref pointer); ushort month = (ushort)ParseField(CronField.Months, ref pointer, ref flags, rng); ParseWhiteSpace(CronField.Months, ref pointer); byte b = (byte)ParseDayOfWeek(ref pointer, ref flags, ref nthWeekDay, rng); ParseEndOfString(ref pointer); if ((b & 0x81) != 0) { b |= 0x81; } return new CronExpression(value, minute, hour, dayOfMonth, month, b, nthWeekDay, lastDayOffset, flags); } } } private unsafe static void SkipWhiteSpaces(ref char* pointer) { checked { while (IsWhiteSpace(*pointer)) { pointer++; } } } private unsafe static void ParseWhiteSpace(CronField prevField, ref char* pointer) { if (!IsWhiteSpace(*pointer)) { ThrowFormatException(prevField, "Unexpected character '{0}'.", *pointer); } SkipWhiteSpaces(ref pointer); } private unsafe static void ParseEndOfString(ref char* pointer) { if (!IsWhiteSpace(*pointer) && !IsEndOfString(*pointer)) { ThrowFormatException(CronField.DaysOfWeek, "Unexpected character '{0}'.", *pointer); } SkipWhiteSpaces(ref pointer); if (!IsEndOfString(*pointer)) { ThrowFormatException("Unexpected character '{0}'.", *pointer); } } private unsafe static CronExpression? ParseMacro(ref char* pointer, int? jitterSeed) { checked { switch (ToUpper(*(pointer++))) { case 65u: return TryParseAnnuallyMacro(ref pointer, jitterSeed); case 68u: return TryParseDailyMacro(ref pointer, jitterSeed); case 69u: return TryParseEveryMacro(ref pointer, jitterSeed); case 72u: return TryParseHourlyMacro(ref pointer, jitterSeed); case 77u: return TryParseMonthlyOrMidnightMacro(ref pointer, jitterSeed); case 87u: return TryParseWeeklyMacro(ref pointer, jitterSeed); case 89u: return TryParseYearlyMacro(ref pointer, jitterSeed); default: pointer--; return null; } } } private unsafe static CronExpression? TryParseYearlyMacro(ref char* pointer, int? jitterSeed) { if (AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'A') && AcceptCharacter(ref pointer, 'R') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { if (jitterSeed.HasValue) { return CronExpression.YearlyWithJitter(jitterSeed.Value); } return CronExpression.Yearly; } return null; } private unsafe static CronExpression? TryParseWeeklyMacro(ref char* pointer, int? jitterSeed) { if (AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'K') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { if (jitterSeed.HasValue) { return CronExpression.WeeklyWithJitter(jitterSeed.Value); } return CronExpression.Weekly; } return null; } private unsafe static CronExpression? TryParseMonthlyOrMidnightMacro(ref char* pointer, int? jitterSeed) { if (AcceptCharacter(ref pointer, 'O') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'T') && AcceptCharacter(ref pointer, 'H') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { if (jitterSeed.HasValue) { return CronExpression.MonthlyWithJitter(jitterSeed.Value); } return CronExpression.Monthly; } if (ToUpper(*(ushort*)checked(unchecked((nuint)pointer) - (nuint)2u)) == 77 && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'D') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'G') && AcceptCharacter(ref pointer, 'H') && AcceptCharacter(ref pointer, 'T')) { if (jitterSeed.HasValue) { return CronExpression.DailyWithJitter(jitterSeed.Value); } return CronExpression.Daily; } return null; } private unsafe static CronExpression? TryParseHourlyMacro(ref char* pointer, int? jitterSeed) { if (AcceptCharacter(ref pointer, 'O') && AcceptCharacter(ref pointer, 'U') && AcceptCharacter(ref pointer, 'R') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { if (jitterSeed.HasValue) { return CronExpression.HourlyWithJitter(jitterSeed.Value); } return CronExpression.Hourly; } return null; } private unsafe static CronExpression? TryParseEveryMacro(ref char* pointer, int? jitterSeed) { if (AcceptCharacter(ref pointer, 'V') && AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'R') && AcceptCharacter(ref pointer, 'Y') && Accept(ref pointer, '_')) { if (AcceptCharacter(ref pointer, 'M') && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'U') && AcceptCharacter(ref pointer, 'T') && AcceptCharacter(ref pointer, 'E')) { if (jitterSeed.HasValue) { return CronExpression.EveryMinuteWithJitter(jitterSeed.Value); } return CronExpression.EveryMinute; } if (*(ushort*)checked(unchecked((nuint)pointer) - (nuint)2u) != 95) { return null; } if (AcceptCharacter(ref pointer, 'S') && AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'C') && AcceptCharacter(ref pointer, 'O') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'D')) { return CronExpression.EverySecond; } } return null; } private unsafe static CronExpression? TryParseDailyMacro(ref char* pointer, int? jitterSeed) { if (AcceptCharacter(ref pointer, 'A') && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { if (jitterSeed.HasValue) { return CronExpression.DailyWithJitter(jitterSeed.Value); } return CronExpression.Daily; } return null; } private unsafe static CronExpression? TryParseAnnuallyMacro(ref char* pointer, int? jitterSeed) { if (AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'N') && AcceptCharacter(ref pointer, 'U') && AcceptCharacter(ref pointer, 'A') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y')) { if (jitterSeed.HasValue) { return CronExpression.YearlyWithJitter(jitterSeed.Value); } return CronExpression.Yearly; } return null; } private unsafe static ulong ParseField(CronField field, ref char* pointer, ref CronExpressionFlag flags, Random? rng) { if (Accept(ref pointer, '*') || Accept(ref pointer, '?')) { if (field.CanDefineInterval) { flags |= CronExpressionFlag.Interval; } return ParseStar(field, ref pointer); } if (Accept(ref pointer, 'H')) { return ParseHash(field, ref pointer, ref flags, rng); } int low = ParseValue(field, ref pointer); ulong num = ParseRange(field, ref pointer, low, ref flags); if (Accept(ref pointer, ',')) { num |= ParseList(field, ref pointer, ref flags); } return num; } private unsafe static ulong ParseDayOfMonth(ref char* pointer, ref CronExpressionFlag flags, ref byte lastDayOffset, Random? rng) { CronField daysOfMonth = CronField.DaysOfMonth; if (Accept(ref pointer, '*') || Accept(ref pointer, '?')) { return ParseStar(daysOfMonth, ref pointer); } if (Accept(ref pointer, 'H')) { return ParseHash(daysOfMonth, ref pointer, ref flags, rng); } if (AcceptCharacter(ref pointer, 'L')) { return ParseLastDayOfMonth(daysOfMonth, ref pointer, ref flags, ref lastDayOffset); } int num = ParseValue(daysOfMonth, ref pointer); if (AcceptCharacter(ref pointer, 'W')) { flags |= CronExpressionFlag.NearestWeekday; return GetBit(num); } ulong num2 = ParseRange(daysOfMonth, ref pointer, num, ref flags); if (Accept(ref pointer, ',')) { num2 |= ParseList(daysOfMonth, ref pointer, ref flags); } return num2; } private unsafe static ulong ParseDayOfWeek(ref char* pointer, ref CronExpressionFlag flags, ref byte nthWeekDay, Random? rng) { CronField daysOfWeek = CronField.DaysOfWeek; if (Accept(ref pointer, '*') || Accept(ref pointer, '?')) { return ParseStar(daysOfWeek, ref pointer); } if (Accept(ref pointer, 'H')) { return ParseHash(daysOfWeek, ref pointer, ref flags, rng); } int num = ParseValue(daysOfWeek, ref pointer); if (AcceptCharacter(ref pointer, 'L')) { return ParseLastWeekDay(num, ref flags); } if (Accept(ref pointer, '#')) { return ParseNthWeekDay(daysOfWeek, ref pointer, num, ref flags, out nthWeekDay); } ulong num2 = ParseRange(daysOfWeek, ref pointer, num, ref flags); if (Accept(ref pointer, ',')) { num2 |= ParseList(daysOfWeek, ref pointer, ref flags); } return num2; } private unsafe static ulong ParseStar(CronField field, ref char* pointer) { if (!Accept(ref pointer, '/')) { return field.AllBits; } return ParseStep(field, ref pointer, field.First, field.Last); } private unsafe static ulong ParseHash(CronField field, ref char* pointer, ref CronExpressionFlag flags, Random? rng) { if (rng == null) { throw new MissingSeedException(); } int num = ((field == CronField.DaysOfMonth) ? 28 : field.Last); if (Accept(ref pointer, '/')) { if (field.CanDefineInterval) { flags |= CronExpressionFlag.Interval; } return ParseHashStep(field, ref pointer, field.First, num, rng); } return GetBit(rng.Next(field.First, checked(num + 1))); } private unsafe static ulong ParseList(CronField field, ref char* pointer, ref CronExpressionFlag flags) { int low = ParseValue(field, ref pointer); ulong num = ParseRange(field, ref pointer, low, ref flags); while (Accept(ref pointer, ',')) { num |= ParseList(field, ref pointer, ref flags); } return num; } private unsafe static ulong ParseRange(CronField field, ref char* pointer, int low, ref CronExpressionFlag flags) { if (!Accept(ref pointer, '-')) { if (!Accept(ref pointer, '/')) { return GetBit(low); } if (field.CanDefineInterval) { flags |= CronExpressionFlag.Interval; } return ParseStep(field, ref pointer, low, field.Last); } if (field.CanDefineInterval) { flags |= CronExpressionFlag.Interval; } int num = ParseValue(field, ref pointer); if (Accept(ref pointer, '/')) { return ParseStep(field, ref pointer, low, num); } return GetBits(field, low, num, 1); } private unsafe static ulong ParseStep(CronField field, ref char* pointer, int low, int high) { int step = ParseNumber(field, ref pointer, 1, field.Last); return GetBits(field, low, high, step); } private unsafe static ulong ParseHashStep(CronField field, ref char* pointer, int low, int high, Random rng) { int num = ParseNumber(field, ref pointer, 1, high); int num2 = rng.Next(0, num); return GetBits(field, checked(low + num2), high, num); } private unsafe static ulong ParseLastDayOfMonth(CronField field, ref char* pointer, ref CronExpressionFlag flags, ref byte lastMonthOffset) { flags |= CronExpressionFlag.DayOfMonthLast; if (Accept(ref pointer, '-')) { lastMonthOffset = checked((byte)ParseNumber(field, ref pointer, 0, field.Last - 1)); } if (AcceptCharacter(ref pointer, 'W')) { flags |= CronExpressionFlag.NearestWeekday; } return field.AllBits; } private unsafe static ulong ParseNthWeekDay(CronField field, ref char* pointer, int dayOfWeek, ref CronExpressionFlag flags, out byte nthDayOfWeek) { nthDayOfWeek = checked((byte)ParseNumber(field, ref pointer, 1, 5)); flags |= CronExpressionFlag.NthDayOfWeek; return GetBit(dayOfWeek); } private static ulong ParseLastWeekDay(int dayOfWeek, ref CronExpressionFlag flags) { flags |= CronExpressionFlag.DayOfWeekLast; return GetBit(dayOfWeek); } private unsafe static bool Accept(ref char* pointer, char character) { checked { if (*pointer == character) { pointer++; return true; } return false; } } private unsafe static bool AcceptCharacter(ref char* pointer, char character) { checked { if (ToUpper(*pointer) == character) { pointer++; return true; } return false; } } private unsafe static int ParseNumber(CronField field, ref char* pointer, int low, int high) { int number = GetNumber(ref pointer, null); if (number == -1 || number < low || number > high) { ThrowFormatException(field, "Value must be a number between {0} and {1} (all inclusive).", low, high); } return number; } private unsafe static int ParseValue(CronField field, ref char* pointer) { int number = GetNumber(ref pointer, field.Names); if (number == -1 || number < field.First || number > field.Last) { ThrowFormatException(field, "Value must be a number between {0} and {1} (all inclusive).", field.First, field.Last); } return number; } private static ulong GetBits(CronField field, int num1, int num2, int step) { if (num2 < num1) { return GetReversedRangeBits(field, num1, num2, step); } checked { if (step == 1) { return unchecked((ulong)(1L << checked(num2 + 1))) - unchecked((ulong)(1L << num1)); } return GetRangeBits(num1, num2, step); } } private static ulong GetRangeBits(int low, int high, int step) { ulong value = 0uL; for (int i = low; i <= high; i = checked(i + step)) { SetBit(ref value, i); } return value; } private static ulong GetReversedRangeBits(CronField field, int num1, int num2, int step) { int num3 = field.Last; checked { if (field == CronField.DaysOfWeek) { num3--; } ulong rangeBits = GetRangeBits(num1, num3, step); num1 = field.First + step - unchecked(checked(num3 - num1) % step) - 1; return rangeBits | GetRangeBits(num1, num2, step); } } private static ulong GetBit(int num1) { return (ulong)(1L << num1); } private unsafe static int GetNumber(ref char* pointer, int[]? names) { checked { if (IsDigit(*pointer)) { int numeric = GetNumeric(*(pointer++)); if (!IsDigit(*pointer)) { return numeric; } numeric = numeric * 10 + GetNumeric(*(pointer++)); if (!IsDigit(*pointer)) { return numeric; } return -1; } if (names == null) { return -1; } if (!IsLetter(*pointer)) { return -1; } uint num = ToUpper(*(pointer++)); if (!IsLetter(*pointer)) { return -1; } num |= ToUpper(*(pointer++)) << 8; if (!IsLetter(*pointer)) { return -1; } num |= ToUpper(*(pointer++)) << 16; int num2 = names.Length; for (int i = 0; i < num2; i++) { if (num == names[i]) { return i; } } return -1; } } private static void SetBit(ref ulong value, int index) { value |= (ulong)(1L << index); } private static bool IsEndOfString(int code) { return code == 0; } private static bool IsWhiteSpace(int code) { if (code != 9) { return code == 32; } return true; } private static bool IsDigit(int code) { if (code >= 48) { return code <= 57; } return false; } private static bool IsLetter(int code) { if (code < 65 || code > 90) { if (code >= 97) { return code <= 122; } return false; } return true; } private static int GetNumeric(int code) { return checked(code - 48); } private static uint ToUpper(uint code) { if (code >= 97 && code <= 122) { return checked(code - 32); } return code; } [MethodImpl(MethodImplOptions.NoInlining)] [DoesNotReturn] private static void ThrowFormatException(CronField field, string format, params object[] args) { throw new CronFormatException(string.Format("{0} {1}: {2}", "The given cron expression has an invalid format.", field, string.Format(CultureInfo.CurrentCulture, format, args))); } [MethodImpl(MethodImplOptions.NoInlining)] [DoesNotReturn] private static void ThrowFormatException(string format, params object[] args) { throw new CronFormatException("The given cron expression has an invalid format. " + string.Format(CultureInfo.CurrentCulture, format, args)); } } internal static class DateTimeHelper { private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1.0); public static DateTimeOffset FloorToSeconds(DateTimeOffset dateTimeOffset) { return dateTimeOffset.AddTicks(checked(-GetExtraTicks(dateTimeOffset.Ticks))); } public static bool IsRound(DateTimeOffset dateTimeOffset) { return GetExtraTicks(dateTimeOffset.Ticks) == 0; } private static long GetExtraTicks(long ticks) { TimeSpan oneSecond = OneSecond; return ticks % oneSecond.Ticks; } } [Serializable] internal class MissingSeedException : Exception { internal const string BaseMessage = "Using H in the format requires providing a jitter seed."; public MissingSeedException() : this("Using H in the format requires providing a jitter seed.") { } public MissingSeedException(string message) : base(message) { } public MissingSeedException(string message, Exception innerException) : base(message, innerException) { } protected MissingSeedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } internal static class TimeZoneHelper { public static bool IsAmbiguousTime(TimeZoneInfo zone, DateTime ambiguousTime) { return zone.IsAmbiguousTime(ambiguousTime.AddTicks(1L)); } public static TimeSpan GetDaylightOffset(TimeZoneInfo zone, DateTime ambiguousDateTime) { TimeSpan[] ambiguousOffsets = GetAmbiguousOffsets(zone, ambiguousDateTime); TimeSpan utcOffset = zone.GetUtcOffset(ambiguousDateTime); if (ambiguousOffsets[0] != utcOffset) { return ambiguousOffsets[0]; } return ambiguousOffsets[1]; } public static DateTimeOffset GetDaylightTimeStart(TimeZoneInfo zone, DateTime invalidDateTime) { DateTime dateTime = new DateTime(invalidDateTime.Year, invalidDateTime.Month, invalidDateTime.Day, invalidDateTime.Hour, invalidDateTime.Minute, 0, 0, invalidDateTime.Kind); while (zone.IsInvalidTime(dateTime)) { dateTime = dateTime.AddMinutes(1.0); } TimeSpan utcOffset = zone.GetUtcOffset(dateTime); return new DateTimeOffset(dateTime, utcOffset); } public static DateTimeOffset GetStandardTimeEnd(TimeZoneInfo zone, DateTime invalidDateTime) { DateTime dateTime = new DateTime(invalidDateTime.Year, invalidDateTime.Month, invalidDateTime.Day, invalidDateTime.Hour, invalidDateTime.Minute, 0, 0, invalidDateTime.Kind); while (zone.IsInvalidTime(dateTime)) { dateTime = dateTime.AddMinutes(-1.0); } TimeSpan utcOffset = zone.GetUtcOffset(dateTime); return new DateTimeOffset(dateTime.AddMinutes(1.0).AddSeconds(-1.0), utcOffset); } public static DateTimeOffset GetStandardTimeStart(TimeZoneInfo zone, DateTime ambiguousTime, TimeSpan daylightOffset) { DateTime dstTransitionEndDateTime = GetDstTransitionEndDateTime(zone, ambiguousTime); TimeSpan utcOffset = zone.GetUtcOffset(ambiguousTime); return new DateTimeOffset(dstTransitionEndDateTime, daylightOffset).ToOffset(utcOffset); } public static DateTimeOffset GetAmbiguousIntervalEnd(TimeZoneInfo zone, DateTime ambiguousTime) { DateTime dstTransitionEndDateTime = GetDstTransitionEndDateTime(zone, ambiguousTime); TimeSpan utcOffset = zone.GetUtcOffset(ambiguousTime); return new DateTimeOffset(dstTransitionEndDateTime, utcOffset); } public static DateTimeOffset GetDaylightTimeEnd(TimeZoneInfo zone, DateTime ambiguousTime, TimeSpan daylightOffset) { return new DateTimeOffset(GetDstTransitionEndDateTime(zone, ambiguousTime).AddTicks(-1L), daylightOffset); } private static TimeSpan[] GetAmbiguousOffsets(TimeZoneInfo zone, DateTime ambiguousTime) { return zone.GetAmbiguousTimeOffsets(ambiguousTime.AddTicks(1L)); } private static DateTime GetDstTransitionEndDateTime(TimeZoneInfo zone, DateTime ambiguousDateTime) { DateTime dateTime = new DateTime(ambiguousDateTime.Year, ambiguousDateTime.Month, ambiguousDateTime.Day, ambiguousDateTime.Hour, ambiguousDateTime.Minute, 0, 0, ambiguousDateTime.Kind); while (zone.IsAmbiguousTime(dateTime)) { dateTime = dateTime.AddMinutes(1.0); } return dateTime; } } }