Can you round a .NET TimeSpan object?


Can you round a .NET TimeSpan object?



Can you round a .NET TimeSpan object?


TimeSpan



I have a Timespan value of: 00:00:00.6193789


Timespan



Is there a simple way to keep it a TimeSpan object but round it to something like
00:00:00.62?


TimeSpan





Just a warning to others. I was going down the road of using TimeSpan as a public property off of a business object. Didn't realize that TimeSpan was not a "XmlSerializable" data type. Discussion on the issue: devnewsgroups.net/group/microsoft.public.dotnet.framework/…
– BuddyJoe
Dec 4 '08 at 0:14




11 Answers
11



Sorry, guys, but both the question and the popular answer so far are wrong :-)



The question is wrong because Tyndall asks for a way to round but shows an example of truncation.



Will Dean's answer is wrong because it also addresses truncation rather than rounding. (I suppose one could argue the answer is right for one of the two questions, but let's leave philosophy aside for the moment...)



Here is a simple technique for rounding:


int precision = 2; // Specify how many digits past the decimal point
TimeSpan t1 = new TimeSpan(19365678); // sample input value

const int TIMESPAN_SIZE = 7; // it always has seven digits
// convert the digitsToShow into a rounding/truncating mask
int factor = (int)Math.Pow(10,(TIMESPAN_SIZE - precision));

Console.WriteLine("Input: " + t1);
TimeSpan truncatedTimeSpan = new TimeSpan(t1.Ticks - (t1.Ticks % factor));
Console.WriteLine("Truncated: " + truncatedTimeSpan);
TimeSpan roundedTimeSpan =
new TimeSpan(((long)Math.Round((1.0*t1.Ticks/factor))*factor));
Console.WriteLine("Rounded: " + roundedTimeSpan);



With the input value and number of digits in the sample code, this is the output:


Input: 00:00:01.9365678
Truncated: 00:00:01.9300000
Rounded: 00:00:01.9400000



Change the precision from 2 digits to 5 digits and get this instead:


Input: 00:00:01.9365678
Truncated: 00:00:01.9365600
Rounded: 00:00:01.9365700



And even change it to 0 to get this result:


Input: 00:00:01.9365678
Truncated: 00:00:01
Rounded: 00:00:02



Finally, if you want just a bit more control over the output, add some formatting. Here is one example, showing that you can separate the precision from the number of displayed digits. The precision is again set to 2 but 3 digits are displayed, as specified in the last argument of the formatting control string:


Console.WriteLine("Rounded/formatted: " +
string.Format("{0:00}:{1:00}:{2:00}.{3:000}",
roundedTimeSpan.Hours, roundedTimeSpan.Minutes,
roundedTimeSpan.Seconds, roundedTimeSpan.Milliseconds));
// Input: 00:00:01.9365678
// Truncated: 00:00:01.9300000
// Rounded: 00:00:01.9400000
// Rounded/formatted: 00:00:01.940



The above material is useful if you are looking for ideas; I have since had time to implement a packaged solution for those looking for ready-to-use code.



Note that this is uncommented code. The fully commented version with XML-doc-comments will be available in my open source library by the end of the quarter. Though I hesitated to post it "raw" like this, I figure that it could still be of some benefit to interested readers.



This code improves upon my code above which, though it rounded, still showed 7 places, padded with zeroes. This finished version rounds and trims to the specified number of digits.



Here is a sample invocation:


Console.Write(new RoundedTimeSpan(19365678, 2).ToString());
// Result = 00:00:01.94



And here is the complete RoundedTimeSpan.cs file:


using System;

namespace CleanCode.Data
{
public struct RoundedTimeSpan
{

private const int TIMESPAN_SIZE = 7; // it always has seven digits

private TimeSpan roundedTimeSpan;
private int precision;

public RoundedTimeSpan(long ticks, int precision)
{
if (precision < 0) { throw new ArgumentException("precision must be non-negative"); }
this.precision = precision;
int factor = (int)System.Math.Pow(10, (TIMESPAN_SIZE - precision));

// This is only valid for rounding milliseconds-will *not* work on secs/mins/hrs!
roundedTimeSpan = new TimeSpan(((long)System.Math.Round((1.0 * ticks / factor)) * factor));
}

public TimeSpan TimeSpan { get { return roundedTimeSpan; } }

public override string ToString()
{
return ToString(precision);
}

public string ToString(int length)
{ // this method revised 2010.01.31
int digitsToStrip = TIMESPAN_SIZE - length;
string s = roundedTimeSpan.ToString();
if (!s.Contains(".") && length == 0) { return s; }
if (!s.Contains(".")) { s += "." + new string('0', TIMESPAN_SIZE); }
int subLength = s.Length - digitsToStrip;
return subLength < 0 ? "" : subLength > s.Length ? s : s.Substring(0, subLength);
}
}
}



