How to round the minute of a datetime object

151,939

Solution 1

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)

Solution 2

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

Solution 3

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

Solution 4

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

Solution 5

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.

Share:
151,939

Related videos on Youtube

Lucas Manco
Author by

Lucas Manco

Updated on July 08, 2022

Comments

  • Lucas Manco
    Lucas Manco almost 2 years

    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?

    • Jus
      Jus almost 3 years
      Today, timestamp has a method floor(...).
  • Lucas Manco
    Lucas Manco over 13 years
    Ah but then the problem here is that the hour must increase as well
  • Omnifarious
    Omnifarious over 13 years
    @Lucas Manco - My solution also works fine and I think makes more sense.
  • Max
    Max over 7 years
    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.
  • skoval00
    skoval00 over 7 years
    Unfortunately this does not work with tz-aware datetime. One should use dt.replace(hour=0, minute=0, second=0) instead of dt.min.
  • Le Droid
    Le Droid over 7 years
    @skoval00 + druska Edited following your advices to support tz-aware datetime. Thanks!
  • Michel Mesquita
    Michel Mesquita over 7 years
    Thanks @skoval00 - it took me a while to figure out why the function was not working with my data
  • CPBL
    CPBL over 7 years
    This does not work at all for me for long periods. e.g. roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundT‌​o=60*60*24*7) vs roundTime(datetime.datetime(2012,12,30,23,44,59,1234),roundT‌​o=60*60*24*7)
  • CPBL
    CPBL over 7 years
    See this to understand the problem: datetime.timedelta(100,1,2,3).seconds == 1
  • CPBL
    CPBL over 7 years
    Also no good: print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.ti‌​medelta(days=15)) 2012-12-20 00:00:00 while print roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.ti‌​medelta(days=15)) 2012-12-21 00:00:00
  • Le Droid
    Le Droid over 7 years
    @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?
  • CPBL
    CPBL over 7 years
    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
    CPBL over 7 years
    @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.
  • spinxz
    spinxz over 6 years
    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
  • Samuel Dauzon
    Samuel Dauzon over 5 years
    Dont' forget to see @Omnifarious answer ! He explain how to get the floor of datetime.
  • poulter7
    poulter7 almost 5 years
    For reference, Timestamp contains floor and ceil as well
  • Bart
    Bart over 4 years
    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.
  • Rahul Bharadwaj
    Rahul Bharadwaj almost 4 years
    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
  • Georgy
    Georgy almost 4 years
    I'm not sure that this answer adds anything new or useful. There was already an answer that explained the same thing: stackoverflow.com/a/56010357/7851470
  • Nguyen Bryan
    Nguyen Bryan almost 4 years
    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.
  • cona
    cona about 3 years
    @poulter7 The floor functions though only works on a single value, not a Datetime index
  • interestedparty333
    interestedparty333 about 3 years
    this may not account for > 60 minutes
  • Cairan Van Rooyen
    Cairan Van Rooyen almost 3 years
    Great answer, with options for rounding up and down, very useful for me!
  • s.paszko
    s.paszko over 2 years
    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.
  • ofo
    ofo over 2 years
    @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.
  • s.paszko
    s.paszko over 2 years
    Big thx for updated answer!
  • s.paszko
    s.paszko over 2 years
    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.
  • ofo
    ofo over 2 years
    @s.paszko Corrected it