Updated on

TimeSpan is a value type that represents a length of time rather than a point in time. A DateTime says when; a TimeSpan says how long.

It stores one 64-bit tick count, where a tick is 100 nanoseconds. That single value is what makes the type cheap, comparable and arithmetic: two TimeSpan values add, subtract, multiply, divide and compare with the ordinary operators.

To download the source code for this article, you can visit our GitHub repository.

Let’s get started.

What Is TimeSpan in C#?

TimeSpan is a value type in the System namespace that represents a length of time, not a point in time. A DateTime says when something happened; a TimeSpan says how long it lasted.

Its value is a 64-bit tick count. One tick is 100 nanoseconds, so a TimeSpan is a whole number of ten-millionths of a second and cannot express anything finer.

That single number is what gives the type its behaviour: copying is cheap, comparison is numeric, and arithmetic works with the ordinary +, -, *, / and comparison operators.

Because the tick count is signed, an interval can be negative. Subtracting a later moment from an earlier one gives a negative TimeSpan rather than an error.

Readings come off the value in two groups. The component properties report the parts of the interval, so Hours on a span of one day and two hours is 2. The total properties report the whole interval in one unit, so TotalHours on that same span is 26.

That is also where a negative interval comes from in practice: subtracting one DateTime from another hands us a TimeSpan, and the order of the two operands decides its sign.

When we need a calendar date or a wall-clock time on its own rather than a length, DateOnly and TimeOnly are the types for that job.

We can create a TimeSpan with any of these constructors:

ConstructorDescription
TimeSpan(Int64)Instantiates TimeSpan object with a specific number of ticks.
TimeSpan(Int32, Int32, Int32)Initializes an instance with a specific number of hours, minutes, and seconds. This overload has no days parameter.
TimeSpan(Int32, Int32, Int32, Int32)This option instantiates an instance with a specific number of days, hours, minutes, and seconds.
TimeSpan(Int32, Int32, Int32, Int32, Int32)Initializes a TimeSpan instance with specific number of days, hours, minutes, seconds, and milliseconds.
TimeSpan(Int32, Int32, Int32, Int32, Int32, Int32)Creates an instance with a specific number of days, hours, minutes, seconds, milliseconds, and microseconds.

Component values roll up rather than being range-checked, so new TimeSpan(1, 60, 3600) is 1 hour plus 60 minutes plus 3600 seconds, which is three hours.

Next, let’s review some of the fields we can use when manipulating TimeSpan values.

What Fields and Constants Does TimeSpan Have?

TimeSpan exposes three named values, MinValue, MaxValue and Zero, plus a set of compile-time constants for converting between time units:

FieldDescription
HoursPerDayRepresents the number of hours in 1 day. This field is constant.
MaxValueRepresents the maximum TimeSpan value, 10675199.02:48:05.4775807. This field is read-only.
MicrosecondsPerDayRepresents the number of microseconds in 1 day. This field is constant.
MicrosecondsPerHourRepresents the number of microseconds in 1 hour. This field is constant.
MicrosecondsPerMillisecondRepresents the number of microseconds in 1 millisecond. This field is constant.
MicrosecondsPerMinuteRepresents the number of microseconds in 1 minute. This field is constant.
MicrosecondsPerSecondRepresents the number of microseconds in 1 second. This field is constant.
MillisecondsPerDayRepresents the number of milliseconds in 1 day. This field is constant.
MillisecondsPerHourRepresents the number of milliseconds in 1 hour. This field is constant.
MillisecondsPerMinuteRepresents the number of milliseconds in 1 minute. This field is constant.
MillisecondsPerSecondRepresents the number of milliseconds in 1 second. This field is constant.
MinutesPerDayRepresents the number of minutes in 1 day. This field is constant.
MinutesPerHourRepresents the number of minutes in 1 hour. This field is constant.
MinValueRepresents the minimum TimeSpan value, -10675199.02:48:05.4775808. This field is read-only.
NanosecondsPerTickRepresents the number of nanoseconds in 1 tick. This field is constant.
SecondsPerDayRepresents the number of seconds in 1 day. This field is constant.
SecondsPerHourRepresents the number of seconds in 1 hour. This field is constant.
SecondsPerMinuteRepresents the number of seconds in 1 minute. This field is constant.
TicksPerDayRepresents the number of ticks in 1 day. This field is constant.
TicksPerHourRepresents the number of ticks in 1 hour. This field is constant.
TicksPerMicrosecondRepresents the number of ticks in 1 microsecond. This field is constant.
TicksPerMillisecondRepresents the number of ticks in 1 millisecond. This field is constant.
TicksPerMinuteRepresents the number of ticks in 1 minute. This field is constant.
TicksPerSecondRepresents the number of ticks in 1 second. This field is constant.
ZeroRepresents the zero TimeSpan value. This field is read-only.

