0

I have a list of objects in C# with "BeginDate" and "EndDate" DateTime properties. I need to create a new list of these objects with all the objects consolidated when the BeginDate of one object matches the EndDate of the previous one within 24 hours, going back until there is a break longer than 24 hours.

For example. I have a list like so

            BeginDate    EndDate
Object 1    12/21/2017   01/20/2018
Object 2    12/01/2017   12/21/2017
Object 3    10/25/2017   12/01/2017
Object 4    09/17/2017   10/25/2017
Object 5    08/01/2017   09/02/2017
Object 6    06/25/2017   07/26/2017
Object 7    04/20/2017   06/25/2017

that needs to be turned into a list like this

            BeginDate    EndDate
Object 1    09/17/2017   01/20/2018
Object 2    08/01/2017   09/02/2017
Object 3    4/20/2017    07/26/2017

My issue is further exacerbated by the fact that if the Object represents an ongoing project, then the EndDate might be a null value. So the original developers chose to use a DateTime? type instead of a regular DateTime value for that field. So you could have a situation where you have

            BeginDate    EndDate
Object 1    12/21/2017   null
Object 2    12/01/2017   12/21/2017

which would have to be converted to either

            BeginDate    EndDate
Object 1    12/01/2017   null

or

            BeginDate    EndDate
Object 1    12/01/2017   (DateTime.Now)

Right now, I'm trying this but it's not completely consolidating all the objects:

for (var index = 0; index < ProjectList.Count; index++)
{
  Project_BL ThisProject = ProjectList[index];
  Project_BL nextProject = ProjectList[index + 1];
  if (index + 1 < ProjectList.Count && ProjectList[index+1] != null)
  {
    DateTime TempEndDate = nextProject.EndDate ?? DateTime.Now;
    TimeSpan DifferenceBetweenProjectsTimespan =
      ThisProject.BeginDate.Subtract(TempEndDate);
    int DifferenceBetweenProjects = (int)DifferenceBetweenProjectsTimespan.TotalHours;
    if (DifferenceBetweenProjects <= 24)
    {                             
      if (IsLongProject == true)
      {
        nextProject.ProjectNature = "Long-term";
      }
      nextProject.EndDate = ThisProject.EndDate;
      ProjectList.RemoveAt(index);                            
      continue;
    }
    else
    {
      ProjectList.Add(ThisProject);
      index++;
      continue;
    }
  }
  else
  {
    ProjectList.Add(ThisProject);
  }
}

return ProjectList;

Anyone have any ideas? I'm banging my head against the wall at this point. So any help would be appreciated.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Jim Sloun
  • 11
  • 1
  • 4
  • https://stackoverflow.com/questions/52499143/accumulate-date-ranges#comment91938908_52499143 may be of interest – mjwills Sep 26 '18 at 21:51
  • The fundamental problem is that as you combine projects, your code would need to start the index completely over to work correctly. because if we had just 4 items that all overlapped but in the order A, C, B, D, your code would only AB CD. I bet if you ordered your items by StartTime and then EndTime, it might work. – Erik Philips Sep 26 '18 at 22:32

4 Answers4

1

