Avoiding common pitfalls of datetime from a webapp's perspective

Pycon India - 2015

Indradhanush Gupta

Target Audience

  • Beginners in Python

  • Web developers

  • And anyone else!

Why this talk?

  • Datetime is heavily used

  • Nuances are not very obvious from the beginning

  • Share my own experiences

import datetime

Naive vs Aware

In [1]:
from datetime import datetime

naive = datetime.now()

print naive
2015-10-04 00:20:19.608365
In [2]:
import pytz

aware = datetime.now(pytz.utc)

print aware
2015-10-03 18:50:21.288492+00:00

Datetime storage in Postgres

Should I ever use a naive datetime object?

import pytz

Adding timezone info to naive datetime

In [3]:
print naive
2015-10-04 00:20:19.608365
In [4]:
ist = pytz.timezone('Asia/Kolkata')
In [5]:
aware = ist.localize(naive)

print aware
2015-10-04 00:20:19.608365+05:30

Converting datetime to another timezone

In [6]:
print aware.astimezone(pytz.utc)
2015-10-03 18:50:19.608365+00:00

Do not use datetime.datetime.replace

In [7]:
utc_now = datetime.now(pytz.utc)

print utc_now
2015-10-03 18:50:36.163853+00:00
In [8]:
stupid_date_in_ist = utc_now.replace(tzinfo=ist)

print stupid_date_in_ist
2015-10-03 18:50:36.163853+05:53
In [9]:
print stupid_date_in_ist.utcoffset()
5:53:00

Lets try datetime.datetime.astimezone

In [10]:
intelligent_date_in_ist = utc_now.astimezone(ist)

print intelligent_date_in_ist
2015-10-04 00:20:36.163853+05:30
In [11]:
print intelligent_date_in_ist.utcoffset()
5:30:00
In [12]:
old_date = datetime(year=1900, month=1, day=1, tzinfo=pytz.utc)

print old_date
1900-01-01 00:00:00+00:00
In [13]:
intelligent_date_in_ist = old_date.astimezone(ist)

print intelligent_date_in_ist
1900-01-01 05:53:00+05:53
In [14]:
print intelligent_date_in_ist.utcoffset()
5:53:00

import production

  • Examples from real life
  • 5:30 PM for the user != 5:30 PM in your DB

import freezegun

In [15]:
from freezegun import freeze_time


# As a context manager
with freeze_time('2010-1-1'):
    print datetime.now()
2010-01-01 00:00:00
In [16]:
# As a decorator
@freeze_time('2010-1-1')
def go_back_in_time():
    print datetime.now()
    
go_back_in_time()        
2010-01-01 00:00:00
In [17]:
@freeze_time('2010-1-1')
class A(object):
    def time(self):
        print datetime.now()
        
A().time()
2010-01-01 00:00:00
In [18]:
# A method that expires all keys older than 30 days.
def expire_keys_older_than_30_days():
    limit = datetime.now(pytz.utc) - timedelta(days=30)
    old_keys = AccessKey.objects.filter(created_at__lte=limit)
    old_keys.update(expired=True)
In [20]:
from django.test import TestCase


class TestDeleteOlderKeys(TestCase):

    def test_expire_keys_older_than_30_days(self):
        with freeze_time('2015-1-1 00:00:00+00:00'):
            AccessKey.objects.create()

        with freeze_time('2015-1-31 00:00:00+00:00'):
            AccessKey.objects.create()

        live_keys = AccessKey.objects.filter(expired=False)

        self.assertEqual(live_keys.count(), 2)

        with freeze_time('2015-3-1 00:00:00+00:00'):
            expire_keys_older_than_30_days()

        live_keys = AccessKey.objects.filter(expired=False)
        self.assertEqual(live_keys.count(), 1)

import misc

datetime.timedelta

In [21]:
from datetime import timedelta

date = datetime.now(pytz.utc)

print date
2015-10-03 18:53:09.315262+00:00
In [22]:
print date - timedelta(weeks=2)
2015-09-19 18:53:09.315262+00:00
In [23]:
print date - timedelta(days=2)
2015-10-01 18:53:09.315262+00:00

dateutil.relativedelta.relativedelta

In [24]:
print date
2015-10-03 18:53:09.315262+00:00
In [25]:
from dateutil.relativedelta import relativedelta

print date - relativedelta(years=2, months=6)
2013-04-03 18:53:09.315262+00:00

dateutil.parser.parse

In [26]:
from dateutil.parser import parse

print parse('2015-1-1')
2015-01-01 00:00:00
In [27]:
print parse('2015')
2015-10-04 00:00:00
In [28]:
print parse('2015-10-04 10:00:00+05:30')
2015-10-04 10:00:00+05:30
In [29]:
print parse('2015-10-04 10:00:00+00:30')
2015-10-04 10:00:00+00:30

import questions

Thank You!