The PerX values are compile-time constants, so TimeSpan.SecondsPerDay replaces a hardcoded 86400 at no runtime cost.

What Properties Does TimeSpan Have in C#?

The properties fall into two groups, and the difference is easiest to see on a single interval:

PropertyDescription
DaysGets the days component of the interval.
HoursGets the hours component of the interval.
MicrosecondsGets the microseconds component of the interval.
MillisecondsGets the milliseconds component of the interval.
MinutesGets the minutes component of the interval.
NanosecondsGets the nanoseconds component of the interval. Because one tick is 100 nanoseconds, this value is always a multiple of 100.
SecondsGets the seconds component of the interval, ranging from -59 to 59.
TicksRepresents the number of ticks in the current TimeSpan instance.
TotalDaysUses whole and fractional numbers to represent the total number of days in the TimeSpan instance.
TotalHoursGets the total number of hours in the current TimeSpan structure.
TotalMicrosecondsRepresents the total number of microseconds in the TimeSpan instance.
TotalMillisecondsThe property gets the total number of milliseconds in the current instance.
TotalMinutesReturns the TimeSpan structure in minutes and uses both whole and fractional values.
TotalNanosecondsThis property represents whole and fractional nanoseconds.
TotalSecondsReturns the value of the current TimeSpan instance as seconds.

A bar representing one day, two hours, three minutes and four seconds, with the component properties Days, Hours, Minutes and Seconds labelled above its segments and the total properties TotalDays, TotalHours, TotalMinutes and TotalSeconds labelled below the whole bar.

Reading the whole interval in one unit is what we want when we report an elapsed time, and it is the same reading behind the number of days between two dates.

Now that we know some of the properties that the TimeSpan struct has, let’s learn how to implement some of the methods we can use to manipulate TimeSpan values.

What Methods Does TimeSpan Have in C#?

The TimeSpan struct has some useful methods to manipulate TimeSpan values, which we’ll now look at.

How to Add TimeSpan Objects

Let’s look at how we can add two TimeSpan values:

var firstTimeSpan = new TimeSpan(1, 60, 3600);
var secondTimeSpan = new TimeSpan(2, 60, 3600);

var actual = firstTimeSpan.Add(secondTimeSpan);
var expected = firstTimeSpan + secondTimeSpan;

Assert.AreEqual(expected, actual);

Here, we implement a method that uses the Add() method and returns a TimeSpan object. Also, we observe that we can implement the addition operation using the + operator.

Compare TimeSpan Objects

If we need to compare two TimeSpan instances to know whether the first value is shorter, equal to, or longer than the second value, we can take advantage of the Compare() method, which returns zero if the values we are comparing are equal.

Therefore, a value of 1 means the first TimeSpan value is greater than the second TimeSpan value. On the other hand, the method returns -1 if the first TimeSpan value is less than the second TimeSpan value.

Now, let’s implement an example:

var firstTimeSpan = new TimeSpan(1, 60, 3600);
var secondTimeSpan = new TimeSpan(1, 60, 3600);
var thirdTimeSpan = new TimeSpan(3, 60, 4000);

var equalTo = TimeSpan.Compare(firstTimeSpan, secondTimeSpan);
var lessThan = TimeSpan.Compare(secondTimeSpan, thirdTimeSpan);
var greaterThan = TimeSpan.Compare(thirdTimeSpan, firstTimeSpan);