Here is my solution (DotNetFiddle Example)

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var projectList = new List<Project>
        {
            new Project{Name = "Object 1", BeginDate = new DateTime(2017,12,21), EndDate = new DateTime(2018,01,20)},
            new Project{Name = "Object 2", BeginDate = new DateTime(2017,12,01), EndDate = new DateTime(2017,12,21)},
            new Project{Name = "Object 3", BeginDate = new DateTime(2017,10,25), EndDate = new DateTime(2017,12,01)},
            new Project{Name = "Object 4", BeginDate = new DateTime(2017,09,17), EndDate = new DateTime(2017,10,25)},
            new Project{Name = "Object 5", BeginDate = new DateTime(2017,08,01), EndDate = new DateTime(2017,09,02)},
            new Project{Name = "Object 6", BeginDate = new DateTime(2017,06,25), EndDate = new DateTime(2017,07,26)},
            new Project{Name = "Object 7", BeginDate = new DateTime(2017,04,20), EndDate = new DateTime(2017,06,25)},
        };

        var newList = new List<Project>();

        while(projectList.Count > 0)
        {
            var item = projectList.ElementAt(0);
            projectList.Remove(item);
            newList.Add(item);

            //Console.WriteLine(item);

            var match = Match(item, projectList);

            while (match != null)
            {
                //Console.WriteLine("match: " + match.ToString());
                projectList.Remove(match);
                item.BeginDate = item.BeginDate < match.BeginDate ? item.BeginDate : match.BeginDate;
                item.EndDate = item.EndDate > match.EndDate ? item.EndDate : match.EndDate;
                item.IsLongTerm = true;
                //Console.WriteLine("adjusted: " + item.ToString());

                match = Match(item, projectList);
            }
        }

        foreach(var project in newList)
        {
            Console.WriteLine(project.ToString());
        }
    }

    private static Project Match(Project project, IEnumerable<Project> projects)
    {
        var result = projects.FirstOrDefault(p => 
            (project.BeginDate.AddDays(-1) < p.BeginDate && p.BeginDate < project.EndDate.AddDays(1) )
            || (project.BeginDate.AddDays(-1) < p.EndDate && p.EndDate < project.EndDate.AddDays(1)) );

        return result;
    }
}

public class Project
{
    public string Name { get; set; }
    public DateTime BeginDate { get; set; }
    public DateTime EndDate { get; set; }
    public bool IsLongTerm { get; set; }
    public override string ToString()
    {
        return Name + " " + BeginDate.ToString("yyyy-MM-dd") + " " + EndDate.ToString("yyyy-MM-dd");
    }
}

Result:

Object 1 2017-09-17 2018-01-20

Object 5 2017-08-01 2017-09-02

Object 6 2017-04-20 2017-07-26

Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • Probably could be sped up significantly if instead of returning an item, I returned all items, then just took the min/man of all the returned items (and removed them all from the list)... – Erik Philips Sep 26 '18 at 23:17
  • Thanks, that did exactly what I needed! – Jim Sloun Sep 27 '18 at 19:00
1

Here is my attempt using simplified enumeration

IEnumerable<Project> Consolidate(IEnumerable<Project> data) {
    // I need to create a new list of these objects with all the objects 
    //consolidated when the BeginDate of one object matches the EndDate 
    //of the previous one within 24 hours, going back until there is a break longer than 24 hours.
    using (var e = data.GetEnumerator()) {
        if (e.MoveNext()) {
            var previous = e.Current;
            while (e.MoveNext()) {
                var next = e.Current;
                if (previous.BeginDate.AddDays(-1) > next.EndDate) {
                    yield return previous;
                    previous = next;
                    continue;
                }
                previous = new Project {
                    BeginDate = next.BeginDate,
                    EndDate = previous.EndDate ?? DateTime.Now
                };
            }
            yield return previous;
        }
    }
}

Which produces the desired result for the above stated cases including null end dates.

There is room to generalize this into an extension method if so desired. You would just need to provide a predicate for the condition and a Func for the building of the consolidated object.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
0

Here you go find my example here

Model

public class Project
{
    public Project(string name, DateTime beginDate) : this(name, beginDate, null) { }
    public Project(string name, DateTime beginDate, DateTime? endDate)
    {
        Name = name;
        BeginDate = beginDate;
        EndDate = endDate;
    }

    public string Name { get; set; }
    public DateTime BeginDate { get; set; }
    public DateTime? EndDate { get; set; }

    public override string ToString()
    {
        return $"{Name}:\t{BeginDate.ToShortDateString()}\t:\t{(EndDate.HasValue ? EndDate.Value.ToShortDateString() : "Present")}";
    }
}

Method

