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