350

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.

martineau
  • 119,623
  • 25
  • 170
  • 301
Kyle Brandt
  • 26,938
  • 37
  • 124
  • 165

17 Answers17

529

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)
Narfanator
  • 5,595
  • 3
  • 39
  • 71
Chris W.
  • 37,583
  • 36
  • 99
  • 136
  • 24
    With a timezone-aware dt, datetime.datetime(dt.year, dt.month, dt.day) throws away the tzinfo information. – ʇsәɹoɈ Mar 29 '11 at 17:11
  • 2
    if you're looking for just today, you can also do datetime.date.today() – galarant May 17 '13 at 17:56
  • 8
    Note that python 2 and python 3 [docs](https://docs.python.org/3.4/library/datetime.html#datetime.datetime.replace) both state that the `replace()` method _returns_ a datetime object, so the correct incantation would be: `dt = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)` – Brad M Oct 11 '14 at 10:06
  • 4
    OP wants `datetime`, not `date` object (that you could get using `dt.date()` call (no need to use the explicit constructor)). The `.replace()` method may fail if [`datetime` adds nanosecond support](http://bugs.python.org/issue15443). You could [use `datetime.combine()` instead](http://stackoverflow.com/a/28311227/4279). – jfs Mar 28 '15 at 11:33
  • @chrisw Why not just write it up in one line `datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)` ? – 3kstc Oct 04 '18 at 07:45
96

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)
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
51

Four years later: another way, avoiding 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

  • When you create a datetime object without passing time properties to the constructor, you get midnight.
  • As others have noted, this assumes you want a datetime object for later use with timedeltas.
  • You can, of course, substitute this for the first line: dt = datetime.datetime.now()
zx81
  • 41,100
  • 9
  • 89
  • 105
27

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?

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
25

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)
ʇsәɹoɈ
  • 22,757
  • 7
  • 55
  • 61
  • 1
    +1: if you put the `replace` option first, since that's probably what they want. – S.Lott Mar 29 '11 at 18:10
  • 1
    It is incorrect to use `tzinfo=now.tzinfo`. The `tzinfo` at midnight may be different e.g., utc offset at `2012-04-01 00:09:00` (9am) in Australia/Melbourne timezone is `AEST+10:00` but it is `AEDT+11:00` at `2012-04-01 00:00:00` (midnight) -- there is end-of-DST transition on that day. You could use `pytz` module to fix it, see my answer. – jfs Mar 28 '15 at 17:51
15

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')
Ben Liu
  • 173
  • 1
  • 4
  • yeah, let's use dt.floor!! – Eiffelbear Mar 04 '21 at 13:01
  • Waw, exactly what I've been looking for the last three hours. Thanks a lot. That's the only solution that enables to pass the truncating frequency as a parameter... – Clej Apr 25 '22 at 14:09
  • Well, `df['timestamp'] = df['timestamp'].dt.floor('d')` will not work. Column name must be different and this is a serious drawback. – cph_sto May 03 '22 at 09:50
  • This only works in the context of pandas (not Python datetime), but it's a great solution in that context. – Attila the Fun Aug 03 '22 at 17:16
10

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
"""
Anton Protopopov
  • 30,354
  • 12
  • 88
  • 93
4

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>)
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49
DmitrySemenov
  • 9,204
  • 15
  • 76
  • 121
  • it returns the wrong time (wrong utc offset) if the result time has a different utc offset e.g., due to a DST transition. See [How do I get the UTC time of “midnight” for a given timezone?](http://stackoverflow.com/a/11236372/4279) – jfs Mar 28 '15 at 11:46
3

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)
Abraham Simpson
  • 163
  • 1
  • 7
3

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
Sandro Munda
  • 39,921
  • 24
  • 98
  • 123
3
>>> import datetime
>>> dt = datetime.datetime.now()
>>> datetime.datetime.date(dt)
datetime.date(2019, 4, 2)
markling
  • 1,232
  • 1
  • 15
  • 28
3

Here is yet another way which fits in one line but is not particularly elegant:

dt = datetime.datetime.fromordinal(datetime.date.today().toordinal())
Valentin B.
  • 602
  • 6
  • 18
1

There is a module datetime_truncate which handlers this for you. It just calls datetime.replace.

kyrre
  • 626
  • 2
  • 9
  • 24
1

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

Matias Thayer
  • 571
  • 3
  • 8
1

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

lionels
  • 782
  • 7
  • 12
1

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

Eugen_R
  • 21
  • 4
0

You can just use

datetime.date.today()

It's light and returns exactly what you want.

Vidal
  • 2,605
  • 2
  • 16
  • 32
Bordotti
  • 103
  • 1
  • 1
  • 8