2

I'm trying to emulate the way Google Calendar and Outlook handle time selection where all time entries are 30 minutes apart. I almost have every thing working with functional tests except for one thing. My problem is that I can't figure out how to ensure I'm always rounding the current date up to 30 minutes.

I'm using the following code:

protected DateTime GetStartTime()
{
    var spanTicks = TimeSpan.FromMinutes(30).Ticks;
    var ticks = (Time.Now.Ticks + spanTicks - 1) / spanTicks;

    return new DateTime(ticks * spanTicks);
}

This works for the majority of the cases except for 12:00am (where I would expect this to return 12:30am) and say 8:56am (where I would expect it to return 9:00am).

How can I fix this?

Samuel Slade
  • 8,405
  • 6
  • 33
  • 55
Nosila
  • 540
  • 7
  • 20
  • 3
    Why should 12:00am become 12:30am? 12:00am is a multiple of 30 minutes. And 8:56am is rounded to 9:00am by your code. I'd say your code is correct. See also: [C# round up time to nearest X minutes](http://stackoverflow.com/questions/7029353/c-sharp-round-up-time-to-nearest-x-minutes/7029464#7029464) – dtb Jan 18 '12 at 14:02
  • 12:00am should become 12:30am because those are the business rules I'm dealing with. – Nosila Jan 18 '12 at 14:10
  • @Nosila: Its okay if those are business rules, but then you're not just "rounding to 30 minutes". You're going to need to explicitly explain the business rules you're trying to implement. – Flater Jun 18 '20 at 09:29

5 Answers5

2

Why bother with ticks? You can force the time to exactly :30 or :00, like this:

protected DateTime GetStartTime() {
    var now = DateTime.Now;
    int addHours;
    int minute;
    if (now.Minute >= 30) {
        addHour = 1;
        minute = 0;
    } else {
        addHour = 0;
        minute = 30;
    }
    return new DateTime(now.Year, now.Month, now.Day, hour, minute, 0).AddHours(addHours);
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Shouldn't 15:45 become 16:00 and not 15:30 when rounding up? – dtb Jan 18 '12 at 14:06
  • 1
    @Groo I fixed the bug with rolling the hour by one. – Sergey Kalinichenko Jan 18 '12 at 14:17
  • It looks like the OP's code is correct (so there's no need for a replacement like this), but doesn't take the OP's business rules into account... – dtb Jan 18 '12 at 14:21
  • @dtb I am not sure the OP's code is correct, though: I suspect that it is going to break on days with leap seconds, and perhaps even around the daylight savings time boundaries. – Sergey Kalinichenko Jan 18 '12 at 14:25
  • @dasblinkenlight: You've got me a little worried about leap seconds and daylight savings time boundaries. I'll have to read into that. – Nosila Jan 18 '12 at 14:43
2

EDIT: ORIGINAL VERSION DID NOT WORK! Came from a bogous implementation in an old Mono!

New Version:

protected DateTime GetStartTime()
{
    DateTime dt=DateTime.Now;
    if (dt.Minute<30) 
    {
        dt=dt.AddMinutes(30-dt.Minute);
    }
    else 
    {
        dt=dt.AddMinutes(60-dt.Minute);
    }

    //dt now has the upcoming half-hour border

    //...

}
Eugen Rieck
  • 64,175
  • 10
  • 70
  • 92
1

Have you tried something like the following? I have successfully used a similar method to round up to the next hour, minute, day etc...

private static readonly long _ticksIn30Mins = TimeSpan.FromMinutes(30).Ticks;

protected DateTime GetRoundedTime(DateTime inputTime) 
{     
    long currentTicks = inputTime.Ticks;
    return new DateTime(currentTicks.RoundUp(_ticksIn30Mins)); 
} 

public static class ExtensionMethods 
{     
    public static long RoundUp(this long i, long toTicks)     
    {         
        return (long)(Math.Round(i / (double)toTicks, 
                  MidpointRounding.AwayFromZero)) * toTicks; 
    } 
} 

This takes the RoundOff method from this previous question. You just need to modify it to always round up by using MidpointRoundingMode.AwayFromZero.

Finally to cope with the specific case of 12:00am becoming 12:30am then check if your before rounding and after rounding value are the same, and if so, increment the roundup amount of ticks (e.g. 30mins)

var currentTime = DateTime.Now;
var rounded = GetRoundedTime(currentTime); 
if (rounded == currentTime) 
{ 
    rounded = new DateTime(rounded.Ticks + _ticksIn30Mins); 
} 

For a tested console application that demonstrates this principle, please see the below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        private static readonly long _ticksIn30Mins = TimeSpan.FromMinutes(30).Ticks; 

        static void Main(string[] args)
        {
            WriteDateString(new DateTime(2012, 01, 18, 09, 45, 11, 152));
            WriteDateString(new DateTime(2012, 01, 18, 12, 15, 11, 999));
            WriteDateString(new DateTime(2012, 01, 18, 12, 00, 00, 000));

            Console.ReadLine();
        }

        private static void WriteDateString(DateTime dateTime)
        {
            Console.WriteLine("Before: {0}, After: {1}", dateTime, GetRoundedTime(dateTime));
        }

        private static DateTime GetRoundedTime(DateTime inputTime)
        {
            long currentTicks = inputTime.Ticks; 
            var rounded = new DateTime(currentTicks.RoundUp(_ticksIn30Mins));
            if (rounded == inputTime)
            {
                rounded = new DateTime(rounded.Ticks + _ticksIn30Mins);
            }
            return rounded;
        } 
    }

    public static class ExtensionMethods
    {
        public static long RoundUp(this long i, long toTicks)
        {
            return (long)(Math.Round(i / (double)toTicks, MidpointRounding.AwayFromZero)) * toTicks;
        }
    }  
}

Output:

Console output from date roundup example

Best regards,

Community
  • 1
  • 1
Dr. Andrew Burnett-Thompson
  • 20,980
  • 8
  • 88
  • 178
1

Let .NET take care of all the special cases for you:

var now = DateTime.Now;
DateTime result = now.AddMinutes(now.Minute >= 30 ? (60-now.Minute) : (30-now.Minute));
result = result.AddSeconds(-1* result.Second); // To reset seconds to 0
result = result.AddMilliseconds(-1* result.Millisecond); // To reset milliseconds to 0
rasmusvhansen
  • 1,452
  • 1
  • 12
  • 13
0
                        String today = DateTime.Now.ToString("dd.MM.yyyy");
                        String hourMinute = DateTime.Now.ToString("HH:mm");

                        string[] sep = hourMinute.Split(":");
                        string hour = sep[0];
                        string minutes = sep[1];

                        int min = Int32.Parse(minutes);

                        if(min >= 30)
                        {
                            minutes = "30";
                        }
                        else
                        {
                            minutes = "00";
                        }

                        hourMinute = hour + ":" + minutes;