Question

Im trying to start a stopwatch from a given time (decimal value pulled from a database). However, because the Stopwatch.Elapsed.Add returns a new Timespan rather than modify the Stopwatch, I can't work out the best way forward.

var offsetTimeStamp = new System.TimeSpan(0,0,0).Add(TimeSpan.FromSeconds((double)jd.ActualTime));
Stopwatch.Elapsed.Add(offsetTimeStamp);
Stopwatch.Start();

Any ideas how I can do this? Cheers

Was it helpful?

Solution

The normal StopWatch does not support initialization with an offset timespan and TimeSpan is a struct, therefore Elapsed is immutable. You could write a wrapper around StopWatch:

public class StopWatchWithOffset
{
    private Stopwatch _stopwatch = null;
    TimeSpan _offsetTimeSpan;

    public StopWatchWithOffset(TimeSpan offsetElapsedTimeSpan)
    {
        _offsetTimeSpan = offsetElapsedTimeSpan;
        _stopwatch = new Stopwatch();
    }

    public void Start()
    {
        _stopwatch.Start();
    }

    public void Stop()
    {
        _stopwatch.Stop();
    }

    public TimeSpan ElapsedTimeSpan
    {
        get
        {
            return _stopwatch.Elapsed + _offsetTimeSpan;
        }
        set
        {
            _offsetTimeSpan = value;
        }
    }
}

Now you can add a start-timespan:

var offsetTimeStamp = TimeSpan.FromHours(1);
var watch = new StopWatchWithOffset(offsetTimeStamp);
watch.Start();
System.Threading.Thread.Sleep(300); 
Console.WriteLine(watch.ElapsedTimeSpan);// 01:00:00.2995983

OTHER TIPS

The Elapsed property of StopWatch is read only, which makes sense. A stopwatch simply measures the amount of time that passed between start and stop.

If you want to add a timespan to the value - get the Elapsed value in a variable and add a timespan to it, after you have measured it (i.e. after stopping).

I think you want to start your Stopwatch after a certain mount of time specified by a TimeSpan. I wonder why you don't want to start your Stopwatch at a time specified by a DateTime instead?

public class MyStopwatch : Stopwatch
{
    public void Start(long afterMiliseconds)
    {
        Timer t = new Timer() { Interval = 1 };
        int i = 0;
        t.Tick += (s, e) =>
        {
            if (i++ == afterMiliseconds)
            {
                Start();
                t.Stop();
            }
        };
        t.Start();
    }
}
//use it
var offsetTimeStamp = new System.TimeSpan(0,0,0).Add(TimeSpan.FromSeconds((double)jd.ActualTime));
myStopwatch.Start((long)offsetTimeStamp.TotalMiliseconds);

This isn't a great fit for the OPs scenario (which I'm guessing they solved 8 years ago), but if you just need to create stopwatches for unit tests or other non-production scenarios then you can use reflection to modify the elapsed time.

This isn't going to give you the best performance, and can break if the underlying implementation of Stopwatch changes, so I'd be very circumspect using this approach.

However, for unit tests where you need to pass around a Stopwatch and can't change to use an alternate implementation, I find this approach to work well and the risk to be acceptable.

/// <summary>
/// Some static mechanisms for creating Stopwatch instances that start from a specific time.
/// </summary>
public static class TestStopwatch
{
    /// <summary>
    /// Creates a <see cref="Stopwatch"/> instance with a specified amount of time already elapsed
    /// </summary>
    /// <param name="start">The <see cref="TimeSpan"/> indicated the elapsed time to start from.</param>
    public static Stopwatch WithElapsed(TimeSpan start)
    {
        var sw = new Stopwatch();

        var elapsedProperty = typeof(Stopwatch).GetField("_elapsed", BindingFlags.NonPublic | BindingFlags.Instance);

        long rawElapsedTicks = start.Ticks;

        if (Stopwatch.IsHighResolution)
        {
            rawElapsedTicks = (long)((double)rawElapsedTicks / (10000000 / (double)Stopwatch.Frequency));
        }

        elapsedProperty.SetValue(sw, rawElapsedTicks);

        return sw;
    }

    /// <summary>
    /// Initializes a new <see cref="Stopwatch"/> instance, sets the elapsed time property to the specified value,
    /// and starts measuring elapsed time.
    /// </summary>
    /// <param name="start">The <see cref="TimeSpan"/> indicated the elapsed time to start from.</param>
    public static Stopwatch StartNew(TimeSpan start)
    {
        var sw = TestStopwatch.WithElapsed(start);
        sw.Start();
        return sw;
    }
}

If you add this file to your project, there is nothing you need to change in your project. This class inherits from the original Stopwatch class and has the same name and the same methods/properties, but with additional features:

  • SetOffset() method
  • Initialization with offset

.

using System;

public class Stopwatch : System.Diagnostics.Stopwatch
{
    TimeSpan _offset = new TimeSpan();

    public Stopwatch()
    {
    }

    public Stopwatch(TimeSpan offset)
    {
        _offset = offset;
    }

    public void SetOffset(TimeSpan offsetElapsedTimeSpan)
    {
        _offset = offsetElapsedTimeSpan;
    }

    public TimeSpan Elapsed
    {
        get{ return base.Elapsed + _offset; }
        set{ _offset = value; }
    }

    public long ElapsedMilliseconds
    {
        get { return base.ElapsedMilliseconds + _offset.Milliseconds; }
    }

    public long ElapsedTicks
    {
        get { return base.ElapsedTicks + _offset.Ticks; }
    }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top