Assert.AreEqual(0, equalTo);
Assert.AreEqual(-1, lessThan);
Assert.AreEqual(1, greaterThan);

Here, we prove that the method compares TimeSpan values accurately.

How to Compare TimeSpan Objects Using the CompareTo() Method

The CompareTo() method achieves the same purpose as the Compare() method:

var firstTimeSpan = new TimeSpan(1, 60, 3600);
var secondTimeSpan = new TimeSpan(1, 60, 3600);
var thirdTimeSpan = new TimeSpan(3, 60, 4000);

var equalTo = firstTimeSpan.CompareTo(secondTimeSpan);
var lessThan = secondTimeSpan.CompareTo(thirdTimeSpan);
var greaterThan = thirdTimeSpan.CompareTo(firstTimeSpan);

Assert.AreEqual(0, equalTo);
Assert.AreEqual(-1, lessThan);
Assert.AreEqual(1, greaterThan);

The CompareTo() method compares the second parameter with the current TimeSpan instance, and we prove it achieves the same results as our previous example.

Divide Operation With TimeSpan Values in C#

The Divide() method supports two overloads. Firstly, one allows us to divide the current TimeSpan instance with another to return a double object. Meanwhile, the other allows us to divide a TimeSpan value with a divisor to return a TimeSpan object.

Without further ado, let’s learn how to implement the first overload of the Divide() method:

var firstTimeSpan = new TimeSpan(4, 0, 0);
var secondTimeSpan = new TimeSpan(2, 0, 0);

var expected = firstTimeSpan.Divide(secondTimeSpan);
var actual = firstTimeSpan / secondTimeSpan;

Assert.AreEqual(expected, actual);
Assert.IsInstanceOfType(actual, typeof(double));
Assert.IsInstanceOfType(expected, typeof(double));

We can verify that dividing firstTimeSpan, four hours, with secondTimeSpan, two hours, returns 2.

Now, let’s learn how to divide a TimeSpan instance with a divisor:

var firstTimeSpan = new TimeSpan(4, 0, 0);
var secondTimeSpan = new TimeSpan(2, 0, 0);

var divisionByMethod = firstTimeSpan.Divide(2);
var divisionByOperator = firstTimeSpan / 2;

Assert.AreEqual(divisionByMethod, secondTimeSpan);
Assert.AreEqual(divisionByOperator, secondTimeSpan);
Assert.IsInstanceOfType(divisionByMethod, typeof(TimeSpan));
Assert.IsInstanceOfType(divisionByOperator, typeof(TimeSpan));

Here, we see that dividing the firstTimeSpan by 2 returns the secondTimeSpan.

Check the Equality of Two TimeSpan Values

We have a lot of options for checking the equality of TimeSpan values. First, we can use the Equals() method to assess whether two TimeSpan values are equal. Moreover, we can use normal mathematical equality operators to achieve the same result.

If you’d like to expand your knowledge about operators in C#, how about checking out our great article on Operator Overloading in C#.

Let’s look at how we use it:

var firstTimeSpan = new TimeSpan(1, 60, 3600);
var secondTimeSpan = new TimeSpan(1, 60, 3600);
var thirdTimeSpan = new TimeSpan(3, 60, 4000);

Assert.IsTrue(firstTimeSpan == secondTimeSpan);
Assert.IsTrue(firstTimeSpan.Equals(secondTimeSpan));
Assert.IsTrue(firstTimeSpan < thirdTimeSpan);
Assert.IsTrue(thirdTimeSpan > secondTimeSpan);
Assert.IsTrue(thirdTimeSpan >= secondTimeSpan);
Assert.IsTrue(firstTimeSpan <= thirdTimeSpan);
Assert.IsTrue(firstTimeSpan != thirdTimeSpan);

Here, we can see that the Equals() method returns a boolean value after comparing whether two TimeSpan values are equal. Also, we see that we can compare TimeSpan values using operators such as >, <, >=, <=, !=, and ==.

Multiply TimeSpan Values in C#

