using System; using System.Collections.Generic; using System.Globalization; using System.Linq; namespace Swan { /// /// Provides extension methods for . /// public static class DateExtensions { private static readonly Dictionary DateRanges = new Dictionary() { { "minute", 59}, { "hour", 23}, { "dayOfMonth", 31}, { "month", 12}, { "dayOfWeek", 6}, }; /// /// Converts the date to a YYYY-MM-DD string. /// /// The on which this method is called. /// The concatenation of date.Year, date.Month and date.Day. public static string ToSortableDate(this DateTime @this) => $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00}"; /// /// Converts the date to a YYYY-MM-DD HH:II:SS string. /// /// The on which this method is called. /// The concatenation of date.Year, date.Month, date.Day, date.Hour, date.Minute and date.Second. public static string ToSortableDateTime(this DateTime @this) => $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00} {@this.Hour:00}:{@this.Minute:00}:{@this.Second:00}"; /// /// Parses a YYYY-MM-DD and optionally it time part, HH:II:SS into a DateTime. /// /// The sortable date. /// /// A new instance of the DateTime structure to /// the specified year, month, day, hour, minute and second. /// /// sortableDate. /// /// Represents errors that occur during application execution. /// /// /// Unable to parse sortable date and time. - sortableDate. /// public static DateTime ToDateTime(this string @this) { if (string.IsNullOrWhiteSpace(@this)) throw new ArgumentNullException(nameof(@this)); var hour = 0; var minute = 0; var second = 0; var dateTimeParts = @this.Split(' '); try { if (dateTimeParts.Length != 1 && dateTimeParts.Length != 2) throw new Exception(); var dateParts = dateTimeParts[0].Split('-'); if (dateParts.Length != 3) throw new Exception(); var year = int.Parse(dateParts[0]); var month = int.Parse(dateParts[1]); var day = int.Parse(dateParts[2]); if (dateTimeParts.Length > 1) { var timeParts = dateTimeParts[1].Split(':'); if (timeParts.Length != 3) throw new Exception(); hour = int.Parse(timeParts[0]); minute = int.Parse(timeParts[1]); second = int.Parse(timeParts[2]); } return new DateTime(year, month, day, hour, minute, second); } catch (Exception) { throw new ArgumentException("Unable to parse sortable date and time.", nameof(@this)); } } /// /// Creates a date range. /// /// The start date. /// The end date. /// /// A sequence of integral numbers within a specified date's range. /// public static IEnumerable DateRange(this DateTime startDate, DateTime endDate) => Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d)); /// /// Rounds up a date to match a timespan. /// /// The datetime. /// The timespan to match. /// /// A new instance of the DateTime structure to the specified datetime and timespan ticks. /// public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan) => new DateTime(((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks) * timeSpan.Ticks); /// /// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC). /// /// The on which this method is called. /// Seconds since Unix epoch. public static long ToUnixEpochDate(this DateTime @this) => new DateTimeOffset(@this).ToUniversalTime().ToUnixTimeSeconds(); /// /// Compares a Date to another and returns a DateTimeSpan. /// /// The date start. /// The date end. /// A DateTimeSpan with the Years, Months, Days, Hours, Minutes, Seconds and Milliseconds between the dates. public static DateTimeSpan GetDateTimeSpan(this DateTime dateStart, DateTime dateEnd) => DateTimeSpan.CompareDates(dateStart, dateEnd); /// /// Compare the Date elements(Months, Days, Hours, Minutes). /// /// The on which this method is called. /// The minute (0-59). /// The hour. (0-23). /// The day of month. (1-31). /// The month. (1-12). /// The day of week. (0-6)(Sunday = 0). /// Returns true if Months, Days, Hours and Minutes match, otherwise false. public static bool AsCronCanRun(this DateTime @this, int? minute = null, int? hour = null, int? dayOfMonth = null, int? month = null, int? dayOfWeek = null) { var results = new List { GetElementParts(minute, @this.Minute), GetElementParts(hour, @this.Hour), GetElementParts(dayOfMonth, @this.Day), GetElementParts(month, @this.Month), GetElementParts(dayOfWeek, (int) @this.DayOfWeek), }; return results.Any(x => x != false); } /// /// Compare the Date elements(Months, Days, Hours, Minutes). /// /// The on which this method is called. /// The minute (0-59). /// The hour. (0-23). /// The day of month. (1-31). /// The month. (1-12). /// The day of week. (0-6)(Sunday = 0). /// Returns true if Months, Days, Hours and Minutes match, otherwise false. public static bool AsCronCanRun(this DateTime @this, string minute = "*", string hour = "*", string dayOfMonth = "*", string month = "*", string dayOfWeek = "*") { var results = new List { GetElementParts(minute, nameof(minute), @this.Minute), GetElementParts(hour, nameof(hour), @this.Hour), GetElementParts(dayOfMonth, nameof(dayOfMonth), @this.Day), GetElementParts(month, nameof(month), @this.Month), GetElementParts(dayOfWeek, nameof(dayOfWeek), (int) @this.DayOfWeek), }; return results.Any(x => x != false); } /// /// Converts a to the RFC1123 format. /// /// The on which this method is called. /// The string representation of according to RFC1123. /// /// If is not a UTC date / time, its UTC equivalent is converted, leaving unchanged. /// public static string ToRfc1123String(this DateTime @this) => @this.ToUniversalTime().ToString("R", CultureInfo.InvariantCulture); private static bool? GetElementParts(int? status, int value) => status.HasValue ? status.Value == value : (bool?) null; private static bool? GetElementParts(string parts, string type, int value) { if (string.IsNullOrWhiteSpace(parts) || parts == "*") return null; if (parts.Contains(",")) { return parts.Split(',').Select(int.Parse).Contains(value); } var stop = DateRanges[type]; if (parts.Contains("/")) { var multiple = int.Parse(parts.Split('/').Last()); var start = type == "dayOfMonth" || type == "month" ? 1 : 0; for (var i = start; i <= stop; i += multiple) if (i == value) return true; return false; } if (parts.Contains("-")) { var range = parts.Split('-'); var start = int.Parse(range.First()); stop = Math.Max(stop, int.Parse(range.Last())); if ((type == "dayOfMonth" || type == "month") && start == 0) start = 1; for (var i = start; i <= stop; i++) if (i == value) return true; return false; } return int.Parse(parts) == value; } } }