Code

Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#10587 closed (duplicate)

Store datetime as UTC and change how Django handles datetime objects

Reported by: gsong Owned by: nobody
Component: Database layer (models, ORM) Version: 1.0
Severity: Keywords:
Cc: django@… Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

Problem

The way Django handles datetime right now makes developing time zone aware applications unnecessarily hard.

I've been doing some research and some thinking about this issue, mainly from these two tickets:

How it is right now

As I understand it right now, this is how datetime works in Django:

  1. For anything that implements time.tzset(), Django sets the timezone for the entire runtime to settings.TIME_ZONE. This means on Windows the setting is ignored. Django on Windows will operate in whatever timezone is specified on the OS.
  1. EXCEPT in the case of PostgreSQL backend, where the db connection initializes with cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]). Since timestamp and time fields are created with time zone, this means PostgreSQL will return datetime values typecasted to settings.TIME_ZONE first.
  1. Django typecasts everything to naive Python datetime objects (no tzinfo attached).

Proposed solution

This blog post is a good overview of how Rails handles time zone support: http://mad.ly/2008/04/09/rails-21-time-zone-support-an-overview/

  1. PostgreSQL should not have time zone set, or have time zone set to 'UTC'.
  1. Django should store all datetime on DB backends as naive UTC datetime.
  1. settings.TIME_ZONE becomes a site-wide runtime typecast directive. This way settings.TIME_ZONE can be optional if someone doesn't care about time zones in their application. In that case it feels more natural to operate in the timezone of the server hosting Django. The default return from model.some_datetime_field could be:


from django.conf import settings
from django.db.backends.util import typecast_timestamp
from dateutil.tz import tzutc, tzstr, tzlocal

utc = tzutc()
server_tz = tzlocal()
settings_tz = None
if settings.TIME_ZONE:
    settings_tz = tzstr(settings.TIME_ZONE)

utc_dt = typecast_timestamp(db_value).replace(tzinfo=utc)
if settings_tz == utc:
    dt = utc_dt
elif settings_tz:
    # Cast it to the specified timezone
    dt = utc_dt.astimezone(settings_tz)
else:
    # Cast to server local time then make it naive
    dt = utc_dt.astimezone(server_tz).replace(tzinfo=None)
return dt
  1. On the way back into the database, Django can do something like:


from django.conf import settings
from dateutil.tz import tzutc, tzstr

utc = tzutc()
server_tz = tzlocal()
settings_tz = None
if settings.TIME_ZONE:
    settings_tz = tzstr(settings.TIME_ZONE)

dt = dt_value_from_model_save
if dt.tzinfo:
    pass # It's a smart date, do nothing.
elif settings_tz:
    dt = dt.replace(tzinfo=settings_tz)
else:
    # Default to server time zone
    dt = dt.replace(tzinfo=server_tz)
dt = dt.astimezone(utc).replace(tzinfo=None)
# Now save dt into the database
  1. I think the following convenience DateTimeField instance methods or attributes might be nice:
  • naive_utc: returns datetime.datetime object in UTC, no tzinfo
  • utc: returns datetime.datetime object in UTC, with tzinfo
  • posix_timestamp: returns the POSIX timestamp as float, such as is returned by time.time(), with the exception that microsecond precision should be retained
  • astimezone(tzinfo): might be convenient for those who operate in naive datetime mode most of the time, but have specific places where time zones are important

Benefits

  1. Django doesn't have to change the entire runtime with os.environ['TZ'] or time.tzset().
  1. Windows servers acts just like Unix servers.
  1. I don't think this will break standard application code, since settings.TIME_ZONE essentially functions the same way as before, except your datetime objects will have tzinfo attached.
  1. For those dealing with time zones, UTC underneath the hood!
  1. If you want UTC everything while working inside Django, settings.TIME_ZONE='UTC'.
  1. If you don't care about time zones, settings.TIME_ZONE=None.

Attachments (0)

Change History (3)

comment:1 Changed 5 years ago by jacob

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Resolution set to duplicate
  • Status changed from new to closed

Duplicate of #2626

comment:2 Changed 5 years ago by ramiro

See also #1480

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.