I just released a new version of my open-source libraries yesterday, sooner than anticipated, including the RoundedTimeSpan I described above. Code is here; for the API start here then navigate to RoundedTimeSpan under the CleanCode.Data namespace. The CleanCode.DLL library includes the code shown above but provides it in a finished package. Note that I have made a slight improvement in the ToString(int) method above since I posted it on 2010.01.06.


RoundedTimeSpan


CleanCode.Data


ToString(int)





This is overkill. Looking over the other answers, none of them is as simple as it can be. See my answer
– ToolmakerSteve
Sep 28 '16 at 9:22





NetMage's newer answer is also nice, if you want multiples of an arbitrary timespan. E.g. sometimes you want seconds, sometimes minutes, sometimes milliseconds.
– ToolmakerSteve
Aug 5 '17 at 11:58




TimeSpan is little more than a wrapper around the 'Ticks' member. It's pretty easy to create a new TimeSpan from a rounded version of another TimeSpan's Ticks.


TimeSpan t1 = new TimeSpan(2345678);
Console.WriteLine(t1);
TimeSpan t2 = new TimeSpan(t1.Ticks - (t1.Ticks % 100000));
Console.WriteLine(t2);



Gives:


00:00:00.2345678
00:00:00.2300000





That is slick. Thanks.
– BuddyJoe
Dec 3 '08 at 21:50





The answer truncates (and to be fair, so did the question). If you instead want to round, some of the other answers do so, but none is as simple as it can be. See my answer.
– ToolmakerSteve
Sep 28 '16 at 9:23





NetMage's newer answer is also nice, if you want multiples of an arbitrary timespan. E.g. sometimes you want seconds, sometimes minutes, sometimes milliseconds.
– ToolmakerSteve
Aug 5 '17 at 11:57




If you want a TimeSpan, its a simple one-liner:


public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds, nDigits ) );
}



If you want a string:


public static string RoundSecondsAsString( TimeSpan span, int nDigits ) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nDigits; i++)
sb.Append( "f" );
return span.ToString( @"hh:mm:ss." + sb );
}



Credits:



cc1960's answer shows use of FromSeconds, but he rounded to whole seconds. My answer generalizes to specified number of digits.


FromSeconds



Ed's answer suggests using a format string, and includes a link to the formatting document.





I like this, but I also like extension methods: public static TimeSpan RoundSeconds(this TimeSpan span, int nDigits = 0)
– NetMage
Dec 16 '16 at 22:31



public static TimeSpan RoundSeconds(this TimeSpan span, int nDigits = 0)





NetMage's newer answer is nice, if you want multiples of an arbitrary timespan. E.g. sometimes you want seconds, sometimes minutes, sometimes milliseconds.
– ToolmakerSteve
Aug 5 '17 at 11:59


new TimeSpan(tmspan.Hours, tmspan.Minutes, tmspan.Seconds, (int)Math.Round(Convert.ToDouble(tmspan.Milliseconds / 10)));





This is what I do. There really should be an easier way, that doesn't involve messing with ticks directly
– rotard
Dec 3 '08 at 21:39





@rotard - there is an easier way that does not involve ticks - use a combination of TotalSeconds, Round, and FromSeconds - see my answer.
– ToolmakerSteve
Sep 28 '16 at 9:27


TotalSeconds


Round


FromSeconds



Given some of the comments about rounding to seconds, I thought rounding to any TimeSpan would be nice:


public static TimeSpan Round(this TimeSpan ts, TimeSpan rnd) {
if (rnd == TimeSpan.Zero)
return ts;
else {
var rndticks = rnd.Ticks;
var ansTicks = ts.Ticks + rndticks / 2;
return TimeSpan.FromTicks(ansTicks - ansTicks % rndticks);
}
}
public static TimeSpan Round(this TimeSpan ts) => ts.Round(TimeSpan.FromSeconds(1));





