A Detailed Guide to C# TimeSpan
By
Community /
Developer
May 02, 2024
Navigate to:
Ah, the C# TimeSpan struct. It’s quite a humble type and somewhat underutilized, if my anecdotal experience counts for anything. Despite that, the C# TimeSpan type has enormous potential to make your code more readable and robust.
Don’t believe me? Well, read the post, and you’ll learn how a type that does the (seemingly) simplest of jobs—representing a time duration—can be so awesome. Here’s how this will go:
- We’ll open the post with a definition and explanation of TimeSpan. You’ll learn what this type is and what kinds of problems it solves.
- Then, you’ll learn about the benefits of using TimeSpan over other alternatives.
- Next, it’s time to get practical: you’ll learn how to get started with TimeSpan, including its basic syntax, ways to initialize it, and an explanation of its key methods and properties.
- Finally, we’ll cover some use cases for this type and some of the most common errors you might face.
Let’s get started.
C# TimeSpan: the fundamentals
Let’s begin with TimeSpan 101.
What is a TimeSpan in C#?
Let’s start by saying that, technically—maybe a bit pedantically—“C# TimeSpan” isn’t a thing. The System.TimeSpan struct is a type in .NET, and its use isn’t restricted to C#; you can also leverage it with any of the .NET languages, such as VB.NET and F#. (But, yes, this post caters to C# programmers, so all examples will be in that language.)
So, what is theSystem.TimeSpan struct? It’s a type from the .NET BCL (Base Class Library) representing a time interval. Do you ever need to represent the concept of “five minutes” or “two hours”? If that’s the case, TimeSpan is what you’re looking for.
C# TimeSpan: importance and benefits
Here’s what I consider the top benefits of using TimeSpan:
- It reduces the likelihood of bugs
- The code becomes more readable
- It provides useful functionality related to time
But couldn’t you simply use a numerical type—like int—to represent a duration?
Yes, but such an approach has several downsides. First, it results in more error-prone code. If you use a primitive type to represent a time interval in C#, you cannot express the unit of measurement you mean.
If a certain portion of the code “thinks” the duration is in minutes while another assumes it’s in seconds, such a mismatch will cause a bug. Yes, you could use comments and/or naming conventions to make such mismatches less likely, but those can only go so far.
Another example: let’s say you have a method that takes two integers as parameters, one representing an identifier and the other a duration. A common error is to pass the two parameters in the wrong order. Such errors are easy to miss during code reviews and would result in a bug. However, if you had used a TimeSpan for the time interval, the error would have been impossible: the compiler would prevent you from passing the wrong type.
Another consequence? Less readable code. If you see a method signature that returns a TimeSpan, then you know that you’re dealing with a duration. An int, on the other hand, could be anything, and you have to resort to naming conventions to understand what it means.
A final downside of using primitives instead of the more specific time is that the primitive can’t provide useful operations with the domain it ostensibly represents. After all, a number is just a number; it can’t know anything about the domain of time.
On the other hand, TimeSpan does provide a lot of useful operations related to the concept of a time interval. Also—and unsurprisingly—it plays well with other time-related types, such as DateTime.
Obtaining a C#TimeSpan
Having covered the what of the TimeSpan type, it’s time for the how. The first thing I’m going to show you is how to get a TimeSpan value. There are several ways you can do that:
Subtracting Two DateTime Values
DateTime now = DateTime.Now;
DateTime twoHoursFromNow = now.AddHours(2);
TimeSpan difference = twoHoursFromNow - now;
Console.WriteLine(difference); // it displays '02:00:00'
Using One of the Factory Methods
var twoHours = TimeSpan.FromHours(2);
var tenMinutes = TimeSpan.FromMinutes(10);
var eightSeconds = TimeSpan.FromSeconds(8);
Adding Two or More TimeSpans Together
var fortyMinutes = TimeSpan.FromMinutes(40);
var twentyMinutes = TimeSpan.FromMinutes(20);
var oneHour = TimeSpan.FromHours(1);
var twoHours = fortyMinutes + twentyMinutes + oneHour;
Console.WriteLine(twoHours); // displays '02:00:00'
Using the Parameterless Constructor (Empty Value)
var time = new TimeSpan();
Console.WriteLine(time); // displays '00:00:00'
Using the Other Several Constructors
var a = new TimeSpan(hours: 2, minutes: 10, seconds: 25);
var b = new TimeSpan(days: 1, hours: 3, minutes: 5, seconds: 48);
var c = new TimeSpan(days: 1, hours: 3, minutes: 5, seconds: 48, milliseconds: 456);
var d = new TimeSpan(days: 1, hours: 3, minutes: 5, seconds: 48, milliseconds: 456, microseconds: 45678);
var e = new TimeSpan(ticks: 10_000_000); // 10 million ticks is equivalent to one second
Once you get down to a microsecond, things can get confusing, so here’s a reminder for those units:
- A millisecond is one-thousandth of a second.
- A microsecond is one-millionth of a second.
- A microsecond contains 1000 nanoseconds. In other words, a nanosecond is one billionth of a second.
- One tick is equivalent to 100 nanoseconds. In other words, one second contains 10 million ticks.
Parsing From a String Representation
Finally, you can also obtain a TimeSpan value by parsing from a string representation, similar to what you can do with dates and numbers. Several methods follow the pattern Parse and TryParse: the methods without the “Try” suffix throw an exception in case the parsing doesn’t succeed, while the methods with the suffix return a boolean indicating the status of the operation. The actual parsed value is returned via an out parameter.
Here’s an example of one of the versions of TryParse():
var success = TimeSpan.TryParse("1:20:15", out TimeSpan result);
var message = success ? $"Timespan value: {result}" : "Input isn't a valid timespan";
Console.WriteLine(message);
What are the key methods and properties associated with Timespan in C#?
Now you know the main ways to get a shiny, new TimeSpan value. That’s great, but what can you do with it? What functionality does this type provide?
As it turns out, the TimeSpan type has many methods, fields, and properties. I won’t cover all of them, but only the most important ones.
Methods
You’ve just seen how you can add two or more TimeSpan values. It’s also possible to perform other operations using the methods Subtract, Multiply, Divide, or the correspondent operators:
var oneHour = TimeSpan.FromHours(1);
var twoHours = oneHour.Multiply(2);
var fourHours = twoHours * 2; // this also works
var threeHours = fourHours.Subtract(oneHour);
var oneAndAHalfHours = threeHours.Divide(2);
Console.WriteLine(oneAndAHalfHours); // displays '01:30:00'
Console.WriteLine(threeHours); // displays '03:00:00'
Console.WriteLine(fourHours); // displays '04:00:00'
It’s important to remember that TimeSpan is an immutable type. Every time you do something to its value, it returns a new value instead of changing the existing one.
Do you need to invert a TimeSpan? That is, make a positive value become negative or vice versa? Then, the Negate() method is your friend. If you need the absolute value of a TimeSpan, though, Duration() is the method you’re looking for.
TimeSpan overrides the Equals method, which enables comparisons by value:
var oneHour = TimeSpan.FromHours(1);
var sixtyMinutes = TimeSpan.FromMinutes(60);
Console.WriteLine(oneHour.Equals(sixtyMinutes) ? "equal" : "not equal"); // displays 'equal'
The method CompareTo can be used to determine whether each TimeSpan value is shorter, longer, or equal to another one, returning an integer (-1, 0, 1) for each case. It’s useful when sorting a list of values.
Properties
TimeSpan offers properties that allow you to get the component portions of its value, such as hours, minutes, days, and so on. Here are all the properties:
- Days
- Hours
- Milliseconds
- Minutes
- Seconds
- Ticks
- TotalDays
- TotalHours
- TotalMilliseconds
- TotalMinutes
- TotalSeconds
The properties that start with “Total” are all of type double. They return the total value of the TimeSpan instance, expressed in the desired unit, and allow for fractional parts. The properties without the “Total” are integers (int and long types), and they return the value of the respective component without any fraction part.
Time for an example:
var ninetyMinutes = new TimeSpan(hours: 1, minutes: 30, seconds: 0);
Console.WriteLine(ninetyMinutes.TotalHours); // displays 1.5
Console.WriteLine(ninetyMinutes.Hours); // displays 1
Console.WriteLine(ninetyMinutes.TotalMinutes); // displays 90
Console.WriteLine(ninetyMinutes.Minutes); // displays 30
How to format a TimeSpan in C#
There are several ways to format a TimeSpan in C#. Let’s cover some of them.
First, you can use the ToString() method to return the default formatting:
var timeSpan = new TimeSpan(days: 2, hours: 1, minutes: 30, seconds: 0);
Console.WriteLine(timeSpan.ToString()); // displays '2.01:30:00'
Keep in mind that the ToString() above isn’t necessary since WriteLine() already calls ToString() on whatever objects it gets passed. I’ve only included it for didactical purposes.
Now, let’s see an example with more components:
var timeSpan = new TimeSpan(3, 15, 30, 45, 500);
Console.WriteLine(timeSpan.ToString(@"d'd 'h'h 'm'm 's's'")); // displays '3d 15h 30m 45s'
And finally, another alternative in which we make use of string interpolation and access the properties of the TimeSpan value directly:
var timeSpan = new TimeSpan(days: 3, hours: 15, minutes: 30, seconds: 45, milliseconds: 500);
var directFormat = $"{timeSpan.Days} days, {timeSpan.Hours} hours, {timeSpan.Minutes} minutes, {timeSpan.Seconds} seconds";
Console.WriteLine(directFormat); // displays '3 days, 15 hours, 30 minutes, 45 seconds'
C# TimeSpan: common issues and how to avoid them
Before wrapping up, let’s cover some common errors people make regarding TimeSpan values and how to avoid them.
Parsing Errors
On this StackOverflow question, the user tried the following:
TimeSpan timeTaken = TimeSpan.Parse("51:45:33");
I failed. Why? In the format above, the first component means hours and its valid range is up to 23. So, for that to work, the person would need to use the Days component to express the complete information.
Misunderstanding the Components
Earlier in the article, you learned the difference between the TimeSpan properties that start with “Total” and the ones that don’t. A common mistake is to misunderstand and swap the types of properties, resulting in unwanted behaviors.
Faulty Arithmetic
There are several ways in which people can make arithmetical mistakes when handling TimeSpan values, but my concern here is one super specific mistake: dismissing daylight saving time when subtracting DateTime values.
Let’s say you have code like this:
var start = DateTime.Now;
// many things happen
var end = DateTime.Now;
TimeSpan duration = end - start;
It might look like there’s nothing wrong here, but there may be a problem lurking if you live in an area where daylight saving time is a thing. If a daylight saving time transition happens between the time the start and end variables are initialized, you will end up with a wrong value, containing either an extra hour or a missing hour.
In these situations, you should use a “neutral” timezone that doesn’t fluctuate with daylight-saving time transitions. In other words, use UTC:
var start = DateTime.UtcNow;
// many things happen
var end = DateTime.UtcNow;
TimeSpan duration = end - start;
C# TimeSpan: bid farewell to primitive obsession
In this post, we’ve introduced you to TimeSpan. Now, you know what it is, what you can use it for, what the benefits are, and how to get started with it.
Now, as a next step, I invite you to keep exploring. Give the TimeSpan documentation a good read. Create toy projects to explore this type and its relation to other time-related types in .NET. And, of course, as soon as you have the chance, use TimeSpan in your real projects. Make primitive obsession a thing of the past.
This post was written by Carlos Schults. Carlos is a skilled software engineer and an accomplished technical writer for various clients. His passion is to get to the bottom (the original source) of things, and captivate readers with approachable and informative technical content.