The Multiply() method helps us multiply the current TimeSpan instance with a given factor to return a TimeSpan value:

var expected = new TimeSpan(4, 0, 0);
var firstTimeSpan = new TimeSpan(2, 0, 0);
var factor = 2;

var multiplyMethod = firstTimeSpan.Multiply(factor);
var multiplyOperator = firstTimeSpan * factor;

Assert.AreEqual(multiplyMethod, multiplyOperator);
Assert.AreEqual(expected, multiplyOperator);
Assert.AreEqual(expected, multiplyMethod);
Assert.IsInstanceOfType(multiplyMethod, typeof(TimeSpan));
Assert.IsInstanceOfType(multiplyOperator, typeof(TimeSpan));

Here, we prove that two hours multiplied by a factor of 2 is four hours, and learn that we can use the * operator to get the same results.

How to Subtract TimeSpan Values in C#

We can use the Subtract() method when we want to find the difference between the current TimeSpan and another TimeSpan value:

var firstTimeSpan = new TimeSpan(4, 0, 0);
var secondTimeSpan = new TimeSpan(2, 0, 0);
var expected = new TimeSpan(2, 0, 0);

var subtractMethod = firstTimeSpan.Subtract(secondTimeSpan);
var subtractOperator = firstTimeSpan - secondTimeSpan;

Assert.AreEqual(subtractMethod, subtractOperator);
Assert.AreEqual(expected, subtractOperator);
Assert.AreEqual(expected, subtractMethod);
Assert.IsInstanceOfType(subtractMethod, typeof(TimeSpan));
Assert.IsInstanceOfType(subtractOperator, typeof(TimeSpan));

We use the Subtract() method to take two hours off four hours and get two hours back. However, we can use the - operator as well to achieve the same result.

Get Absolute Values From TimeSpan Objects

We can utilize the Duration() method to return the absolute value of the current TimeSpan instance. So, let’s understand how it works:

var firstTimeSpan = new TimeSpan(4, 0, 0);

var actual = firstTimeSpan.Duration();
var expected = new TimeSpan(4, 0, 0);

Assert.AreEqual(expected, actual);
Assert.IsInstanceOfType(actual, typeof(TimeSpan));

We convert a TimeSpan instance to its absolute value and learn that the absolute value of the firstTimeSpan object is four hours.

Negate TimeSpan Values in C#

In some cases, we may need to negate TimeSpan values, which we can achieve by making use of the Negate() method:

var firstTimeSpan = new TimeSpan(2, 60, 3600);
var expected = new TimeSpan(-2, -60, -3600);

var negateMethod = firstTimeSpan.Negate();
var negateOperator = -(firstTimeSpan);

Assert.AreEqual(expected, negateMethod);
Assert.AreEqual(negateMethod, negateOperator);
Assert.IsInstanceOfType(negateMethod, typeof(TimeSpan));

The method negates each component of the TimeSpan instance. Also, we learn that we can achieve the same result with the unary - operator, which the type overloads.

Create TimeSpan From the Number of Days

We can create a new TimeSpan object that is accurate to the last millisecond by invoking the FromDays() method:

var days = 2;
var expected = new TimeSpan(46, 60, 3600);

var actual = TimeSpan.FromDays(days);
var totalDays = actual.TotalDays;

Assert.AreEqual(expected, actual);
Assert.AreEqual(days, totalDays);

Here, we see that we can create a TimeSpan object and validate its accuracy by comparing it with the TotalDays field.

Microsoft’s release notes for .NET 9 put it plainly:

.NET 9 adds new overloads that let you create TimeSpan objects from integers. There are new overloads from FromDays, FromHours, FromMinutes, FromSeconds, FromMilliseconds, and FromMicroseconds.

That is from What’s new in .NET libraries for .NET 9 on Microsoft Learn.

Those overloads exist because the double overloads cannot represent every fractional value exactly, so a value with milliseconds in it can lose a tick on the way in. The integer overloads take each unit as its own argument and lose nothing:

