Related
I have the following code to do this, but how can I do it better? Right now I think it's better than nested loops, but it starts to get Perl-one-linerish when you have a generator in a list comprehension.
day_count = (end_date - start_date).days + 1
for single_date in [d for d in (start_date + timedelta(n) for n in range(day_count)) if d <= end_date]:
print strftime("%Y-%m-%d", single_date.timetuple())
Notes
I'm not actually using this to print. That's just for demo purposes.
The start_date and end_date variables are datetime.date objects because I don't need the timestamps. (They're going to be used to generate a report).
Sample Output
For a start date of 2009-05-30 and an end date of 2009-06-09:
2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09
Why are there two nested iterations? For me it produces the same list of data with only one iteration:
for single_date in (start_date + timedelta(n) for n in range(day_count)):
print ...
And no list gets stored, only one generator is iterated over. Also the "if" in the generator seems to be unnecessary.
After all, a linear sequence should only require one iterator, not two.
Update after discussion with John Machin:
Maybe the most elegant solution is using a generator function to completely hide/abstract the iteration over the range of dates:
from datetime import date, timedelta
def daterange(start_date, end_date):
for n in range(int((end_date - start_date).days)):
yield start_date + timedelta(n)
start_date = date(2013, 1, 1)
end_date = date(2015, 6, 2)
for single_date in daterange(start_date, end_date):
print(single_date.strftime("%Y-%m-%d"))
NB: For consistency with the built-in range() function this iteration stops before reaching the end_date. So for inclusive iteration use the next day, as you would with range().
This might be more clear:
from datetime import date, timedelta
start_date = date(2019, 1, 1)
end_date = date(2020, 1, 1)
delta = timedelta(days=1)
while start_date <= end_date:
print(start_date.strftime("%Y-%m-%d"))
start_date += delta
Use the dateutil library:
from datetime import date
from dateutil.rrule import rrule, DAILY
a = date(2009, 5, 30)
b = date(2009, 6, 9)
for dt in rrule(DAILY, dtstart=a, until=b):
print dt.strftime("%Y-%m-%d")
This python library has many more advanced features, some very useful, like relative deltas—and is implemented as a single file (module) that's easily included into a project.
Pandas is great for time series in general, and has direct support for date ranges.
import pandas as pd
daterange = pd.date_range(start_date, end_date)
You can then loop over the daterange to print the date:
for single_date in daterange:
print (single_date.strftime("%Y-%m-%d"))
It also has lots of options to make life easier. For example if you only wanted weekdays, you would just swap in bdate_range. See http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
The power of Pandas is really its dataframes, which support vectorized operations (much like numpy) that make operations across large quantities of data very fast and easy.
EDIT:
You could also completely skip the for loop and just print it directly, which is easier and more efficient:
print(daterange)
import datetime
def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False):
# inclusive=False to behave like range by default
if step.days > 0:
while start < stop:
yield start
start = start + step
# not +=! don't modify object passed in if it's mutable
# since this function is not restricted to
# only types from datetime module
elif step.days < 0:
while start > stop:
yield start
start = start + step
if inclusive and start == stop:
yield start
# ...
for date in daterange(start_date, end_date, inclusive=True):
print strftime("%Y-%m-%d", date.timetuple())
This function does more than you strictly require, by supporting negative step, etc. As long as you factor out your range logic, then you don't need the separate day_count and most importantly the code becomes easier to read as you call the function from multiple places.
This is the most human-readable solution I can think of.
import datetime
def daterange(start, end, step=datetime.timedelta(1)):
curr = start
while curr < end:
yield curr
curr += step
Numpy's arange function can be applied to dates:
import numpy as np
from datetime import datetime, timedelta
d0 = datetime(2009, 1,1)
d1 = datetime(2010, 1,1)
dt = timedelta(days = 1)
dates = np.arange(d0, d1, dt).astype(datetime)
The use of astype is to convert from numpy.datetime64 to an array of datetime.datetime objects.
Why not try:
import datetime as dt
start_date = dt.datetime(2012, 12,1)
end_date = dt.datetime(2012, 12,5)
total_days = (end_date - start_date).days + 1 #inclusive 5 days
for day_number in range(total_days):
current_date = (start_date + dt.timedelta(days = day_number)).date()
print current_date
Show the last n days from today:
import datetime
for i in range(0, 100):
print((datetime.date.today() + datetime.timedelta(i)).isoformat())
Output:
2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04
For completeness, Pandas also has a period_range function for timestamps that are out of bounds:
import pandas as pd
pd.period_range(start='1/1/1626', end='1/08/1627', freq='D')
import datetime
def daterange(start, stop, step_days=1):
current = start
step = datetime.timedelta(step_days)
if step_days > 0:
while current < stop:
yield current
current += step
elif step_days < 0:
while current > stop:
yield current
current += step
else:
raise ValueError("daterange() step_days argument must not be zero")
if __name__ == "__main__":
from pprint import pprint as pp
lo = datetime.date(2008, 12, 27)
hi = datetime.date(2009, 1, 5)
pp(list(daterange(lo, hi)))
pp(list(daterange(hi, lo, -1)))
pp(list(daterange(lo, hi, 7)))
pp(list(daterange(hi, lo, -7)))
assert not list(daterange(lo, hi, -1))
assert not list(daterange(hi, lo))
assert not list(daterange(lo, hi, -7))
assert not list(daterange(hi, lo, 7))
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
I have a similar problem, but I need to iterate monthly instead of daily.
This is my solution
import calendar
from datetime import datetime, timedelta
def days_in_month(dt):
return calendar.monthrange(dt.year, dt.month)[1]
def monthly_range(dt_start, dt_end):
forward = dt_end >= dt_start
finish = False
dt = dt_start
while not finish:
yield dt.date()
if forward:
days = days_in_month(dt)
dt = dt + timedelta(days=days)
finish = dt > dt_end
else:
_tmp_dt = dt.replace(day=1) - timedelta(days=1)
dt = (_tmp_dt.replace(day=dt.day))
finish = dt < dt_end
Example #1
date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01
2017-01-01
Example #2
date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01
You can generate a series of date between two dates using the pandas library simply and trustfully
import pandas as pd
print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')
You can change the frequency of generating dates by setting freq as D, M, Q, Y
(daily, monthly, quarterly, yearly
)
Using pendulum.period:
import pendulum
start = pendulum.from_format('2020-05-01', 'YYYY-MM-DD', formatter='alternative')
end = pendulum.from_format('2020-05-02', 'YYYY-MM-DD', formatter='alternative')
period = pendulum.period(start, end)
for dt in period:
print(dt.to_date_string())
> pip install DateTimeRange
from datetimerange import DateTimeRange
def dateRange(start, end, step):
rangeList = []
time_range = DateTimeRange(start, end)
for value in time_range.range(datetime.timedelta(days=step)):
rangeList.append(value.strftime('%m/%d/%Y'))
return rangeList
dateRange("2018-09-07", "2018-12-25", 7)
Out[92]:
['09/07/2018',
'09/14/2018',
'09/21/2018',
'09/28/2018',
'10/05/2018',
'10/12/2018',
'10/19/2018',
'10/26/2018',
'11/02/2018',
'11/09/2018',
'11/16/2018',
'11/23/2018',
'11/30/2018',
'12/07/2018',
'12/14/2018',
'12/21/2018']
For those who are interested in Pythonic functional way:
from datetime import date, timedelta
from itertools import count, takewhile
for d in takewhile(lambda x: x<=date(2009,6,9), map(lambda x:date(2009,5,30)+timedelta(days=x), count())):
print(d)
What about the following for doing a range incremented by days:
for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
# Do stuff here
startDate and stopDate are datetime.date objects
For a generic version:
for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
# Do stuff here
startTime and stopTime are datetime.date or datetime.datetime object
(both should be the same type)
stepTime is a timedelta object
Note that .total_seconds() is only supported after python 2.7 If you are stuck with an earlier version you can write your own function:
def total_seconds( td ):
return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
This function has some extra features:
can pass a string matching the DATE_FORMAT for start or end and it is converted to a date object
can pass a date object for start or end
error checking in case the end is older than the start
import datetime
from datetime import timedelta
DATE_FORMAT = '%Y/%m/%d'
def daterange(start, end):
def convert(date):
try:
date = datetime.datetime.strptime(date, DATE_FORMAT)
return date.date()
except TypeError:
return date
def get_date(n):
return datetime.datetime.strftime(convert(start) + timedelta(days=n), DATE_FORMAT)
days = (convert(end) - convert(start)).days
if days <= 0:
raise ValueError('The start date must be before the end date.')
for n in range(0, days):
yield get_date(n)
start = '2014/12/1'
end = '2014/12/31'
print list(daterange(start, end))
start_ = datetime.date.today()
end = '2015/12/1'
print list(daterange(start, end))
Here's code for a general date range function, similar to Ber's answer, but more flexible:
def count_timedelta(delta, step, seconds_in_interval):
"""Helper function for iterate. Finds the number of intervals in the timedelta."""
return int(delta.total_seconds() / (seconds_in_interval * step))
def range_dt(start, end, step=1, interval='day'):
"""Iterate over datetimes or dates, similar to builtin range."""
intervals = functools.partial(count_timedelta, (end - start), step)
if interval == 'week':
for i in range(intervals(3600 * 24 * 7)):
yield start + datetime.timedelta(weeks=i) * step
elif interval == 'day':
for i in range(intervals(3600 * 24)):
yield start + datetime.timedelta(days=i) * step
elif interval == 'hour':
for i in range(intervals(3600)):
yield start + datetime.timedelta(hours=i) * step
elif interval == 'minute':
for i in range(intervals(60)):
yield start + datetime.timedelta(minutes=i) * step
elif interval == 'second':
for i in range(intervals(1)):
yield start + datetime.timedelta(seconds=i) * step
elif interval == 'millisecond':
for i in range(intervals(1 / 1000)):
yield start + datetime.timedelta(milliseconds=i) * step
elif interval == 'microsecond':
for i in range(intervals(1e-6)):
yield start + datetime.timedelta(microseconds=i) * step
else:
raise AttributeError("Interval must be 'week', 'day', 'hour' 'second', \
'microsecond' or 'millisecond'.")
import datetime
from dateutil.rrule import DAILY,rrule
date=datetime.datetime(2019,1,10)
date1=datetime.datetime(2019,2,2)
for i in rrule(DAILY , dtstart=date,until=date1):
print(i.strftime('%Y%b%d'),sep='\n')
OUTPUT:
2019Jan10
2019Jan11
2019Jan12
2019Jan13
2019Jan14
2019Jan15
2019Jan16
2019Jan17
2019Jan18
2019Jan19
2019Jan20
2019Jan21
2019Jan22
2019Jan23
2019Jan24
2019Jan25
2019Jan26
2019Jan27
2019Jan28
2019Jan29
2019Jan30
2019Jan31
2019Feb01
2019Feb02
from datetime import date,timedelta
delta = timedelta(days=1)
start = date(2020,1,1)
end=date(2020,9,1)
loop_date = start
while loop_date<=end:
print(loop_date)
loop_date+=delta
You can use Arrow:
This is example from the docs, iterating over hours:
from arrow import Arrow
>>> start = datetime(2013, 5, 5, 12, 30)
>>> end = datetime(2013, 5, 5, 17, 15)
>>> for r in Arrow.range('hour', start, end):
... print repr(r)
...
<Arrow [2013-05-05T12:30:00+00:00]>
<Arrow [2013-05-05T13:30:00+00:00]>
<Arrow [2013-05-05T14:30:00+00:00]>
<Arrow [2013-05-05T15:30:00+00:00]>
<Arrow [2013-05-05T16:30:00+00:00]>
To iterate over days, you can use like this:
>>> start = Arrow(2013, 5, 5)
>>> end = Arrow(2013, 5, 5)
>>> for r in Arrow.range('day', start, end):
... print repr(r)
(Didn't check if you can pass datetime.date objects, but anyways Arrow objects are easier in general)
If you are going to use dynamic timedelta then you can use:
1. With while loop
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
while start <= end:
yield start
start += delta
2. With for loop
from datetime import datetime, timedelta
from typing import Generator
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
3. If you are using async/await
async def datetime_range(start: datetime, end: datetime, delta: timedelta) -> AsyncGenerator[datetime, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
4. List comprehension
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> List[datetime]:
delta_units = int((end - start) / delta)
return [start + (delta * index) for index in range(delta_units + 1)]
Then 1 and 2 solutions simply can be used like this
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part for time_part in datetime_range(start, end, delta)]
# or
for time_part in datetime_range(start, end, delta):
print(time_part)
3-third solution can be used like this in async context. Because it retruns an async generator object, which can be used only in async context
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part async for time_part in datetime_range(start, end, delta)]
async for time_part in datetime_range(start, end, delta):
print(time_part)
The benefit of the solutions about is that all of them are using dynamic timedelta. This can be very usefull in cases when you do not know which time delta you will have.
Slightly different approach to reversible steps by storing range args in a tuple.
def date_range(start, stop, step=1, inclusive=False):
day_count = (stop - start).days
if inclusive:
day_count += 1
if step > 0:
range_args = (0, day_count, step)
elif step < 0:
range_args = (day_count - 1, -1, step)
else:
raise ValueError("date_range(): step arg must be non-zero")
for i in range(*range_args):
yield start + timedelta(days=i)
So I have two functions for converting python datetime.datetime() objects to and from milliseconds. I cannot figure out where this is going wrong. Here's what I'm working with:
>>> import datetime
>>> def mil_to_date(mil):
"""date items from REST services are reported in milliseconds,
this function will convert milliseconds to datetime objects
Required:
mil -- time in milliseconds
"""
if mil == None:
return None
elif mil < 0:
return datetime.datetime.utcfromtimestamp(0) + datetime.timedelta(seconds=(mil/1000))
else:
return datetime.datetime.fromtimestamp(mil / 1000)
>>> def date_to_mil(date):
"""converts datetime.datetime() object to milliseconds
date -- datetime.datetime() object"""
if isinstance(date, datetime.datetime):
epoch = datetime.datetime.utcfromtimestamp(0)
return long((date - epoch).total_seconds() * 1000.0)
>>> mil = 1394462888000
>>> date = mil_to_date(mil)
>>> date
datetime.datetime(2014, 3, 10, 9, 48, 8) #this is correct
>>> d2m = date_to_mil(date)
>>> d2m
1394444888000L
>>> mil
1394462888000L
>>> date2 = mil_to_date(d2m)
>>> date2
datetime.datetime(2014, 3, 10, 4, 48, 8) #why did I lose 5 hours??
For some reason, I am losing 5 hours. Am I overlooking something obvious? Or is there a problem with one or both of my functions?
The reason for this is that date_to_mil works with UTC and mil_to_date doesn't. You should replace utcfromtimestamp with fromtimestamp.
Further explanation:
In your code, epoch is the date of the epoch in UTC (but the object is without any time-zone). But date is local since fromtimestamp returns a local time:
If optional argument tz is None or not specified, the timestamp is
converted to the platform’s local date and time, and the returned
datetime object is naive
So you subtract the UTC epoch from the local datetime and you get a delay which is your local delay to UTC.
If input is UTC then to get POSIX timestamp as integer milliseconds:
from datetime import datetime, timedelta
def timestamp_millis(utc_time, epoch=datetime(1970, 1, 1)):
"""Return milliseconds since Epoch as integer."""
td = utc_time - epoch
return (td.microseconds + (td.seconds + td.days * 86400) * 10**6) // 10**3
Note: the formula may produce a different result from: int(td.total_seconds() * 1000).
And in reverse: to get UTC time from POSIX time given as milliseconds:
def datetime_from_millis(millis, epoch=datetime(1970, 1, 1)):
"""Return UTC time that corresponds to milliseconds since Epoch."""
return epoch + timedelta(milliseconds=millis)
It supports both positive and negative millis.
Note: None handling happens outside these functions.
Example:
>>> datetime_from_millis(1394462888000)
datetime.datetime(2014, 3, 10, 14, 48, 8)
>>> datetime.utcfromtimestamp(1394462888)
datetime.datetime(2014, 3, 10, 14, 48, 8)
>>> timestamp_millis(_)
1394462888000
The result is different from the one in your question!
gmtime(0).year != 1970 and TZ=right/UTC cases are ignored.
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?
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)
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
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)))
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
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.
Here is a simpler generalized solution without floating point precision issues and external library dependencies:
import datetime
def time_mod(time, delta, epoch=None):
if epoch is None:
epoch = datetime.datetime(1970, 1, 1, tzinfo=time.tzinfo)
return (time - epoch) % delta
def time_round(time, delta, epoch=None):
mod = time_mod(time, delta, epoch)
if mod < delta / 2:
return time - mod
return time + (delta - mod)
def time_floor(time, delta, epoch=None):
mod = time_mod(time, delta, epoch)
return time - mod
def time_ceil(time, delta, epoch=None):
mod = time_mod(time, delta, epoch)
if mod:
return time + (delta - mod)
return time
In your case:
>>> tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
>>> time_round(tm, datetime.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)
>>> time_floor(tm, datetime.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 3, 50)
>>> time_ceil(tm, datetime.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)
if you don't want to use condition, you can use modulo operator:
minutes = int(round(tm.minute, -1)) % 60
UPDATE
did you want something like this?
def timeround10(dt):
a, b = divmod(round(dt.minute, -1), 60)
return '%i:%02i' % ((dt.hour + a) % 24, b)
timeround10(datetime.datetime(2010, 1, 1, 0, 56, 0)) # 0:56
# -> 1:00
timeround10(datetime.datetime(2010, 1, 1, 23, 56, 0)) # 23:56
# -> 0:00
.. if you want result as string. for obtaining datetime result, it's better to use timedelta - see other responses ;)
i'm using this. it has the advantage of working with tz aware datetimes.
def round_minutes(some_datetime: datetime, step: int):
""" round up to nearest step-minutes """
if step > 60:
raise AttrbuteError("step must be less than 60")
change = timedelta(
minutes= some_datetime.minute % step,
seconds=some_datetime.second,
microseconds=some_datetime.microsecond
)
if change > timedelta():
change -= timedelta(minutes=step)
return some_datetime - change
it has the disadvantage of only working for timeslices less than an hour.
A straightforward approach:
def round_time(dt, round_to_seconds=60):
"""Round a datetime object to any number of seconds
dt: datetime.datetime object
round_to_seconds: closest number of seconds for rounding, Default 1 minute.
"""
rounded_epoch = round(dt.timestamp() / round_to_seconds) * round_to_seconds
rounded_dt = datetime.datetime.fromtimestamp(rounded_epoch).astimezone(dt.tzinfo)
return rounded_dt
This will do it, I think it uses a very useful application of round.
from typing import Literal
import math
def round_datetime(dt: datetime.datetime, step: datetime.timedelta, d: Literal['no', 'up', 'down'] = 'no'):
step = step.seconds
round_f = {'no': round, 'up': math.ceil, 'down': math.floor}
return datetime.datetime.fromtimestamp(step * round_f[d](dt.timestamp() / step))
date = datetime.datetime(year=2022, month=11, day=16, hour=10, minute=2, second=30, microsecond=424242)#
print('Original:', date)
print('Standard:', round_datetime(date, datetime.timedelta(minutes=5)))
print('Down: ', round_datetime(date, datetime.timedelta(minutes=5), d='down'))
print('Up: ', round_datetime(date, datetime.timedelta(minutes=5), d='up'))
The result:
Original: 2022-11-16 10:02:30.424242
Standard: 2022-11-16 10:05:00
Down: 2022-11-16 10:00:00
Up: 2022-11-16 10:05:00
yes, if your data belongs to a DateTime column in a pandas series, you can round it up using the built-in pandas.Series.dt.round function.
See documentation here on pandas.Series.dt.round.
In your case of rounding to 10min it will be Series.dt.round('10min') or Series.dt.round('600s') like so:
pandas.Series(tm).dt.round('10min')
Edit to add Example code:
import datetime
import pandas
tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
tm_rounded = pandas.Series(tm).dt.round('10min')
print(tm_rounded)
>>> 0 2010-06-10 04:00:00
dtype: datetime64[ns]
I came up with this very simple function, working with any timedelta as long as it's either a multiple or divider of 60 seconds. It's also compatible with timezone-aware datetimes.
#!/usr/env python3
from datetime import datetime, timedelta
def round_dt_to_delta(dt, delta=timedelta(minutes=30)):
ref = datetime.min.replace(tzinfo=dt.tzinfo)
return ref + round((dt - ref) / delta) * delta
Output:
In [1]: round_dt_to_delta(datetime(2012,12,31,23,44,49), timedelta(seconds=15))
Out[1]: datetime.datetime(2012, 12, 31, 23, 44, 45)
In [2]: round_dt_to_delta(datetime(2012,12,31,23,44,49), timedelta(minutes=15))
Out[2]: datetime.datetime(2012, 12, 31, 23, 45)
General Function to round down times of minutes:
from datetime import datetime
def round_minute(date: datetime = None, round_to: int = 1):
"""
round datetime object to minutes
"""
if not date:
date = datetime.now()
date = date.replace(second=0, microsecond=0)
delta = date.minute % round_to
return date.replace(minute=date.minute - delta)
Those seem overly complex
def round_down_to():
num = int(datetime.utcnow().replace(second=0, microsecond=0).minute)
return num - (num%10)
def get_rounded_datetime(self, dt, freq, nearest_type='inf'):
if freq.lower() == '1h':
round_to = 3600
elif freq.lower() == '3h':
round_to = 3 * 3600
elif freq.lower() == '6h':
round_to = 6 * 3600
else:
raise NotImplementedError("Freq %s is not handled yet" % freq)
# // is a floor division, not a comment on following line:
seconds_from_midnight = dt.hour * 3600 + dt.minute * 60 + dt.second
if nearest_type == 'inf':
rounded_sec = int(seconds_from_midnight / round_to) * round_to
elif nearest_type == 'sup':
rounded_sec = (int(seconds_from_midnight / round_to) + 1) * round_to
else:
raise IllegalArgumentException("nearest_type should be 'inf' or 'sup'")
dt_midnight = datetime.datetime(dt.year, dt.month, dt.day)
return dt_midnight + datetime.timedelta(0, rounded_sec)
Based on Stijn Nevens and modified for Django use to round current time to the nearest 15 minute.
from datetime import date, timedelta, datetime, time
def roundTime(dt=None, dateDelta=timedelta(minutes=1)):
roundTo = dateDelta.total_seconds()
if dt == None : dt = 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 + timedelta(0,rounding-seconds,-dt.microsecond)
dt = roundTime(datetime.now(),timedelta(minutes=15)).strftime('%H:%M:%S')
dt = 11:45:00
if you need full date and time just remove the .strftime('%H:%M:%S')
Not the best for speed when the exception is caught, however this would work.
def _minute10(dt=datetime.utcnow()):
try:
return dt.replace(minute=round(dt.minute, -1))
except ValueError:
return dt.replace(minute=0) + timedelta(hours=1)
Timings
%timeit _minute10(datetime(2016, 12, 31, 23, 55))
100000 loops, best of 3: 5.12 µs per loop
%timeit _minute10(datetime(2016, 12, 31, 23, 31))
100000 loops, best of 3: 2.21 µs per loop
A two line intuitive solution to round to a given time unit, here seconds, for a datetime object t:
format_str = '%Y-%m-%d %H:%M:%S'
t_rounded = datetime.strptime(datetime.strftime(t, format_str), format_str)
If you wish to round to a different unit simply alter format_str.
This approach does not round to arbitrary time amounts as above methods, but is a nicely Pythonic way to round to a given hour, minute or second.
Other solution:
def round_time(timestamp=None, lapse=0):
"""
Round a timestamp to a lapse according to specified minutes
Usage:
>>> import datetime, math
>>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 0)
datetime.datetime(2010, 6, 10, 3, 56)
>>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 1)
datetime.datetime(2010, 6, 10, 3, 57)
>>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), -1)
datetime.datetime(2010, 6, 10, 3, 55)
>>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3)
datetime.datetime(2019, 3, 11, 9, 24)
>>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3*60)
datetime.datetime(2019, 3, 11, 12, 0)
>>> round_time(datetime.datetime(2019, 3, 11, 10, 0, 0), 3)
datetime.datetime(2019, 3, 11, 10, 0)
:param timestamp: Timestamp to round (default: now)
:param lapse: Lapse to round in minutes (default: 0)
"""
t = timestamp or datetime.datetime.now() # type: Union[datetime, Any]
surplus = datetime.timedelta(seconds=t.second, microseconds=t.microsecond)
t -= surplus
try:
mod = t.minute % lapse
except ZeroDivisionError:
return t
if mod: # minutes % lapse != 0
t += datetime.timedelta(minutes=math.ceil(t.minute / lapse) * lapse - t.minute)
elif surplus != datetime.timedelta() or lapse < 0:
t += datetime.timedelta(minutes=(t.minute / lapse + 1) * lapse - t.minute)
return t
Hope this helps!
The shortest way I know
min = tm.minute // 10 * 10
Most of the answers seem to be too complicated for such a simple question.
Assuming your_time is the datetime object your have, the following rounds (actually floors) it at a desired resolution defined in minutes.
from math import floor
your_time = datetime.datetime.now()
g = 10 # granularity in minutes
print(
datetime.datetime.fromtimestamp(
floor(your_time.timestamp() / (60*g)) * (60*g)
))
The function below with minimum of import will do the job. You can round to anything you want by setting te parameters unit, rnd, and frm. Play with the function and you will see how easy it will be.
def toNearestTime(ts, unit='sec', rnd=1, frm=None):
''' round to nearest Time format
param ts = time string to round in '%H:%M:%S' or '%H:%M' format :
param unit = specify unit wich must be rounded 'sec' or 'min' or 'hour', default is seconds :
param rnd = to which number you will round, the default is 1 :
param frm = the output (return) format of the time string, as default the function take the unit format'''
from time import strftime, gmtime
ts = ts + ':00' if len(ts) == 5 else ts
if 'se' in unit.lower():
frm = '%H:%M:%S' if frm is None else frm
elif 'm' in unit.lower():
frm = '%H:%M' if frm is None else frm
rnd = rnd * 60
elif 'h' in unit.lower():
frm = '%H' if frm is None else frm
rnd = rnd * 3600
secs = sum(int(x) * 60 ** i for i, x in enumerate(reversed(ts.split(':'))))
rtm = int(round(secs / rnd, 0) * rnd)
nt = strftime(frm, gmtime(rtm))
return nt
Call function as follow:
Round to nearest 5 minutes with default ouput format = hh:mm as follow
ts = '02:27:29'
nt = toNearestTime(ts, unit='min', rnd=5)
print(nt)
output: '02:25'
Or round to nearest hour with ouput format hh:mm:ss as follow
ts = '10:30:01'
nt = toNearestTime(ts, unit='hour', rnd=1, frm='%H:%M:%S')
print(nt)
output: '11:00:00'
last updated version
How do I tell the time difference in minutes between two datetime objects?
>>> import datetime
>>> first_time = datetime.datetime.now()
>>> later_time = datetime.datetime.now()
>>> difference = later_time - first_time
datetime.timedelta(0, 8, 562000)
>>> seconds_in_day = 24 * 60 * 60
>>> divmod(difference.days * seconds_in_day + difference.seconds, 60)
(0, 8) # 0 minutes, 8 seconds
Subtracting the later time from the first time difference = later_time - first_time creates a datetime object that only holds the difference.
In the example above it is 0 minutes, 8 seconds and 562000 microseconds.
Using datetime example
>>> from datetime import datetime
>>> then = datetime(2012, 3, 5, 23, 8, 15) # Random date in the past
>>> now = datetime.now() # Now
>>> duration = now - then # For build-in functions
>>> duration_in_s = duration.total_seconds() # Total number of seconds between dates
Duration in years
>>> years = divmod(duration_in_s, 31536000)[0] # Seconds in a year=365*24*60*60 = 31536000.
Duration in days
>>> days = duration.days # Build-in datetime function
>>> days = divmod(duration_in_s, 86400)[0] # Seconds in a day = 86400
Duration in hours
>>> hours = divmod(duration_in_s, 3600)[0] # Seconds in an hour = 3600
Duration in minutes
>>> minutes = divmod(duration_in_s, 60)[0] # Seconds in a minute = 60
Duration in seconds
[!] See warning about using duration in seconds in the bottom of this post
>>> seconds = duration.seconds # Build-in datetime function
>>> seconds = duration_in_s
Duration in microseconds
[!] See warning about using duration in microseconds in the bottom of this post
>>> microseconds = duration.microseconds # Build-in datetime function
Total duration between the two dates
>>> days = divmod(duration_in_s, 86400) # Get days (without [0]!)
>>> hours = divmod(days[1], 3600) # Use remainder of days to calc hours
>>> minutes = divmod(hours[1], 60) # Use remainder of hours to calc minutes
>>> seconds = divmod(minutes[1], 1) # Use remainder of minutes to calc seconds
>>> print("Time between dates: %d days, %d hours, %d minutes and %d seconds" % (days[0], hours[0], minutes[0], seconds[0]))
or simply:
>>> print(now - then)
Edit 2019
Since this answer has gained traction, I'll add a function, which might simplify the usage for some
from datetime import datetime
def getDuration(then, now = datetime.now(), interval = "default"):
# Returns a duration as specified by variable interval
# Functions, except totalDuration, returns [quotient, remainder]
duration = now - then # For build-in functions
duration_in_s = duration.total_seconds()
def years():
return divmod(duration_in_s, 31536000) # Seconds in a year=31536000.
def days(seconds = None):
return divmod(seconds if seconds != None else duration_in_s, 86400) # Seconds in a day = 86400
def hours(seconds = None):
return divmod(seconds if seconds != None else duration_in_s, 3600) # Seconds in an hour = 3600
def minutes(seconds = None):
return divmod(seconds if seconds != None else duration_in_s, 60) # Seconds in a minute = 60
def seconds(seconds = None):
if seconds != None:
return divmod(seconds, 1)
return duration_in_s
def totalDuration():
y = years()
d = days(y[1]) # Use remainder to calculate next variable
h = hours(d[1])
m = minutes(h[1])
s = seconds(m[1])
return "Time between dates: {} years, {} days, {} hours, {} minutes and {} seconds".format(int(y[0]), int(d[0]), int(h[0]), int(m[0]), int(s[0]))
return {
'years': int(years()[0]),
'days': int(days()[0]),
'hours': int(hours()[0]),
'minutes': int(minutes()[0]),
'seconds': int(seconds()),
'default': totalDuration()
}[interval]
# Example usage
then = datetime(2012, 3, 5, 23, 8, 15)
now = datetime.now()
print(getDuration(then)) # E.g. Time between dates: 7 years, 208 days, 21 hours, 19 minutes and 15 seconds
print(getDuration(then, now, 'years')) # Prints duration in years
print(getDuration(then, now, 'days')) # days
print(getDuration(then, now, 'hours')) # hours
print(getDuration(then, now, 'minutes')) # minutes
print(getDuration(then, now, 'seconds')) # seconds
Warning: Caveat about built-in .seconds and .microseconds
datetime.seconds and datetime.microseconds are capped to [0,86400) and [0,10^6) respectively.
They should be used carefully if timedelta is bigger than the max returned value.
Examples:
end is 1h and 200μs after start:
>>> start = datetime(2020,12,31,22,0,0,500)
>>> end = datetime(2020,12,31,23,0,0,700)
>>> delta = end - start
>>> delta.microseconds
RESULT: 200
EXPECTED: 3600000200
end is 1d and 1h after start:
>>> start = datetime(2020,12,30,22,0,0)
>>> end = datetime(2020,12,31,23,0,0)
>>> delta = end - start
>>> delta.seconds
RESULT: 3600
EXPECTED: 90000
New at Python 2.7 is the timedelta instance method .total_seconds(). From the Python docs, this is equivalent to (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6.
Reference: http://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds
>>> import datetime
>>> time1 = datetime.datetime.now()
>>> time2 = datetime.datetime.now() # waited a few minutes before pressing enter
>>> elapsedTime = time2 - time1
>>> elapsedTime
datetime.timedelta(0, 125, 749430)
>>> divmod(elapsedTime.total_seconds(), 60)
(2.0, 5.749430000000004) # divmod returns quotient and remainder
# 2 minutes, 5.74943 seconds
Just subtract one from the other. You get a timedelta object with the difference.
>>> import datetime
>>> d1 = datetime.datetime.now()
>>> d2 = datetime.datetime.now() # after a 5-second or so pause
>>> d2 - d1
datetime.timedelta(0, 5, 203000)
>>> dd = d2 - d1
>>> print (dd.days) # get days
>>> print (dd.seconds) # get seconds
>>> print (dd.microseconds) # get microseconds
>>> print (int(round(dd.total_seconds()/60, 0))) # get minutes
If a, b are datetime objects then to find the time difference between them in Python 3:
from datetime import timedelta
time_difference = a - b
time_difference_in_minutes = time_difference / timedelta(minutes=1)
On earlier Python versions:
time_difference_in_minutes = time_difference.total_seconds() / 60
If a, b are naive datetime objects such as returned by datetime.now() then the result may be wrong if the objects represent local time with different UTC offsets e.g., around DST transitions or for past/future dates. More details: Find if 24 hrs have passed between datetimes - Python.
To get reliable results, use UTC time or timezone-aware datetime objects.
Use divmod:
now = int(time.time()) # epoch seconds
then = now - 90000 # some time in the past
d = divmod(now-then,86400) # days
h = divmod(d[1],3600) # hours
m = divmod(h[1],60) # minutes
s = m[1] # seconds
print '%d days, %d hours, %d minutes, %d seconds' % (d[0],h[0],m[0],s)
To just find the number of days: timedelta has a 'days' attribute. You can simply query that.
>>>from datetime import datetime, timedelta
>>>d1 = datetime(2015, 9, 12, 13, 9, 45)
>>>d2 = datetime(2015, 8, 29, 21, 10, 12)
>>>d3 = d1- d2
>>>print d3
13 days, 15:59:33
>>>print d3.days
13
Just thought it might be useful to mention formatting as well in regards to timedelta. strptime() parses a string representing a time according to a format.
from datetime import datetime
datetimeFormat = '%Y/%m/%d %H:%M:%S.%f'
time1 = '2016/03/16 10:01:28.585'
time2 = '2016/03/16 09:56:28.067'
time_dif = datetime.strptime(time1, datetimeFormat) - datetime.strptime(time2,datetimeFormat)
print(time_dif)
This will output:
0:05:00.518000
This is how I get the number of hours that elapsed between two datetime.datetime objects:
before = datetime.datetime.now()
after = datetime.datetime.now()
hours = math.floor(((after - before).seconds) / 3600)
To get the hour, minute and second, you can do this
>>> import datetime
>>> first_time = datetime.datetime.now()
>>> later_time = datetime.datetime.now()
>>> difference = later_time - first_time
>>> m, s = divmod(difference.total_seconds(), 60)
>>> print("H:M:S is {}:{}:{}".format(m//60, m%60, s))
I use somethign like this :
from datetime import datetime
def check_time_difference(t1: datetime, t2: datetime):
t1_date = datetime(
t1.year,
t1.month,
t1.day,
t1.hour,
t1.minute,
t1.second)
t2_date = datetime(
t2.year,
t2.month,
t2.day,
t2.hour,
t2.minute,
t2.second)
t_elapsed = t1_date - t2_date
return t_elapsed
# usage
f = "%Y-%m-%d %H:%M:%S+01:00"
t1 = datetime.strptime("2018-03-07 22:56:57+01:00", f)
t2 = datetime.strptime("2018-03-07 22:48:05+01:00", f)
elapsed_time = check_time_difference(t1, t2)
print(elapsed_time)
#return : 0:08:52
This will give the difference in seconds (then just divide by 60 to get minutes):
import time
import datetime
t_start = datetime.datetime.now()
time.sleep(10)
t_end = datetime.datetime.now()
elapsedTime = (t_end - t_start )
print(elapsedTime.total_seconds())
outputs:
10.009222
This is the simplest way in my opinion, and you don't need to worry about precision or overflow.
For instance, using elapsedTime.seconds you lose a lot of precision (it returns an integer). Also, elapsedTime.microseconds is capped at 10^6, as this answer pointed out. So, for example, for a 10 second sleep(), elapsedTime.microseconds gives 8325 (which is wrong, should be around 10,000,000).
this is to find the difference between current time and 9.30 am
t=datetime.now()-datetime.now().replace(hour=9,minute=30)
Based on #Attaque great answer, I propose a shorter simplified version of the datetime difference calculator:
seconds_mapping = {
'y': 31536000,
'm': 2628002.88, # this is approximate, 365 / 12; use with caution
'w': 604800,
'd': 86400,
'h': 3600,
'min': 60,
's': 1,
'mil': 0.001,
}
def get_duration(d1, d2, interval, with_reminder=False):
if with_reminder:
return divmod((d2 - d1).total_seconds(), seconds_mapping[interval])
else:
return (d2 - d1).total_seconds() / seconds_mapping[interval]
I've changed it to avoid declaring repetetive functions, removed the pretty print default interval and added support for milliseconds, weeks and ISO months (bare in mind months are just approximate, based on assumption that each month is equal to 365/12).
Which produces:
d1 = datetime(2011, 3, 1, 1, 1, 1, 1000)
d2 = datetime(2011, 4, 1, 1, 1, 1, 2500)
print(get_duration(d1, d2, 'y', True)) # => (0.0, 2678400.0015)
print(get_duration(d1, d2, 'm', True)) # => (1.0, 50397.12149999989)
print(get_duration(d1, d2, 'w', True)) # => (4.0, 259200.00149999978)
print(get_duration(d1, d2, 'd', True)) # => (31.0, 0.0014999997802078724)
print(get_duration(d1, d2, 'h', True)) # => (744.0, 0.0014999997802078724)
print(get_duration(d1, d2, 'min', True)) # => (44640.0, 0.0014999997802078724)
print(get_duration(d1, d2, 's', True)) # => (2678400.0, 0.0014999997802078724)
print(get_duration(d1, d2, 'mil', True)) # => (2678400001.0, 0.0004999997244524721)
print(get_duration(d1, d2, 'y', False)) # => 0.08493150689687975
print(get_duration(d1, d2, 'm', False)) # => 1.019176965856293
print(get_duration(d1, d2, 'w', False)) # => 4.428571431051587
print(get_duration(d1, d2, 'd', False)) # => 31.00000001736111
print(get_duration(d1, d2, 'h', False)) # => 744.0000004166666
print(get_duration(d1, d2, 'min', False)) # => 44640.000024999994
print(get_duration(d1, d2, 's', False)) # => 2678400.0015
print(get_duration(d1, d2, 'mil', False)) # => 2678400001.4999995
This is my approach using mktime.
from datetime import datetime, timedelta
from time import mktime
yesterday = datetime.now() - timedelta(days=1)
today = datetime.now()
difference_in_seconds = abs(mktime(yesterday.timetuple()) - mktime(today.timetuple()))
difference_in_minutes = difference_in_seconds / 60
In Other ways to get difference between date;
import dateutil.parser
import datetime
last_sent_date = "" # date string
timeDifference = current_date - dateutil.parser.parse(last_sent_date)
time_difference_in_minutes = (int(timeDifference.days) * 24 * 60) + int((timeDifference.seconds) / 60)
So get output in Min.
Thanks
I have used time differences for continuous integration tests to check and improve my functions. Here is simple code if somebody need it
from datetime import datetime
class TimeLogger:
time_cursor = None
def pin_time(self):
global time_cursor
time_cursor = datetime.now()
def log(self, text=None) -> float:
global time_cursor
if not time_cursor:
time_cursor = datetime.now()
now = datetime.now()
t_delta = now - time_cursor
seconds = t_delta.total_seconds()
result = str(now) + ' tl -----------> %.5f' % seconds
if text:
result += " " + text
print(result)
self.pin_time()
return seconds
time_logger = TimeLogger()
Using:
from .tests_time_logger import time_logger
class Tests(TestCase):
def test_workflow(self):
time_logger.pin_time()
... my functions here ...
time_logger.log()
... other function(s) ...
time_logger.log(text='Tests finished')
and i have something like that in log output
2019-12-20 17:19:23.635297 tl -----------> 0.00007
2019-12-20 17:19:28.147656 tl -----------> 4.51234 Tests finished
You may find this fast snippet useful in not so much long time intervals:
from datetime import datetime as dttm
time_ago = dttm(2017, 3, 1, 1, 1, 1, 1348)
delta = dttm.now() - time_ago
days = delta.days # can be converted into years which complicates a bit…
hours, minutes, seconds = map(int, delta.__format__('').split('.')[0].split(' ')[-1].split(':'))
tested on Python v.3.8.6
Here is an answer that is easy to generalise or turn into a function and which is reasonable compact and easy to follow.
ts_start=datetime(2020, 12, 1, 3, 9, 45)
ts_end=datetime.now()
ts_diff=ts_end-ts_start
secs=ts_diff.total_seconds()
days,secs=divmod(secs,secs_per_day:=60*60*24)
hrs,secs=divmod(secs,secs_per_hr:=60*60)
mins,secs=divmod(secs,secs_per_min:=60)
secs=round(secs, 2)
answer='Duration={} days, {} hrs, {} mins and {} secs'.format(int(days),int(hrs),int(mins),secs)
print(answer)
It gives an answer in the form Duration=270 days, 10 hrs, 32 mins and 42.13 secs
This might help someone, find is expired or not with this method its calculating with days. There are dt.seconds and dt.microseconds also available
from datetime import datetime
# updated_at = "2022-10-20T07:18:56.950563"
def is_expired(updated_at):
expires_in = 7 #days
datetime_format = '%Y-%m-%dT%H:%M:%S.%f'
time_difference = datetime.now() - datetime.strptime(updated_at, datetime_format)
return True if time_difference.days > expires_in else False
import datetime
date = datetime.date(1, 1, 1)
#combine a dummy date to the time
datetime1 = datetime.datetime.combine(date, start_time)
datetime2 = datetime.datetime.combine(date, stop_time)
#compute the difference
time_elapsed = datetime1 - datetime2
start_time --> start time for datetime object
end_time--> end time for datetime object
we cannot directly subtract the datetime.time objects
hence we need to add a random date to it (we use combine)
or you can use the "today" instead of (1,1,1)
hope this helps
I have the following code to do this, but how can I do it better? Right now I think it's better than nested loops, but it starts to get Perl-one-linerish when you have a generator in a list comprehension.
day_count = (end_date - start_date).days + 1
for single_date in [d for d in (start_date + timedelta(n) for n in range(day_count)) if d <= end_date]:
print strftime("%Y-%m-%d", single_date.timetuple())
Notes
I'm not actually using this to print. That's just for demo purposes.
The start_date and end_date variables are datetime.date objects because I don't need the timestamps. (They're going to be used to generate a report).
Sample Output
For a start date of 2009-05-30 and an end date of 2009-06-09:
2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09
Why are there two nested iterations? For me it produces the same list of data with only one iteration:
for single_date in (start_date + timedelta(n) for n in range(day_count)):
print ...
And no list gets stored, only one generator is iterated over. Also the "if" in the generator seems to be unnecessary.
After all, a linear sequence should only require one iterator, not two.
Update after discussion with John Machin:
Maybe the most elegant solution is using a generator function to completely hide/abstract the iteration over the range of dates:
from datetime import date, timedelta
def daterange(start_date, end_date):
for n in range(int((end_date - start_date).days)):
yield start_date + timedelta(n)
start_date = date(2013, 1, 1)
end_date = date(2015, 6, 2)
for single_date in daterange(start_date, end_date):
print(single_date.strftime("%Y-%m-%d"))
NB: For consistency with the built-in range() function this iteration stops before reaching the end_date. So for inclusive iteration use the next day, as you would with range().
This might be more clear:
from datetime import date, timedelta
start_date = date(2019, 1, 1)
end_date = date(2020, 1, 1)
delta = timedelta(days=1)
while start_date <= end_date:
print(start_date.strftime("%Y-%m-%d"))
start_date += delta
Use the dateutil library:
from datetime import date
from dateutil.rrule import rrule, DAILY
a = date(2009, 5, 30)
b = date(2009, 6, 9)
for dt in rrule(DAILY, dtstart=a, until=b):
print dt.strftime("%Y-%m-%d")
This python library has many more advanced features, some very useful, like relative deltas—and is implemented as a single file (module) that's easily included into a project.
Pandas is great for time series in general, and has direct support for date ranges.
import pandas as pd
daterange = pd.date_range(start_date, end_date)
You can then loop over the daterange to print the date:
for single_date in daterange:
print (single_date.strftime("%Y-%m-%d"))
It also has lots of options to make life easier. For example if you only wanted weekdays, you would just swap in bdate_range. See http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
The power of Pandas is really its dataframes, which support vectorized operations (much like numpy) that make operations across large quantities of data very fast and easy.
EDIT:
You could also completely skip the for loop and just print it directly, which is easier and more efficient:
print(daterange)
import datetime
def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False):
# inclusive=False to behave like range by default
if step.days > 0:
while start < stop:
yield start
start = start + step
# not +=! don't modify object passed in if it's mutable
# since this function is not restricted to
# only types from datetime module
elif step.days < 0:
while start > stop:
yield start
start = start + step
if inclusive and start == stop:
yield start
# ...
for date in daterange(start_date, end_date, inclusive=True):
print strftime("%Y-%m-%d", date.timetuple())
This function does more than you strictly require, by supporting negative step, etc. As long as you factor out your range logic, then you don't need the separate day_count and most importantly the code becomes easier to read as you call the function from multiple places.
This is the most human-readable solution I can think of.
import datetime
def daterange(start, end, step=datetime.timedelta(1)):
curr = start
while curr < end:
yield curr
curr += step
Numpy's arange function can be applied to dates:
import numpy as np
from datetime import datetime, timedelta
d0 = datetime(2009, 1,1)
d1 = datetime(2010, 1,1)
dt = timedelta(days = 1)
dates = np.arange(d0, d1, dt).astype(datetime)
The use of astype is to convert from numpy.datetime64 to an array of datetime.datetime objects.
Why not try:
import datetime as dt
start_date = dt.datetime(2012, 12,1)
end_date = dt.datetime(2012, 12,5)
total_days = (end_date - start_date).days + 1 #inclusive 5 days
for day_number in range(total_days):
current_date = (start_date + dt.timedelta(days = day_number)).date()
print current_date
Show the last n days from today:
import datetime
for i in range(0, 100):
print((datetime.date.today() + datetime.timedelta(i)).isoformat())
Output:
2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04
For completeness, Pandas also has a period_range function for timestamps that are out of bounds:
import pandas as pd
pd.period_range(start='1/1/1626', end='1/08/1627', freq='D')
import datetime
def daterange(start, stop, step_days=1):
current = start
step = datetime.timedelta(step_days)
if step_days > 0:
while current < stop:
yield current
current += step
elif step_days < 0:
while current > stop:
yield current
current += step
else:
raise ValueError("daterange() step_days argument must not be zero")
if __name__ == "__main__":
from pprint import pprint as pp
lo = datetime.date(2008, 12, 27)
hi = datetime.date(2009, 1, 5)
pp(list(daterange(lo, hi)))
pp(list(daterange(hi, lo, -1)))
pp(list(daterange(lo, hi, 7)))
pp(list(daterange(hi, lo, -7)))
assert not list(daterange(lo, hi, -1))
assert not list(daterange(hi, lo))
assert not list(daterange(lo, hi, -7))
assert not list(daterange(hi, lo, 7))
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
I have a similar problem, but I need to iterate monthly instead of daily.
This is my solution
import calendar
from datetime import datetime, timedelta
def days_in_month(dt):
return calendar.monthrange(dt.year, dt.month)[1]
def monthly_range(dt_start, dt_end):
forward = dt_end >= dt_start
finish = False
dt = dt_start
while not finish:
yield dt.date()
if forward:
days = days_in_month(dt)
dt = dt + timedelta(days=days)
finish = dt > dt_end
else:
_tmp_dt = dt.replace(day=1) - timedelta(days=1)
dt = (_tmp_dt.replace(day=dt.day))
finish = dt < dt_end
Example #1
date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01
2017-01-01
Example #2
date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01
You can generate a series of date between two dates using the pandas library simply and trustfully
import pandas as pd
print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')
You can change the frequency of generating dates by setting freq as D, M, Q, Y
(daily, monthly, quarterly, yearly
)
Using pendulum.period:
import pendulum
start = pendulum.from_format('2020-05-01', 'YYYY-MM-DD', formatter='alternative')
end = pendulum.from_format('2020-05-02', 'YYYY-MM-DD', formatter='alternative')
period = pendulum.period(start, end)
for dt in period:
print(dt.to_date_string())
> pip install DateTimeRange
from datetimerange import DateTimeRange
def dateRange(start, end, step):
rangeList = []
time_range = DateTimeRange(start, end)
for value in time_range.range(datetime.timedelta(days=step)):
rangeList.append(value.strftime('%m/%d/%Y'))
return rangeList
dateRange("2018-09-07", "2018-12-25", 7)
Out[92]:
['09/07/2018',
'09/14/2018',
'09/21/2018',
'09/28/2018',
'10/05/2018',
'10/12/2018',
'10/19/2018',
'10/26/2018',
'11/02/2018',
'11/09/2018',
'11/16/2018',
'11/23/2018',
'11/30/2018',
'12/07/2018',
'12/14/2018',
'12/21/2018']
For those who are interested in Pythonic functional way:
from datetime import date, timedelta
from itertools import count, takewhile
for d in takewhile(lambda x: x<=date(2009,6,9), map(lambda x:date(2009,5,30)+timedelta(days=x), count())):
print(d)
What about the following for doing a range incremented by days:
for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
# Do stuff here
startDate and stopDate are datetime.date objects
For a generic version:
for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
# Do stuff here
startTime and stopTime are datetime.date or datetime.datetime object
(both should be the same type)
stepTime is a timedelta object
Note that .total_seconds() is only supported after python 2.7 If you are stuck with an earlier version you can write your own function:
def total_seconds( td ):
return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
This function has some extra features:
can pass a string matching the DATE_FORMAT for start or end and it is converted to a date object
can pass a date object for start or end
error checking in case the end is older than the start
import datetime
from datetime import timedelta
DATE_FORMAT = '%Y/%m/%d'
def daterange(start, end):
def convert(date):
try:
date = datetime.datetime.strptime(date, DATE_FORMAT)
return date.date()
except TypeError:
return date
def get_date(n):
return datetime.datetime.strftime(convert(start) + timedelta(days=n), DATE_FORMAT)
days = (convert(end) - convert(start)).days
if days <= 0:
raise ValueError('The start date must be before the end date.')
for n in range(0, days):
yield get_date(n)
start = '2014/12/1'
end = '2014/12/31'
print list(daterange(start, end))
start_ = datetime.date.today()
end = '2015/12/1'
print list(daterange(start, end))
Here's code for a general date range function, similar to Ber's answer, but more flexible:
def count_timedelta(delta, step, seconds_in_interval):
"""Helper function for iterate. Finds the number of intervals in the timedelta."""
return int(delta.total_seconds() / (seconds_in_interval * step))
def range_dt(start, end, step=1, interval='day'):
"""Iterate over datetimes or dates, similar to builtin range."""
intervals = functools.partial(count_timedelta, (end - start), step)
if interval == 'week':
for i in range(intervals(3600 * 24 * 7)):
yield start + datetime.timedelta(weeks=i) * step
elif interval == 'day':
for i in range(intervals(3600 * 24)):
yield start + datetime.timedelta(days=i) * step
elif interval == 'hour':
for i in range(intervals(3600)):
yield start + datetime.timedelta(hours=i) * step
elif interval == 'minute':
for i in range(intervals(60)):
yield start + datetime.timedelta(minutes=i) * step
elif interval == 'second':
for i in range(intervals(1)):
yield start + datetime.timedelta(seconds=i) * step
elif interval == 'millisecond':
for i in range(intervals(1 / 1000)):
yield start + datetime.timedelta(milliseconds=i) * step
elif interval == 'microsecond':
for i in range(intervals(1e-6)):
yield start + datetime.timedelta(microseconds=i) * step
else:
raise AttributeError("Interval must be 'week', 'day', 'hour' 'second', \
'microsecond' or 'millisecond'.")
import datetime
from dateutil.rrule import DAILY,rrule
date=datetime.datetime(2019,1,10)
date1=datetime.datetime(2019,2,2)
for i in rrule(DAILY , dtstart=date,until=date1):
print(i.strftime('%Y%b%d'),sep='\n')
OUTPUT:
2019Jan10
2019Jan11
2019Jan12
2019Jan13
2019Jan14
2019Jan15
2019Jan16
2019Jan17
2019Jan18
2019Jan19
2019Jan20
2019Jan21
2019Jan22
2019Jan23
2019Jan24
2019Jan25
2019Jan26
2019Jan27
2019Jan28
2019Jan29
2019Jan30
2019Jan31
2019Feb01
2019Feb02
from datetime import date,timedelta
delta = timedelta(days=1)
start = date(2020,1,1)
end=date(2020,9,1)
loop_date = start
while loop_date<=end:
print(loop_date)
loop_date+=delta
You can use Arrow:
This is example from the docs, iterating over hours:
from arrow import Arrow
>>> start = datetime(2013, 5, 5, 12, 30)
>>> end = datetime(2013, 5, 5, 17, 15)
>>> for r in Arrow.range('hour', start, end):
... print repr(r)
...
<Arrow [2013-05-05T12:30:00+00:00]>
<Arrow [2013-05-05T13:30:00+00:00]>
<Arrow [2013-05-05T14:30:00+00:00]>
<Arrow [2013-05-05T15:30:00+00:00]>
<Arrow [2013-05-05T16:30:00+00:00]>
To iterate over days, you can use like this:
>>> start = Arrow(2013, 5, 5)
>>> end = Arrow(2013, 5, 5)
>>> for r in Arrow.range('day', start, end):
... print repr(r)
(Didn't check if you can pass datetime.date objects, but anyways Arrow objects are easier in general)
If you are going to use dynamic timedelta then you can use:
1. With while loop
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
while start <= end:
yield start
start += delta
2. With for loop
from datetime import datetime, timedelta
from typing import Generator
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
3. If you are using async/await
async def datetime_range(start: datetime, end: datetime, delta: timedelta) -> AsyncGenerator[datetime, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
4. List comprehension
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> List[datetime]:
delta_units = int((end - start) / delta)
return [start + (delta * index) for index in range(delta_units + 1)]
Then 1 and 2 solutions simply can be used like this
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part for time_part in datetime_range(start, end, delta)]
# or
for time_part in datetime_range(start, end, delta):
print(time_part)
3-third solution can be used like this in async context. Because it retruns an async generator object, which can be used only in async context
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part async for time_part in datetime_range(start, end, delta)]
async for time_part in datetime_range(start, end, delta):
print(time_part)
The benefit of the solutions about is that all of them are using dynamic timedelta. This can be very usefull in cases when you do not know which time delta you will have.
Slightly different approach to reversible steps by storing range args in a tuple.
def date_range(start, stop, step=1, inclusive=False):
day_count = (stop - start).days
if inclusive:
day_count += 1
if step > 0:
range_args = (0, day_count, step)
elif step < 0:
range_args = (day_count - 1, -1, step)
else:
raise ValueError("date_range(): step arg must be non-zero")
for i in range(*range_args):
yield start + timedelta(days=i)