views:

3419

answers:

6

Hi all,

I want to round a DateTime to the nearest 5 seconds. This is the way I'm currently doing it but I was wondering if there was a better or more concise way?

DateTime now = DateTime.Now;
int second = 0;

// round to nearest 5 second mark
if (now.Second % 5 > 2.5)
{
    // round up
    second = now.Second + (5 - (now.Second % 5));
}
else
{
    // round down
    second = now.Second - (now.Second % 5);
}

DateTime rounded = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, second);

Please note that I've found these two previous questions, however they truncate rather than round the time.

+1  A: 

Like you mentioned, it's fairly easy to truncate. So, just add 2.5 seconds, then truncate down.

Ben Alpert
If I add 2.5 seconds, truncate to the nearest 5 seconds and subtract the 2.5 seconds, I'll end up with 2.5sec, 7.5sec, 12.5sec etc...
Damovisa
So, leaving out the last step (add 2.5 seconds) should work...
Damovisa
+11  A: 

The Ticks count of a DateTime represents 100-nanosecond intervals, so you can round to the nearest 5 seconds by rounding to the nearest 50000000-tick interval like this:

  DateTime now = DateTime.Now;
  DateTime rounded = new DateTime(((now.Ticks + 25000000) / 50000000) * 50000000);

That's more concise, but not necessarily better. It depends on whether you prefer brevity and speed over code clarity. Yours is arguably easier to understand.

JayMcClellan
This works well because 59 seconds rounded to the nearest 5 will yield 60, which you can't pass as the 'seconds' parameter to the DateTime constructor. This way you avoid that pitfall.
Matt Hamilton
Yeah, that's a good point - I missed that problem in my code...
Damovisa
One potential pitfall, to criticize my own answer, is that I'm not sure how the DateTime accounts for leap-seconds. The tick count is measured from 12:00:00 midnight, January 1, 0001. So depending on the number of leap seconds since then and whether DateTime accounts for them, you might find that the resulting Seconds value is not a multiple of 5.
JayMcClellan
Wow, now that is detailed... I think I'm happy enough to ignore a potential error every approximately 18 months.
Damovisa
you can make it more readable by using TimeSpan.TicksPerSecond.
Erich Mirabal
Or use 24999999 to get the behavior in the question, where multiples of 2.5 round *down*. Other than that, good solution. Is there *any* language that allows you to use commas in numbers to make them more readable? I would love to see this feature since numbers like 50000000 annoy the carp out of me. 50,000,000 would be so much better.
paxdiablo
@Pax - you're right, the question will round down for anything between 2.5 and 3sec. It's not really how I need it to behave, I was just rushed.
Damovisa
JayMcClellan: It doesn't matter. .NET (and Windows) can't handle leap seconds.
Porges
+1  A: 

I can't think of a better way, although I would probably factor out the round method:

static int Round(int n, int r)
{
    if ((n % r) <= r / 2)
    {
        return n - (n % r); 
    }
    return n + (r - (n % r));
}

Also, % returns an int, so comparing it to 2.5 strikes me as a little odd, even though it is correct. I'd use >= 3.

RossFabricant
Yeah, I know what you mean with the comparing it with 2.5 - it felt a little uncomfortable. As you say, it is correct though, and it makes it clearer what the intention is. 2.5 is clearly half of 5 while 3 seems not to fit.
Damovisa
I prefer to round integers like this: `((n + (r>>1)) / r) * r` (round midpoints up) or `((n + r>>1 - 1) / r) * r` (round midpoints down). If I know `r` is odd I just use the first one because they work the same for odd `r`. This approach uses only one division (vs 3) and no branching compared to your function.
romkyns
+1  A: 

I couldn't recognize the difference between C# and a bar of soap but, if you're looking for a more concise solution, I would just put the whole thing in a function - there's little that will be more concise in your code than a call to said function:

DateTime rounded = roundTo5Secs (DateTime.Now);

Then you can put whatever you want in the function and just document how it works, such as (assuming these are all integer operations):

secBase = now.Second / 5;
secExtra = now.Second % 5;
if (secExtra > 2) {
    return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute,
        secBase + 5);
}
return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute,
    secBase);

You may also need some extra checks if secBase goes to 60 (unless C# DateTime objects are smart enough to bump up the minute (and hour if minute goes to 60, and so on).

paxdiablo
Yeah, fair point. I will be doing this - I just didn't make it clear in the question.
Damovisa
+1  A: 

How about this (blending a few answers together)? I think it conveys the meaning well and should handle the edge cases (rounding to the next minute) elegantly due to AddSeconds.

// truncate to multiple of 5
int second = 5 * (int) (now.Second / 5);
DateTime dt = new DateTime(..., second);

// round-up if necessary
if (now.Second % 5 > 2.5)
{
    dt = dt.AddSeconds(5);
}

The Ticks approach as shown by Jay is more concise, but may be a bit less readable. If you use that approach, at least reference TimeSpan.TicksPerSecond.

Erich Mirabal
-1: doesn't work if the original time contains fractions of a second.
Joe
You are right. I rolled back to a previous edit that was more clear in handling that case
Erich Mirabal
Removed -1 then!
Joe
Thanks for keeping me honest :) I woke up this morning to this -1 and was like: "Argh! Stupid mistake. Don't think you can optimize while sleepy!"
Erich Mirabal
As a matter of curiosity, should the answers that result in an exception (from mishandling the case where second == 60) also not get voted down?
Erich Mirabal