Can you round a .NET TimeSpan
object?
I have a Timespan
value of: 00:00:00.6193789
Is there a simple way to keep it a TimeSpan
object but round it to something like
00:00:00.62?
Can you round a .NET TimeSpan
object?
I have a Timespan
value of: 00:00:00.6193789
Is there a simple way to keep it a TimeSpan
object but round it to something like
00:00:00.62?
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.
Simplest one-liner if you are rounding to whole seconds:
public static TimeSpan RoundSeconds( TimeSpan span ) {
return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds ) );
}
To round to up to 3 digits (e.g. tenths, hundredths of second, or milliseconds:
public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
// TimeSpan.FromSeconds rounds to nearest millisecond, so nDigits should be 3 or less - won't get good answer beyond 3 digits.
Debug.Assert( nDigits <= 3 );
return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds, nDigits ) );
}
For more than 3 digits, its slightly more complex - but still a one-liner. This can also be used for 3 or less digits - it is a replacement for the version shown above:
public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
return TimeSpan.FromTicks( (long)( Math.Round( span.TotalSeconds, nDigits ) * TimeSpan.TicksPerSecond) );
}
If you want a string (according to a comment, this technique only works up to 7 digits):
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 );
}
For time of day, as hours and minutes, rounded:
public static TimeSpan RoundMinutes(TimeSpan span)
{
return TimeSpan.FromMinutes(Math.Round(span.TotalMinutes));
}
If you have a DateTime, and want to extract time of day rounded:
DateTime dt = DateTime.Now();
TimeSpan hhmm = RoundMinutes(dt.TimeOfDay);
To display rounded 24 hour time:
string hhmmStr = RoundMinutes(dt.TimeOfDay).ToString(@"hh\:mm");
To display time of day in current culture:
string hhmmStr = new DateTime().Add(RoundMinutes(dt.TimeOfDay)).ToShortTimeString();
Credits:
cc1960's answer shows use of FromSeconds, but he rounded to whole seconds. My answer generalizes to specified number of digits.
Ed's answer suggests using a format string, and includes a link to the formatting document.
Chris Marisic shows how to apply ToShortTimeString
to a TimeSpan (by first converting to a DateTime
).
To round to multiple of some other unit, such as 1/30 second:
// Rounds span to multiple of "unitInSeconds".
// NOTE: This will be close to the requested multiple,
// but is not exact when unit cannot be exactly represented by a double.
// e.g. "unitInSeconds = 1/30" isn't EXACTLY 1/30,
// so the returned value won't be exactly a multiple of 1/30.
public static double RoundMultipleAsSeconds( TimeSpan span, double unitInSeconds )
{
return unitInSeconds * Math.Round( span.TotalSeconds / unitInSeconds );
}
public static TimeSpan RoundMultipleAsTimeSpan( TimeSpan span, double unitInSeconds )
{
return TimeSpan.FromTicks( (long)(RoundMultipleAsSeconds( span, unitInSeconds ) * TimeSpan.TicksPerSecond) );
// IF USE THIS: TimeSpan.FromSeconds rounds the result to nearest millisecond.
//return TimeSpan.FromSeconds( RoundMultipleAsSeconds( span, unitInSeconds ) );
}
// Rounds "span / n".
// NOTE: This version might be a hair closer in some cases,
// but probably not enough to matter, and can only represent units that are "1 / N" seconds.
public static double RoundOneOverNAsSeconds( TimeSpan span, double n )
{
return Math.Round( span.TotalSeconds * n ) / n;
}
public static TimeSpan RoundOneOverNAsTimeSpan( TimeSpan span, double n )
{
return TimeSpan.FromTicks( (long)(RoundOneOverNAsSeconds( span, n ) * TimeSpan.TicksPerSecond) );
// IF USE THIS: TimeSpan.FromSeconds rounds the result to nearest millisecond.
//return TimeSpan.FromSeconds( RoundOneOverNAsSeconds( span, n ) );
}
To use one of these to round to multiples of 1/30 second:
private void Test()
{
long ticks = (long) (987.654321 * TimeSpan.TicksPerSecond);
TimeSpan span = TimeSpan.FromTicks( ticks );
TestRound( span, 30 );
TestRound( TimeSpan.FromSeconds( 987 ), 30 );
}
private static void TestRound(TimeSpan span, int n)
{
var answer1 = RoundMultipleAsSeconds( span, 1.0 / n );
var answer2 = RoundMultipleAsTimeSpan( span, 1.0 / n );
var answer3 = RoundOneOverNAsSeconds( span, n );
var answer4 = RoundOneOverNAsTimeSpan( span, n );
}
Results viewed in debugger:
// for 987.654321 seconds:
answer1 987.66666666666663 double
answer2 {00:16:27.6666666} System.TimeSpan
answer3 987.66666666666663 double
answer4 {00:16:27.6666666} System.TimeSpan
// for 987 seconds:
answer1 987 double
answer2 {00:16:27} System.TimeSpan
answer3 987 double
answer4 {00:16:27} System.TimeSpan
public static TimeSpan RoundSeconds(this TimeSpan span, int nDigits = 0)
–
Ange RoundSecondsAsString
: Mind that the maximum number of digits (f
's) that you can append is 7! Also there will be an orphaned decimal point at the end if nDigits == 0
–
Froehlich 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
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 + Math.Sign(ts.Ticks) * rndTicks / 2;
return TimeSpan.FromTicks(ansTicks - ansTicks % rndTicks);
}
}
public static TimeSpan Round(this TimeSpan ts) => ts.Round(TimeSpan.FromSeconds(1));
Given the potential inaccuracies rounding to ticks when dealing with fractional units (per @ToolmakerSteve), I am adding a fractional rounding option for when you need higher accuracy and are rounded to a computer fractional seconds:
public static TimeSpan RoundToFraction(this TimeSpan ts, long num, long den) => (den == 0.0) ? TimeSpan.Zero : TimeSpan.FromTicks((long)Math.Round(Math.Round((double)ts.Ticks * (double)den / num / TimeSpan.TicksPerSecond) * (double)num / den * TimeSpan.TicksPerSecond));
public static TimeSpan RoundToFraction(this TimeSpan ts, long den) => (den == 0.0) ? TimeSpan.Zero : TimeSpan.FromTicks((long)(Math.Round((double)ts.Ticks * den / TimeSpan.TicksPerSecond) / den * TimeSpan.TicksPerSecond));
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.) –
Epistrophe TimeSpan
is one tick, doesn't that mean you can't round a TimeSpan
evenly to 1/30 of a second, no matter how you do the math? –
Ange TimeSpan.FromSeconds
is specified as only being accurate to the millisecond (!). –
Ange FromSeconds
- it is only accurate to the millisecond. I wrote my own that converts to ticks and then to TimeSpan
. –
Ange FromTicks
. –
Ange TimeSpan.FromDays(-4).Round(TimeSpan.FromDays(1))
results in -3 days. –
Criollo TimeSpan
s. –
Ange new TimeSpan(tmspan.Hours, tmspan.Minutes, tmspan.Seconds, (int)Math.Round(Convert.ToDouble(tmspan.Milliseconds / 10)));
My solution:
static TimeSpan RoundToSec(TimeSpan ts)
{
return TimeSpan.FromSeconds((int)(ts.TotalSeconds));
}
Math.Round
instead of (int)
, as I show in my first code snippet. –
Epistrophe Not sure about TimeSpan, but you might check this post on DateTimes:
http://mikeinmadison.wordpress.com/2008/03/12/datetimeround/
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)));
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
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.
public static DateTime RoundToMinute(this DateTime date)
{
return DateTime.MinValue.AddMinutes(Math.Round((date - DateTime.MinValue).TotalMinutes));
}
© 2022 - 2024 — McMap. All rights reserved.