132

I have a datetime object produced using strptime().

>>> tm
datetime.datetime(2010, 6, 10, 3, 56, 23)

What I need to do is round the minute to the closest 10th minute. What I have been doing up to this point was taking the minute value and using round() on it.

min = round(tm.minute, -1)

However, as with the above example, it gives an invalid time when the minute value is greater than 56. i.e.: 3:60

What is a better way to do this? Does datetime support this?

ofo
  • 1,160
  • 10
  • 21
Lucas Manco
  • 1,329
  • 2
  • 9
  • 3

22 Answers22

167

This will get the 'floor' of a datetime object stored in tm rounded to the 10 minute mark before tm.

tm = tm - datetime.timedelta(minutes=tm.minute % 10,
                             seconds=tm.second,
                             microseconds=tm.microsecond)

If you want classic rounding to the nearest 10 minute mark, do this:

discard = datetime.timedelta(minutes=tm.minute % 10,
                             seconds=tm.second,
                             microseconds=tm.microsecond)
tm -= discard
if discard >= datetime.timedelta(minutes=5):
    tm += datetime.timedelta(minutes=10)

or this:

tm += datetime.timedelta(minutes=5)
tm -= datetime.timedelta(minutes=tm.minute % 10,
                         seconds=tm.second,
                         microseconds=tm.microsecond)
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
115

General function to round a datetime at any time lapse in seconds:

def roundTime(dt=None, roundTo=60):
   """Round a datetime object to any time lapse in seconds
   dt : datetime.datetime object, default now.
   roundTo : Closest number of seconds to round to, default 1 minute.
   Author: Thierry Husson 2012 - Use it as you want but don't blame me.
   """
   if dt == None : dt = datetime.datetime.now()
   seconds = (dt.replace(tzinfo=None) - dt.min).seconds
   rounding = (seconds+roundTo/2) // roundTo * roundTo
   return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)

Samples with 1 hour rounding & 30 minutes rounding:

print roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60)
2013-01-01 00:00:00

print roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=30*60)
2012-12-31 23:30:00
Prune
  • 76,765
  • 14
  • 60
  • 81
Le Droid
  • 4,534
  • 3
  • 37
  • 32
  • 16
    Unfortunately this does not work with tz-aware datetime. One should use `dt.replace(hour=0, minute=0, second=0)` instead of `dt.min`. – skoval00 Oct 15 '16 at 06:30
  • 4
    @skoval00 + druska Edited following your advices to support tz-aware datetime. Thanks! – Le Droid Oct 18 '16 at 23:56
  • Thanks @skoval00 - it took me a while to figure out why the function was not working with my data – Michel Mesquita Nov 17 '16 at 10:30
  • 1
    This does not work at all for me for long periods. e.g. `roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60*24*7)` vs `roundTime(datetime.datetime(2012,12,30,23,44,59,1234),roundTo=60*60*24*7)` – CPBL Dec 08 '16 at 18:46
  • See this to understand the problem: `datetime.timedelta(100,1,2,3).seconds == 1` – CPBL Dec 08 '16 at 18:49
  • @CPBL This function is to answer "How to round the minute" and is named "roundTime", not roundWeek or roundMonth. To round to closest monday you could replace the line with `tdelta = (dt.replace(tzinfo=None) - dt.min); seconds = tdelta.seconds + tdelta.days * 86400` but I will not edit this as it doesn't make sense to roundTime something bigger than a day as the understanding of it would be arbitrary. Like, what do you expect if you round to 3 days? – Le Droid Dec 23 '16 at 00:12
  • @LeDroid : Thanks; quite right. But your code says "any time laps[e] in seconds". Not sure how 3 days is harder to imagine than, say, 7: I guess in either case, you need to pick an arbitrary starting day. Anyway, I agree that should be a new question, but your code could clarify (or enforce) its limitation. – CPBL Dec 23 '16 at 12:20
  • Dont' forget to see @Omnifarious answer ! He explain how to get the *floor* of datetime. – Samuel Dauzon Nov 07 '18 at 00:14
21

I used Stijn Nevens code (thank you Stijn) and have a little add-on to share. Rounding up, down and rounding to nearest.

