pandemic
pandemic

Reputation: 1195

Split the TimeRange to list based on day

I have a date range bounded by start and end and I want to get a list of ranges back where that original range is split in to ranges on individual days. So Jan 1st to Jan 7th would return 7 ranges, one for each day in that range.

Basically what I need is:

var start = new DateTime(2017, 08, 05, 09, 00, 00);
var end = new DateTime(2017, 08, 07, 16, 00, 00);

var splittedTimeRanges = SplitDate(start, end);
// splittedTimeRanges[0]
//{05.08.2017 9:00:00 - 06.08.2017 00:00:00 | 0.15:00} 
// splittedTimeRanges[1]
//{06.08.2017 00:00:00 - 07.08.2017 00:00:00 | 0.24:00}
// splittedTimeRanges[2]
//{07.08.2017 00:00:00 - 07.08.2017 16:00:00 | 0.16:00}

I would like to avoid using Tuple and 3rd party libraries like this. Is there any way to have clean and short code to accomplish this?

Upvotes: 1

Views: 899

Answers (2)

Zyu
Zyu

Reputation: 1

I encountered a need for a similar method, wrote this that should cover most edge cases because it uses dates by design and navigates only with AddDays().

It works by first checking for single day and two-day time ranges, if it's not that then it adds the beginning and end of specified range to the timeSpans list.

Then it loops over every day in-between and gets the TimeSpan for it.

Example:

SplitDateTime(DateTime.Now, DateTime.Now.AddHours(44))

would return a list of these TimeSpans:

12:00 - 00:00 | 00:00 - 00:00 | 00:00 - 08:00

public static List<TimeSpan> SplitDateTime(DateTime start, DateTime end)
{
    var timeSpans = new List<TimeSpan>();

    // If range is one day, return only that
    if (end.Date == start.Date)
    {
        timeSpans.Add(end - start);
        return timeSpans;
    }

    // Add hours from first day and last day
    timeSpans.Add(start.Date.AddDays(1) - start);
    timeSpans.Add(end - end.Date);

    // Current day here is the first day that is not the start day
    var currentDay = start.Date.AddDays(1);

    // If range crosses two days or end is bigger than currentDay, then return as is
    if (end.Date == start.Date.AddDays(1) || end.Date < currentDay)
    {
        return timeSpans;
    }

    while (currentDay < end.Date)
    {
        // Start from 00:00 and get TimeSpan to 00:00 next day
        timeSpans.Add(currentDay.Date.AddDays(1) - currentDay);

        currentDay = currentDay.AddDays(1);
    }

    return timeSpans;
}

Upvotes: 0

Bradley Uffner
Bradley Uffner

Reputation: 16991

I think this will do it for you:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TimeSplit
{
    class Program
    {
        static void Main(string[] args)
        {
            var range = new DateRange()
            {
                Start = new DateTime(2017, 08, 05, 09, 00, 00),
                End = new DateTime(2017, 08, 07, 16, 00, 00)
            };

            foreach (var r in SplitInToDays(range))
            {
                Console.WriteLine($"{r.Start} - {r.End} - {r.Duration}");
            }
            Console.ReadLine();
        }

        public static IEnumerable<DateRange> SplitInToDays(DateRange range)
        {
            var ranges = new List<DateRange>();

            var tempRange = new DateRange() { Start = range.Start, End = range.End };

            while (tempRange.Start.Date != tempRange.End.Date)
            {
                var dateRange = new DateRange()
                {
                    Start = tempRange.Start,
                    End = tempRange.Start.Date.AddDays(1)
                };
                ranges.Add(dateRange);
                tempRange.Start = dateRange.End;
            }

            ranges.Add(tempRange);

            return ranges;
        }
    }

    public class DateRange
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }

        public TimeSpan Duration => End - Start;
    }
}

It creates a temporary range instance from the original one passed in. Then it loops as long as the start and end days of that range are different. Each loop it takes the starting time as the start of a new range, and ends it with midnight of the next day. Then it advances the temp range to start and the end of the range that was just created. Once start and end are on the same day, it just adds what is left.

There is no error checking, so it can easily blow up, or get stuck in a loop given bad input values. I'll leave checking for that up to you.

It produces the following output for me:

8/5/2017 9:00:00 AM - 8/6/2017 12:00:00 AM - 15:00:00

8/6/2017 12:00:00 AM - 8/7/2017 12:00:00 AM - 1.00:00:00

8/7/2017 12:00:00 AM - 8/7/2017 4:00:00 PM - 16:00:00

Upvotes: 1

Related Questions