How to calculate average of some timespans in C#

Viewed 7432

I already search this question but unfortunately couldn't find proper answer.

I want to calculate the average of time spent on doing something in 10 different day and I have 10 datetimepicker for start time and also 10 datetimepicker for end time for each day (in total 20 datetimepicker).

now I want to get the average of time spent for work in these 10 days.

this is what I've done for calculating timespan in each day and now I don't know how to calculate average if these timespans

of course I want to know is there any shorter way to get the job done?

DateTime Dt1 = dateTimePicker1.Value;
DateTime Dt2 = dateTimePicker2.Value;
.
.
.
DateTime Dt20 = dateTimePicker20.Value;

TimeSpan Day1 = DateTime.Parse(Dt11.TimeOfDay.ToString()).Subtract(DateTime.Parse(Dt1.TimeOfDay.ToString()));
TimeSpan Day2 = DateTime.Parse(Dt12.TimeOfDay.ToString()).Subtract(DateTime.Parse(Dt2.TimeOfDay.ToString()));
.
.
.

TimeSpan Day10 = DateTime.Parse(Dt20.TimeOfDay.ToString()).Subtract(DateTime.Parse(Dt10.TimeOfDay.ToString()));

I want to find average of Day1 to Day10

3 Answers

Given an IEnumerable<TimeSpan> you can average them with this extension method:

public static TimeSpan Average(this IEnumerable<TimeSpan> spans) => TimeSpan.FromSeconds(spans.Select(s => s.TotalSeconds).Average());

So, convert your results to a List:

var durs = new List<TimeSpan>();
durs.Add(Dt11.TimeOfDay.Subtract(Dt1.TimeOfDay));
durs.Add(Dt12.TimeOfDay.Subtract(Dt2.TimeOfDay));
.
.
.

durs.Add(Dt20.TimeOfDay.Subtract(Dt10.TimeOfDay));

Now compute the average:

var avgDurs = durs.Average();

PS: Created an Aggregate version of @MattJohnson's answer:

public static TimeSpan Mean(this IEnumerable<TimeSpan> source) => TimeSpan.FromTicks(source.Aggregate((m: 0L, r: 0L, n: source.Count()), (tm, s) => {
        var r = tm.r + s.Ticks % tm.n;
        return (tm.m + s.Ticks / tm.n + r / tm.n, r % tm.n, tm.n);
    }).m);

You can find average by instantiating a new TimeSpan based on number of ticks.

    var time_spans = new List<TimeSpan>() { new TimeSpan(24, 10, 0), new TimeSpan(12, 0, 45), new TimeSpan(23, 30, 0), new TimeSpan(11, 34, 0) };

    var average = new TimeSpan(Convert.ToInt64(time_spans.Average(t => t.Ticks)));

     Console.WriteLine(average); 

result 17:48:41.2500000

Just to follow up on NetMage's perfectly good answer, note that his Average extension method is using .TotalSeconds, which returns a double of whole and fractional seconds, with millisecond precision. That is probably fine if you are taking values from time-pickers, but in the general case it will result in a small loss of precision.

Additionally, the TimeSpan.FromSeconds method can overflow when dealing with large values. This can be reproduced even when not averaging:

TimeSpan.FromSeconds(TimeSpan.MaxValue.TotalSeconds)  // will throw an OverflowException

Patrick Hofman made a good point in the comments that just switching from seconds to ticks could also result in overflow, and thus another solution is needed.

Adapting the technique given in this answer, we can compute the average of a collection of TimeSpan values using an arithmetic mean approach, without loss of precision and without overflowing:

public static TimeSpan Mean(this ICollection<TimeSpan> source)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    long mean = 0L;
    long remainder = 0L;
    int n = source.Count;
    foreach (var item in source)
    {
        long ticks = item.Ticks;
        mean += ticks / n;
        remainder += ticks % n;
        mean += remainder / n;
        remainder %= n;
    }

    return TimeSpan.FromTicks(mean);
}

Use it similarly to other extension methods, such as:

TimeSpan average = mylistoftimespans.Mean();
Related