var fromDouble = TimeSpan.FromSeconds(101.832);
var fromIntegers = TimeSpan.FromSeconds(seconds: 101, milliseconds: 832);

Assert.AreEqual(1018319999L, fromDouble.Ticks);
Assert.AreEqual(1018320000L, fromIntegers.Ticks);
Assert.AreNotEqual(fromDouble, fromIntegers);

The two differ by a single tick, and the integer overload is the one holding the value we asked for.

Instantiate TimeSpan From Number of Hours

Likewise, we can create TimeSpan objects if we know the number of hours using the FromHours() method:

var hours = 4;
var expected = new TimeSpan(2, 60, 3600);

var actual = TimeSpan.FromHours(hours);
var totalHours = actual.TotalHours;

Assert.AreEqual(expected, actual);
Assert.AreEqual(hours, totalHours);

Here, we create a TimeSpan object as we know the number of hours to use. Also, we can use theFromMinutes() method to create a TimeSpan instance from the number of minutes.

To create TimeSpan instances from seconds, milliseconds, microseconds, and ticks, we use the FromSeconds(), FromMilliseconds(), FromMicroseconds(), and FromTicks() methods respectively.

How to Convert TimeSpan Values to Strings

The TimeSpan.ToString() method allows us to convert the value of the current TimeSpan instance to its equivalent string representation.

If you’d like to learn more about the ToString() method, check out our great article ToString Method in C#.

Now, let’s learn how to invoke this method with a simple example:

var firstTimeSpan = new TimeSpan(2, 60, 3600);
var expected = "04:00:00";

var actual = firstTimeSpan.ToString();

Assert.AreEqual(expected, actual);
Assert.IsInstanceOfType(actual, typeof(string));

The method takes a TimeSpan value and returns its string representation by invoking the TimeSpan.ToString() method.

How Do We Format a TimeSpan in C#?

TimeSpan.ToString() with no argument uses the constant format, "c", which produces [-][d.]hh:mm:ss[.fffffff] and ignores the current culture.

Three standard specifiers exist. "c" is invariant and is the default. "g" is the general short format and prints only the parts it needs. "G" is the general long format and always prints days and seven fractional digits. Both "g" and "G" are culture sensitive, because the fractional separator follows the culture.

Anything longer than a single character is a custom format string, and that is where most attempts fail. In a custom format the colon and the period are pattern characters rather than literals, so ToString("hh:mm") throws a FormatException. The separators have to be escaped, and then it works.

Custom patterns read components, never totals. hh gives the hours part of the interval, so a 26-hour span formats as 02. To print a total we format the number instead, with something like $"{span.TotalHours:F1} hours".

SpecifierNameCulture sensitiveProducesOutput for new TimeSpan(1, 2, 3, 4, 5)
"c"Constant (invariant)No[-][d.]hh:mm:ss[.fffffff]1.02:03:04.0050000
"t", "T"Identical to "c"Noas "c"1.02:03:04.0050000
"g"General shortYes[-][d:]h:mm:ss[.FFFFFFF], only the parts needed1:2:03:04.005
"G"General longYes[-]d:hh:mm:ss.fffffff, always days and 7 digits1:02:03:04.0050000
anything longerCustom format stringn/awhatever the pattern says, separators escapedhh\:mm gives 02:03

The same specifiers with a different meaning are what we reach for when we are formatting a DateTime rather than an interval, so the two sets are worth keeping apart.

Let’s see the specifiers at work:

var interval = new TimeSpan(1, 2, 3, 4, 5);

Assert.AreEqual("1.02:03:04.0050000", interval.ToString("c", CultureInfo.InvariantCulture));
Assert.AreEqual("1:2:03:04.005", interval.ToString("g", CultureInfo.InvariantCulture));
Assert.AreEqual("02:03", interval.ToString(@"hh\:mm", CultureInfo.InvariantCulture));
Assert.ThrowsExactly<FormatException>(() => interval.ToString("hh:mm", CultureInfo.InvariantCulture));

Notice the escaped colon on the third assertion and the bare one on the fourth: the same two characters, one of which throws.

How Do We Parse a String Into a TimeSpan in C#?

