0

I am trying to get a datetime seven days prior to another date.

So I am doing in the console:

import datetime
from dateutil.relativedelta import relativedelta

dt = datetime.date(2014, 10, 18)
dt_minus_one_week = datetime.date(2014, 10, 18) - relativedelta(days=7)

The result is, as expected, datetime.date(2014, 10, 11). However, I am running a webservice (using eve, but I think that this is unimportant) application for a long time, and then when I invoke the method to get the one week older date, I get datetime.date(2014, 10, 10). The code is exactly the same as above.

If I restart the app, the date is what I expected it to be. Why is this happening? Is relativedelta nondeterministic? Is there any way to "reset" it so I can get the right value again?

ely
  • 74,674
  • 34
  • 147
  • 228
Bruno Saboia
  • 332
  • 3
  • 18
  • 2
    Can you reproduce the problem in a context that does not require waiting "a long time" and trying again in some web service? If not, we may not be able to help you as there is no way for us to know whatever else is going on in the web service. – ely Oct 19 '14 at 14:32
  • I have tried it, but I don't know how to do it. It seems that the only way to repro it is to have a long-time running app. – Bruno Saboia Oct 19 '14 at 14:45
  • 1
    Is datetime.date(2014, 10, 18) hard-coded in your app? Or are you using something like dt = datetime.date.today()? If so, and your app runs longer than 24 hours, then `dt` will no longer represent today... – unutbu Oct 19 '14 at 14:48
  • @unutbu I use datetime.datetime.now(pytz.timezone('US/Pacific')).date() – Bruno Saboia Oct 19 '14 at 15:11
  • 3
    So what happens in 24 hours? Is the value refreshed or will `dt` then be a day old? – unutbu Oct 19 '14 at 15:27
  • What untubu wanted to ask is - do you by any chance assign `datetime.datetime.now(pytz.timezone('US/Pacific')).date()` to a var and then reuse it for 24 hours? That would result in behavior you are seeing. – Pedja Oct 19 '14 at 15:30
  • 1
    why not use `timedelta`? `earlierdate = datetime.date.today() - datetime.timedelta(days=7)` – RattleyCooper Oct 19 '14 at 16:14
  • Let me be more clear. I have a method to get D-1: `def get_d_minus_one_pacific_local_date(): return datetime.datetime.now(pytz.timezone('US/Pacific')).date() - relativedelta(days=1)` Then I have the method to get the one week old date: `def get_relative_date(init=get_d_minus_one(), *args, **kwargs): return init + datetime.timedelta(*args, **kwargs)` I call it using `get_relative_date(days=-7)` and I got the wrong date calling this method, but I run it on the console and gives me the expected date – Bruno Saboia Oct 19 '14 at 16:45
  • 2
    Ok, your problem is python's calculation of default arguments: http://stackoverflow.com/a/530768/632706. To be more precise, when you set the default value of init in get_relative_date definition, it will not be recalculated again. – Pedja Oct 19 '14 at 18:39
  • @Pedja: I think your answer is correct. Please post it as an answer. – unutbu Oct 19 '14 at 18:54
  • @untubu, thanks, posted, though I guess this should probably be closed as duplicate now that the facts are all here. – Pedja Oct 19 '14 at 19:07
  • @Padja: It might be a duplicate of something, but I think the current convention is that questions are not dupes if the answer is the same, they are dupes only if the question is the same. Otherwise, it might be hard for others to find the answer if they are seaching for the question. – unutbu Oct 19 '14 at 19:11

3 Answers3

4

From the description of your functions in the comments, you have stepped on a common python "landmine".

def get_d_minus_one_pacific_local_date():
    return datetime.datetime.now(
            pytz.timezone('US/Pacific')).date() - relativedelta(days=1)

def get_relative_date(init=get_d_minus_one_pacific_local_date(), *args, **kwargs):
    return init + datetime.timedelta(*args, **kwargs)

# ...
get_relative_date(days=-7)

When you set the default value of init in get_relative_date definition, it will not be recalculated again. So when the next day comes, it will use the value obtained at the time of function definition.

See: https://stackoverflow.com/a/530768/632706

Community
  • 1
  • 1
Pedja
  • 186
  • 1
  • 8
  • Thanks, Pedja. That makes a lot of sense. I am waiting until tomorrow to test, but for now I will accept it as an answer. – Bruno Saboia Oct 20 '14 at 15:56
1

If you are only dealing with days, I would just use the datetime module.

import datetime

old_date = datetime.date(2014, 10, 18)

new_date = old_date - datetime.timedelta(days=7)

The output would be datetime.date(2014, 10, 11). I have used timedelta a bit and haven't had a problem with inaccurate dates.

RattleyCooper
  • 4,997
  • 5
  • 27
  • 43
1

Suppose the web server is set up in the US/Hawaii timezone and the current localtime is 11PM on 2014-10-17. Then

In [57]: datetime.datetime(2014, 10, 17, 23, 0, 0, tzinfo=pytz.timezone('US/Pacific')).date()
Out[57]: datetime.date(2014, 10, 17)

However, the current time in US/Pacific is

In [44]: now = datetime.datetime(2014, 10, 17, 23, 0, 0)

In [45]: hawaii = pytz.timezone('US/Hawaii')

In [46]: pacific = pytz.timezone('US/Pacific')

In [47]: pacific.normalize(hawaii.localize(now).astimezone(pacific)).date()
Out[47]: datetime.date(2014, 10, 18)

This would cause the symptom you are seeing.

In short, you almost never want to build a timezone-aware datetime by directly supplying it to tzinfo:

datetime.datetime.now(pytz.timezone('US/Pacific')).date()

If you are using pytz, use the pytz timezone's localize method:

tzone.localize(naive_date)

By the way,

datetime.datetime.now(pytz.timezone('US/Pacific')).date()

is always equivalent to

datetime.datetime.now().date()

or

datetime.date.today()

datetime.datetime.now(pytz.timezone('US/Pacific')) is the same as datetime.datetime.now() with the tzinfo set to pytz.timezone('US/Pacific'), but if you then call the date method, then the tzinfo does not matter, since all you get back is the year, month and date.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Thanks @unutbu. But let me disagree with your last paragraph. I am at -3 GMT. On 2014-10-20 00:00:00 of my time, `datetime.datetime.now(pytz.timezone('US/Pacific')).date()` will yield `datetime.date(2014, 10, 19)`. I really don't like this approach, but this system is not using UTC, which is actually the root cause of this mess. – Bruno Saboia Oct 19 '14 at 17:52
  • Are you saying if you run `datetime.datetime.now().date()` you get `datetime.date(2014, 10, 20)` and yet `datetime.datetime.now(pytz.timezone('US/Pacific')).date()` returns `datetime.date(2014, 10, 19)`? AFAICS, that not the way it should work. – unutbu Oct 19 '14 at 18:02