update 2019-03-09 = comment Spinxz incorporated; thank you.

update 2019-12-27 = comment Bart incorporated; thank you.

Tested for date_delta of "X hours" or "X minutes" or "X seconds".

import datetime

def round_time(dt=None, date_delta=datetime.timedelta(minutes=1), to='average'):
    """
    Round a datetime object to a multiple of a timedelta
    dt : datetime.datetime object, default now.
    dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
    from:  http://stackoverflow.com/questions/3463930/how-to-round-the-minute-of-a-datetime-object-python
    """
    round_to = date_delta.total_seconds()
    if dt is None:
        dt = datetime.now()
    seconds = (dt - dt.min).seconds

    if seconds % round_to == 0 and dt.microsecond == 0:
        rounding = (seconds + round_to / 2) // round_to * round_to
    else:
        if to == 'up':
            # // is a floor division, not a comment on following line (like in javascript):
            rounding = (seconds + dt.microsecond/1000000 + round_to) // round_to * round_to
        elif to == 'down':
            rounding = seconds // round_to * round_to
        else:
            rounding = (seconds + round_to / 2) // round_to * round_to

    return dt + datetime.timedelta(0, rounding - seconds, - dt.microsecond)

# test data
print(round_time(datetime.datetime(2019,11,1,14,39,00), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,2,14,39,00,1), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,3,14,39,00,776980), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,4,14,39,29,776980), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2018,11,5,14,39,00,776980), date_delta=datetime.timedelta(seconds=30), to='down'))
print(round_time(datetime.datetime(2018,11,6,14,38,59,776980), date_delta=datetime.timedelta(seconds=30), to='down'))
print(round_time(datetime.datetime(2017,11,7,14,39,15), date_delta=datetime.timedelta(seconds=30), to='average'))
print(round_time(datetime.datetime(2017,11,8,14,39,14,999999), date_delta=datetime.timedelta(seconds=30), to='average'))
print(round_time(datetime.datetime(2019,11,9,14,39,14,999999), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2012,12,10,23,44,59,7769),to='average'))
print(round_time(datetime.datetime(2012,12,11,23,44,59,7769),to='up'))
print(round_time(datetime.datetime(2010,12,12,23,44,59,7769),to='down',date_delta=datetime.timedelta(seconds=1)))
print(round_time(datetime.datetime(2011,12,13,23,44,59,7769),to='up',date_delta=datetime.timedelta(seconds=1)))
print(round_time(datetime.datetime(2012,12,14,23,44,59),date_delta=datetime.timedelta(hours=1),to='down'))
print(round_time(datetime.datetime(2012,12,15,23,44,59),date_delta=datetime.timedelta(hours=1),to='up'))
print(round_time(datetime.datetime(2012,12,16,23,44,59),date_delta=datetime.timedelta(hours=1)))
print(round_time(datetime.datetime(2012,12,17,23,00,00),date_delta=datetime.timedelta(hours=1),to='down'))
print(round_time(datetime.datetime(2012,12,18,23,00,00),date_delta=datetime.timedelta(hours=1),to='up'))
print(round_time(datetime.datetime(2012,12,19,23,00,00),date_delta=datetime.timedelta(hours=1)))
MZA
  • 999
  • 11
  • 16
  • This helped me. I want to add that if using it in PySpark, to parse the date time as a string rather than a date time object. – Max Sep 22 '16 at 01:24
  • 5
    The 'up' rounding is maybe not doing what most people expect. You would round up to the next date_delta even if dt would not need rounding: e.g. 15:30:00.000 with round_to = 60 would become 15:31:00.000 – spinxz Sep 18 '17 at 10:55
  • The `up` rounding is anyhow inaccurate with this function; `2019-11-07 14:39:00.776980` with `date_delta` equal to e.g. 30 sec and `to='up'` results in `2019-11-07 14:39:00`. – Bart Nov 07 '19 at 14:45
  • 1
    Thanks a lot!! Although `up` rounding might not be a common use case, it is needed when you are dealing with applications which starts at minute boundary – Rahul Bharadwaj Jul 08 '20 at 13:27
  • Great answer, with options for rounding up and down, very useful for me! – Cairan Van Rooyen Jul 14 '21 at 09:41
