Here’s how to round a DateTime up to the nearest 30 minutes:
public static class DateTimeExtensions
{
public static DateTime RoundUpToNearest30(this DateTime datetime)
{
double atMinuteInBlock = datetime.TimeOfDay.TotalMinutes % 30;
double minutesToAdd = 30 - atMinuteInBlock;
return datetime.AddMinutes(minutesToAdd);
}
}
Code language: C# (cs)
When the time is 3:38 pm, it rounds to 4:00 pm. When it’s 5:03 pm, it rounds to 5:30 pm. When it’s exactly 2:00 pm, it’ll round up to 2:30 pm (note: see the What if you’re at the start of a 30 minute block? section).
In this article, I’ll explain how this works, show how to round down, and show the unit tests.
Table of Contents
How this works
By rounding to the nearest 30 minutes, we are dividing time into 30 minute blocks. We need to know how many minutes we are at in the current 30 minute block, and subtract that from 30 to get to the next 30 minute block.
For example, let’s say we have time 14:18:05.001. How minutes are we at in the current 30 minute block?
First, in time 14:18:05.001, we have = (14 hours * 60) + 18 minutes + (5 seconds / 60) + (1 millisecond / 60000) = 858.08335 total minutes.
We are in the 30 minute block that starts at 840 minutes (14:00). To know how many minutes we are in this block, we use the modulus operator (%) to get the remainder of dividing the total minutes by 30. If we are at the exact start of a 30 minute block (like 840), the remainder would be 0
All of this is represented by this code:
double atMinuteInBlock = datetime.TimeOfDay.TotalMinutes % 30;
Code language: C# (cs)
We are at minute 18.08335 in the 30 minute block. We can subtract this from 30 minutes to find out how many minutes we are from the next 30 minute block (we are 11.91665 minutes away):
This is the following line:
double minutesToAdd = 30 - atMinuteInBlock;
Code language: C# (cs)
Finally, adding this number of minutes to our time 14:18:05.001 gives us 14:30:00.000.
Read more about converting DateTime to string in the format you want.
What if you’re at the start of a 30 minute block?
When it’s 2 pm or 4:30 pm, do you need to round up, or keep the DateTime as is? There’s no right or wrong answer, and it’ll depend on your exact requirements.
The code shown in this article is assuming you always want to round up (or down), even if you’re at the start of a 30 minute block. This can be changed to return the DateTime as is by checking if you’re at the 0th minute in the block:
double atMinuteInBlock = datetime.TimeOfDay.TotalMinutes % 30;
if (atMinuteInBlock == 0)
return datetime;
double minutesToAdd = 30 - atMinuteInBlock;
return datetime.AddMinutes(minutesToAdd);
Code language: C# (cs)
Rounding to different numbers
All of the code shown in this article have hardcoded rounding to 30 minutes. This works with any number of minutes though. You can generalize the method by adding a parameter:
public static DateTime RoundUpToNearest(this DateTime datetime, int roundToMinutes)
{
double minutes = datetime.TimeOfDay.TotalMinutes % roundToMinutes;
double minutesToAdd = roundToMinutes - minutes;
return datetime.AddMinutes(minutesToAdd);
}
Code language: C# (cs)
Here’s an example of rounding to the nearest 15 minutes:
dateTimeToRound.RoundUpToNearest(15);
Code language: C# (cs)
Rounding down
The following code rounds down to the nearest 30 minutes:
public static DateTime RoundDownToNearest30(this DateTime datetime)
{
double minutes = datetime.TimeOfDay.TotalMinutes % 30;
return datetime.AddMinutes(-minutes);
}
Code language: C# (cs)
When it’s 4:02 pm, it rounds down to 4:00 pm. When it’s 11:59:59.999 pm, it rounds down to 11:30 pm. When it’s exactly 2 pm, it rounds down to 1:30 pm.
This is using the same logic as the round up logic, where it tries to figure out how many minutes we are in the current 30 minute block. The difference is that it subtracts these minutes in order to round down.
Unit tests
Here are the unit tests for the RoundUpToNearest30() method . It uses parameterized tests to test the edges of 30 minute blocks (at the start, 1 millisecond in, and 1 millisecond from the next block) as well as a time in the middle of the block:
[TestMethod()]
public void WhenAtStartOf30Minutes_DoesntRound()
{
//arrange
DateTime dateTimeToRound = DateTime.Parse("2021-12-20 14:00:00.000");
DateTime expected = DateTime.Parse("2021-12-20 14:30:00.000");
//act
var actual = dateTimeToRound.RoundUpToNearest30();
//assert
Assert.AreEqual(expected, actual);
}
[DataRow("2021-12-20 14:00:00.001")]
[DataRow("2021-12-20 14:18:05.001")]
[DataRow("2021-12-20 14:29:59.999")]
[TestMethod()]
public void WhenMinuteBetween0And30_RoundsTo30(string datetimeString)
{
//arrange
DateTime dateTimeToRound = DateTime.Parse(datetimeString);
DateTime expected = DateTime.Parse("2021-12-20 14:30:00.000");
//act
var actual = dateTimeToRound.RoundUpToNearest30();
//assert
Assert.AreEqual(expected, actual);
}
[DataRow("2021-12-20 14:30:00.001")]
[DataRow("2021-12-20 14:48:05.001")]
[DataRow("2021-12-20 14:59:59.999")]
[TestMethod()]
public void WhenMinuteBetween30And59_RoundsTo00(string datetimeString)
{
//arrange
DateTime dateTimeToRound = DateTime.Parse(datetimeString);
DateTime expected = DateTime.Parse("2021-12-20 15:00:00.000");
//act
var actual = dateTimeToRound.RoundUpToNearest30();
//assert
Assert.AreEqual(expected, actual);
}
[TestMethod()]
public void WhenRoundToMidnight_IncrementsDate()
{
//arrange
DateTime dateTimeToRound = DateTime.Parse("2021-12-20 23:59:59.999");
DateTime expected = DateTime.Parse("2021-12-21 00:00:00.000");
//act
var actual = dateTimeToRound.RoundUpToNearest30();
//assert
Assert.AreEqual(expected, actual);
}
Code language: C# (cs)
Read more about parsing DateTime from a string (which is what the unit tests are doing above).