25

I have a python script that uses this call to get yesterday's date in YYYY-MM-DD format:

str(date.today() - timedelta(days=1)))

It works most of the time, but when the script ran this morning at 2013-03-11 0:35 CDT it returned "2013-03-09" instead of "2013-03-10".

Presumably daylight saving time (which started yesterday) is to blame. I guess the way timedelta(days=1) is implemented it subtracted 24 hours, and 24 hours before 2013-03-11 0:35 CDT was 2013-03-09 23:35 CST, which led to the result of "2013-03-09".

So what's a good DST-safe way to get yesterday's date in python?

UPDATE: After bukzor pointed out that my code should have worked properly, I went back to the script and determined it wasn't being used. It sets the default value, but a wrapper shell script was setting the date explicitly. So the bug is in the shell script, not the python script.

Ike Walker
  • 64,401
  • 14
  • 110
  • 109
  • 2
    Such a problem would only happen with a timezeone-aware datetime object, which `date.today()` is not. How exactly is the object instantiated? – bukzor Mar 11 '13 at 17:38
  • @bukzor you're right. It turns out a shell script was passing in the bad date, see my update above. – Ike Walker Mar 11 '13 at 18:09
  • This question should be closed; the OP asserts that his bug is elsewhere. – Robᵩ Nov 05 '13 at 16:04
  • @Robᵩ: people from google might find the question useful. Timezones are not trivial matter. For example, your answer (with ordinals) makes it clear that "yesterday" is needed and not "24 ago" that as the question shows can be a different day. `date.today() - timedelta(days=1)` is shorter but the intent is less clear. It is correct if you want a day before. It is incorrect if you want a time moment 24 hours ago. – jfs Dec 04 '13 at 22:35
  • related: [How can I subtract a day from a Python date?](https://stackoverflow.com/q/441147/4279) – jfs Jun 20 '17 at 21:54

3 Answers3

39
datetime.date.fromordinal(datetime.date.today().toordinal()-1)
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
10

I'm not able to reproduce your issue in python2.7 or python3.2:

>>> import datetime
>>> today = datetime.date(2013, 3, 11)
>>> print today
2013-03-11
>>> day = datetime.timedelta(days=1)
>>> print today - day
2013-03-10

It seems to me that this is already the simplest implementation of a "daylight-savings safe" yesterday() function.

bukzor
  • 37,539
  • 11
  • 77
  • 111
  • did you set you system time to 2013-03-11 0:35 before trying this? The problem is experienced for 1 hour after midnight when daylight savings time is applied - the day only had 23 hours. – tdelaney Mar 11 '13 at 17:52
  • @tdelaney: Setting the system time is a roundabout way to get the same results. in both cases you get a "naive" datetime.date object with this value. – bukzor Mar 11 '13 at 21:45
  • @tdelaney: the code works as is because it uses *naive* `date` objects. It correctly prints the local date but It tells us nothing about the actual time relative to UTC: [it might be ambiguous or even non-existing time](http://stackoverflow.com/a/29832960/4279) (e.g., there is timezone in Brazil that has DST transitions at midnight). In particular, the utc offsets may be different at `today` and `today - DAY`. You need the tz database to [find the corresponding UTC time (e.g., expressed as aware datetime objects)](http://stackoverflow.com/a/15345272/4279). – jfs Apr 23 '15 at 23:55
7

You'll get 2013-03-10 if you use naive datetime object that knows nothing about timezones (and DST in particular):

from datetime import datetime, timedelta

dt_naive = datetime(2013, 3, 11, 0, 35)
print((dt_naive - timedelta(days=1)).date()) # ignores DST
# -> 2013-03-10

2013-03-09 is correct if you are interested what date it was 24 hours ago.

import pytz # $ pip install pytz

local_tz = pytz.timezone("America/Chicago") # specify your local timezone
dt = local_tz.localize(dt_naive, is_dst=None) # raise if dt_naive is ambiguous
yesterday = local_tz.normalize(dt - timedelta(days=1)).date()
print(yesterday)
# -> 2013-03-09

Note: .date() strips timezone info so you'll get 2013-03-10 again:

print(dt.date() - timedelta(days=1))
# -> 2013-03-10

To get yesterday in particular timezone:

from datetime import datetime, time, timedelta
import pytz # $ pip install pytz

tz = pytz.timezone("America/Chicago")
yesterday = datetime.now(tz).date() - timedelta(days=1)

# to add timezone info back (to get yesterday's midnight)
midnight = tz.localize(datetime.combine(yesterday, time(0, 0)), is_dst=None)

Getting yesterday by stripping timezone info might fail if the timezone has missing days around that time. Then this method would produce non-existing date in the given timezone (tz.localize() raises an error).

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