I like this (so upvoted). And it will work correctly for the vast majority of situations people will use. FWIW, Be aware of a minor limitation: Only correct if the desired "rnd" (divisor) is expressible as a whole number of ticks. For example, if one wanted to round a large number of ticks to nearest 30th of a second, don't ask me why :D, this would not be helpful. Fortunately, all the common cases work fine - IF one second is a power-of-10 of ticks (e.g. 100,000 ticks = 1 second), then 1/10th second, 1/100th second etc work, as well as 2/10th second, 2/100th, .., and 5/10th second, ...
– ToolmakerSteve
Apr 16 '17 at 5:22






Good point, but that is no different than most other floating point operations in programming. OTOH, it is difficult to create 1/30 of a second TimeSpan since Microsoft didn't implement FromTotal* variations of the factory functions so hopefully you need to know what you're doing to get there.
– NetMage
Apr 19 '17 at 22:44





On the contrary, it is different in the following way: your solution introduces a limitation that does not exist in the underlying data. You introduced this limitation by using a divisor, "rnd" that is an integer number of ticks. The result may be noticeably wrong if someone wants a divisor that is not exactly expressible as an integer number of ticks. This is a larger error than floating point round off error, which would at most result in an answer that was off by 1. Anyone using this needs to understand this limitation.
– ToolmakerSteve
Aug 5 '17 at 11:29






.. in practice, it looks like it only matters for tiny values of rnd. So 1/30 of a second the error is not enough to matter for almost any use. The smaller rnd gets, the more it may become an issue. I just wanted to flag the fact that there is a potential inaccuracy here, which happens because TimeSpan is not a floating point value, so becomes more inaccurate as values get smaller. (The solution in those cases would be to not attempt to represent rnd as a TimeSpan; instead use a floating-point number of seconds.)
– ToolmakerSteve
Aug 5 '17 at 11:40



rnd


rnd


rnd


TimeSpan





.. or even better, pass in the number you want to divide by. I mean, if you want a result in 30ths of a second, take "30" as the second parameter, instead of "1/30". [Again, let me stress that I did upvote the answer as-is; I am making minor comments that won't matter for most people looking for a solution. This is a very useful answer.]
– ToolmakerSteve
Aug 5 '17 at 11:51




Not sure about TimeSpan, but you might check this post on DateTimes:
http://mikeinmadison.wordpress.com/2008/03/12/datetimeround/



Here is a nice Extention-Method:


public static TimeSpan RoundToSeconds(this TimeSpan timespan, int seconds = 1)
{
long offset = (timespan.Ticks >= 0) ? TimeSpan.TicksPerSecond / 2 : TimeSpan.TicksPerSecond / -2;
return TimeSpan.FromTicks((timespan.Ticks + offset) / TimeSpan.TicksPerSecond * TimeSpan.TicksPerSecond);
}



And here are some Examples:


DateTime dt1 = DateTime.Now.RoundToSeconds(); // round to full seconds
DateTime dt2 = DateTime.Now.RoundToSeconds(5 * 60); // round to full 5 minutes



Yet another way to round milliseconds to the nearest second.


private const long TicksPer1000Milliseconds = 1000 * TimeSpan.TicksPerMillisecond;

// Round milliseconds to nearest second
// To round up, add the sub-second ticks required to reach the next second
// To round down, subtract the sub-second ticks
elapsedTime = new TimeSpan(elapsedTime.Ticks + (elapsedTime.Milliseconds >= 500 ? TicksPer1000Milliseconds - (elapsedTime.Ticks % TicksPer1000Milliseconds) : -(elapsedTime.Ticks % TicksPer1000Milliseconds)));



My solution:


static TimeSpan RoundToSec(TimeSpan ts)
{
return TimeSpan.FromSeconds((int)(ts.TotalSeconds));
}



An extension method if you need to work with DateTime instead, but still want to round the time. In my case, I wanted to round to the minute.


DateTime


public static DateTime RoundToMinute(this DateTime date)
{
var roundedTimeSpan = TimeSpan.FromMinutes(Math.Round(date.TimeOfDay.TotalMinutes));
return new DateTime(date.Year, date.Month, date.Day, roundedTimeSpan.Hours, roundedTimeSpan.Minutes, 0);
}



Instead of mathematically trying to round the value, you could simply display only the components of the TimeSpan that you want to show.


TimeSpan


TimeSpan currentTimeLapsed = DateTime.Now.Subtract(startTime);
Console.WriteLine (currentTimeLapsed.Hours.ToString() + ":" + currentTimeLapsed.Minutes.ToString() + ":" + currentTimeLapsed.Seconds.ToString());






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Comments

Popular posts from this blog

paramiko-expect timeout is happening after executing the command

Export result set on Dbeaver to CSV

Opening a url is failing in Swift