Ceil a datetime to next quarter of an hour - python

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.

Related

Find how many seconds overlap between two different time Intervals

I have two UNIX timestamps that have distance in seconds D, where D = end - start. I want to find how many of these D seconds belong to an another time window [start2, end2], i.e. how many seconds intersect. Obviously it is not time2 - time1.
For example
start timestamp 1621707530 is Saturday, May 22, 2021 6:18:50 PM (GMT)
end timestamp 1621736330 is Sunday, May 23, 2021 2:18:50 AM (GMT)
How to find (preferably in Python) how many seconds between 01:00 AM and 02:00 AM belong to my [start, end] window?
For example interval [09:00 AM, 10:00 AM] does not intersect with [01:00 AM,02:00 AM], however interval [00:55 AM,01:03 AM] intersects for 3 minutes, or 180 seconds.
My code so far:
from datetime import datetime
from collections import namedtuple
Range = namedtuple('Range', ['start', 'end'])
r1 = Range(start=datetime(2021, 5, 22, 22, 23, 00),
end=datetime(2021, 5, 23, 2, 1, 2))
r2 = Range(start=datetime(2021, 5, 23, 1, 0, 00),
end=datetime(2021, 5, 23, 2, 0, 00))
latest_start = max(r1.start, r2.start)
earliest_end = min(r1.end, r2.end)
delta = (earliest_end - latest_start).seconds
overlap = max(0, delta)
print(overlap)
Can it be done with a simpler method?
Based on the answer: https://stackoverflow.com/a/46992092/14265469
The condition if (r1.start <= r2.end) and (r2.start <= r1.end): works surprisingly well:
from datetime import datetime
from collections import namedtuple
Range = namedtuple('Range', ['start', 'end'])
r1 = Range(start=datetime(2021, 5, 22, 22, 23, 20),
end=datetime(2021, 5, 22, 22, 23, 55))
r2 = Range(start=datetime(2021, 5, 22, 22, 23, 0),
end=datetime(2021, 5, 22, 22, 23, 25))
delta = "No overlapping"
if (r1.start <= r2.end) and (r2.start <= r1.end):
latest_start = max(r1.start, r2.start)
earliest_end = min(r1.end, r2.end)
delta = (earliest_end - latest_start).seconds
print(delta) # 5

generate random dates with days and time in python

