C# DateTime helpers

Date: 2019-10-23
using System;
using System.Collections.Generic;
using System.Linq;

namespace Helpers
{
    public static class DateTimeHelper
    {
        public static DateTime? GetDateTimeFromInput(string dateTimeStr, DateTime? def = null)
        {
            if (string.IsNullOrWhiteSpace(dateTimeStr))
                return def;
            if (DateTime.TryParse(dateTimeStr.Trim(), CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var dateTime))
                return dateTime;
            return def;
        }


        public static TimeSpan? GetTimeFromInput(string timeStr, TimeSpan? def = null)
        {
            if (string.IsNullOrWhiteSpace(timeStr))
                return def;
            if (TimeSpan.TryParse(timeStr.Trim(), CultureInfo.InvariantCulture, out var time))
                return time;
            return def;
        }

        public static DateTime? GetCombinedDateTime(DateTime? date, TimeSpan? time)
        {
            if (date == null) return null;
            var d = date.Value.Date;
            if (time == null) return d;
            return d.Add(time.Value);
        }        
        
        public static int ISO8601WeekNumber2(DateTime fromDate)
        {
            var culture = new System.Globalization.CultureInfo("nl-NL"); 
            var format = culture.DateTimeFormat; 
            var weekOfYear = culture.Calendar.GetWeekOfYear(fromDate, format.CalendarWeekRule, format.FirstDayOfWeek);
        }


        public static int ISO8601WeekNumber(DateTime fromDate)
        {
            // Get jan 1st of the year
            DateTime startOfYear = fromDate.AddDays(-fromDate.Day + 1).AddMonths(-fromDate.Month + 1);
            // Get dec 31st of the year
            DateTime endOfYear = startOfYear.AddYears(1).AddDays(-1);
            // ISO 8601 weeks start with Monday 
            // The first week of a year includes the first Thursday 
            // DayOfWeek returns 0 for sunday up to 6 for saterday
            int[] iso8601Correction = { 6, 7, 8, 9, 10, 4, 5 };
            int nds = fromDate.Subtract(startOfYear).Days + iso8601Correction[(int)startOfYear.DayOfWeek];
            int wk = nds / 7;
            switch (wk)
            {
                case 0:
                    // Return weeknumber of dec 31st of the previous year
                    return ISO8601WeekNumber(startOfYear.AddDays(-1));
                case 53:
                    // If dec 31st falls before thursday it is week 01 of next year
                    if (endOfYear.DayOfWeek < DayOfWeek.Thursday)
                        return 1;
                    else
                        return wk;
                default: return wk;
            }
        }

        public static YearWeek GetWeekFromDate(DateTime date)
        {
            return new YearWeek
            {
                Year = date.Year,
                Week = ISO8601WeekNumber(date)
            };
        }

        public static DateTime GetDateFromISO8601Week(YearWeek week)
        {
            return GetDateFromISO8601Week(week.Year, week.Week);
        }

        public static DateTime GetDateFromISO8601Week(int year, int week)
        {
            var dayOfYear = (week - 1) * 7;
            var result = new DateTime(year, 1, 1).AddDays(dayOfYear);
            var dayOfWeek = (int)result.DayOfWeek;
            if (dayOfWeek <= (int)DayOfWeek.Thursday)
            {
                result = result.AddDays((-dayOfWeek) + 1);
            }
            else
            {
                result = result.AddDays(8 - dayOfWeek);
            }
            return result;
        }

        public static Range<DateTime> GetDateRangeFromISO8601Week(YearWeek week)
        {
            return GetDateRangeFromISO8601Week(week.Year, week.Week);
        }

        public static Range<DateTime> GetDateRangeFromISO8601Week(int year, int week)
        {
            var range = new Range<DateTime>();
            range.Minimum = GetDateFromISO8601Week(year, week);
            range.Maximum = range.Minimum.AddDays(6);
            return range;
        }


        public static List<DateTime> GetlistOfWeekDays(DateTime startDateParam)
        {
            DateTime startDate = startDateParam;// startDateParam.AddDays(-(((dow - (int)DayOfWeek.Monday) + 7) % 7));
            while (startDate.DayOfWeek != DayOfWeek.Monday)
            {
                startDate = startDate.AddDays(-1);
            }

            var endDate = startDate.AddDays(7);

            //the number of days in our range of dates
            var numDays = (int)((endDate - startDate).TotalDays);
            List<DateTime> myDates = Enumerable
                       //creates an IEnumerable of ints from 0 to numDays
                       .Range(0, numDays)
                       //now for each of those numbers (0..numDays), 
                       //select startDate plus x number of days
                       .Select(x => startDate.AddDays(x))
                       //and make a list
                       .ToList();

            return myDates;
        }