20

From the best answer I modified to an adapted version using only datetime objects, this avoids having to do the conversion to seconds and makes the calling code more readable:

def roundTime(dt=None, dateDelta=datetime.timedelta(minutes=1)):
    """Round a datetime object to a multiple of a timedelta
    dt : datetime.datetime object, default now.
    dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
    Author: Thierry Husson 2012 - Use it as you want but don't blame me.
            Stijn Nevens 2014 - Changed to use only datetime objects as variables
    """
    roundTo = dateDelta.total_seconds()

    if dt == None : dt = datetime.datetime.now()
    seconds = (dt - dt.min).seconds
    # // is a floor division, not a comment on following line:
    rounding = (seconds+roundTo/2) // roundTo * roundTo
    return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)

Samples with 1 hour rounding & 15 minutes rounding:

print roundTime(datetime.datetime(2012,12,31,23,44,59),datetime.timedelta(hour=1))
2013-01-01 00:00:00

print roundTime(datetime.datetime(2012,12,31,23,44,49),datetime.timedelta(minutes=15))
2012-12-31 23:30:00
Stijn Nevens
  • 246
  • 2
  • 5
  • 1
    Also no good: `print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15))` `2012-12-20 00:00:00` while `print roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15))` `2012-12-21 00:00:00` – CPBL Dec 08 '16 at 18:53
  • 4
    Follow-up to above: Just pointing out that it does not work for arbitrary time deltas, e.g. those over 1 day. This question is about rounding minutes, so that's an appropriate restriction, but it could be clearer in the way the code is written. – CPBL Dec 23 '16 at 12:16
17

Here is a simpler generalized solution without floating point precision issues and external library dependencies:

import datetime

def time_mod(time, delta, epoch=None):
    if epoch is None:
        epoch = datetime.datetime(1970, 1, 1, tzinfo=time.tzinfo)
    return (time - epoch) % delta

def time_round(time, delta, epoch=None):
    mod = time_mod(time, delta, epoch)
    if mod < delta / 2:
       return time - mod
    return time + (delta - mod)

def time_floor(time, delta, epoch=None):
    mod = time_mod(time, delta, epoch)
    return time - mod

def time_ceil(time, delta, epoch=None):
    mod = time_mod(time, delta, epoch)
    if mod:
        return time + (delta - mod)
    return time

In your case:

>>> tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
>>> time_round(tm, datetime.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)
>>> time_floor(tm, datetime.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 3, 50)
>>> time_ceil(tm, datetime.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)
ofo
  • 1,160
  • 10
  • 21
  • This is `time round to` function. How to make `Time floor to` function? I mean for example if time is between 00:00 and 00:10 then it's floored to 00:00. If it's between 00:10 and 00:20 then it's floored to 00:10 etc. – s.paszko Oct 07 '21 at 15:02
  • 1
    @s.paszko replace three lines starting from `if mod < (delta / 2):` with a single line `return time - mod` in the `time_round` function. Updated the answer accordingly. – ofo Oct 07 '21 at 15:33
  • Big thx for updated answer! – s.paszko Oct 07 '21 at 15:57
  • There is an error in `time_ceil`. If mod is `zero` then it should leave original time. Like in math Ceil(1) = 1, but Ceil(1.000001) is 2. – s.paszko Oct 08 '21 at 10:13
  • @s.paszko Corrected it – ofo Oct 08 '21 at 15:42
16

Pandas has a datetime round feature, but as with most things in Pandas it needs to be in Series format.

>>> ts = pd.Series(pd.date_range(Dt(2019,1,1,1,1),Dt(2019,1,1,1,4),periods=8))
>>> print(ts)
0   2019-01-01 01:01:00.000000000
1   2019-01-01 01:01:25.714285714
2   2019-01-01 01:01:51.428571428
3   2019-01-01 01:02:17.142857142
4   2019-01-01 01:02:42.857142857
5   2019-01-01 01:03:08.571428571
6   2019-01-01 01:03:34.285714285
7   2019-01-01 01:04:00.000000000
dtype: datetime64[ns]

