public FrequencyComponents GetClientFrequencyComponents()
        {
            var result = new FrequencyComponents {
                Recurrence = Recurrence
            };

            switch (Recurrence)
            {
            case RecurrenceType.Instant:
                break;

            case RecurrenceType.Daily:
                result.Time = ClientRunTime.TimeOfDay;
                break;

            case RecurrenceType.Weekly:
                result.Time    = ClientRunTime.TimeOfDay;
                result.WeekDay = ClientRunTime.DayOfWeek;
                break;

            case RecurrenceType.Monthly:
                result.Time             = ClientRunTime.TimeOfDay;
                result.Day              = ClientRunTime.Day;
                result.IsLastDayOfMonth = result.Day >= 28;
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(Recurrence), Recurrence, $"{nameof(Recurrence)} was out of range.");
            }

            return(result);
        }
        public static Frequency FromClientFrequencyComponents(FrequencyComponents clientComponents, TimeZoneInfo clientTimeZone)
        {
            if (clientComponents == null)
            {
                throw new ArgumentNullException(nameof(clientComponents));
            }
            if (clientTimeZone == null)
            {
                throw new ArgumentNullException(nameof(clientTimeZone));
            }

            var clientNow            = TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, clientTimeZone);
            var nearestClientRunTime = clientNow;
            var minutes = TimeSpan.FromMinutes((clientNow.Hour * 60) + clientNow.Minute);

            // NOTE: logic should break with InvalidOperationException because of invalid object state if any
            // ReSharper disable PossibleInvalidOperationException
            if (clientComponents.Recurrence == RecurrenceType.Daily && clientComponents.Time.Value <= minutes)
            {
                nearestClientRunTime = new DateTime(clientNow.Year, clientNow.Month, clientNow.Day).Add(TimeSpan.FromHours(24) + clientComponents.Time.Value);
            }
            else if (clientComponents.Recurrence == RecurrenceType.Weekly && (clientNow.DayOfWeek != clientComponents.WeekDay.Value || clientComponents.Time.Value <= minutes))
            {
                var daysToAdd = ((int)clientComponents.WeekDay.Value - (int)clientNow.DayOfWeek + 7) % 7;

                var nearestWeekDay = clientNow.AddDays(daysToAdd == 0 ? 7 : daysToAdd);

                nearestClientRunTime = new DateTime(nearestWeekDay.Year, nearestWeekDay.Month, nearestWeekDay.Day).Add(clientComponents.Time.Value);
            }
            else if (clientComponents.Recurrence == RecurrenceType.Monthly && (clientNow.Day != clientComponents.Day.Value || clientComponents.Time.Value <= minutes))
            {
                var daysLeftInCurrentMonth = DateTime.DaysInMonth(clientNow.Year, clientNow.Month) - clientNow.Day;
                var daysToAdd = clientNow.Day < clientComponents.Day.Value
                    ? clientComponents.IsLastDayOfMonth.HasValue && clientComponents.IsLastDayOfMonth.Value
                        ? daysLeftInCurrentMonth
                        : clientComponents.Day.Value - clientNow.Day
                    : clientComponents.IsLastDayOfMonth.HasValue && clientComponents.IsLastDayOfMonth.Value
                        ? daysLeftInCurrentMonth + DateTime.DaysInMonth(clientNow.Year, clientNow.Month + 1)
                        : daysLeftInCurrentMonth + clientComponents.Day.Value;

                var nearestMonthDay = clientNow.AddDays(daysToAdd);

                nearestClientRunTime = new DateTime(nearestMonthDay.Year, nearestMonthDay.Month, nearestMonthDay.Day).Add(clientComponents.Time.Value);
            }
            else if (clientComponents.Recurrence != RecurrenceType.Instant)
            {
                nearestClientRunTime = new DateTime(clientNow.Year, clientNow.Month, clientNow.Day).Add(clientComponents.Time.Value);
            }
            // ReSharper restore PossibleInvalidOperationException

            var result = FromUtcDateTime(TimeZoneInfo.ConvertTime(nearestClientRunTime, clientTimeZone, TimeZoneInfo.Utc), clientComponents.Recurrence, clientTimeZone);

            return(result);
        }