        public static DateTime FirstDateOfWeek(int year, int weekOfYear, System.Globalization.CultureInfo ci)
        {
            DateTime jan1 = new DateTime(year, 1, 1);
            int daysOffset = (int)ci.DateTimeFormat.FirstDayOfWeek - (int)jan1.DayOfWeek;
            DateTime firstWeekDay = jan1.AddDays(daysOffset);
            int firstWeek = ci.Calendar.GetWeekOfYear(jan1, ci.DateTimeFormat.CalendarWeekRule, ci.DateTimeFormat.FirstDayOfWeek);
            if (firstWeek <= 1 || firstWeek > 50)
            {
                weekOfYear -= 1;
            }
            return firstWeekDay.AddDays(weekOfYear * 7);
        }
        public static DateTime LastDateOfWeek(int year, int weekOfYear, System.Globalization.CultureInfo ci)
        {
            DateTime jan1 = new DateTime(year, 1, 1);
            int daysOffset = (int)ci.DateTimeFormat.FirstDayOfWeek - (int)jan1.DayOfWeek;
            DateTime firstWeekDay = jan1.AddDays(daysOffset);
            int firstWeek = ci.Calendar.GetWeekOfYear(jan1, ci.DateTimeFormat.CalendarWeekRule, ci.DateTimeFormat.FirstDayOfWeek);
            if (firstWeek <= 1 || firstWeek > 50)
            {
                weekOfYear -= 1;
            }
            return firstWeekDay.AddDays((weekOfYear + 1 * 7) - 1);
        }

        public static IEnumerable<DateTime> EachDay(DateTime start, DateTime end)
        {
            // Remove time info from start date (we only care about day). 
            DateTime currentDay = new DateTime(start.Year, start.Month, start.Day);
            while (currentDay <= end)
            {
                yield return currentDay;
                currentDay = currentDay.AddDays(1);
            }
        }

        public static IEnumerable<Tuple<int, int, int>> EachDayOfMonth(DateTime start, DateTime end)
        {
            // Remove time info from start date (we only care about day). 
            DateTime currentDay = new DateTime(start.Year, start.Month, start.Day);
            while (currentDay <= end)
            {
                yield return new Tuple<int, int, int>(currentDay.Year, currentDay.Month, currentDay.Day);
                currentDay = currentDay.AddDays(1);
            }
        }

        public static IEnumerable<Tuple<int, int>> EachMonth(DateTime start, DateTime end)
        {
            // Remove time info from start date (we only care about day). 
            var firstMonth = new DateTime(start.Year, start.Month, 1);
            var currentMonth = firstMonth;
            while (currentMonth <= end)
            {
                yield return new Tuple<int, int>(currentMonth.Year, currentMonth.Month);
                currentMonth = currentMonth.AddMonths(1);
            }
        }

        public static IEnumerable<YearWeek> EachWeek(YearWeek yearWeek, int numberOfWeeks)
        {
            var firstDateOfWeek = GetDateFromISO8601Week(yearWeek.Year, yearWeek.Week);
            return EachWeek(firstDateOfWeek, numberOfWeeks);
        }

        public static IEnumerable<YearWeek> EachWeek(DateTime start, int numberOfWeeks)
        {
            // Remove time info from start date (we only care about day). 
            DateTime currentDay = new DateTime(start.Year, start.Month, start.Day);
            int lastWeek = 0;
            int weekCounter = 0;
            while (weekCounter < numberOfWeeks)
            {
                var week = DateTimeHelper.ISO8601WeekNumber(currentDay);
                var year = currentDay.Year;
                if (week == 1)
                {
                    year = currentDay.AddMonths(1).Year;
                }
                else if (week >= 52)
                {
                    year = currentDay.AddMonths(-1).Year;
                }

                if (week != lastWeek)
                {
                    lastWeek = week;
                    weekCounter += 1;
                    currentDay = currentDay.AddDays(7);
                    yield return new YearWeek { Year = year, Week = week };
                }
                else
                {
                    currentDay = currentDay.AddDays(1);
                }
            }
        }

        public static IEnumerable<YearWeek> EachWeek(DateTime start, DateTime end)
        {
            // Remove time info from start date (we only care about day). 
            DateTime currentDay = new DateTime(start.Year, start.Month, start.Day);
            int lastWeek = 0;
            while (currentDay <= end)
            {
                var week = DateTimeHelper.ISO8601WeekNumber(currentDay);
                var year = currentDay.Year;
                if (week == 1)
                {
                    year = currentDay.AddMonths(1).Year;
                }
                else if (week >= 52)
                {
                    year = currentDay.AddMonths(-1).Year;
                }

                if (week != lastWeek)
                {
                    lastWeek = week;
                    currentDay = currentDay.AddDays(7);
                    yield return new YearWeek { Year = year, Week = week };
                }
                else
                {
                    currentDay = currentDay.AddDays(1);
                }
            }
        }