>>> ts.dt.round('1min')
0   2019-01-01 01:01:00
1   2019-01-01 01:01:00
2   2019-01-01 01:02:00
3   2019-01-01 01:02:00
4   2019-01-01 01:03:00
5   2019-01-01 01:03:00
6   2019-01-01 01:04:00
7   2019-01-01 01:04:00
dtype: datetime64[ns]

Docs - Change the frequency string as needed.

Erock618
  • 453
  • 4
  • 10
3

if you don't want to use condition, you can use modulo operator:

minutes = int(round(tm.minute, -1)) % 60

UPDATE

did you want something like this?

def timeround10(dt):
    a, b = divmod(round(dt.minute, -1), 60)
    return '%i:%02i' % ((dt.hour + a) % 24, b)

timeround10(datetime.datetime(2010, 1, 1, 0, 56, 0)) # 0:56
# -> 1:00

timeround10(datetime.datetime(2010, 1, 1, 23, 56, 0)) # 23:56
# -> 0:00

.. if you want result as string. for obtaining datetime result, it's better to use timedelta - see other responses ;)

mykhal
  • 19,175
  • 11
  • 72
  • 80
3

i'm using this. it has the advantage of working with tz aware datetimes.

def round_minutes(some_datetime: datetime, step: int):
    """ round up to nearest step-minutes """
    if step > 60:
        raise AttrbuteError("step must be less than 60")

    change = timedelta(
        minutes= some_datetime.minute % step,
        seconds=some_datetime.second,
        microseconds=some_datetime.microsecond
    )

    if change > timedelta():
        change -= timedelta(minutes=step)

    return some_datetime - change

it has the disadvantage of only working for timeslices less than an hour.

David Chan
  • 7,347
  • 1
  • 28
  • 49
3

A straightforward approach:

def round_time(dt, round_to_seconds=60):
    """Round a datetime object to any number of seconds
    dt: datetime.datetime object
    round_to_seconds: closest number of seconds for rounding, Default 1 minute.
    """
    rounded_epoch = round(dt.timestamp() / round_to_seconds) * round_to_seconds
    rounded_dt = datetime.datetime.fromtimestamp(rounded_epoch).astimezone(dt.tzinfo)
    return rounded_dt
slamm
  • 171
  • 1
  • 4
3

General Function to round down times of minutes:

from datetime import datetime
def round_minute(date: datetime = None, round_to: int = 1):
    """
    round datetime object to minutes
    """
    if not date:
        date = datetime.now()
    date = date.replace(second=0, microsecond=0)
    delta = date.minute % round_to
    return date.replace(minute=date.minute - delta)
3

This will do it, I think it uses a very useful application of round.

from typing import Literal
import math

def round_datetime(dt: datetime.datetime, step: datetime.timedelta, d: Literal['no', 'up', 'down'] = 'no'):
    step = step.seconds
    round_f = {'no': round, 'up': math.ceil, 'down': math.floor}
    return datetime.datetime.fromtimestamp(step * round_f[d](dt.timestamp() / step))

date = datetime.datetime(year=2022, month=11, day=16, hour=10, minute=2, second=30, microsecond=424242)#
print('Original:', date)
print('Standard:', round_datetime(date, datetime.timedelta(minutes=5)))
print('Down:    ', round_datetime(date, datetime.timedelta(minutes=5), d='down'))
print('Up:      ', round_datetime(date, datetime.timedelta(minutes=5), d='up'))

The result:

Original: 2022-11-16 10:02:30.424242
Standard: 2022-11-16 10:05:00
Down:     2022-11-16 10:00:00
Up:       2022-11-16 10:05:00
2

yes, if your data belongs to a DateTime column in a pandas series, you can round it up using the built-in pandas.Series.dt.round function. See documentation here on pandas.Series.dt.round. In your case of rounding to 10min it will be Series.dt.round('10min') or Series.dt.round('600s') like so:

pandas.Series(tm).dt.round('10min')

Edit to add Example code:

import datetime
import pandas

tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
tm_rounded = pandas.Series(tm).dt.round('10min')
print(tm_rounded)

>>> 0   2010-06-10 04:00:00
dtype: datetime64[ns]
  • I'm not sure that this answer adds anything new or useful. There was already an answer that explained the same thing: https://stackoverflow.com/a/56010357/7851470 – Georgy Jul 08 '20 at 14:54
  • yes, thank you for pointing these out to me. It's my mistake for not including samples code into my response, and also not checking out all other people's responses. I will try to improve on this aspect. – Nguyen Bryan Jul 10 '20 at 10:17
2

I came up with this very simple function, working with any timedelta as long as it's either a multiple or divider of 60 seconds. It's also compatible with timezone-aware datetimes.

#!/usr/env python3
from datetime import datetime, timedelta

def round_dt_to_delta(dt, delta=timedelta(minutes=30)):
    ref = datetime.min.replace(tzinfo=dt.tzinfo)
    return ref + round((dt - ref) / delta) * delta

Output:

In [1]: round_dt_to_delta(datetime(2012,12,31,23,44,49), timedelta(seconds=15))
Out[1]: datetime.datetime(2012, 12, 31, 23, 44, 45)
In [2]: round_dt_to_delta(datetime(2012,12,31,23,44,49), timedelta(minutes=15))
Out[2]: datetime.datetime(2012, 12, 31, 23, 45)
leo
  • 21
  • 2
1

Those seem overly complex

def round_down_to():
    num = int(datetime.utcnow().replace(second=0, microsecond=0).minute)
    return num - (num%10)
Ashton Honnecke
  • 688
  • 10
  • 16
0
def get_rounded_datetime(self, dt, freq, nearest_type='inf'):

    if freq.lower() == '1h':
        round_to = 3600
    elif freq.lower() == '3h':
        round_to = 3 * 3600
    elif freq.lower() == '6h':
        round_to = 6 * 3600
    else:
        raise NotImplementedError("Freq %s is not handled yet" % freq)

    # // is a floor division, not a comment on following line:
    seconds_from_midnight = dt.hour * 3600 + dt.minute * 60 + dt.second
    if nearest_type == 'inf':
        rounded_sec = int(seconds_from_midnight / round_to) * round_to
    elif nearest_type == 'sup':
        rounded_sec = (int(seconds_from_midnight / round_to) + 1) * round_to
    else:
        raise IllegalArgumentException("nearest_type should be  'inf' or 'sup'")

    dt_midnight = datetime.datetime(dt.year, dt.month, dt.day)

    return dt_midnight + datetime.timedelta(0, rounded_sec)
JulienV
  • 358
  • 2
  • 9
0

Based on Stijn Nevens and modified for Django use to round current time to the nearest 15 minute.

from datetime import date, timedelta, datetime, time

    def roundTime(dt=None, dateDelta=timedelta(minutes=1)):

        roundTo = dateDelta.total_seconds()

        if dt == None : dt = datetime.now()
        seconds = (dt - dt.min).seconds
        # // is a floor division, not a comment on following line:
        rounding = (seconds+roundTo/2) // roundTo * roundTo
        return dt + timedelta(0,rounding-seconds,-dt.microsecond)

    dt = roundTime(datetime.now(),timedelta(minutes=15)).strftime('%H:%M:%S')

 dt = 11:45:00

if you need full date and time just remove the .strftime('%H:%M:%S')

WayBehind
  • 1,607
  • 3
  • 39
  • 58
0

Not the best for speed when the exception is caught, however this would work.

def _minute10(dt=datetime.utcnow()):
    try:
        return dt.replace(minute=round(dt.minute, -1))
    except ValueError:
        return dt.replace(minute=0) + timedelta(hours=1)

Timings

%timeit _minute10(datetime(2016, 12, 31, 23, 55))
100000 loops, best of 3: 5.12 µs per loop

%timeit _minute10(datetime(2016, 12, 31, 23, 31))
100000 loops, best of 3: 2.21 µs per loop
James
  • 796
  • 1
  • 8
  • 19
0

A two line intuitive solution to round to a given time unit, here seconds, for a datetime object t:

