Reputation: 28457
What is a classy way to way truncate a python datetime
object?
In this particular case, to the day. So basically setting hour, minute, seconds, and microseconds to 0.
I would like the output to also be a datetime
object, not a string.
Upvotes: 378
Views: 419084
Reputation: 2049
The other answers I see can introduce bugs, as they may remove tzinfo
, or don't use time.min
which ensures that any additional precision added to Python in the future is also accounted for. The way to get the start of the day in a clean and reliable way is:
from datetime import datetime, time, timezone
def start_of_day(dt: datetime) -> datetime:
return datetime.combine(dt, time.min, tzinfo=dt.tzinfo)
print(start_of_day(datetime.now()))
print(start_of_day(datetime.now(tz=timezone.utc)))
This outputs today:
2024-04-03 00:00:00
2024-04-03 00:00:00+00:00
Upvotes: 0
Reputation: 49
You could do it by specifying isoformat
>>> import datetime
>>> datetime.datetime.now().isoformat(timespec='seconds', sep=' ')
2022-11-24 12:42:05
The documentation offers more details about the isoformat()
usage.
https://docs.python.org/3/library/datetime.html#datetime.datetime.isoformat
Upvotes: 1
Reputation: 892
If you want to truncate to an arbitrary timedelta:
from datetime import datetime, timedelta
truncate = lambda t, d: t + (datetime.min - t) % - d
# 2022-05-04 15:54:19.979349
now = datetime.now()
# truncates to the last 15 secondes
print(truncate(now, timedelta(seconds=15)))
# truncates to the last minute
print(truncate(now, timedelta(minutes=1)))
# truncates to the last 2 hours
print(truncate(now, timedelta(hours=2)))
# ...
"""
2022-05-04 15:54:15
2022-05-04 15:54:00
2022-05-04 14:00:00
"""
PS: This is for python3
Upvotes: 4
Reputation: 111
You can just use
datetime.date.today()
It's light and returns exactly what you want.
Upvotes: 1
Reputation: 632
Here is yet another way which fits in one line but is not particularly elegant:
dt = datetime.datetime.fromordinal(datetime.date.today().toordinal())
Upvotes: 3
Reputation: 213
See more at https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.floor.html
It's now 2019, I think the most efficient way to do it is:
df['truncate_date'] = df['timestamp'].dt.floor('d')
Upvotes: 19
Reputation: 10375
There is a great library used to manipulate dates: Delorean
import datetime
from delorean import Delorean
now = datetime.datetime.now()
d = Delorean(now, timezone='US/Pacific')
>>> now
datetime.datetime(2015, 3, 26, 19, 46, 40, 525703)
>>> d.truncate('second')
Delorean(datetime=2015-03-26 19:46:40-07:00, timezone='US/Pacific')
>>> d.truncate('minute')
Delorean(datetime=2015-03-26 19:46:00-07:00, timezone='US/Pacific')
>>> d.truncate('hour')
Delorean(datetime=2015-03-26 19:00:00-07:00, timezone='US/Pacific')
>>> d.truncate('day')
Delorean(datetime=2015-03-26 00:00:00-07:00, timezone='US/Pacific')
>>> d.truncate('month')
Delorean(datetime=2015-03-01 00:00:00-07:00, timezone='US/Pacific')
>>> d.truncate('year')
Delorean(datetime=2015-01-01 00:00:00-07:00, timezone='US/Pacific')
and if you want to get datetime value back:
>>> d.truncate('year').datetime
datetime.datetime(2015, 1, 1, 0, 0, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
Upvotes: 4
Reputation: 1384
>>> import datetime
>>> dt = datetime.datetime.now()
>>> datetime.datetime.date(dt)
datetime.date(2019, 4, 2)
Upvotes: 3
Reputation: 39279
I think this is what you're looking for...
>>> import datetime
>>> dt = datetime.datetime.now()
>>> dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) # Returns a copy
>>> dt
datetime.datetime(2011, 3, 29, 0, 0)
But if you really don't care about the time aspect of things, then you should really only be passing around date
objects...
>>> d_truncated = datetime.date(dt.year, dt.month, dt.day)
>>> d_truncated
datetime.date(2011, 3, 29)
Upvotes: 557
Reputation: 350
If you are dealing with a Series of type DateTime there is a more efficient way to truncate them, specially when the Series object has a lot of rows.
You can use the floor function
For example, if you want to truncate it to hours:
Generate a range of dates
times = pd.Series(pd.date_range(start='1/1/2018 04:00:00', end='1/1/2018 22:00:00', freq='s'))
We can check it comparing the running time between the replace and the floor functions.
%timeit times.apply(lambda x : x.replace(minute=0, second=0, microsecond=0))
>>> 341 ms ± 18.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit times.dt.floor('h')
>>>>2.26 ms ± 451 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Upvotes: 3
Reputation: 31692
You could use pandas for that (although it could be overhead for that task). You could use round, floor and ceil like for usual numbers and any pandas frequency from offset-aliases:
import pandas as pd
import datetime as dt
now = dt.datetime.now()
pd_now = pd.Timestamp(now)
freq = '1d'
pd_round = pd_now.round(freq)
dt_round = pd_round.to_pydatetime()
print(now)
print(dt_round)
"""
2018-06-15 09:33:44.102292
2018-06-15 00:00:00
"""
Upvotes: 12
Reputation: 621
6 years later... I found this post and I liked more the numpy aproach:
import numpy as np
dates_array = np.array(['2013-01-01', '2013-01-15', '2013-01-30']).astype('datetime64[ns]')
truncated_dates = dates_array.astype('datetime64[D]')
cheers
Upvotes: 1
Reputation: 23509
You cannot truncate a datetime object because it is immutable.
However, here is one way to construct a new datetime with 0 hour, minute, second, and microsecond fields, without throwing away the original date or tzinfo:
newdatetime = now.replace(hour=0, minute=0, second=0, microsecond=0)
Upvotes: 26
Reputation: 41848
replace
I know the accepted answer from four years ago works, but this seems a tad lighter than using replace
:
dt = datetime.date.today()
dt = datetime.datetime(dt.year, dt.month, dt.day)
Notes
datetime
object without passing time properties to the constructor, you get midnight.dt = datetime.datetime.now()
Upvotes: 53
Reputation: 414875
To get a midnight corresponding to a given datetime object, you could use datetime.combine()
method:
>>> from datetime import datetime, time
>>> dt = datetime.utcnow()
>>> dt.date()
datetime.date(2015, 2, 3)
>>> datetime.combine(dt, time.min)
datetime.datetime(2015, 2, 3, 0, 0)
The advantage compared to the .replace()
method is that datetime.combine()
-based solution will continue to work even if datetime
module introduces the nanoseconds support.
tzinfo
can be preserved if necessary but the utc offset may be different at midnight e.g., due to a DST transition and therefore a naive solution (setting tzinfo
time attribute) may fail. See How do I get the UTC time of “midnight” for a given timezone?
Upvotes: 28
Reputation: 646
There is a module datetime_truncate which handlers this for you. It just calls datetime.replace.
Upvotes: 1
Reputation: 107786
Use a date
not a datetime
if you dont care about the time.
>>> now = datetime.now()
>>> now.date()
datetime.date(2011, 3, 29)
You can update a datetime like this:
>>> now.replace(minute=0, hour=0, second=0, microsecond=0)
datetime.datetime(2011, 3, 29, 0, 0)
Upvotes: 103
Reputation: 41078
You can use datetime.strftime to extract the day, the month, the year...
Example :
from datetime import datetime
d = datetime.today()
# Retrieves the day and the year
print d.strftime("%d-%Y")
Output (for today):
29-2011
If you just want to retrieve the day, you can use day attribute like :
from datetime import datetime
d = datetime.today()
# Retrieves the day
print d.day
Ouput (for today):
29
Upvotes: 3