        public static int YearWeekCount(int year)
        {
            var week = ISO8601WeekNumber(new DateTime(year + 1, 1, 1));

            if (week == 53)
            {
                return 53;
            }
            else
            {
                return 52;
            }
        }

        public class YearWeek : IComparable
        {
            public int Year { get; set; }
            public int Week { get; set; }

            public int CompareTo(object o)
            {
                int c = 0;
                var b = (YearWeek)o;
                if (b != null)
                {
                    c = Year.CompareTo(b.Year);
                    if (c == 0)
                    {
                        c = Week.CompareTo(b.Week);
                    }
                }
                return c;
            }

            public override bool Equals(object obj)
            {
                if (obj != null && obj is YearWeek)
                {
                    var yw = (YearWeek)obj;
                    return yw.Year == Year && yw.Week == Week;
                }
                return false;
            }

            public override int GetHashCode()
            {
                return Year.GetHashCode() + Week.GetHashCode();
            }
        }

        public static bool IsGreaterThan<T>(this T value, T other) where T : IComparable
        {
            return value.CompareTo(other) > 0;
        }

        public static bool IsLessThan<T>(this T value, T other) where T : IComparable
        {
            return value.CompareTo(other) < 0;
        }

        public static YearWeek AddWeeks(YearWeek yearWeek, int numberOfWeeks)
        {
            int weeksToGo = Math.Abs(numberOfWeeks);
            int step = numberOfWeeks / Math.Abs(numberOfWeeks);
            var current = new YearWeek { Year = yearWeek.Year, Week = yearWeek.Week };

            while (weeksToGo > 0)
            {
                current.Week += step;
                if (current.Week < 1)
                {
                    current.Year -= 1;
                    current.Week = YearWeekCount(current.Year);
                }
                else if (current.Week > 52)
                {
                    var wc = YearWeekCount(current.Year);
                    if (current.Week > wc)
                    {
                        current.Week = 1;
                        current.Year += 1;
                    }
                }
                weeksToGo -= 1;
            }
            return current;
        }

        public static int GetWeekDiff(YearWeek a, YearWeek b)
        {
            int diff = 0;
            int cmp = 0;
            var c = new YearWeek { Year = b.Year, Week = b.Week };
            while (true)
            {
                cmp = c.CompareTo(a);
                if (cmp == 0)
                {
                    return diff;
                }
                else
                {
                    diff += cmp;
                    c = AddWeeks(c, -cmp);
                }
            }
        }

        public static bool IsDateInRange(DateTime? val, DateTime? min, DateTime? max)
        {
            return val.HasValue && (!min.HasValue || val.Value >= min.Value) && (!max.HasValue || val.Value <= max.Value);
        }


        public static bool DateRangeOverlap(DateTime? min1, DateTime? max1, DateTime? min2, DateTime? max2)
        {
            return DateRangeOverlap(min1.GetValueOrDefault(DateTime.MinValue), max1.GetValueOrDefault(DateTime.MaxValue),
                                    min2.GetValueOrDefault(DateTime.MinValue), max2.GetValueOrDefault(DateTime.MaxValue));
        }

        public static bool DateRangeOverlap(DateTime min1, DateTime max1, DateTime min2, DateTime max2)
        {
            // from: http://stackoverflow.com/questions/7325124/how-check-intersection-of-datetime-periods
            if (min1 > max1 || min2 > max2)
                throw new Exception("Invalid date range");

            if (min1 == max1 || min2 == max2)
                return false; // No actual date range

            if (min1 == min2 || max1 == max2)
                return true; // If any set is the same time, then by default there must be some overlap. 

            if (min1 < min2)
            {
                if (max1 > min2 && max1 < max2)
                    return true; // Condition 1

                if (max1 > max2)
                    return true; // Condition 3
            }
            else
            {
                if (max2 > min1 && max2 < max1)
                    return true; // Condition 2

                if (max2 > max1)
                    return true; // Condition 4
            }

            return false;
        }

        public static TimeSpan DateRangeOverlapTimeSpan(DateTime min1, DateTime max1, DateTime min2, DateTime max2)
        {
            if (min1 > max1 || min2 > max2)
            {
                throw new Exception("Invalid date range");
            }

            // Cases: 
            //            [---- Date1 ----]
            //                [--2C--]  
            //      [------------2D------------]   
            //      [----2A----]    [----2B----]
            // [--E--]                        [--F--]

            // 2E, 2F: skip
            if (max2 <= min1 || min2 >= max1)
            {
                return new TimeSpan(0);
            }

            // 2C
            if (min1 <= min2 && max1 >= max2)
            {
                return (max2 - min2).Duration();
            }
            // 2D
            if (min1 >= min2 && max1 <= max2)
            {
                return (max1 - min1).Duration();
            }

            // 2A
            if (min1 >= min2 && max1 >= max2 && max2 >= min1)
            {
                return (max2 - min1).Duration();
            }
            // 2B
            if (min1 <= min2 && max1 >= min2 && max2 >= max1)
            {
                return (max1 - min2).Duration();
            }

            return new TimeSpan(0);
        }