format_str = '%Y-%m-%d %H:%M:%S'
t_rounded = datetime.strptime(datetime.strftime(t, format_str), format_str)

If you wish to round to a different unit simply alter format_str.

This approach does not round to arbitrary time amounts as above methods, but is a nicely Pythonic way to round to a given hour, minute or second.

barnhillec
  • 346
  • 1
  • 5
  • 15
0

Other solution:

def round_time(timestamp=None, lapse=0):
    """
    Round a timestamp to a lapse according to specified minutes

    Usage:

    >>> import datetime, math
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 0)
    datetime.datetime(2010, 6, 10, 3, 56)
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 1)
    datetime.datetime(2010, 6, 10, 3, 57)
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), -1)
    datetime.datetime(2010, 6, 10, 3, 55)
    >>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3)
    datetime.datetime(2019, 3, 11, 9, 24)
    >>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3*60)
    datetime.datetime(2019, 3, 11, 12, 0)
    >>> round_time(datetime.datetime(2019, 3, 11, 10, 0, 0), 3)
    datetime.datetime(2019, 3, 11, 10, 0)

    :param timestamp: Timestamp to round (default: now)
    :param lapse: Lapse to round in minutes (default: 0)
    """
    t = timestamp or datetime.datetime.now()  # type: Union[datetime, Any]
    surplus = datetime.timedelta(seconds=t.second, microseconds=t.microsecond)
    t -= surplus
    try:
        mod = t.minute % lapse
    except ZeroDivisionError:
        return t
    if mod:  # minutes % lapse != 0
        t += datetime.timedelta(minutes=math.ceil(t.minute / lapse) * lapse - t.minute)
    elif surplus != datetime.timedelta() or lapse < 0:
        t += datetime.timedelta(minutes=(t.minute / lapse + 1) * lapse - t.minute)
    return t

Hope this helps!

juliocesar
  • 5,706
  • 8
  • 44
  • 63
0

The shortest way I know

min = tm.minute // 10 * 10

0

Most of the answers seem to be too complicated for such a simple question.

Assuming your_time is the datetime object your have, the following rounds (actually floors) it at a desired resolution defined in minutes.

from math import floor

your_time = datetime.datetime.now() 

g = 10  # granularity in minutes
print(
datetime.datetime.fromtimestamp(
floor(your_time.timestamp() / (60*g)) * (60*g)
))
bonobo
  • 200
  • 1
  • 10
0

The function below with minimum of import will do the job. You can round to anything you want by setting te parameters unit, rnd, and frm. Play with the function and you will see how easy it will be.

def toNearestTime(ts, unit='sec', rnd=1, frm=None):
    ''' round to nearest Time format
    param ts = time string to round in '%H:%M:%S' or '%H:%M' format :
    param unit = specify unit wich must be rounded 'sec' or 'min' or 'hour', default is seconds :
    param rnd = to which number you will round, the default is 1 :
    param frm = the output (return) format of the time string, as default the function take the unit format'''
    from time import strftime, gmtime

    ts = ts + ':00' if len(ts) == 5 else ts
    if 'se' in unit.lower():
        frm = '%H:%M:%S' if frm is None else frm
    elif 'm' in unit.lower():
        frm = '%H:%M' if frm is None else frm
        rnd = rnd * 60
    elif 'h' in unit.lower():
        frm = '%H' if frm is None else frm
        rnd = rnd * 3600
    secs = sum(int(x) * 60 ** i for i, x in enumerate(reversed(ts.split(':'))))
    rtm = int(round(secs / rnd, 0) * rnd)
    nt = strftime(frm, gmtime(rtm))
    return nt

Call function as follow: Round to nearest 5 minutes with default ouput format = hh:mm as follow

ts = '02:27:29'
nt = toNearestTime(ts, unit='min', rnd=5)
print(nt)
output: '02:25'

Or round to nearest hour with ouput format hh:mm:ss as follow

ts = '10:30:01'
nt = toNearestTime(ts, unit='hour', rnd=1, frm='%H:%M:%S')
print(nt)
output: '11:00:00'

last updated version