How to calculate total time per day from this object

• Question

• Hello!

I have this data class TimeLog with a startDate, and endDate That calculates uptime for an object

Then I have this List<TimeLog> log, like this

startdate = 2012-08-01 12:00
endDate = 2012-08-01 14:00

startdate = 2012-08-01 15:00
endDate = 2012-08-03 19:00

startdate = 2012-08-04 12:00
endDate = 2012-08-04 15:00

Now i would like to iterate this list and get total uptime per day, like this in a Dictionary<dateTime,double>

2012-08-01 TotalUptime: 11 h
2012-08-02 TotalUptime: 24 h
2012-08-03 TotalUptime: 19 h
2012-08-04 TotalUptime: 3 h

Any ideas?

Thursday, August 2, 2012 7:13 AM

• I would approach it like so (code is untested, will definitely need tweaking, but might give you some fresh ideas):

```        class TimeLog
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}

class DailyDuration
{
public DateTime Day { get; set; }
public TimeSpan Duration { get; set; }
}

private List<DailyDuration> SplitPeriod(DateTime start, DateTime end)
{
List<DailyDuration> result = new List<DailyDuration>();

DateTime durationStart = start;

while (durationEnd < end)
{
result.Add(new DailyDuration() { Day = durationStart.Date, Duration = durationEnd - durationStart });
durationStart = durationEnd;
}

result.Add(new DailyDuration() { Day = durationStart.Date, Duration = end - durationStart });

return result;
}

public Dictionary<DateTime, double> GetUptimePerDay(List<TimeLog> timeLog)
{
return
timeLog
.Select(l => SplitPeriod(l.StartDate, l.EndDate))
.SelectMany(p => p)
.GroupBy(d => d.Day)
.Select(g => new { Day = g.Key, TotalHours = g.Sum(d => d.Duration.TotalHours) })
.ToDictionary(k => k.Day, v => v.TotalHours);
}

```

DailyDuration is a helper class that holds the uptime for a single day. The method SplitPeriod splits a period into DailyDuration objects. The method GetUptimePerDay converts a list of timeLogs into a dictionary with uptime in hours per day.

The SplitPeriod method could definitely be rewritten to express the intention better; it also does not take into account timeLogs without an end date.

The GetUptimePerDay uses a bit of LINQ wizardry to convert the list of timeLogs into the dictionary. Of note is the "SelectMany(p => p)", which is a dirty trick to flatten the list of DailyDuration lists that the first Select statement produces.

I hope this gives you some ideas!

Hey, look! This system allows signatures of more than 60 cha

• Proposed as answer by Monday, August 6, 2012 2:57 AM
• Marked as answer by Monday, August 6, 2012 5:00 AM
Friday, August 3, 2012 8:37 AM

All replies

• It looks like your main problem are the periods that extend over multiple days. I would create a method that receives a startDateTime and an endDateTime, and then splits that period up in a list of TimeSpans -- one timespan for each day that is covered by the period. After that, it is a relatively simple matter of combining all the TimeSpan lists in one big list, and generating a sum of the total hours per day. This could probably be done with one LINQ query.

Hey, look! This system allows signatures of more than 60 cha

Thursday, August 2, 2012 9:13 AM
• Well this is how i finally solved it:

```private void GetValue(List<PingerLog> list, DateTime startDate)
{
TimeSpan time = new TimeSpan();
TreeNode DateNode = new TreeNode(startDate.Date.ToString("yyyy-MM-dd"));
TreeNode blocksNode = new TreeNode("Time Blocks");
foreach (PingerLog p in list)
{
if (p.OnlineDate.Date == startDate.Date && p.OfflineDate.Date == startDate.Date)
{
//Block on same day
blocksNode.Nodes.Add(string.Format("{0:hh\\:mm\\:ss}", p.OnlineDate.TimeOfDay) + " - " + string.Format("{0:hh\\:mm\\:ss}", p.OfflineDate.TimeOfDay) + " [" + p.OfflineMode + "]");
}
else if (p.OnlineDate.Date == startDate.Date && p.OfflineDate.Date != startDate.Date && p.OfflineDate != DateTime.MinValue)
{
//Started this day but did not end this day
blocksNode.Nodes.Add(string.Format("{0:hh\\:mm\\:ss}", p.OnlineDate.TimeOfDay) + " - 23:59:59 -->");
}
else if (p.OnlineDate.Date != startDate.Date && p.OfflineDate.Date == startDate.Date)
{
//Did not started this day but ended on this date
blocksNode.Nodes.Add("--> 00:00:00 - " + string.Format("{0:hh\\:mm\\:ss}", p.OfflineDate.TimeOfDay) + " [" + p.OfflineMode + "]");
}
else if (p.OnlineDate.Date < startDate.Date && p.OfflineDate.Date > startDate.Date)
{
//Is in a block
}
else if (p.OnlineDate.Date == startDate.Date && p.OfflineDate == DateTime.MinValue)
{
//Online now
blocksNode.Nodes.Add(string.Format("{0:hh\\:mm\\:ss}", p.OnlineDate.TimeOfDay) + " - ????");
}
}
}```
But is there a 'cleaner' more nicer way?

Friday, August 3, 2012 7:39 AM
• I would approach it like so (code is untested, will definitely need tweaking, but might give you some fresh ideas):

```        class TimeLog
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}

class DailyDuration
{
public DateTime Day { get; set; }
public TimeSpan Duration { get; set; }
}

private List<DailyDuration> SplitPeriod(DateTime start, DateTime end)
{
List<DailyDuration> result = new List<DailyDuration>();

DateTime durationStart = start;

while (durationEnd < end)
{
result.Add(new DailyDuration() { Day = durationStart.Date, Duration = durationEnd - durationStart });
durationStart = durationEnd;
}

result.Add(new DailyDuration() { Day = durationStart.Date, Duration = end - durationStart });

return result;
}

public Dictionary<DateTime, double> GetUptimePerDay(List<TimeLog> timeLog)
{
return
timeLog
.Select(l => SplitPeriod(l.StartDate, l.EndDate))
.SelectMany(p => p)
.GroupBy(d => d.Day)
.Select(g => new { Day = g.Key, TotalHours = g.Sum(d => d.Duration.TotalHours) })
.ToDictionary(k => k.Day, v => v.TotalHours);
}

```

DailyDuration is a helper class that holds the uptime for a single day. The method SplitPeriod splits a period into DailyDuration objects. The method GetUptimePerDay converts a list of timeLogs into a dictionary with uptime in hours per day.

The SplitPeriod method could definitely be rewritten to express the intention better; it also does not take into account timeLogs without an end date.

The GetUptimePerDay uses a bit of LINQ wizardry to convert the list of timeLogs into the dictionary. Of note is the "SelectMany(p => p)", which is a dirty trick to flatten the list of DailyDuration lists that the first Select statement produces.

I hope this gives you some ideas!

Hey, look! This system allows signatures of more than 60 cha

• Proposed as answer by Monday, August 6, 2012 2:57 AM
• Marked as answer by Monday, August 6, 2012 5:00 AM
Friday, August 3, 2012 8:37 AM
• Perfect!

Thank you a lot!

Monday, August 6, 2012 5:00 AM