        public static TimeSpan TimespanRangeOverlapTimeSpan(TimeSpan min1, TimeSpan max1, TimeSpan min2, TimeSpan max2)
        {
            if (min1 > max1 || min2 > max2)
            {
                throw new Exception("Invalid date range");
            }

            // Cases: 
            //            [---- Date1 ----]
            //                [--2C--]  
            //      [------------2D------------]   
            //      [----2A----]    [----2B----]
            // [--E--]                        [--F--]

            // 2E, 2F: skip
            if (max2 <= min1 || min2 >= max1)
            {
                return new TimeSpan(0);
            }

            // 2C
            if (min1 <= min2 && max1 >= max2)
            {
                return (max2 - min2).Duration();
            }
            // 2D
            if (min1 >= min2 && max1 <= max2)
            {
                return (max1 - min1).Duration();
            }

            // 2A
            if (min1 >= min2 && max1 >= max2 && max2 >= min1)
            {
                return (max2 - min1).Duration();
            }
            // 2B
            if (min1 <= min2 && max1 >= min2 && max2 >= max1)
            {
                return (max1 - min2).Duration();
            }

            return new TimeSpan(0);
        }


        public static TimeSpan TimespanBetweenDatetimes(TimeSpan from, TimeSpan to)
        {
            // Next day
            if (IsGreaterThan<TimeSpan>(from, to))
            {
                TimeSpan timespanday = TimeSpan.FromDays(1);
                to = to.Add(timespanday);
            }

            TimeSpan totaltime = new TimeSpan(to.Ticks - from.Ticks);
            return totaltime;
        }

        public static Double GetUnixTimestampFromDate(DateTime targetDateTime)
        {
            DateTime zeroDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            TimeSpan timeDifference = targetDateTime.ToUniversalTime() - zeroDateTime;
            return timeDifference.TotalSeconds;
        }

        public static DateTime? GetDateFromAS400(long date)
        {
            return GetDateFromAS400(date.ToString());
        }

        public static DateTime? GetDateFromAS400(string date)
        {
            if (date.Length == 8)
                // formaat YYYYMMDDD
                return new DateTime(Convert.ToInt32(date.Substring(0, 4)), Convert.ToInt32(date.Substring(4, 2)), Convert.ToInt32(date.Substring(6, 2)));
            else
                return null;
        }

        public static long SetDateToAS400(DateTime date)
        {
            return long.Parse(string.Format("{0:yyyyMMdd}", date));
        }

    }

}
public class YearWeek : IComparable, IComparable<YearWeek>
{
    public int Year { get; set; }
    public int Week { get; set; }
    public string Key => ToString();

    public static YearWeek FromInts(int year, int week)
    {
        return new YearWeek { Year = year, Week = week };
    }

    public int CompareTo(object o) => InternalCompareTo(o as YearWeek);

    public override bool Equals(object obj)
    {
        if (obj == null || obj is not YearWeek yw) return false;
        return yw.Year == Year && yw.Week == Week;
    }

    public override string ToString() => $"{Year}-{Week}";

    public override int GetHashCode() => Year.GetHashCode() + Week.GetHashCode();

    public int AsInt() => int.Parse($"{Year}{Week:D2}");

    public YearWeek Copy() => (YearWeek)MemberwiseClone();

    public int WeekCountTo(YearWeek yearWeek)
    {
        var i = 0;
        var current = this;
        while (current.IsLessThan(yearWeek))
        {
            current = DateTimeHelper.AddWeeks(current, 1);
            i += 1;
        }
        return i;
    }

    public static YearWeek FromInt(int? yearWeekInt)
    {
        if (yearWeekInt == null) return null;
        var yearWeekStr = yearWeekInt.ToString();
        var year = int.Parse(yearWeekStr.Substring(0, yearWeekStr.Length - 2));
        var week = int.Parse(yearWeekStr.Substring(yearWeekStr.Length - 2));
        return new YearWeek { Year = year, Week = week };
    }

    public static YearWeek Now() => DateTimeHelper.GetWeekFromDate(DateTime.Now);

    public int CompareTo(YearWeek other) => InternalCompareTo(other);

    private int InternalCompareTo(YearWeek o) => this?.AsInt() ?? 0 - o?.AsInt() ?? 0;

    public YearWeek AddWeeks(int count) => DateTimeHelper.AddWeeks(this, count);

}
26920cookie-checkC# DateTime helpers