TimeSpan.Parse() throws on bad input and TimeSpan.TryParse() returns false, so TryParse() is the one to reach for on anything a person typed.

The accepted shape is [-][d.]hh:mm[:ss[.fffffff]], and two things about it surprise people. A bare number is days, so "6" parses to six days rather than six hours. And the hours field accepts only 00 through 23, so "24:00" does not parse at all.

The second case is worth being precise about, because it looks like a success. TryParse("24:00", out var result) returns false and leaves result at TimeSpan.Zero. Zero is not midnight and it is not a parsed value: it is the default that every failed parse writes to the out parameter. Code that ignores the bool and uses result anyway reads a failure as an interval of no time.

When the input has a fixed shape, TimeSpan.TryParseExact() takes a format string and rejects anything that does not match it.

The same two methods exist for dates, and parsing a string into a DateTime follows the same shape with a different set of patterns.

Let’s parse a few strings:

Assert.IsFalse(TimeSpan.TryParse("24:00", out var failed));
Assert.AreEqual(TimeSpan.Zero, failed);

Assert.IsTrue(TimeSpan.TryParse("23:00", out var parsed));
Assert.AreEqual(new TimeSpan(23, 0, 0), parsed);

Assert.IsTrue(TimeSpan.TryParse("6", out var days));
Assert.AreEqual(TimeSpan.FromDays(6), days);

The middle pair is the control: "23:00" differs from "24:00" by one hour and by everything.

What Are TimeSpan.MinValue, MaxValue and Zero?

Three named values bound the type. TimeSpan.Zero is an interval of no time. TimeSpan.MaxValue is 10675199.02:48:05.4775807 and TimeSpan.MinValue is -10675199.02:48:05.4775808, which are the largest and smallest long tick counts, a little over 10.6 million days either side of zero.

The asymmetry in those two strings is not a typo, and it bites. MinValue is one tick further from zero than MaxValue, so TimeSpan.MinValue.Negate() and TimeSpan.MinValue.Duration() both throw an OverflowException instead of returning MaxValue. Any arithmetic that leaves the range throws as well, including adding a single tick to MaxValue.

TimeSpan.Zero needs the same care for a different reason. It is the value a default-constructed TimeSpan holds and the value a failed TryParse() writes, so finding it in a variable proves nothing about how it got there.

An infinite wait is not MaxValue either. APIs that take a timeout use Timeout.InfiniteTimeSpan, which is minus one millisecond, as the sentinel meaning no timeout at all.

Let’s check the boundaries:

Assert.AreEqual("10675199.02:48:05.4775807", TimeSpan.MaxValue.ToString());
Assert.AreEqual(long.MaxValue, TimeSpan.MaxValue.Ticks);
Assert.AreEqual(long.MinValue, TimeSpan.MinValue.Ticks);

Assert.ThrowsExactly<OverflowException>(() => TimeSpan.MinValue.Negate());
Assert.AreEqual(TimeSpan.FromMilliseconds(-1), Timeout.InfiniteTimeSpan);

Notice that MinValue.Negate() throws rather than returning MaxValue: the range is one tick wider on the negative side.

Timing an operation is the other everyday use, and measuring elapsed time with Stopwatch reports its result as a TimeSpan.

When Should We Use TimeSpan in C#?

TimeSpans come in handy to limit the time a user can spend performing some action. For example, gaming applications use them to limit how long a player has to complete a certain level or challenge before they lose their progress.

Additionally, TimeSpans can help manage applications’ scheduling by setting times when tasks should start and end.

TimeSpans can come in handy in database systems when tracking changes over time or setting limits on how long they store records.

Finally, they provide an easy way to measure durations between events, making them very useful for performance testing and analysis.

Conclusion

The TimeSpan struct is an incredibly useful tool in C# that can provide great flexibility and functionality for applications. By understanding how to create, format, and use TimeSpan objects, we can add valuable features to our programs. Have you used TimeSpans in your code? What tips would you share with other developers? Let us know in the comments below.

Tested with .NET 10.0.10 and MSTest 4.4.0.