Sum of TimeSpans in C#
Asked Answered
C

9

68

I have a collection of objects that include a TimeSpan variable:

MyObject
{ 
    TimeSpan TheDuration { get; set; }
}

I want to use LINQ to sum those times. Of course, (from r in MyCollection select r.TheDuration).Sum(); doesn't work!

I'm thinking of changing the datatype of TheDuration to an int and then summing it and converting the sum to a TimeSpan. That will be messy because each TheDuration in my collection is used in as a timespan somewhere else.

Any suggestion on this summation?

Cnut answered 16/1, 2011 at 0:33 Comment(1)
I used this approach within Linq querry: ... DelaySum = TimeSpan.FromTicks(x.Sum(y => y.DELAY_TS.Value.Ticks)) ... seems working for my purpose. Hope this helps someone out there as well :-).Sag
P
141

Unfortunately, there isn't a an overload of Sum that accepts an IEnumerable<TimeSpan>. Additionally, there's no current way of specifying operator-based generic constraints for type-parameters, so even though TimeSpan is "natively" summable, that fact can't be picked up easily by generic code.

One option would be to, as you say, sum up an integral-type equivalent to the timespan instead, and then turn that sum into a TimeSpan again. The ideal property for this is TimeSpan.Ticks, which round-trips accurately. But it's not necessary to change the property-type on your class at all; you can just project:

var totalSpan = new TimeSpan(myCollection.Sum(r => r.TheDuration.Ticks));

Alternatively, if you want to stick to the TimeSpan's + operator to do the summing, you can use the Aggregate operator:

var totalSpan = myCollection.Aggregate
                (TimeSpan.Zero, 
                (sumSoFar, nextMyObject) => sumSoFar + nextMyObject.TheDuration);
Proverbs answered 16/1, 2011 at 0:38 Comment(1)
Just a small correction. It should be var totalSpan = new TimeSpan(myCollection.Sum(r => r.Ticks)); in case if myCollection is List<TimeSpan>().Callum
I
56

This works well (code based on Ani's answer)

public static class StatisticExtensions
{    
    public static TimeSpan Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, TimeSpan> selector)
    {
        return source.Select(selector).Aggregate(TimeSpan.Zero, (t1, t2) => t1 + t2);
    }
}

Usage :

If Periods is a list of objects with a Duration property

TimeSpan total = Periods.Sum(s => s.Duration)
Indication answered 1/11, 2012 at 1:54 Comment(0)
B
5

I believe this is the cleanest LINQ extension:

public static class LinqExtensions
{
    public static TimeSpan Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, TimeSpan> func)
    {
        return new TimeSpan(source.Sum(item => func(item).Ticks));
    }
}

Usage is the same:

TimeSpan total = Periods.Sum(s => s.Duration)
Bioscope answered 7/3, 2017 at 22:52 Comment(1)
brilliant, brilliant brilliant...Gilemette
J
2

This works for both a collection, and a property within a collection;

void Main()
{
    var periods = new[] {
        new TimeSpan(0, 10, 0),
        new TimeSpan(0, 10, 0),
        new TimeSpan(0, 10, 0),
    };
    TimeSpan total = periods.Sum();
    TimeSpan total2 = periods.Sum(p => p);

    Debug.WriteLine(total);
    Debug.WriteLine(total2);

    // output: 00:30:00
    // output: 00:30:00
}


public static class LinqExtensions
{
    public static TimeSpan Sum(this IEnumerable<TimeSpan> timeSpanCollection)
    {
        return timeSpanCollection.Sum(s => s);
    }

    public static TimeSpan Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, TimeSpan> func)
    {
        return new TimeSpan(source.Sum(item => func(item).Ticks));
    }
}
Jabot answered 15/6, 2017 at 14:36 Comment(0)
R
1

Here's what I tried and it worked:

System.Collections.Generic.List<MyObject> collection = new List<MyObject>();
MyObject mb = new MyObject();
mb.TheDuration = new TimeSpan(100000);
collection.Add(mb);
mb.TheDuration = new TimeSpan(100000);
collection.Add(mb);
mb.TheDuration = new TimeSpan(100000);
collection.Add(mb);
var sum = (from r in collection select r.TheDuration.Ticks).Sum();
Console.WriteLine( sum.ToString());
//here we have new timespan that is sum of all time spans
TimeSpan sumedup = new TimeSpan(sum);


public class MyObject
{
    public TimeSpan TheDuration { get; set; }
}
Rayford answered 16/1, 2011 at 0:51 Comment(2)
this is the same as @Ani's first solution, just with a query expression instead of dot notationTeets
@Broken Glass somewhat.. by the time i posted this answer there was already 2 answers to it.. ;) Guys are real fast hereRayford
C
1

I put this in a class to add an extension method to a collection of timespans:

public static class Extensions:
{
    public static TimeSpan TotalTime(this IEnumerable<TimeSpan> TheCollection)
    {
        int i = 0;
        int TotalSeconds = 0;

        var ArrayDuration = TheCollection.ToArray();

        for (i = 0; i < ArrayDuration.Length; i++)
        {
            TotalSeconds = (int)(ArrayDuration[i].TotalSeconds) + TotalSeconds;
        }

        return TimeSpan.FromSeconds(TotalSeconds);
    }
}

So now, I can write TotalDuration = (my LINQ query that returns a collection of timespan).TotalTime();

Voila!

Cnut answered 18/1, 2011 at 0:41 Comment(5)
you should have asked for an extension method then ;-)Teets
"I want to use linq to sum those times." - you changed your mind?Mcclimans
I'm still using linq: I can query my data and then add .TotalTime(); at the end of the query and it works. I'm still learning the framework as you can see.Cnut
@grantnz: it turns out I'm not using TimeSpan anymore.Cnut
per @Indication you should rewrite this extension to use Ani's Aggregate answerOpine
S
1

I created extension method for IEnumerable<TimeSpan>

public static class TimeSpanExtensions
{
    public static TimeSpan Sum(this IEnumerable<TimeSpan> source)
    {
        return source.Aggregate(TimeSpan.Zero, (subtotal, time) => subtotal.Add(time));
    }
}

Thanks to that I can use:

 IEnumerable<TimeSpan> times = new List<TimeSpan>();
 TimeSpan totalTime = times.Sum();
Schrecklichkeit answered 26/8, 2021 at 16:42 Comment(1)
For future readers: the answer of grantnz is more robust, as it allows selectors (x => x.TimeSpan)Dario
M
0

You can use .Aggregate rather than .Sum, and pass it a timespan-summing function that you write, like this:

    TimeSpan AddTimeSpans(TimeSpan a, TimeSpan b)
    {
        return a + b;
    }
Mcguire answered 16/1, 2011 at 0:44 Comment(0)
D
0

Once you understand that timespans can't be summed and know to use Ticks, it seems to me that this extension to just convert a long into a timespan looks more linq-ee. I believe it lets the reader have a more readable view of the operation:

var times = new[] { new TimeSpan(0, 10, 0), new TimeSpan(0, 20, 0), new TimeSpan(0, 30, 0) };

times.Sum(p => p.Ticks)
     .ToTimeSpan();      // output: 01:00:00
     

Here is the one extension:

public static class LongExtensions
{
    public static TimeSpan ToTimeSpan(this long ticks)
        => new TimeSpan(ticks);
}
Deportment answered 17/7, 2020 at 21:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.