view ServerMonitor/Objects/Schedule.cs @ 17:68d7834dc28e

More comments.
author Brad Greco <brad@bgreco.net>
date Sat, 25 May 2019 15:14:26 -0400
parents d92176c5398a
children 2342e9459444
line wrap: on
line source

using System;

namespace ServerMonitorApp
{
    /// <summary>Schedule to control when a check is automatically executed.<summary>
    public class Schedule
    {
        // Required empty constructor for XML serialization.
        public Schedule() { }

        /// <summary>Schedule constructor</summary>
        /// <param name="units">The time units to use.</param>
        /// <param name="frequency">How frequently to run the check.</param>
        /// <param name="startTime">Time of day the check should begin running.</param>
        /// <param name="endTime">Time of day the check should stop running.</param>
        /// <remarks>
        /// If endTime is before startTime, then the endTime is interpreted as being on the next day.
        /// For example, if startTime = 17:00 and endTime is 8:00, then the check will start running
        /// at 17:00 and stop running at 8:00 the next day.
        /// </remarks>
        public Schedule(FrequencyUnits units, int frequency, TimeSpan startTime, TimeSpan endTime)
        {
            Units = units;
            Frequency = frequency;
            StartTime = startTime;
            EndTime = endTime;
        }

        /// <summary>How often the check should be executed.</summary>
        public int Frequency { get; set; }

        /// <summary>The time units used to interpret the frequency.</summary>
        public FrequencyUnits Units { get; set; }

        /// <summary>Time of day the check should begin running.</summary>
        public TimeSpan StartTime { get; set; }

        /// <summary>Time of day the check should stop running.</summary>
        public TimeSpan EndTime { get; set; }

        /// <summary>Given the last time a check was scheduled to run, calculates the next time in the future it should run.</summary>
        /// <param name="lastScheduledTime">The last time a check was scheduled to run.</param>
        public DateTime GetNextTime(DateTime lastScheduledTime)
        {
            return GetNextTime(lastScheduledTime, DateTime.Now);
        }

        /// <summary>Given the last time a check was scheduled to run, calculates the next time after the given date it should run.</summary>
        /// <param name="lastScheduledTime">The last time a check was scheduled to run.</param>
        /// <param name="minStartTime">The earliest allowed time to return.</param>
        /// <remarks>
        /// The next execution time of a check cannot necessarily be determined by taking the
        /// last execution time and adding the configured number of time units. The computer might
        /// have been asleep or the program might have not been running, so the next date might
        /// be in the past.
        /// 
        /// To best follow the schedule, we take the last execution time and fast-forward time
        /// by adding the configured time interval until we get a resulting time that is in the future.
        /// For example, suppose a check is scheduled to run every 5 minutes starting at 7:00.
        /// The check last ran at 7:40, and the computer was suspended shortly thereafter and resumed
        /// at 8:28. The next execution time is determined by adding 5 minutes to 7:40 until we obtain
        /// a time after 8:28, in this case, 8:30.
        /// </remarks>
        public DateTime GetNextTime(DateTime lastScheduledTime, DateTime minStartTime)
        {
            // Start by setting the next time to the last time the check was run.
            DateTime nextTime = lastScheduledTime;
            if (Units == FrequencyUnits.Day)
            {
                // If the check is scheduled only once a day, simply add days until we find a time in the future.
                while (nextTime < minStartTime)
                    nextTime = nextTime.AddDays(Frequency).Date.Add(StartTime);
            }
            else
            {
                // If the last run time was more than a day ago, fast-forward a day at a time to reduce the number of loops.
                if (nextTime < minStartTime.AddHours(-24))
                    nextTime = minStartTime.Date.Add(StartTime).AddHours(-24);
                // Add the configured time interval to the last run time until we obtain a time in the future.
                while (nextTime < minStartTime)
                {
                    switch (Units)
                    {
                        case FrequencyUnits.Second: nextTime = nextTime.AddSeconds(Frequency); break;
                        case FrequencyUnits.Minute: nextTime = nextTime.AddMinutes(Frequency); break;
                        case FrequencyUnits.Hour:   nextTime = nextTime.AddHours(Frequency);   break;
                        default: throw new InvalidOperationException("Unexpected frequency units: " + Units);
                    }
                }
                // Now we have the next date and time, but we don't know yet if it is within
                // the active times of day the check is allowed to run between.
                if (StartTime < EndTime)
                {
                    // The allowed start and end times are on the same day.
                    // If the next scheduled time is too early (before the allowed start time),
                    // wait to run it until the start time happens.
                    if (nextTime.TimeOfDay < StartTime)
                        nextTime = nextTime.Date + StartTime;
                    // If the next scheduled time is too late (after the allowed end time),
                    // wait to run it until the start time happens on the following day.
                    else if (nextTime.TimeOfDay > EndTime)
                        nextTime = nextTime.Date.AddDays(1) + StartTime;
                }
                else if (nextTime.TimeOfDay > EndTime && nextTime.TimeOfDay < StartTime)
                {
                    // The allowed start time is on the day after the allowed end time,
                    // and the next scheduled time is too early (before the allowed start time).
                    // Wait to run the check until the allowed start time happens.
                    nextTime = nextTime.Date + StartTime;
                }
            }
            return nextTime;
        }

        public override string ToString()
        {
            return string.Format("Every {0} {1}", Frequency, Units.ToString().ToLower() + (Frequency == 1 ? "" : "s"));
        }
    }

    /// <summary>Units of time that a check can be scheduled to run in intervals of.</summary>
    public enum FrequencyUnits { Second, Minute, Hour, Day }
}