private static List<Project> Consolidate(List<Project> projects)
{
    var result = new List<Project>();

    // if empty return
    if (!projects.Any()) return result;

    // adding first project to a new list
    // and removing it from the main list
    result.Add(projects.First());
    projects.Remove(projects.First());

    // loop over all projects in the main list
    projects.ForEach(p =>
    {
        var endDate = p.EndDate.HasValue ? p.EndDate.Value : DateTime.Today;

        // checking if any of the projects in the new list
        // starts within 24 hours of the end date of the
        // current project
        if (result.Any(r => r.BeginDate >= endDate && r.BeginDate.Subtract(endDate) <= TimeSpan.FromDays(1) && r.Name != p.Name))
        {
            // if you find any get it
            var match = result.First(r => r.BeginDate >= endDate && r.BeginDate.Subtract(endDate) <= TimeSpan.FromDays(1) && r.Name != p.Name);
            var index = result.IndexOf(match);
            // create new project that consalidate both projects
            result[index] = new Project(match.Name, p.BeginDate, match.EndDate);
        }
        else
        {
            // if didn't find any add the current 
            // project to the new list
            result.Add(p);
        }
    });

    return result;
}

Sample Result

P6: 4/20/2017   :   7/26/2017
P5: 8/1/2017    :   9/2/2017
P1: 9/17/2017   :   1/20/2018

P6: 4/20/2017   :   Present
P5: 8/1/2017    :   9/2/2017
P4: 9/17/2017   :   Present
P1: 10/25/2017  :   1/20/2018
Ahmed Sherien
  • 313
  • 1
  • 2
  • 15
0

Here is my approach...

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var projectList = new List<Project>
        {
            new Project{Id = "Object 0", BeginDate = new DateTime(2018,01,20), EndDate = null},
            new Project{Id = "Object 1", BeginDate = new DateTime(2017,12,21), EndDate = new DateTime(2018,01,20)},
            new Project{Id = "Object 2", BeginDate = new DateTime(2017,12,01), EndDate = new DateTime(2017,12,21)},
            new Project{Id = "Object 3", BeginDate = new DateTime(2017,10,25), EndDate = new DateTime(2017,12,01)},
            new Project{Id = "Object 4", BeginDate = new DateTime(2017,09,17), EndDate = new DateTime(2017,10,25)},
            new Project{Id = "Object 5", BeginDate = new DateTime(2017,08,01), EndDate = new DateTime(2017,09,02)},
            new Project{Id = "Object 6", BeginDate = new DateTime(2017,06,25), EndDate = new DateTime(2017,07,26)},
            new Project{Id = "Object 7", BeginDate = new DateTime(2017,04,20), EndDate = new DateTime(2017,06,25)},
        };

        var resultsList = new List<Project>();
        var previousProject = new Project();
        var currentProject = new Project();

        foreach(var p in projectList.OrderBy(p => p.BeginDate))
        {
            if (string.IsNullOrEmpty(previousProject.Id))
            {
                previousProject = currentProject = p;
                continue;
            }

            if (p.BeginDate.AddDays(-1)<=previousProject.EndDate)
            {
                currentProject.EndDate = p.EndDate;
                previousProject = currentProject;
                continue;
            }
            else
            {
                resultsList.Add(currentProject);
                previousProject = currentProject = p;
            }
        }
        resultsList.Add(currentProject);

        foreach(var p in resultsList)
        {
            var endDate = p.EndDate?.ToString("yyyy-MM-dd");
            Console.WriteLine("{0}\t{1}\t{2}", p.Id, p.BeginDate.ToString("yyyy-MM-dd"), ((string.IsNullOrEmpty(endDate))?"Null":endDate));
        }
    }
}

public class Project
{
    public string Id { get; set; }
    public DateTime BeginDate { get; set; }
    public DateTime? EndDate { get; set; }
}
Mauricio Atanache
  • 2,424
  • 1
  • 13
  • 18