hey guys i was trying to create a code that generate dates in given range but i faced some problems
the code is as follows:
import datetime
import random
import os
from random import randrange
year_b = 2019
month_b = 1
day_b = 1
year_e = 2020
month_e = 1
day_e = 1
def date_range(start, end, step: datetime.timedelta):
while start < end:
yield start
start += step
rand_list=[5,8,6,9,10]
for d in date_range(
start=datetime.datetime(year_b, month_b, day_b),
end=datetime.datetime(year_e, month_e, day_e),
step=datetime.timedelta(days=rand.choice(rand_list)),
):
print(d)
os.system("pause")
output:
2019-01-01 00:00:00
2019-01-09 00:00:00
2019-01-17 00:00:00
2019-01-25 00:00:00
2019-02-02 00:00:00
Press any key to continue . . .
first problem that the code only select one random value from the list and add it to the date but i need it to select random value for each date generated
second problem is that code the time it not been generated randomly
any ideas to solve those problems???!!
"Random" is often poorly defined. Why not select a specific number of samples uniformly from the distribution?
from datetime import datetime, timedelta
def items(start, end, samples):
total_sec = int((end - start).total_seconds())
deltas = random.sample(range(total_sec), samples) # xrange if py2k!
return (start + timedelta(seconds=delta) for delta in sorted(deltas))
Then you have
samples = 10
start = datetime(2019, 1, 1)
end = datetime(2020, 1, 1)
print(list(items(start, end, samples)))
giving, e.g.:
[datetime.datetime(2019, 1, 12, 16, 40, 53),
datetime.datetime(2019, 2, 1, 1, 41, 45),
datetime.datetime(2019, 2, 25, 10, 29, 51),
datetime.datetime(2019, 3, 10, 10, 24, 48),
datetime.datetime(2019, 4, 3, 12, 46, 14),
datetime.datetime(2019, 8, 12, 18, 30, 57),
datetime.datetime(2019, 9, 11, 3, 59, 6),
datetime.datetime(2019, 9, 27, 3, 9, 36),
datetime.datetime(2019, 10, 13, 14, 23, 37),
datetime.datetime(2019, 12, 14, 12, 23, 5)]
From this base you can easily modify to various other distributions, or use days or microseconds, or allow duplicates.
After reading your question more closely, I'm not entirely convinced this answer is what you're looking for, but I'll leave it up in case it is helpful.
The problem is because randrange(10) evaluates as soon as it is called. You can instead make date_range take a callable that returns a "random" time delta (or a variable delta, or whatever you'd like).
def adjustable_date_range(start, end, random_delta_maker):
while start < end:
yield start
start += random_delta_maker
And use it like so in your example by passing a callable that makes a random time delta on [0-10) days:
for d in adjustable_date_range(
start=datetime.datetime(year_b, month_b, day_b),
end=datetime.datetime(year_e, month_e, day_e),
step=lambda: datetime.timedelta(days=randrange(10)),
):
print(d)
now not all the months are showing:
from datetime import datetime, timedelta
import random
samples = 10
start = datetime(2018, 1, 1)
end = datetime(2020, 1, 1)
def items(start, end, samples):
total_sec = int((end - start).total_seconds())
deltas = random.sample(range(total_sec), samples) # xrange if py2k!
return (start + timedelta(seconds=delta) for delta in sorted(deltas))
for _ in list(items(start, end, samples)):
print(_)
output:
2018-02-01 18:25:48
2018-02-20 20:24:23
2018-06-07 22:03:48
2018-07-20 07:15:37
2018-08-22 07:04:06
2018-08-28 18:02:07
2018-10-09 03:40:58
2019-01-04 15:11:40
2019-03-22 12:16:58
2019-07-22 14:44:00

Generate random list of timestamps within multiple time intervals in python

Is there any efficient way to generate a list of N random timeframes which do not intersect each other given the initial lower and upper bounds as well as the time intervals that these time periods should have. For example in the following case I want 10 timestamps between 09:00-17:00:
Initial start time: {datetime} YYYY-MM-DD 09:00:00
Initial end time: {datetime} YYYY-MM-DD 17:00:00
Timestamp intervals (in minutes): [32 24 4 20 40 8 27 18 3 4]
where the first time period 32 minutes long, the next 24 and so on.
The way I am doing it at the moment is by using more or less the following code snippet:
def random_time(start, end, timeframe=None):
sec_diff = int((end - start).total_seconds())
secs_to_add = random.randint(0, sec_diff)
return start + timedelta(seconds=secs_to_add)
def in_datetimes_range(self, x, starts, ends):
return np.any((starts <= x) & (x <= ends))
n = 10
dadate = datetime.now()
year = self.dadate.year
month = self.dadate.month
day = self.dadate.day
start = datetime(year, month, day, 9, 0, 0)
end = datetime(year, month, day, 17, 0, 0)
timeframe = [32 24 4 20 40 8 27 18 3 4]
startTimes = []
endTimes = []
for i in range(0, n):
while True:
startTime = random_time(start, end)
endTime = startTime + timedelta(minutes=int(timeframe[i]))
if startTimes:
startTimesAsNpArray = np.array(startTimes)
endTimesAsNpArray = np.array(endTimes)
#check if new time period falls inside existing timeframes or if existing timeframes fall within new time period
inner_bound = np.logical_or(in_datetimes_range(startTime, startTimesAsNpArray, endTimesAsNpArray), in_datetimes_range(endTime, startTimesAsNpArray, endTimesAsNpArray))
outer_bound = np.logical_or(in_datetimes_range(startTimesAsNpArray, startTime, endTime), in_datetimes_range(endTimesAsNpArray, startTime, endTime))
if not inner_bound and not outer_bound:
startTimes.append(startTime)
endTimes.append(endTime)
break
but this is really inefficient and I was looking for something more reliable if possible.
Here is a way to do it: the idea is that if we remove the total duration of the periods from the time available, generate start times in the period that is left, and then postpone them with the cumulated periods before them, we are sure that the intervals won't overlap.
from datetime import datetime, timedelta
import random
def generate_periods(start, end, durations):
durations = [timedelta(minutes=m) for m in durations]
total_duration = sum(durations, timedelta())
nb_periods = len(durations)
open_duration = (end - start) - total_duration
delays = sorted(timedelta(seconds=s)
for s in random.sample(range(0, int(open_duration.total_seconds())), nb_periods))
periods = []
periods_before = timedelta()
for delay, duration in zip(delays, durations):
periods.append((start + delay + periods_before,
start + delay + periods_before + duration))
periods_before += duration
return periods
Sample run:
durations = [32, 24, 4, 20, 40, 8, 27, 18, 3, 4]
start_time = datetime(2019, 9, 2, 9, 0, 0)
end_time = datetime(2019, 9, 2, 17, 0, 0)
generate_periods(start_time, end_time, durations)
# [(datetime.datetime(2019, 9, 2, 9, 16, 1),
# datetime.datetime(2019, 9, 2, 9, 48, 1)),
# (datetime.datetime(2019, 9, 2, 9, 58, 57),
# datetime.datetime(2019, 9, 2, 10, 22, 57)),
# (datetime.datetime(2019, 9, 2, 10, 56, 41),
# datetime.datetime(2019, 9, 2, 11, 0, 41)),
# (datetime.datetime(2019, 9, 2, 11, 2, 37),
# datetime.datetime(2019, 9, 2, 11, 22, 37)),
# (datetime.datetime(2019, 9, 2, 11, 48, 17),
# datetime.datetime(2019, 9, 2, 12, 28, 17)),
# (datetime.datetime(2019, 9, 2, 13, 4, 28),
# datetime.datetime(2019, 9, 2, 13, 12, 28)),
# (datetime.datetime(2019, 9, 2, 15, 13, 3),
# datetime.datetime(2019, 9, 2, 15, 40, 3)),
# (datetime.datetime(2019, 9, 2, 16, 6, 44),
# datetime.datetime(2019, 9, 2, 16, 24, 44)),
# (datetime.datetime(2019, 9, 2, 16, 37, 42),
# datetime.datetime(2019, 9, 2, 16, 40, 42)),
# (datetime.datetime(2019, 9, 2, 16, 42, 50),
# datetime.datetime(2019, 9, 2, 16, 46, 50))]
Like this?
import pandas as pd
from datetime import datetime
date = datetime.now()
start = datetime(date.year, date.month, date.day, 9, 0, 0)
end = datetime(date.year, date.month, date.day, 17, 0, 0)
interval = 32
periods = (end-start).seconds/60/interval
times = pd.date_range(start.strftime("%m/%d/%Y, %H:%M:%S"), periods=periods, freq=str(interval)+'min')
or like this
# =============================================================================
# or if you want the results as a dataframe
# =============================================================================
def xyz(interval):
date = datetime.now()
start = datetime(date.year, date.month, date.day, 9, 0, 0)
end = datetime(date.year, date.month, date.day, 17, 0, 0)
periods = (end-start).seconds/60/interval
return pd.date_range(start.strftime("%m/%d/%Y, %H:%M:%S"), periods=periods, freq=str(interval)+'min')
timeframes = [32,24,4,20,40,8,27,18,3,4]
df_output=pd.DataFrame(index=timeframes, data=[xyz(x) for x in timeframes])

tuple of datetime objects in Python

I want to write a function that returns a tuple of (start,end) where start is the Monday at 00:00:00:000000 and end is Sunday at 23:59:59:999999. start and end are datetime objects. No other information is given about day, month or year. i tried this function
def week_start_end(date):
start= date.strptime("00:00:00.000000", "%H:%M:%S.%f")
end = date.strptime("23:59:59.999999", "%H:%M:%S.%f")
return (start,end)
print week_start_end(datetime(2013, 8, 15, 12, 0, 0))
should return (datetime(2013, 8, 11, 0, 0, 0, 0), datetime(2013, 8, 17, 23, 59, 59, 999999))
but the function returns tuple with dates (datetime.datetime(1900, 1, 1, 0, 0), datetime.datetime(1900, 1, 1, 23, 59, 59, 999999))
I think using datetime.isocalendar is a nice solution. This give the correct outputs for your example:
import datetime
def iso_year_start(iso_year):
"The gregorian calendar date of the first day of the given ISO year"
fourth_jan = datetime.date(iso_year, 1, 4)
delta = datetime.timedelta(fourth_jan.isoweekday()-1)
return fourth_jan - delta
def iso_to_gregorian(iso_year, iso_week, iso_day):
"Gregorian calendar date for the given ISO year, week and day"
year_start = iso_year_start(iso_year)
return year_start + datetime.timedelta(days=iso_day-1, weeks=iso_week-1)
def week_start_end(date):
year = date.isocalendar()[0]
week = date.isocalendar()[1]
d1 = iso_to_gregorian(year, week, 0)
d2 = iso_to_gregorian(year, week, 6)
d3 = datetime.datetime(d1.year, d1.month, d1.day, 0,0,0,0)
d4 = datetime.datetime(d2.year, d2.month, d2.day, 23,59,59,999999)
return (d3,d4)
As an example:
>>> d = datetime.datetime(2013, 8, 15, 12, 0, 0)
>>> print week_start_end(d)
(datetime.datetime(2013, 8, 11, 0, 0), datetime.datetime(2013, 8, 17, 23, 59, 59, 999999))
And should help you with your problem.

Datetime - 10 Hours

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)

Categories

Resources