3

I have a model that uses a function to return a default datetime:

class Company(models.Model):
    q1_results_date = models.DateField(
        verbose_name='Q1 financial results',
        default=quarter_results_date(1),
        blank=False,
        null=False,
    )

def quarter_results_date(month):
    return datetime.datetime(
        datetime.datetime.now().year,
        month,
        calendar.monthrange(datetime.datetime.now().year, month)[1]
    )

I want to unit test this, which requires me to set datetime.now() to a known value. To do this I am using freezegun.freeze_time:

def test_quarter_results_date(self):
    with freeze_time("2012-01-14"):
        print('check datetime.now()', datetime.now())
        c = Company.objects.create()
    ...

However, although the print statement shows 2012-01-14, the datetime is not frozen as it still uses today's date when evaluating c1.q1_results_date.

How can I correct this?

alias51
  • 8,178
  • 22
  • 94
  • 166
  • Does this answer https://stackoverflow.com/questions/4481954/trying-to-mock-datetime-date-today-but-not-working – JPG Jul 10 '20 at 15:36
  • This is because the value is evaluated when the class is initialized, this is long *before* the freezegun. In fact it is basically when you start the server. So that means that if you run the server long enough, it will not "update" if the year changes. – Willem Van Onsem Jul 10 '20 at 15:37

1 Answers1

2

The reason that this will not work is because you call the function. This thus means the datetime is evaluated when the class is interpreted, so this is basically when you start the server. At that moment the freezegun is not yet active.

This thus also means that if you later run the server for some long time, and the year gets incremented, it will still use the old value.

You can pass a callable to the default value, and thus use a helper function for example:

def quarter_results_date(month):
    yr = datetime.datetime.now().year
    __, dy = calendar.monthrange(yr, month)
    return datetime.datetime(
        yr,
        month,
        dy
    )

def quarter_results_date_first():
    return quarter_results_date(1)

class Company(models.Model):
    q1_results_date = models.DateField(
        verbose_name='Q1 financial results',
        default=quarter_results_date_first,
        blank=False,
        null=False,
    )

Note that no parenthesis are used for the default=quarter_results_date_first, so we pass a reference to the function, not a datetime value.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555