C# TimeRange

Date: 2019-10-23
public interface ITimeRange 
{ 
    DateTime? Start { get; }
    DateTime? End { get; }
}
public enum OverlapType
{ 
	Invalid = 0,
	Before = 1,
	After = 2,
	Contains = 3,
	Contained = 4,
	StartOverlaps = 5,
	EndOverlaps = 6
}

public static class TimeRangeExtensions
{
	public static bool HasOverlap(this ITimeRange timeRange, ITimeRange outer) => (int)GetOverlapType(timeRange, outer) > 3;

	public static TimeSpan GetOverlap(this ITimeRange timeRange, ITimeRange outer) => DateRangeOverlap(timeRange.Start, timeRange.End, outer.Start, outer.End);

	private static TimeSpan DateRangeOverlap(DateTime? min1, DateTime? max1, DateTime? min2, DateTime? max2)
	{
		return DateRangeOverlap(
			min1 ?? DateTime.MinValue,
			max1 ?? DateTime.MaxValue,
			min2 ?? DateTime.MinValue,
			max2 ?? DateTime.MaxValue);
	}

	private static TimeSpan DateRangeOverlap(DateTime min1, DateTime max1, DateTime min2, DateTime max2)
	{
		if (min1 > max1 || min2 > max2)
			return new TimeSpan(0);  // throw new Exception("Invalid date range");

		if (max2 <= min1 || min2 >= max1)
			return new TimeSpan(0);
		
		if (min1 <= min2 && max1 >= max2)
			return (max2 - min2).Duration();

		if (min1 >= min2 && max1 <= max2)
			return (max1 - min1).Duration();

		if (min1 >= min2 && max1 >= max2 && max2 >= min1)
			return (max2 - min1).Duration();

		if (min1 <= min2 && max1 >= min2 && max2 >= max1)
			return (max1 - min2).Duration();

		return new TimeSpan(0);
	}

	public static OverlapType GetOverlapType(this ITimeRange timeRange, ITimeRange outer) => DateRangeOverlapType(timeRange.Start, timeRange.End, outer.Start, outer.End);

	private static OverlapType DateRangeOverlapType(DateTime? min1, DateTime? max1, DateTime? min2, DateTime? max2)
	{
		return DateRangeOverlapType(
			min1 ?? DateTime.MinValue,
			max1 ?? DateTime.MaxValue,
			min2 ?? DateTime.MinValue,
			max2 ?? DateTime.MaxValue);
	}

	private static OverlapType DateRangeOverlapType(DateTime min1, DateTime max1, DateTime min2, DateTime max2)
	{
		if (min1 > max1 || min2 > max2)
			return OverlapType.Invalid;

		if (max2 <= min1)
			return OverlapType.Before;

		if(min2 >= max1)
			return OverlapType.After;

		if (min1 <= min2 && max1 >= max2)
			return OverlapType.Contains;

		if (min1 >= min2 && max1 <= max2)
			return OverlapType.Contained;

		if (min1 >= min2 && max1 >= max2 && max2 >= min1)
			return OverlapType.StartOverlaps;

		if (min1 <= min2 && max1 >= min2 && max2 >= max1)
			return OverlapType.EndOverlaps;

		return OverlapType.Invalid; // not possible
	}
}

public sealed class TimeRange : ITimeRange
{
	private TimeRange(DateTime? start, DateTime? end)
	{
		Start = start;
		End = end;
	}

	public DateTime? Start { get; private set; }

	public DateTime? End { get; private set; }

	public static ITimeRange Today => new TimeRange(DateTime.Today, DateTime.Today.AddDays(1).AddSeconds(-1));

	public static ITimeRange GetRelativeYear(int relative = 0)
	{
		var month = DateTime.Now.AddYears(relative);
		var start = new DateTime(month.Year, 1, 1);
		var end = start.AddYears(1).AddSeconds(-1);
		return new TimeRange(start, end);
	}

	public static ITimeRange GetRelativeMonth(int relative = 0)
	{
		var month = DateTime.Now.AddMonths(relative);
		var start = new DateTime(month.Year, month.Month, 1);
		var end = start.AddMonths(1).AddSeconds(-1);
		return new TimeRange(start, end);
	}

	public static ITimeRange GetRelativeWeek(int relative)
	{
		var date = DateTime.Now;
		while (date.DayOfWeek != DayOfWeek.Monday)
			date = date.AddDays(-1);
		date = date.AddDays(relative * 7);
		return new TimeRange(date, date.AddDays(6));
	}

	public static ITimeRange GetRelativeDay(int relative)
	{
		var date = DateTime.Today;
		date = date.AddDays(relative);
		return new TimeRange(date, date.AddDays(1).AddSeconds(-1));
	}
}
/// Old code

using System;

