Ceil a datetime to next quarter of an hour

This one takes microseconds into account!

import math

def ceil_dt(dt):
    # how many secs have passed this hour
    nsecs = dt.minute*60 + dt.second + dt.microsecond*1e-6  
    # number of seconds to next quarter hour mark
    # Non-analytic (brute force is fun) way:  
    #   delta = next(x for x in xrange(0,3601,900) if x>=nsecs) - nsecs
    # analytic way:
    delta = math.ceil(nsecs / 900) * 900 - nsecs
    #time + number of seconds to quarter hour mark.
    return dt + datetime.timedelta(seconds=delta)

t1 = datetime.datetime(2017, 3, 6, 7, 0)
assert ceil_dt(t1) == t1

t2 = datetime.datetime(2017, 3, 6, 7, 1)
assert ceil_dt(t2) == datetime.datetime(2017, 3, 6, 7, 15)

t3 = datetime.datetime(2017, 3, 6, 7, 15)
assert ceil_dt(t3) == t3

t4 = datetime.datetime(2017, 3, 6, 7, 16)
assert ceil_dt(t4) == datetime.datetime(2017, 3, 6, 7, 30)

t5 = datetime.datetime(2017, 3, 6, 7, 30)
assert ceil_dt(t5) == t5

t6 = datetime.datetime(2017, 3, 6, 7, 31)
assert ceil_dt(t6) == datetime.datetime(2017, 3, 6, 7, 45)

t7 = datetime.datetime(2017, 3, 6, 7, 45)
assert ceil_dt(t7) == t7

t8 = datetime.datetime(2017, 3, 6, 7, 46)
assert ceil_dt(t8) == datetime.datetime(2017, 3, 6, 8, 0)

Explanation of delta:

  • 900 seconds is 15 minutes (a quarter of an hour sans leap seconds which I don’t think datetime handles…)
  • nsecs / 900 is the number of quarter hour chunks that have transpired. Taking the ceil of this rounds up the number of quarter hour chunks.
  • Multiply the number of quarter hour chunks by 900 to figure out how many seconds have transpired in since the start of the hour after “rounding”.

Leave a Comment