How to calculate average of some timespans in C#
Asked Answered
K

3

8

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

Kuehl answered 10/11, 2017 at 16:55 Comment(10)
Well it would be a lot easier if you had an array or a List instead of 20 separate variables, to start with. (It's not clear why you're formatting TimeSpan values and then reparsing them, either.)Protoactinium
add them all up and divide by the number. And as skeet says - put them in an arrayBethesda
I'm curious. Why did you decide to create a string just to parse it back again? I'm really interested on your motivation on that, because I see that approach all the time and it's never required, nor desirable.Teen
Also, are you guaranteed that all pairs of values are on the same date? Or could you have a pair that spans midnight, such as two hours between 11pm and 1am?Teen
@MattJohnson Honestly this is my first time working with datetimepicker and I parse it back just because I saw someone did that in one of topics,Kuehl
@MattJohnson yes all of these pair are only in 1 day from 00:00:00 to 24:00:00 not in midnightKuehl
@Kuehl - sure, but might your user pick them out of sequence?Teen
@JonSkeet would you give me a hint to how do what you said ?Kuehl
@MattJohnson yes it could happen that user entered wrong time :( and it's another question that how could I face these cases ?Kuehl
If all DateTimes have the same Date, why strip it off? You are only interested in the differences anyway. So var MyTimeSpan = myDateTime1 - myDateTime2; seems to do the trick without TimeOfDay or ToString() (that last one is really amazingly useless but incredibly common for some reason).Debose
A
18

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);
Angelineangelique answered 10/11, 2017 at 16:59 Comment(8)
It would be better to use ticks instead of seconds, to not lose any information in the finer precision. Though probably doesn't matter in the OP's case, coming from a picker control, if one is going to keep an extension method around, one would want it to be usable in a variety of scenarios.Teen
@MattJohnson Adding ticks can become a problem when overflowing. Seconds is a better option then.Affinity
thanks but I get this errore: 'List<TimeSpan>' does not contain a definition for 'Average' and the best extension method overload 'Queryable.Average(IQueryable<int>)' requires a receiver of type 'IQueryable<int>Kuehl
add this using System.Linq;Bisutun
@PatrickHofman - true, but there are other averaging methods that can be used to overcome that. Though I do agree with you in this particular case. :)Teen
@PatrickHofman - see my answer below for how that can be overcome. :)Teen
@MattJohnson FYI, TimeSpan is accurate to the millisecond which is why FromSeconds takes a double.Angelineangelique
@PatrickHofman @MattJohnson doing the math implies that it will overflow if your total TimeSpanexceeds 29,227 years, which I imagine is fine in most circumstances. OTOH, while seconds lose precision, they are good for about 1,084 times as long a period.Angelineangelique
B
5

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

Bisutun answered 10/11, 2017 at 17:33 Comment(0)
T
4

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();
Teen answered 10/11, 2017 at 18:23 Comment(12)
thanks what should I do for negative ticks? how could I check them and multiply them in -1 if they are negative?Kuehl
There's no extra code needed to handle negative values. Everything here is signed.Teen
Seems like a good time for a complicated Aggregate. BTW, why us ICollection instead of IEnumerable?Angelineangelique
To avoid possible two enumerations due to having to call .Count()Teen
I created an Aggregate version over IEnumerable (not all IEnumerable implement ICollection) and interestingly Mean of (5,10,15) seconds returns a slightly bad result versus Average, though over a sample of 7 TimeSpans, Mean is more accurate. Note that TimeSpan seconds is accurate to the millisecond.Angelineangelique
Thanks for reminding me it was a double. I updated my answer. I tested the scenario you described and didn't get a bad result with my implementation, so I think maybe something in using Aggregate. (Again, I like your answer too; it works in the majority of cases, especially the one the OP described.)Teen
You were right, my implementation had a subtle error that made it off a little :)Angelineangelique
@Kuehl - please accept NetMage's answer. It is a better fit for your question. I just wanted this one here because it is likely to come up in search results. Thanks.Teen
@MattJohnson that was nice, but hardly necessary - now we are tied :)Angelineangelique
@MattJohnson I did it :) and tanks a lot for your answer because it was your answer that get me the idea to how adapt 2 answer to get what I need from my code. anyhow tanks a lot to both of you <3Kuehl
@MattJohnson Your Overflow example would seem to indicate a bug in TimeSpan.Angelineangelique
It's not so much TimeSpan, but just due to double. It's already a well known example, and in the documentation here.Teen

© 2022 - 2024 — McMap. All rights reserved.