How to round down a datetime to the nearest 5 Minutes?

Question:

I need a Python3 function that rounds down a datetime.datetime object to the nearest 5 minutes. Yes, this has been discussed in previous SO posts here and here and even here, but I’m having no luck implementing their solutions.

NOTE: I can not use pandas

I want a function, given the below DateTime (%Y%m%d%H%M) objects, returns the following:

INPUT         OUTPUT
202301131600  202301131600
202301131602  202301131600
202301131604  202301131600
202301131605  202301131605
202301131609  202301131605
202301131610  202301131610

Here’s my code, using timedelta as a mechanism:

from datetime import datetime
from datetime import timedelta

def roundDownDateTime(dt):
    # Arguments:
    #   dt      DateTime object
    delta = timedelta(minutes=5)
    return dt - (datetime.min - dt) % delta

tmpDate = datetime.now()
# Print the current time and then rounded-down time:
print("t"+tmpDate.strftime('%Y%m%d%H%M')+"  -->  "+(roundDownDateTime(tmpDate)).strftime('%Y%m%d%H%M') )

Here’s some output when I test the code multiple times:

202301131652  -->  202301131650
202301131700  -->  202301131655
202301131701  -->  202301131657

Ugh, no good! I adapted my function to this:

def roundDownDateTime(dt):
    # Arguments:
    #   dt      DateTime object
    n = dt - timedelta(minutes=5)
    return datetime(year=n.year, month=n.month, day=n.day, hour=n.hour)

But that was even worse:

202301131703  -->  202301131600
202301131707  -->  202301131700
202301131710  -->  202301131700

I am all thumbs when figuring out this basic datetime arithmetic stuff; can anyone see my error?

Asked By: Pete

||

Answers:

You have (datetime.min - dt) backwards – this results in a negative value which doesn’t behave the way you expect with %. If you swap to (dt - datetime.min) you get your expected results:

In []:
def roundDownDateTime(dt, delta=timedelta(minutes=5)):
    return dt - (dt - datetime.min) % delta

tmpDate = datetime.now()
tmpDate

Out[]:
datetime.datetime(2023, 1, 13, 11, 36, 7, 821196)

In []:
roundDownDateTime(tmpDate)

Out[]:
datetime.datetime(2023, 1, 13, 11, 35)

In []:
roundDownDateTime(tmpDate, timedelta(minutes=10)

Out[]:
datetime.datetime(2023, 1, 13, 11, 30)
Answered By: AChampion

Since you can only affect the minutes by rounding down to the nearest 5 minutes, just figure out how many minutes you need to subtract. Set everything else from the original datetime object, and seconds and microseconds to zero:

def roundDownDateTime(dt):
    delta_min = dt.minute % 5
    return datetime.datetime(dt.year, dt.month, dt.day,
                             dt.hour, dt.minute - delta_min)

To test:

import datetime

expio = [['202301131600', '202301131600'],
 ['202301131602', '202301131600'],
 ['202301131604', '202301131600'],
 ['202301131605', '202301131605'],
 ['202301131609', '202301131605'],
 ['202301131610', '202301131610']]

for i, eo in expio:
    o = roundDownDateTime(datetime.datetime.strptime(i, "%Y%m%d%H%M")).strftime("%Y%m%d%H%M")
    assert eo == o

asserts all True

Answered By: Pranav Hosangadi

I think I would be inclined to obtain the timestamp and round it then convert back to datetime:

def round_datetime(dt, secs):
    return datetime.datetime.fromtimestamp(secs * (dt.timestamp() // secs))

You might test with:

import datetime
import time

def round_datetime(dt, secs):
    return datetime.datetime.fromtimestamp(secs * (dt.timestamp() // secs))

while True:
    now = datetime.datetime.now()
    print(now, round_datetime(now, 5 * 60))
    time.sleep(1)
Answered By: JonSG