namespace TimeRanges
{
    public interface ITimeRange
    {
        DateTime Start { get; }
        DateTime End { get; }
    }
    public class TimeRange : ITimeRange
    {
        private TimeRange(DateTime start, DateTime end)
        {
            Start = start;
            End = end;
        }

        public DateTime Start { get; }

        public DateTime End { get; }

        public static ITimeRange GetTimeRange(DateTime start, DateTime end)
        {
            return new TimeRange(start, end);
        }

        public static bool IsInRange(ITimeRange timeRange, DateTime? date)
        {
            return date.HasValue && date >= timeRange.Start && date <= timeRange.End;
        }

        public static ITimeRange GetPreviousMonth()
        {
            var prevMonth = DateTime.Now.AddMonths(-1);
            var start = new DateTime(prevMonth.Year, prevMonth.Month, 1);
            var end = start.AddMonths(1).AddSeconds(-1);
            return GetTimeRange(start, end);
        }

        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 ITimeRange GetRelativeMonth(int relative = 0)
        {
            var month = DateTime.Now.AddMonths(relative);
            var start = new DateTime(month.Year, month.Month, 1);
            var end = start.AddMonths(1).AddSeconds(-1);
            return GetTimeRange(start, end);
        }

        public static ITimeRange GetRelativeWeek(int relative)
        {
            var date = DateTime.Now;
            while (date.DayOfWeek != DayOfWeek.Monday)
                date = date.AddDays(-1);

            date = date.AddDays(relative * 7);
            return GetTimeRange(date, date.AddDays(6));
        }

        public static string GetWeekDescription(DateTime dateTime)
        {
            var week = ISO8601WeekNumber(dateTime);
            var year = dateTime.Year;
            if (week == 1)
            {
                year = dateTime.AddMonths(1).Year;
            }
            else if (week >= 52)
            {
                year = dateTime.AddMonths(-1).Year;
            }
            return $"Week {week} {year}";
        }
    }
}
public interface IWithValidTime
{
    IDateTimeRange ValidTime { get; }
}

public interface IDateTimeRange
{
    DateTime? Min { get; }
    DateTime? Max { get; }
}

public class TimeRange : IDateTimeRange
{
    public DateTime? Min { get; }
    public DateTime? Max { get; }
    private TimeRange(DateTime? min, DateTime? max)
    {
        Min = min;
        Max = max;
    }

    public static IDateTimeRange From(DateTime? min, DateTime? max)
    {
        return new TimeRange(min, max);
    }

    public static IDateTimeRange Now()
    {
        return new TimeRange(DateTime.Now, DateTime.Now);
    }

    public static IDateTimeRange FromDates(DateTime? from, DateTime? to)
    {
        var fromDate = from?.Date;
        var toDate = to.HasValue ? to.Value.Date + new TimeSpan(23, 59, 59) : to;
        return From(fromDate, toDate);
    }

    public enum OverlapType
    {
        Invalid = 0,
        Before = 1,
        After = 2,
        Contains = 3,
        Contained = 4,
        StartOverlaps = 5,
        EndOverlaps = 6
    }
    public static bool ObjectIsValid(IWithValidTime obj, IDateTimeRange inTime) => HasOverlap(obj.ValidTime, inTime);
    public static bool ObjectIsValidNow(IWithValidTime obj) => HasOverlap(obj.ValidTime, Now());
    public static bool HasOverlap(IDateTimeRange timeRange, IDateTimeRange outer) => (int)GetOverlapType(timeRange, outer) >= 3;
    public static OverlapType GetOverlapType(IDateTimeRange timeRange, IDateTimeRange outer) => DateRangeOverlapType(timeRange.Min, timeRange.Max, outer.Min, outer.Max);
    private static OverlapType DateRangeOverlapType(DateTime? min1, DateTime? max1, DateTime? min2, DateTime? max2)
    {
        return DateRangeOverlapType(
            min1 ?? DateTime.MinValue,
            max1 ?? DateTime.MaxValue,
            min2 ?? DateTime.MinValue,
            max2 ?? DateTime.MaxValue);
    }
    private static OverlapType DateRangeOverlapType(DateTime min1, DateTime max1, DateTime min2, DateTime max2)
    {
        if (min1 > max1 || min2 > max2)
            return OverlapType.Invalid;
        if (max2 <= min1)
            return OverlapType.Before;
        if (min2 >= max1)
            return OverlapType.After;
        if (min1 <= min2 && max1 >= max2)
            return OverlapType.Contains;
        if (min1 >= min2 && max1 <= max2)
            return OverlapType.Contained;
        if (min1 >= min2 && max1 >= max2 && max2 >= min1)
            return OverlapType.StartOverlaps;
        if (min1 <= min2 && max1 >= min2 && max2 >= max1)
            return OverlapType.EndOverlaps;
        return OverlapType.Invalid; // not possible
    }
}
26940cookie-checkC# TimeRange