How to round the minute of a datetime object

Viewed 162438

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?

21 Answers

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)

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.

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.

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

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]

Those seem overly complex

def round_down_to():
    num = int(datetime.utcnow().replace(second=0, microsecond=0).minute)
    return num - (num%10)

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)

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.

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!

The shortest way I know

min = tm.minute // 10 * 10

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)
))

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)

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

Related