Decompiled source of Cron Job v1.12.0

CronJob.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
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+05f30f41dc4f285e3424a8d17d297196f00efe31")]
[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]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 = "";
	}
	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;

		protected CronBaseJob(CronEntryData data)
		{
		}
	}
	[BepInPlugin("cron_job", "Cron Job", "1.12")]
	public class CronJob : BaseUnityPlugin
	{
		public const string GUID = "cron_job";

		public const string NAME = "Cron Job";

		public const string VERSION = "1.12";

		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 HashSet<ZNetPeer> HandledPeers = new HashSet<ZNetPeer>();

		private static DateTime? Parse(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)
			{
				DateTime dateTime2 = (job.UseGameTime ? dateTime : utcNow);
				DateTime dateTime3 = (job.UseGameTime ? PreviousGameTime : Previous);
				if (!(dateTime2 < dateTime3))
				{
					DateTime value = dateTime2;
					DateTime? dateTime4 = Parse(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(Vector2i 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_02ef: 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_036b: 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 cron) => !cron.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 cron2 in list)
			{
				DateTime value = utcNow;
				DateTime? dateTime = Parse(cron2.Schedule, previous);
				if (value < dateTime)
				{
					continue;
				}
				Vector3 pos = ZoneSystem.GetZonePos(zone);
				if (((int)cron2.Biomes != 0 && (instance3.GetBiome(pos.x, pos.y, 0.02f, false) & cron2.Biomes) == 0 && (instance3.GetBiome(pos.x + 32f, pos.y + 32f, 0.02f, false) & cron2.Biomes) == 0 && (instance3.GetBiome(pos.x + 32f, pos.y - 32f, 0.02f, false) & cron2.Biomes) == 0 && (instance3.GetBiome(pos.x - 32f, pos.y + 32f, 0.02f, false) & cron2.Biomes) == 0 && (instance3.GetBiome(pos.x - 32f, pos.y + 32f, 0.02f, false) & cron2.Biomes) == 0) || (cron2.Locations.Count > 0 && (!instance.m_locationInstances.TryGetValue(zone, out var value2) || !cron2.Locations.Contains(value2.m_location?.m_prefabName ?? ""))))
				{
					continue;
				}
				if (cron2.Objects.Count > 0)
				{
					int num = instance2.SectorToIndex(zone);
					if (num < 0 || num >= instance2.m_objectsBySector.Length)
					{
						continue;
					}
					List<ZDO> list2 = instance2.m_objectsBySector[num];
					if (list2 == null || list2.All((ZDO zdo) => !cron2.Objects.Contains(zdo.m_prefab)))
					{
						continue;
					}
				}
				if (cron2.BannedObjects.Count > 0)
				{
					int num2 = instance2.SectorToIndex(zone);
					if (num2 < 0 || num2 >= instance2.m_objectsBySector.Length)
					{
						continue;
					}
					List<ZDO> list3 = instance2.m_objectsBySector[num2];
					if (list3 == null || list3.Any((ZDO zdo) => cron2.BannedObjects.Contains(zdo.m_prefab)))
					{
						continue;
					}
				}
				RunJob(cron2.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(), cron2.Chance, cron2.Log);
			}
			return true;
		}

		[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)
		{
			Action action2 = 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
				{
					action2();
				}
			}
		}

		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)
		{
			return new HashSet<int>(from s in Split(arg)
				select StringExtensionMethods.GetStableHashCode(s));
		}

		public static HashSet<string> ToSet(string arg)
		{
			return new HashSet<string>(Split(arg));
		}

		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<Vector2i, DateTime> ZoneTimestamps = new Dictionary<Vector2i, DateTime>();

		private static readonly HashSet<Vector2i> Zones = new HashSet<Vector2i>();

		private static readonly HashSet<Vector2i> PlayerZones = new HashSet<Vector2i>();

		[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<Vector2i, DateTime> kvp) => $"{kvp.Key.x},{kvp.Key.y}", (KeyValuePair<Vector2i, DateTime> kvp) => kvp.Value.Ticks);
			contents = Data.Serializer().Serialize((object)dictionary2);
			File.WriteAllText(FilePathZone, contents);
		}

		public static void ZoneTrackFromFile()
		{
			ZoneTimestamps = new Dictionary<Vector2i, DateTime>();
			if (!File.Exists(FilePathZone))
			{
				return;
			}
			try
			{
				ZoneTimestamps = ((IEnumerable<KeyValuePair<string, long>>)Data.Read<Dictionary<string, long>>(FilePathZone)).ToDictionary((Func<KeyValuePair<string, long>, Vector2i>)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 Vector2i(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<Vector2i> 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 (Vector2i zone in Zones)
			{
				Poke(zone, playerZones.Contains(zone));
			}
		}

		private static void Poke(Vector2i 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]) : null);
			if (CronManager.Execute(zone, hasPlayer, previous))
			{
				ZoneTimestamps[zone] = DateTime.UtcNow;
			}
		}

		private static void TrackPeer(HashSet<Vector2i> 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_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			Vector2i zone = ZoneSystem.GetZone(pos);
			int num = instance.m_activeArea + instance.m_activeDistantArea;
			Vector2i val = default(Vector2i);
			for (int i = zone.y - num; i <= zone.y + num; i++)
			{
				for (int j = zone.x - num; j <= zone.x + num; j++)
				{
					((Vector2i)(ref val))..ctor(j, i);
					if (instance.IsZoneGenerated(val))
					{
						zones.Add(val);
					}
				}
			}
		}

		private static HashSet<Vector2i> 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 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);
			int num = year - 1;
			return (num * 365 + num / 4 - num / 100 + num / 400 + array[month - 1] + day - 1) * 864000000000L + ((long)hour * 3600L + (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)
		{
			second = (int)(ticks / 10000000 % 60);
			if (ticks % 10000000 != 0L)
			{
				second++;
			}
			minute = (int)(ticks / 600000000 % 60);
			hour = (int)(ticks / 36000000000L % 24);
			int num = (int)(ticks / 864000000000L);
			int num2 = num / 146097;
			num -= num2 * 146097;
			int num3 = num / 36524;
			if (num3 == 4)
			{
				num3 = 3;
			}
			num -= num3 * 36524;
			int num4 = num / 1461;
			num -= num4 * 1461;
			int num5 = 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 = year - 1;
			return (DayOfWeek)((int)((num * 365 + num / 4 - num / 100 + 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 != 0)
			{
				return day;
			}
			if (dayOfWeek != 0)
			{
				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)
		{
			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 day + 7 > GetDaysInMonth(year, month);
		}
	}
	internal sealed class CronExpression : IEquatable<CronExpression>
	{
		private const long NotFound = 0L;

		private const int MaxYear = 2499;

		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 long _second;

		private readonly long _minute;

		private readonly int _hour;

		private readonly int _dayOfMonth;

		private readonly short _month;

		private readonly byte _dayOfWeek;

		private readonly byte _nthDayOfWeek;

		private readonly byte _lastMonthOffset;

		private readonly CronExpressionFlag _flags;

		internal CronExpression(long second, long minute, int hour, int dayOfMonth, short 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, CronFormat format)
		{
			if (string.IsNullOrEmpty(expression))
			{
				throw new ArgumentNullException("expression");
			}
			return CronParser.Parse(expression, format);
		}

		public static bool TryParse(string expression, out CronExpression cronExpression)
		{
			return TryParse(expression, CronFormat.Standard, out cronExpression);
		}

		public static bool TryParse(string expression, CronFormat format, out CronExpression cronExpression)
		{
			if (expression == null)
			{
				throw new ArgumentNullException("expression");
			}
			try
			{
				cronExpression = Parse(expression, format);
				return true;
			}
			catch (CronFormatException)
			{
				cronExpression = null;
				return false;
			}
		}

		public DateTime? GetNextOccurrence(DateTime fromUtc, bool inclusive = false)
		{
			if (fromUtc.Kind != DateTimeKind.Utc)
			{
				ThrowWrongDateTimeKindException("fromUtc");
			}
			if (fromUtc.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("fromUtc");
			}
			long num = FindOccurrence(fromUtc.Ticks, inclusive);
			if (num == 0L)
			{
				return null;
			}
			return new DateTime(num, DateTimeKind.Utc);
		}

		public IEnumerable<DateTime> GetOccurrences(DateTime fromUtc, DateTime toUtc, bool fromInclusive = true, bool toInclusive = false)
		{
			if (fromUtc > toUtc)
			{
				ThrowFromShouldBeLessThanToException("fromUtc", "toUtc");
			}
			if (fromUtc.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("fromUtc");
			}
			if (toUtc.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("toUtc");
			}
			DateTime? occurrence = GetNextOccurrence(fromUtc, fromInclusive);
			while (occurrence < toUtc || (occurrence == toUtc && toInclusive))
			{
				yield return occurrence.Value;
				occurrence = GetNextOccurrence(occurrence.Value);
			}
		}

		public DateTime? GetNextOccurrence(DateTime fromUtc, TimeZoneInfo zone, bool inclusive = false)
		{
			if (fromUtc.Kind != DateTimeKind.Utc)
			{
				ThrowWrongDateTimeKindException("fromUtc");
			}
			if (fromUtc.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("fromUtc");
			}
			if (zone == null)
			{
				ThrowArgumentNullException("zone");
			}
			if (zone == UtcTimeZone)
			{
				long num = FindOccurrence(fromUtc.Ticks, inclusive);
				if (num == 0L)
				{
					return null;
				}
				return new DateTime(num, DateTimeKind.Utc);
			}
			DateTimeOffset fromUtc2 = new DateTimeOffset(fromUtc);
			return GetOccurrenceConsideringTimeZone(fromUtc2, zone, inclusive)?.UtcDateTime;
		}

		public IEnumerable<DateTime> GetOccurrences(DateTime fromUtc, DateTime toUtc, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false)
		{
			if (fromUtc > toUtc)
			{
				ThrowFromShouldBeLessThanToException("fromUtc", "toUtc");
			}
			if (fromUtc.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("fromUtc");
			}
			if (toUtc.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("toUtc");
			}
			DateTime? occurrence = GetNextOccurrence(fromUtc, zone, fromInclusive);
			while (occurrence < toUtc || (occurrence == toUtc && toInclusive))
			{
				yield return occurrence.Value;
				occurrence = GetNextOccurrence(occurrence.Value, zone);
			}
		}

		public DateTimeOffset? GetNextOccurrence(DateTimeOffset from, TimeZoneInfo zone, bool inclusive = false)
		{
			if (from.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("from");
			}
			if (zone == null)
			{
				ThrowArgumentNullException("zone");
			}
			if (zone == UtcTimeZone)
			{
				long num = FindOccurrence(from.UtcTicks, inclusive);
				if (num == 0L)
				{
					return null;
				}
				return new DateTimeOffset(num, TimeSpan.Zero);
			}
			return GetOccurrenceConsideringTimeZone(from, zone, inclusive);
		}

		public IEnumerable<DateTimeOffset> GetOccurrences(DateTimeOffset from, DateTimeOffset to, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false)
		{
			if (from > to)
			{
				ThrowFromShouldBeLessThanToException("from", "to");
			}
			if (from.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("from");
			}
			if (to.Year > 2499)
			{
				ThrowDateTimeExceedsMaxException("to");
			}
			DateTimeOffset? occurrence = GetNextOccurrence(from, zone, fromInclusive);
			while (occurrence < to || (occurrence == to && toInclusive))
			{
				yield return occurrence.Value;
				occurrence = GetNextOccurrence(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 (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()
		{
			long second = _second;
			int num = second.GetHashCode() * 397;
			second = _minute;
			int num2 = (((((num ^ second.GetHashCode()) * 397) ^ _hour) * 397) ^ _dayOfMonth) * 397;
			short month = _month;
			int num3 = (num2 ^ month.GetHashCode()) * 397;
			byte dayOfWeek = _dayOfWeek;
			int num4 = (num3 ^ dayOfWeek.GetHashCode()) * 397;
			dayOfWeek = _nthDayOfWeek;
			int num5 = (num4 ^ dayOfWeek.GetHashCode()) * 397;
			dayOfWeek = _lastMonthOffset;
			return ((num5 ^ 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 != 0L)
					{
						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, dateTime3.Ticks - 1, inclusive);
					if (num2 != 0L)
					{
						return new DateTimeOffset(num2, utcOffset);
					}
				}
				dateTime = dateTime3;
				inclusive = true;
			}
			long num3 = FindOccurrence(dateTime.Ticks, inclusive);
			if (num3 == 0L)
			{
				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 long FindOccurrence(long startTimeTicks, long endTimeTicks, bool startInclusive)
		{
			long num = FindOccurrence(startTimeTicks, startInclusive);
			if (num == 0L || num > endTimeTicks)
			{
				return 0L;
			}
			return num;
		}

		private long FindOccurrence(long ticks, bool startInclusive)
		{
			if (!startInclusive)
			{
				ticks++;
			}
			CalendarHelper.FillDateTimeParts(ticks, out var second, out var minute, out var hour, out var day, out var month, out var year);
			int firstSet = GetFirstSet(_dayOfMonth);
			int fieldValue = second;
			int fieldValue2 = minute;
			int fieldValue3 = hour;
			int fieldValue4 = day;
			int fieldValue5 = month;
			int 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_00f6:
			while (fieldValue4 <= GetLastDayOfMonth(num, fieldValue5))
			{
				if (HasFlag(CronExpressionFlag.DayOfMonthLast))
				{
					fieldValue4 = GetLastDayOfMonth(num, fieldValue5);
				}
				int num2 = 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 = num2;
				if (!Move(_dayOfMonth, ref fieldValue4))
				{
					break;
				}
				continue;
				IL_018a:
				long num3 = CalendarHelper.DateTimeToTicks(num, fieldValue5, fieldValue4, fieldValue3, fieldValue2, fieldValue);
				if (num3 >= ticks)
				{
					return num3;
				}
				goto IL_01a5;
				IL_017d:
				fieldValue = GetFirstSet(_second);
				goto IL_018a;
			}
			goto IL_01bc;
			IL_01bc:
			if (!Move(_month, ref fieldValue5) && ++num >= 2499)
			{
				return 0L;
			}
			fieldValue4 = firstSet;
			goto IL_00f6;
		}

		private static bool Move(long fieldBits, ref int fieldValue)
		{
			if (fieldBits >> ++fieldValue == 0L)
			{
				fieldValue = GetFirstSet(fieldBits);
				return false;
			}
			fieldValue += GetFirstSet(fieldBits >> fieldValue);
			return true;
		}

		private int GetLastDayOfMonth(int year, int month)
		{
			return 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(long value)
		{
			ulong num = (ulong)((value & -value) * 157587932685088877L) >> 58;
			return DeBruijnPositions[num];
		}

		private bool HasFlag(CronExpressionFlag value)
		{
			return (_flags & value) != 0;
		}

		private static StringBuilder AppendFieldValue(StringBuilder expressionBuilder, CronField field, long fieldValue)
		{
			if (field.AllBits == fieldValue)
			{
				return expressionBuilder.Append('*');
			}
			if (field == CronField.DaysOfWeek)
			{
				fieldValue &= ~(1 << field.Last);
			}
			int firstSet = GetFirstSet(fieldValue);
			while (true)
			{
				expressionBuilder.Append(firstSet);
				if (fieldValue >> ++firstSet == 0L)
				{
					break;
				}
				expressionBuilder.Append(',');
				firstSet = GetFirstSet(fieldValue >> firstSet << firstSet);
			}
			return expressionBuilder;
		}

		private StringBuilder AppendDayOfMonth(StringBuilder expressionBuilder, int domValue)
		{
			if (HasFlag(CronExpressionFlag.DayOfMonthLast))
			{
				expressionBuilder.Append('L');
				if (_lastMonthOffset != 0)
				{
					expressionBuilder.Append($"-{_lastMonthOffset}");
				}
			}
			else
			{
				AppendFieldValue(expressionBuilder, CronField.DaysOfMonth, (uint)domValue);
			}
			if (HasFlag(CronExpressionFlag.NearestWeekday))
			{
				expressionBuilder.Append('W');
			}
			return expressionBuilder;
		}

		private void AppendDayOfWeek(StringBuilder expressionBuilder, int dowValue)
		{
			AppendFieldValue(expressionBuilder, CronField.DaysOfWeek, dowValue);
			if (HasFlag(CronExpressionFlag.DayOfWeekLast))
			{
				expressionBuilder.Append('L');
			}
			else if (HasFlag(CronExpressionFlag.NthDayOfWeek))
			{
				expressionBuilder.Append($"#{_nthDayOfWeek}");
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		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)]
		private static void ThrowWrongDateTimeKindException(string paramName)
		{
			throw new ArgumentException("The supplied DateTime must have the Kind property set to Utc", paramName);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void ThrowDateTimeExceedsMaxException(string paramName)
		{
			throw new ArgumentException($"The supplied DateTime is after the supported year of {2499}.", paramName);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void ThrowArgumentNullException(string paramName)
		{
			throw new ArgumentNullException(paramName);
		}

		private static bool GetBit(long value, int index)
		{
			return (value & (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 readonly string Name;

		public readonly int First;

		public readonly int Last;

		public readonly int[] Names;

		public readonly bool CanDefineInterval;

		public readonly long AllBits;

		static CronField()
		{
			MonthNames = new string[13]
			{
				null, "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++)
			{
				string text = MonthNames[i].ToUpperInvariant();
				_ = new char[3]
				{
					text[0],
					text[1],
					text[2]
				};
				int num = (int)(text[0] | ((uint)text[1] << 8) | ((uint)text[2] << 16));
				MonthNamesArray[i] = num;
			}
			for (int j = 0; j < DayOfWeekNames.Length; j++)
			{
				string text2 = DayOfWeekNames[j].ToUpperInvariant();
				_ = new char[3]
				{
					text2[0],
					text2[1],
					text2[2]
				};
				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++)
			{
				AllBits |= 1L << i;
			}
		}

		public override string ToString()
		{
			return Name;
		}
	}
	[Flags]
	internal enum CronFormat
	{
		Standard = 0,
		IncludeSeconds = 1
	}
	[Serializable]
	internal class CronFormatException : FormatException
	{
		private 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)
			: this(message, null)
		{
		}

		public CronFormatException(string message, Exception innerException)
			: base("The given cron expression has an invalid format. " + message, innerException)
		{
		}

		internal CronFormatException(CronField field, string message)
			: this(string.Format("{0} {1}: {2}", "The given cron expression has an invalid format.", field, message))
		{
		}

		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)
		{
			fixed (char* ptr = expression)
			{
				char* pointer = ptr;
				SkipWhiteSpaces(ref pointer);
				if (Accept(ref pointer, '@'))
				{
					CronExpression cronExpression = ParseMacro(ref pointer);
					SkipWhiteSpaces(ref pointer);
					if (cronExpression == null || !IsEndOfString(*pointer))
					{
						ThrowFormatException("Macro: Unexpected character '{0}' on position {1}.", *pointer, pointer - ptr);
					}
					return cronExpression;
				}
				long value = 0L;
				byte nthWeekDay = 0;
				byte lastDayOffset = 0;
				CronExpressionFlag flags = (CronExpressionFlag)0;
				if (format == CronFormat.IncludeSeconds)
				{
					value = ParseField(CronField.Seconds, ref pointer, ref flags);
					ParseWhiteSpace(CronField.Seconds, ref pointer);
				}
				else
				{
					SetBit(ref value, CronField.Seconds.First);
				}
				long minute = ParseField(CronField.Minutes, ref pointer, ref flags);
				ParseWhiteSpace(CronField.Minutes, ref pointer);
				int hour = (int)ParseField(CronField.Hours, ref pointer, ref flags);
				ParseWhiteSpace(CronField.Hours, ref pointer);
				int dayOfMonth = (int)ParseDayOfMonth(ref pointer, ref flags, ref lastDayOffset);
				ParseWhiteSpace(CronField.DaysOfMonth, ref pointer);
				short month = (short)ParseField(CronField.Months, ref pointer, ref flags);
				ParseWhiteSpace(CronField.Months, ref pointer);
				byte b = (byte)ParseDayOfWeek(ref pointer, ref flags, ref nthWeekDay);
				ParseEndOfString(ref pointer);
				if ((b & 0x81u) != 0)
				{
					b = (byte)(b | 0x81u);
				}
				return new CronExpression(value, minute, hour, dayOfMonth, month, b, nthWeekDay, lastDayOffset, flags);
			}
		}

		private unsafe static void SkipWhiteSpaces(ref char* pointer)
		{
			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)
		{
			switch (ToUpper(*(pointer++)))
			{
			case 65:
				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'))
				{
					return CronExpression.Yearly;
				}
				return null;
			case 68:
				if (AcceptCharacter(ref pointer, 'A') && AcceptCharacter(ref pointer, 'I') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y'))
				{
					return CronExpression.Daily;
				}
				return null;
			case 69:
				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'))
					{
						return CronExpression.EveryMinute;
					}
					if (*(pointer - 1) != '_')
					{
						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;
			case 72:
				if (AcceptCharacter(ref pointer, 'O') && AcceptCharacter(ref pointer, 'U') && AcceptCharacter(ref pointer, 'R') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y'))
				{
					return CronExpression.Hourly;
				}
				return null;
			case 77:
				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'))
				{
					return CronExpression.Monthly;
				}
				if (ToUpper(*(pointer - 1)) == 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'))
				{
					return CronExpression.Daily;
				}
				return null;
			case 87:
				if (AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'K') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y'))
				{
					return CronExpression.Weekly;
				}
				return null;
			case 89:
				if (AcceptCharacter(ref pointer, 'E') && AcceptCharacter(ref pointer, 'A') && AcceptCharacter(ref pointer, 'R') && AcceptCharacter(ref pointer, 'L') && AcceptCharacter(ref pointer, 'Y'))
				{
					return CronExpression.Yearly;
				}
				return null;
			default:
				pointer--;
				return null;
			}
		}

		private unsafe static long ParseField(CronField field, ref char* pointer, ref CronExpressionFlag flags)
		{
			if (Accept(ref pointer, '*') || Accept(ref pointer, '?'))
			{
				if (field.CanDefineInterval)
				{
					flags |= CronExpressionFlag.Interval;
				}
				return ParseStar(field, ref pointer);
			}
			int low = ParseValue(field, ref pointer);
			long num = ParseRange(field, ref pointer, low, ref flags);
			if (Accept(ref pointer, ','))
			{
				num |= ParseList(field, ref pointer, ref flags);
			}
			return num;
		}

		private unsafe static long ParseDayOfMonth(ref char* pointer, ref CronExpressionFlag flags, ref byte lastDayOffset)
		{
			CronField daysOfMonth = CronField.DaysOfMonth;
			if (Accept(ref pointer, '*') || Accept(ref pointer, '?'))
			{
				return ParseStar(daysOfMonth, ref pointer);
			}
			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);
			}
			long num2 = ParseRange(daysOfMonth, ref pointer, num, ref flags);
			if (Accept(ref pointer, ','))
			{
				num2 |= ParseList(daysOfMonth, ref pointer, ref flags);
			}
			return num2;
		}

		private unsafe static long ParseDayOfWeek(ref char* pointer, ref CronExpressionFlag flags, ref byte nthWeekDay)
		{
			CronField daysOfWeek = CronField.DaysOfWeek;
			if (Accept(ref pointer, '*') || Accept(ref pointer, '?'))
			{
				return ParseStar(daysOfWeek, ref pointer);
			}
			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);
			}
			long num2 = ParseRange(daysOfWeek, ref pointer, num, ref flags);
			if (Accept(ref pointer, ','))
			{
				num2 |= ParseList(daysOfWeek, ref pointer, ref flags);
			}
			return num2;
		}

		private unsafe static long 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 long ParseList(CronField field, ref char* pointer, ref CronExpressionFlag flags)
		{
			int low = ParseValue(field, ref pointer);
			long num = ParseRange(field, ref pointer, low, ref flags);
			while (Accept(ref pointer, ','))
			{
				num |= ParseList(field, ref pointer, ref flags);
			}
			return num;
		}

		private unsafe static long 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 long 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 long ParseLastDayOfMonth(CronField field, ref char* pointer, ref CronExpressionFlag flags, ref byte lastMonthOffset)
		{
			flags |= CronExpressionFlag.DayOfMonthLast;
			if (Accept(ref pointer, '-'))
			{
				lastMonthOffset = (byte)ParseNumber(field, ref pointer, 0, field.Last - 1);
			}
			if (AcceptCharacter(ref pointer, 'W'))
			{
				flags |= CronExpressionFlag.NearestWeekday;
			}
			return field.AllBits;
		}

		private unsafe static long ParseNthWeekDay(CronField field, ref char* pointer, int dayOfWeek, ref CronExpressionFlag flags, out byte nthDayOfWeek)
		{
			nthDayOfWeek = (byte)ParseNumber(field, ref pointer, 1, 5);
			flags |= CronExpressionFlag.NthDayOfWeek;
			return GetBit(dayOfWeek);
		}

		private static long ParseLastWeekDay(int dayOfWeek, ref CronExpressionFlag flags)
		{
			flags |= CronExpressionFlag.DayOfWeekLast;
			return GetBit(dayOfWeek);
		}

		private unsafe static bool Accept(ref char* pointer, char character)
		{
			if (*pointer == character)
			{
				pointer++;
				return true;
			}
			return false;
		}

		private unsafe static bool AcceptCharacter(ref char* pointer, char character)
		{
			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 long GetBits(CronField field, int num1, int num2, int step)
		{
			if (num2 < num1)
			{
				return GetReversedRangeBits(field, num1, num2, step);
			}
			if (step == 1)
			{
				return (1L << num2 + 1) - (1L << num1);
			}
			return GetRangeBits(num1, num2, step);
		}

		private static long GetRangeBits(int low, int high, int step)
		{
			long value = 0L;
			for (int i = low; i <= high; i += step)
			{
				SetBit(ref value, i);
			}
			return value;
		}

		private static long GetReversedRangeBits(CronField field, int num1, int num2, int step)
		{
			int num3 = field.Last;
			if (field == CronField.DaysOfWeek)
			{
				num3--;
			}
			long rangeBits = GetRangeBits(num1, num3, step);
			num1 = field.First + step - (num3 - num1) % step - 1;
			return rangeBits | GetRangeBits(num1, num2, step);
		}

		private static long GetBit(int num1)
		{
			return 1L << num1;
		}

		private unsafe static int GetNumber(ref char* pointer, int[] names)
		{
			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;
			}
			int 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 long value, int index)
		{
			value |= 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 code - 48;
		}

		private static int ToUpper(int code)
		{
			if (code >= 97 && code <= 122)
			{
				return code - 32;
			}
			return code;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void ThrowFormatException(CronField field, string format, params object[] args)
		{
			throw new CronFormatException(field, string.Format(CultureInfo.CurrentCulture, format, args));
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void ThrowFormatException(string format, params object[] args)
		{
			throw new CronFormatException(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(-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;
		}
	}
	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 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;
		}
	}
}