Let's imagine this datetime
>>> import datetime
>>> dt = datetime.datetime(2012, 10, 25, 17, 32, 16)
I'd like to ceil it to the next quarter of hour, in order to get
datetime.datetime(2012, 10, 25, 17, 45)
I imagine something like
>>> quarter = datetime.timedelta(minutes=15)
>>> import math
>>> ceiled_dt = math.ceil(dt / quarter) * quarter
But of course, this does not work
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".
#Mark Dickinson suggested the best formula so far:
def ceil_dt(dt, delta):
return dt + (datetime.min - dt) % delta
In Python 3, for an arbitrary time delta (not just 15 minutes):
#!/usr/bin/env python3
import math
from datetime import datetime, timedelta
def ceil_dt(dt, delta):
return datetime.min + math.ceil((dt - datetime.min) / delta) * delta
print(ceil_dt(datetime(2012, 10, 25, 17, 32, 16), timedelta(minutes=15)))
# -> 2012-10-25 17:45:00
To avoid intermediate floats, divmod() could be used:
def ceil_dt(dt, delta):
q, r = divmod(dt - datetime.min, delta)
return (datetime.min + (q + 1)*delta) if r else dt
Example:
>>> ceil_dt(datetime(2012, 10, 25, 17, 32, 16), timedelta(minutes=15))
datetime.datetime(2012, 10, 25, 17, 45)
>>> ceil_dt(datetime.min, datetime.resolution)
datetime.datetime(1, 1, 1, 0, 0)
>>> ceil_dt(datetime.min, 2*datetime.resolution)
datetime.datetime(1, 1, 1, 0, 0)
>>> ceil_dt(datetime.max, datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999999)
>>> ceil_dt(datetime.max, 2*datetime.resolution)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in ceil_dt
OverflowError: date value out of range
>>> ceil_dt(datetime.min+datetime.resolution, datetime.resolution)
datetime.datetime(1, 1, 1, 0, 0, 0, 1)
>>> ceil_dt(datetime.min+datetime.resolution, 2*datetime.resolution)
datetime.datetime(1, 1, 1, 0, 0, 0, 2)
>>> ceil_dt(datetime.max-datetime.resolution, datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999998)
>>> ceil_dt(datetime.max-datetime.resolution, 2*datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999998)
>>> ceil_dt(datetime.max-2*datetime.resolution, datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999997)
>>> ceil_dt(datetime.max-2*datetime.resolution, 2*datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999998)
>>> ceil_dt(datetime.max-timedelta(1), datetime.resolution)
datetime.datetime(9999, 12, 30, 23, 59, 59, 999999)
>>> ceil_dt(datetime.max-timedelta(1), 2*datetime.resolution)
datetime.datetime(9999, 12, 31, 0, 0)
>>> ceil_dt(datetime.min, datetime.max-datetime.min)
datetime.datetime(1, 1, 1, 0, 0)
>>> ceil_dt(datetime.max, datetime.max-datetime.min)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999999)
def ceil(dt):
if dt.minute % 15 or dt.second:
return dt + datetime.timedelta(minutes = 15 - dt.minute % 15,
seconds = -(dt.second % 60))
else:
return dt
This gives you:
>>> ceil(datetime.datetime(2012,10,25, 17,45))
datetime.datetime(2012, 10, 25, 17, 45)
>>> ceil(datetime.datetime(2012,10,25, 17,45,1))
datetime.datetime(2012, 10, 25, 18, 0)
>>> ceil(datetime.datetime(2012,12,31,23,59,0))
datetime.datetime(2013,1,1,0,0)
You just need to calculate correct minutes and add them in datetime object after setting minutes, seconds to zero
import datetime
def quarter_datetime(dt):
minute = (dt.minute//15+1)*15
return dt.replace(minute=0, second=0)+datetime.timedelta(minutes=minute)
for minute in [12, 22, 35, 52]:
print quarter_datetime(datetime.datetime(2012, 10, 25, 17, minute, 16))
It works for all cases:
2012-10-25 17:15:00
2012-10-25 17:30:00
2012-10-25 17:45:00
2012-10-25 18:00:00
The formula proposed here by #Mark Dickinson worked beautifully, but I needed a solution that also handled timezones and Daylight Savings Time (DST).
Using pytz, I arrived at:
import pytz
from datetime import datetime, timedelta
def datetime_ceiling(dt, delta):
# Preserve original timezone info
original_tz = dt.tzinfo
if original_tz:
# If the original was timezone aware, translate to UTC.
# This is necessary because datetime math does not take
# DST into account, so first we normalize the datetime...
dt = dt.astimezone(pytz.UTC)
# ... and then make it timezone naive
dt = dt.replace(tzinfo=None)
# We only do math on a timezone naive object, which allows
# us to pass naive objects directly to the function
dt = dt + ((datetime.min - dt) % delta)
if original_tz:
# If the original was tz aware, we make the result aware...
dt = pytz.UTC.localize(dt)
# ... then translate it from UTC back its original tz.
# This translation applies appropriate DST status.
dt = dt.astimezone(original_tz)
return dt
A nearly identical floor function can be made by changing one line of code:
def datetime_floor(dt, delta):
...
dt = dt - ((datetime.min - dt) % delta)
...
The following datetime is three minutes before the transition from DST back to Standard Time (STD):
datetime.datetime(2020, 11, 1, 1, 57, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
Assuming the above as dt, we can round down to the nearest five minute increment using our floor function:
>>> datetime_floor(dt, timedelta(minutes=5))
datetime.datetime(2020, 11, 1, 1, 55, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
The timezone and relationship to DST is preserved. (The same would be true for the ceiling function.)
On this date DST will end at 2 am, at which point the time will "roll back" to 1am STD. If we use our ceiling function to round up from 1:57am DST, we should not end up at 2am DST, but rather at 1:00am STD, which is the result we get:
>>> datetime_ceiling(dt, timedelta(minutes=5))
datetime.datetime(2020, 11, 1, 1, 0, tzinfo=<DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>)
Here is my code working with any periods:
def floorDT(dt, secperiod):
tstmp = dt.timestamp()
return datetime.datetime.fromtimestamp(
math.floor(tstmp/secperiod)*secperiod).astimezone().astimezone(datetime.timezone.utc)
def ceilDT(dt, secperiod):
tstmp = dt.timestamp()
return datetime.datetime.fromtimestamp(
math.ceil(tstmp/secperiod)*secperiod).astimezone().astimezone(datetime.timezone.utc)
Note: we must use astimezone().astimezone() trick else it uses local timezone during converting from timestamp
Based on the example by Mark Dickinson's, here is an enhanced datetime round up function that also preserves the timezone information of the original timestamp (both naive and timezone aware):
def round_datetime_up(
ts: datetime.datetime,
delta: datetime.timedelta,
offset: datetime.timedelta = datetime.timedelta(minutes=0)) -> datetime.datetime:
"""Snap to next available timedelta.
Preserve any timezone info on `ts`.
If we are at the the given exact delta, then do not round, only add offset.
:param ts: Timestamp we want to round
:param delta: Our snap grid
:param offset: Add a fixed time offset at the top of rounding
:return: Rounded up datetime
"""
rounded = ts + (datetime.datetime.min.replace(tzinfo=ts.tzinfo) - ts) % delta
return rounded + offset
See full code and tests.
Consider:
now = datetime.datetime.now()
now
datetime.datetime(2009, 11, 6, 16, 6, 42, 812098)
How would I create a new datetime object (past) and minus n values from the hours?
Use timedelta in the datetime module:
import datetime
now = datetime.datetime.now()
past = now - datetime.timedelta(hours=10)
Use a timedelta object.
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2009, 11, 6, 16, 35, 50, 593000)
>>> ten_hours = datetime.timedelta(hours=10)
>>> now + ten_hours
datetime.datetime(2009, 11, 7, 2, 35, 50, 593000)
>>> now - ten_hours
datetime.datetime(2009, 11, 6, 6, 35, 50, 593000)
Use a timedelta object.
from datetime import datetime
back = datetime.now() - timedelta(hours=10)