locked
How to calculate total time per day from this object RRS feed

  • 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

Answers

  • 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;
                DateTime durationEnd = start.AddDays(1).Date;
    
                while (durationEnd < end)
                {
                    result.Add(new DailyDuration() { Day = durationStart.Date, Duration = durationEnd - durationStart });
                    durationStart = durationEnd;
                    durationEnd = durationEnd.AddDays(1);
                }
    
                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 Bob Shen Monday, August 6, 2012 2:57 AM
    • Marked as answer by Tobias_K 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
                        time = time.Add(p.OfflineDate.Subtract(p.OnlineDate));
                        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
                        time = time.Add(TimeSpan.FromHours(24) - p.OnlineDate.TimeOfDay);
                        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
                        time = time.Add(p.OfflineDate.TimeOfDay);
                        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
                         time = time.Add(TimeSpan.FromHours(24));
                         blocksNode.Nodes.Add("--> 00:00:00 - 23:59:59 -->");
                     }
                    else if (p.OnlineDate.Date == startDate.Date && p.OfflineDate == DateTime.MinValue)
                    {
                        //Online now
                        time = time.Add(DateTime.Now.Subtract(p.OnlineDate));
                        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;
                DateTime durationEnd = start.AddDays(1).Date;
    
                while (durationEnd < end)
                {
                    result.Add(new DailyDuration() { Day = durationStart.Date, Duration = durationEnd - durationStart });
                    durationStart = durationEnd;
                    durationEnd = durationEnd.AddDays(1);
                }
    
                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 Bob Shen Monday, August 6, 2012 2:57 AM
    • Marked as answer by Tobias_K 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