3

Let's say I have the following dates:

"2017-11-06 15:29", 
"2017-11-06 15:30", 
"2017-11-06 15:51", 
"2017-11-06 16:30", 
"2017-11-06 16:31"

I would like to group them in 60 minute chunks, relative from the first item:

0: ["2017-11-06 15:29", "2017-11-06 15:30", "2017-11-06 15:51"]
1: ["2017-11-06 16:30", "2017-11-06 16:31"]

I tried dates.GroupWhile((previous, current) => previous.AddMinutes(60) >= current) from Grouping Contiguous Dates, but they all end up in the same group.

filur
  • 2,116
  • 6
  • 24
  • 47

3 Answers3

5

That method doesn't really do what you want, it's grouping based on last element. I think you want something like this:

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> source, Func<T, T, bool> continueSelector)
{
    using (var enumerator = source.GetEnumerator())
    {
        if (!enumerator.MoveNext())
        {
            yield break;
        }

        var currentGroup = new List<T> { enumerator.Current };
        while (enumerator.MoveNext())
        {
            var current = enumerator.Current;

            if (continueSelector(currentGroup.First(), current))
            {
                currentGroup.Add(current);
            }
            else
            {
                yield return currentGroup;
                currentGroup = new List<T> { current };
            }
        }

        yield return currentGroup;
    }
}

This would be grouping based on first element of each new group making them relative to each other and giving you your expected result if you use it like this:

dates.GroupWhile((groupDate, current) => (current - groupDate).TotalSeconds < 3600)
Simon Karlsson
  • 4,090
  • 22
  • 39
4

You need the first time, then you can use TimeSpan.TotalHours (cast the double to int):

DateTime firstDt = dates.Min(); 
var hourGroups = dates.GroupBy(dt => (int)(dt - firstDt).TotalHours);

Sample:

var dates = Enumerable.Range(1, 30).Select(i => DateTime.Now.AddMinutes(15 * i)).ToList();

and result:

foreach (var dtGroup in hourGroups)
    Console.WriteLine(String.Join(", ", dtGroup));

07.11.2017 10:17:47, 07.11.2017 10:32:47, 07.11.2017 10:47:47, 07.11.2017 11:02:47
07.11.2017 11:17:47, 07.11.2017 11:32:47, 07.11.2017 11:47:47, 07.11.2017 12:02:47
07.11.2017 12:17:47, 07.11.2017 12:32:47, 07.11.2017 12:47:47, 07.11.2017 13:02:47
07.11.2017 13:17:47, 07.11.2017 13:32:47, 07.11.2017 13:47:47, 07.11.2017 14:02:47
07.11.2017 14:17:47, 07.11.2017 14:32:47, 07.11.2017 14:47:47, 07.11.2017 15:02:47
07.11.2017 15:17:47, 07.11.2017 15:32:47, 07.11.2017 15:47:47, 07.11.2017 16:02:47
07.11.2017 16:17:47, 07.11.2017 16:32:47, 07.11.2017 16:47:47, 07.11.2017 17:02:47
07.11.2017 17:17:47, 07.11.2017 17:32:47
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

Firstly start by converting your strings into DateTime:

string[] strDates =
{
    "2017-11-06 15:29",
    "2017-11-06 15:30",
    "2017-11-06 15:51",
    "2017-11-06 16:30",
    "2017-11-06 16:31",
    "2017-11-06 16:28"
};

var dates = strDates.Select(date => DateTime.Parse(date))
    .OrderBy(date => date)  // Only needed if the original times might be unordered.
    .ToArray();

Then you can group the dates by hour from the first time like so:

var startTicks = dates[0].Ticks;

var grouped = dates.GroupBy(date => (date.Ticks - startTicks)/TimeSpan.TicksPerHour);

This works by grouping the times using their tick counts divided by the number ticks per hour - which has the effect of grouping them all by hours (but relative to the first time in the list).

My test code printed them out thusly:

foreach (var group in grouped)
    Console.WriteLine(string.Join(", ", group));

Giving the following output:

06/11/2017 15:29:00, 06/11/2017 15:30:00, 06/11/2017 15:51:00, 06/11/2017 16:28:00
06/11/2017 16:30:00, 06/11/2017 16:31:00
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276