Make a stopwatch in C#

[stopwatch]

This program is a simple stopwatch. When you click the Start button, the program changes the button’s caption to Stop and starts a Timer. As the stopwatch Timer runs, the program displays the elapsed time. Click the Stop button to stop the Timer.

This example is almost trivial, but it actually raises a few interesting issues.

The following code shows the button’s Click event handler.

// Start or stop the stopwatch.
private DateTime StartTime;
private void btnStart_Click(object sender, EventArgs e)
{
    tmrClock.Enabled = !tmrClock.Enabled;
    btnStart.Text = tmrClock.Enabled ? "Stop" : "Start";
    StartTime = DateTime.Now;
}

This code first toggles the Timer component’s Enabled property so it starts if it is stopped and vice versa.

Next the code sets the button’s Text property appropriately. If the Timer is enabled, then then Timer is running so the button’s Text property should be “Stop.” If the Timer is disabled, then the Timer is not running so the button’s Text property should be “Start.” (I usually don’t use the ?: operator because it’s confusing, but it does shorten the code in this case and it’s something every C# programmer should know how to use even if you don’t use it much.)

Finally this code records the current time in the StartTime variable.

Every time the stopwatch Timer ticks, the following event handler executes to display the elapsed time.

// Display the new elapsed time.
private void tmrClock_Tick(object sender, EventArgs e)
{
    TimeSpan elapsed = DateTime.Now - StartTime;

    // Start with the days if greater than 0.
    string text = "";
    if (elapsed.Days > 0)
        text += elapsed.Days.ToString() + ".";

    // Convert milliseconds into tenths of seconds.
    int tenths = elapsed.Milliseconds / 100;

    // Compose the rest of the elapsed time.
    text +=
        elapsed.Hours.ToString("00") + ":" +
        elapsed.Minutes.ToString("00") + ":" +
        elapsed.Seconds.ToString("00") + "." +
        tenths.ToString("0");

    lblElapsed.Text = text;
}

This code gets the elapsed time since the stopwatch started by subtracting the start time from the current time.

The elapsed TimeSpan variable has Days, Hours, Minutes, Seconds, and Milliseconds properties to tell you how much time has elapsed. Each of those values does not count the higher-level values. For example, if 1 hour and 10 minutes has elapsed, then the Hour property is 1 and the Minutes property is 10. The Minutes property is not 70 to represent the entire elapsed time in minutes.

The TimeSpan class also has TotalDays, TotalHours, and similar properties to represent the time in a single unit. In this example, TotalMinutes would be 70. This example doesn’t use the Total properties.

If the number of elapsed days is at least 1, the code adds the number of days to the variable text.

The code then converts the number of milliseconds into tenths of a second. This is one of the places where this example gets a bit tricky. The expression elapsed.Milliseconds / 100 truncates the result. For example, if the number of milliseconds is 199, this code truncates the result into 1 tenth of a second.

However, you would probably want to round this off to 2 tenths of a second. You could do that with some extra code, but what happens if the number of milliseconds is 999? Then you would round that off to 10 tenths of a second. That would mean you would need to add 1 additional second to the number of elapsed seconds. But then what if the number of elapsed seconds is 59? Then you would need to add a minute to the number of elapsed minutes.

You can probably see where this is headed. You would need special cases to add extra seconds, minutes, hours, and days. Handling all of those special cases makes the code much longer and harder to read and debug. This stopwatch simply truncates the number of tenths. The result may be off by as much as 0.05 seconds. That’s only 1/20 of a second and the elapsed time is only off by that amount for a tiny amount of time, so I’m willing to live with it to simplify the code.

In fact, you could make the stopwatch code even simpler by simply displaying the value elapsed.ToString(). Unfortunately that displays the elapsed time in the format 1.02:03:04.0050000. That gives an unreasonable impression of the time’s precision.

You could try to remove trailing digits, but then you’re basically truncating the milliseconds. You can try to round off but then you have the same problem as before.

To make parsing this value even trickier, the time doesn’t follow a consistent format. For example, it does not include the number of days of that number is 0. It also doesn’t include the milliseconds if they happen to be 0.

Another approach is available in Visual Studio 2010 and later. In earlier versions, the TimeSpan class’s ToString method takes no parameters and just provides a result similar to 1.02:03:04.0050000. In Visual Studio 2010 and later, the ToString method can take a formatting string. The standard format strings display milliseconds with at least 3 digits. You can use a custom format string, for example ts.ToString(@"dd\:hh\:mm\:ss\.f"), but then the result isn’t locale-aware. If you need the stopwatch to run in other countries, you will need to detect the country and adjust the format accordingly. At least ToString() and the standard formats such as ToString("g") are locale-aware so you don’t need to worry about the country.

For information on standard TimeSpan formats, see Standard TimeSpan Format Strings. For information on custom TimeSpan formats, see Custom TimeSpan Format Strings.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in internationalization, miscellany, strings and tagged , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *