Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added support for time zones. Thanks Luke Plant for the review. Fixed #…

…2626.

For more information on this project, see this thread:
http://groups.google.com/group/django-developers/browse_thread/thread/cf0423bbb85b1bbf



git-svn-id: http://code.djangoproject.com/svn/django/trunk@17106 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 9b1cb755a28f020e27d4268c214b25315d4de42e 1 parent 01f7034
@aaugustin aaugustin authored
Showing with 2,719 additions and 283 deletions.
  1. +7 −2 django/conf/global_settings.py
  2. +4 −1 django/conf/project_template/project_name/settings.py
  3. +3 −0  django/contrib/admin/util.py
  4. +5 −13 django/contrib/humanize/templatetags/humanize.py
  5. +42 −30 django/contrib/humanize/tests.py
  6. +2 −1  django/contrib/syndication/views.py
  7. +5 −0 django/core/context_processors.py
  8. +15 −10 django/core/serializers/json.py
  9. +3 −0  django/db/backends/__init__.py
  10. +22 −5 django/db/backends/mysql/base.py
  11. +36 −9 django/db/backends/oracle/base.py
  12. +11 −6 django/db/backends/postgresql_psycopg2/base.py
  13. +45 −11 django/db/backends/sqlite3/base.py
  14. +5 −1 django/db/backends/util.py
  15. +77 −91 django/db/models/fields/__init__.py
  16. +1 −1  django/db/utils.py
  17. +13 −5 django/forms/fields.py
  18. +31 −0 django/forms/util.py
  19. +2 −1  django/forms/widgets.py
  20. +6 −1 django/template/base.py
  21. +8 −4 django/template/context.py
  22. +2 −0  django/template/debug.py
  23. +2 −2 django/template/defaultfilters.py
  24. +191 −0 django/templatetags/tz.py
  25. +4 −1 django/utils/cache.py
  26. +10 −4 django/utils/dateformat.py
  27. +93 −0 django/utils/dateparse.py
  28. +3 −2 django/utils/feedgenerator.py
  29. +5 −11 django/utils/timesince.py
  30. +266 −0 django/utils/timezone.py
  31. +19 −0 django/utils/tzinfo.py
  32. +25 −0 docs/howto/custom-template-tags.txt
  33. +13 −0 docs/ref/models/querysets.txt
  34. +42 −12 docs/ref/settings.txt
  35. +39 −24 docs/ref/templates/builtins.txt
  36. +125 −0 docs/ref/utils.txt
  37. +52 −1 docs/releases/1.4.txt
  38. +3 −1 docs/topics/cache.txt
  39. +6 −4 docs/topics/i18n/index.txt
  40. +429 −0 docs/topics/i18n/timezones.txt
  41. +11 −11 tests/modeltests/fixtures/tests.py
  42. +3 −3 tests/modeltests/serializers/tests.py
  43. 0  tests/modeltests/timezones/__init__.py
  44. +15 −0 tests/modeltests/timezones/admin.py
  45. +17 −0 tests/modeltests/timezones/fixtures/users.xml
  46. +13 −0 tests/modeltests/timezones/forms.py
  47. +8 −0 tests/modeltests/timezones/models.py
  48. +871 −0 tests/modeltests/timezones/tests.py
  49. +10 −0 tests/modeltests/timezones/urls.py
  50. +35 −8 tests/modeltests/validation/test_error_messages.py
  51. +15 −3 tests/regressiontests/cache/tests.py
  52. +2 −2 tests/regressiontests/datatypes/tests.py
  53. +1 −1  tests/regressiontests/defaultfilters/tests.py
  54. +1 −1  tests/regressiontests/utils/dateformat.py
  55. +1 −0  tests/regressiontests/utils/tests.py
  56. +9 −0 tests/regressiontests/utils/timesince.py
  57. +18 −0 tests/regressiontests/utils/timezone.py
  58. +17 −0 tests/regressiontests/utils/tzinfo.py
View
9 django/conf/global_settings.py
@@ -31,9 +31,13 @@
# Local time zone for this installation. All choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
-# systems may support all possibilities).
+# systems may support all possibilities). When USE_TZ is True, this is
+# interpreted as the default user time zone.
TIME_ZONE = 'America/Chicago'
+# If you set this to True, Django will use timezone-aware datetimes.
+USE_TZ = False
+
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
@@ -119,7 +123,7 @@
LANGUAGE_COOKIE_NAME = 'django_language'
# If you set this to True, Django will format dates, numbers and calendars
-# according to user current locale
+# according to user current locale.
USE_L10N = False
# Not-necessarily-technical managers of the site. They get broken link
@@ -192,6 +196,7 @@
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
+ 'django.core.context_processors.tz',
# 'django.core.context_processors.request',
'django.contrib.messages.context_processors.messages',
)
View
5 django/conf/project_template/project_name/settings.py
@@ -40,9 +40,12 @@
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale
+# calendars according to the current locale.
USE_L10N = True
+# If you set this to False, Django will not use timezone-aware datetimes.
+USE_TZ = True
+
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
View
3  django/contrib/admin/util.py
@@ -7,6 +7,7 @@
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
+from django.utils import timezone
from django.utils.encoding import force_unicode, smart_unicode, smart_str
from django.utils.translation import ungettext
from django.core.urlresolvers import reverse
@@ -293,6 +294,8 @@ def display_for_field(value, field):
return _boolean_icon(value)
elif value is None:
return EMPTY_CHANGELIST_VALUE
+ elif isinstance(field, models.DateTimeField):
+ return formats.localize(timezone.aslocaltime(value))
elif isinstance(field, models.DateField) or isinstance(field, models.TimeField):
return formats.localize(value)
elif isinstance(field, models.DecimalField):
View
18 django/contrib/humanize/templatetags/humanize.py
@@ -7,7 +7,7 @@
from django.utils.encoding import force_unicode
from django.utils.formats import number_format
from django.utils.translation import pgettext, ungettext, ugettext as _
-from django.utils.tzinfo import LocalTimezone
+from django.utils.timezone import is_aware, utc
register = template.Library()
@@ -158,8 +158,8 @@ def naturalday(value, arg=None):
except ValueError:
# Date arguments out of range
return value
- today = datetime.now(tzinfo).replace(microsecond=0, second=0, minute=0, hour=0)
- delta = value - today.date()
+ today = datetime.now(tzinfo).date()
+ delta = value - today
if delta.days == 0:
return _(u'today')
elif delta.days == 1:
@@ -174,18 +174,10 @@ def naturaltime(value):
For date and time values shows how many seconds, minutes or hours ago
compared to current timestamp returns representing string.
"""
- try:
- value = datetime(value.year, value.month, value.day, value.hour, value.minute, value.second)
- except AttributeError:
- return value
- except ValueError:
+ if not isinstance(value, date): # datetime is a subclass of date
return value
- if getattr(value, 'tzinfo', None):
- now = datetime.now(LocalTimezone(value))
- else:
- now = datetime.now()
- now = now - timedelta(0, 0, now.microsecond)
+ now = datetime.now(utc if is_aware(value) else None)
if value < now:
delta = now - value
if delta.days != 0:
View
72 django/contrib/humanize/tests.py
@@ -1,11 +1,12 @@
from __future__ import with_statement
-from datetime import timedelta, date, datetime
+import datetime
from django.template import Template, Context, defaultfilters
from django.test import TestCase
from django.utils import translation, tzinfo
from django.utils.translation import ugettext as _
from django.utils.html import escape
+from django.utils.timezone import utc
class HumanizeTests(TestCase):
@@ -88,10 +89,10 @@ def test_apnumber(self):
self.humanize_tester(test_list, result_list, 'apnumber')
def test_naturalday(self):
- today = date.today()
- yesterday = today - timedelta(days=1)
- tomorrow = today + timedelta(days=1)
- someday = today - timedelta(days=10)
+ today = datetime.date.today()
+ yesterday = today - datetime.timedelta(days=1)
+ tomorrow = today + datetime.timedelta(days=1)
+ someday = today - datetime.timedelta(days=10)
notdate = u"I'm not a date value"
test_list = (today, yesterday, tomorrow, someday, notdate, None)
@@ -103,41 +104,46 @@ def test_naturalday(self):
def test_naturalday_tz(self):
from django.contrib.humanize.templatetags.humanize import naturalday
- today = date.today()
- tz_one = tzinfo.FixedOffset(timedelta(hours=-12))
- tz_two = tzinfo.FixedOffset(timedelta(hours=12))
+ today = datetime.date.today()
+ tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12))
+ tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12))
# Can be today or yesterday
- date_one = datetime(today.year, today.month, today.day, tzinfo=tz_one)
+ date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one)
naturalday_one = naturalday(date_one)
# Can be today or tomorrow
- date_two = datetime(today.year, today.month, today.day, tzinfo=tz_two)
+ date_two = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_two)
naturalday_two = naturalday(date_two)
# As 24h of difference they will never be the same
self.assertNotEqual(naturalday_one, naturalday_two)
def test_naturaltime(self):
+ class naive(datetime.tzinfo):
+ def utcoffset(self, dt):
+ return None
# we're going to mock datetime.datetime, so use a fixed datetime
- now = datetime(2011, 8, 15)
+ now = datetime.datetime(2011, 8, 15)
test_list = [
now,
- now - timedelta(seconds=1),
- now - timedelta(seconds=30),
- now - timedelta(minutes=1, seconds=30),
- now - timedelta(minutes=2),
- now - timedelta(hours=1, minutes=30, seconds=30),
- now - timedelta(hours=23, minutes=50, seconds=50),
- now - timedelta(days=1),
- now - timedelta(days=500),
- now + timedelta(seconds=1),
- now + timedelta(seconds=30),
- now + timedelta(minutes=1, seconds=30),
- now + timedelta(minutes=2),
- now + timedelta(hours=1, minutes=30, seconds=30),
- now + timedelta(hours=23, minutes=50, seconds=50),
- now + timedelta(days=1),
- now + timedelta(days=500),
+ now - datetime.timedelta(seconds=1),
+ now - datetime.timedelta(seconds=30),
+ now - datetime.timedelta(minutes=1, seconds=30),
+ now - datetime.timedelta(minutes=2),
+ now - datetime.timedelta(hours=1, minutes=30, seconds=30),
+ now - datetime.timedelta(hours=23, minutes=50, seconds=50),
+ now - datetime.timedelta(days=1),
+ now - datetime.timedelta(days=500),
+ now + datetime.timedelta(seconds=1),
+ now + datetime.timedelta(seconds=30),
+ now + datetime.timedelta(minutes=1, seconds=30),
+ now + datetime.timedelta(minutes=2),
+ now + datetime.timedelta(hours=1, minutes=30, seconds=30),
+ now + datetime.timedelta(hours=23, minutes=50, seconds=50),
+ now + datetime.timedelta(days=1),
+ now + datetime.timedelta(days=500),
+ now.replace(tzinfo=naive()),
+ now.replace(tzinfo=utc),
]
result_list = [
'now',
@@ -157,14 +163,20 @@ def test_naturaltime(self):
'23 hours from now',
'1 day from now',
'1 year, 4 months from now',
+ 'now',
+ 'now',
]
# mock out datetime so these tests don't fail occasionally when the
# test runs too slow
- class MockDateTime(datetime):
+ class MockDateTime(datetime.datetime):
@classmethod
- def now(self):
- return now
+ def now(self, tz=None):
+ if tz is None or tz.utcoffset(now) is None:
+ return now
+ else:
+ # equals now.replace(tzinfo=utc)
+ return now.replace(tzinfo=tz) + tz.utcoffset(now)
# naturaltime also calls timesince/timeuntil
from django.contrib.humanize.templatetags import humanize
View
3  django/contrib/syndication/views.py
@@ -6,6 +6,7 @@
from django.utils import feedgenerator, tzinfo
from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode
from django.utils.html import escape
+from django.utils.timezone import is_naive
def add_domain(domain, url, secure=False):
if not (url.startswith('http://')
@@ -164,7 +165,7 @@ def get_feed(self, obj, request):
author_email = author_link = None
pubdate = self.__get_dynamic_attr('item_pubdate', item)
- if pubdate and not pubdate.tzinfo:
+ if pubdate and is_naive(pubdate):
ltz = tzinfo.LocalTimezone(pubdate)
pubdate = pubdate.replace(tzinfo=ltz)
View
5 django/core/context_processors.py
@@ -48,6 +48,11 @@ def i18n(request):
return context_extras
+def tz(request):
+ from django.utils import timezone
+
+ return {'TIME_ZONE': timezone.get_current_timezone_name()}
+
def static(request):
"""
Adds static-related context variables to the context.
View
25 django/core/serializers/json.py
@@ -8,8 +8,8 @@
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
-from django.utils import datetime_safe
from django.utils import simplejson
+from django.utils.timezone import is_aware
class Serializer(PythonSerializer):
"""
@@ -39,19 +39,24 @@ class DjangoJSONEncoder(simplejson.JSONEncoder):
"""
JSONEncoder subclass that knows how to encode date/time and decimal types.
"""
-
- DATE_FORMAT = "%Y-%m-%d"
- TIME_FORMAT = "%H:%M:%S"
-
def default(self, o):
+ # See "Date Time String Format" in the ECMA-262 specification.
if isinstance(o, datetime.datetime):
- d = datetime_safe.new_datetime(o)
- return d.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
+ r = o.isoformat()
+ if o.microsecond:
+ r = r[:23] + r[26:]
+ if r.endswith('+00:00'):
+ r = r[:-6] + 'Z'
+ return r
elif isinstance(o, datetime.date):
- d = datetime_safe.new_date(o)
- return d.strftime(self.DATE_FORMAT)
+ return o.isoformat()
elif isinstance(o, datetime.time):
- return o.strftime(self.TIME_FORMAT)
+ if is_aware(o):
+ raise ValueError("JSON can't represent timezone-aware times.")
+ r = o.isoformat()
+ if o.microsecond:
+ r = r[:12]
+ return r
elif isinstance(o, decimal.Decimal):
return str(o)
else:
View
3  django/db/backends/__init__.py
@@ -10,6 +10,7 @@
from django.db.backends import util
from django.db.transaction import TransactionManagementError
from django.utils.importlib import import_module
+from django.utils.timezone import is_aware
class BaseDatabaseWrapper(local):
@@ -743,6 +744,8 @@ def value_to_db_time(self, value):
"""
if value is None:
return None
+ if is_aware(value):
+ raise ValueError("Django does not support timezone-aware times.")
return unicode(value)
def value_to_db_decimal(self, value, max_digits, decimal_places):
View
27 django/db/backends/mysql/base.py
@@ -33,6 +33,7 @@
from django.db.backends.mysql.introspection import DatabaseIntrospection
from django.db.backends.mysql.validation import DatabaseValidation
from django.utils.safestring import SafeString, SafeUnicode
+from django.utils.timezone import is_aware, is_naive, utc
# Raise exceptions for database warnings if DEBUG is on
from django.conf import settings
@@ -43,16 +44,29 @@
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
+# It's impossible to import datetime_or_None directly from MySQLdb.times
+datetime_or_None = conversions[FIELD_TYPE.DATETIME]
+
+def datetime_or_None_with_timezone_support(value):
+ dt = datetime_or_None(value)
+ # Confirm that dt is naive before overwriting its tzinfo.
+ if dt is not None and settings.USE_TZ and is_naive(dt):
+ dt = dt.replace(tzinfo=utc)
+ return dt
+
# MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
# timedelta in terms of actual behavior as they are signed and include days --
# and Django expects time, so we still need to override that. We also need to
# add special handling for SafeUnicode and SafeString as MySQLdb's type
# checking is too tight to catch those (see Django ticket #6052).
+# Finally, MySQLdb always returns naive datetime objects. However, when
+# timezone support is active, Django expects timezone-aware datetime objects.
django_conversions = conversions.copy()
django_conversions.update({
FIELD_TYPE.TIME: util.typecast_time,
FIELD_TYPE.DECIMAL: util.typecast_decimal,
FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
+ FIELD_TYPE.DATETIME: datetime_or_None_with_timezone_support,
})
# This should match the numerical portion of the version numbers (we can treat
@@ -238,8 +252,11 @@ def value_to_db_datetime(self, value):
return None
# MySQL doesn't support tz-aware datetimes
- if value.tzinfo is not None:
- raise ValueError("MySQL backend does not support timezone-aware datetimes.")
+ if is_aware(value):
+ if settings.USE_TZ:
+ value = value.astimezone(utc).replace(tzinfo=None)
+ else:
+ raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
# MySQL doesn't support microseconds
return unicode(value.replace(microsecond=0))
@@ -248,9 +265,9 @@ def value_to_db_time(self, value):
if value is None:
return None
- # MySQL doesn't support tz-aware datetimes
- if value.tzinfo is not None:
- raise ValueError("MySQL backend does not support timezone-aware datetimes.")
+ # MySQL doesn't support tz-aware times
+ if is_aware(value):
+ raise ValueError("MySQL backend does not support timezone-aware times.")
# MySQL doesn't support microseconds
return unicode(value.replace(microsecond=0))
View
45 django/db/backends/oracle/base.py
@@ -44,6 +44,7 @@ def _setup_environment(environ):
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e)
+from django.conf import settings
from django.db import utils
from django.db.backends import *
from django.db.backends.signals import connection_created
@@ -51,6 +52,7 @@ def _setup_environment(environ):
from django.db.backends.oracle.creation import DatabaseCreation
from django.db.backends.oracle.introspection import DatabaseIntrospection
from django.utils.encoding import smart_str, force_unicode
+from django.utils.timezone import is_aware, is_naive, utc
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
@@ -333,11 +335,17 @@ def tablespace_sql(self, tablespace, inline=False):
return "TABLESPACE %s" % self.quote_name(tablespace)
def value_to_db_datetime(self, value):
+ if value is None:
+ return None
+
# Oracle doesn't support tz-aware datetimes
- if getattr(value, 'tzinfo', None) is not None:
- raise ValueError("Oracle backend does not support timezone-aware datetimes.")
+ if is_aware(value):
+ if settings.USE_TZ:
+ value = value.astimezone(utc).replace(tzinfo=None)
+ else:
+ raise ValueError("Oracle backend does not support timezone-aware datetimes when USE_TZ is False.")
- return super(DatabaseOperations, self).value_to_db_datetime(value)
+ return unicode(value)
def value_to_db_time(self, value):
if value is None:
@@ -346,9 +354,9 @@ def value_to_db_time(self, value):
if isinstance(value, basestring):
return datetime.datetime.strptime(value, '%H:%M:%S')
- # Oracle doesn't support tz-aware datetimes
- if value.tzinfo is not None:
- raise ValueError("Oracle backend does not support timezone-aware datetimes.")
+ # Oracle doesn't support tz-aware times
+ if is_aware(value):
+ raise ValueError("Oracle backend does not support timezone-aware times.")
return datetime.datetime(1900, 1, 1, value.hour, value.minute,
value.second, value.microsecond)
@@ -472,9 +480,28 @@ def _cursor(self):
# Set oracle date to ansi date format. This only needs to execute
# once when we create a new connection. We also set the Territory
# to 'AMERICA' which forces Sunday to evaluate to a '1' in TO_CHAR().
- cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' "
- "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF' "
- "NLS_TERRITORY = 'AMERICA'")
+ cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
+ " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'"
+ " NLS_TERRITORY = 'AMERICA'"
+ + (" TIME_ZONE = 'UTC'" if settings.USE_TZ else ''))
+
+ def datetime_converter(dt):
+ # Confirm that dt is naive before overwriting its tzinfo.
+ if dt is not None and is_naive(dt):
+ dt = dt.replace(tzinfo=utc)
+ return dt
+
+ def output_type_handler(cursor, name, default_type,
+ size, precision, scale):
+ # datetimes are returned as TIMESTAMP, except the results
+ # of "dates" queries, which are returned as DATETIME.
+ if settings.USE_TZ and default_type in (Database.TIMESTAMP,
+ Database.DATETIME):
+ return cursor.var(default_type,
+ arraysize=cursor.arraysize,
+ outconverter=datetime_converter)
+
+ self.connection.outputtypehandler = output_type_handler
if 'operators' not in self.__dict__:
# Ticket #14149: Check whether our LIKE implementation will
View
17 django/db/backends/postgresql_psycopg2/base.py
@@ -13,8 +13,9 @@
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
from django.db.backends.postgresql_psycopg2.version import get_version
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
-from django.utils.safestring import SafeUnicode, SafeString
from django.utils.log import getLogger
+from django.utils.safestring import SafeUnicode, SafeString
+from django.utils.timezone import utc
try:
import psycopg2 as Database
@@ -32,6 +33,11 @@
logger = getLogger('django.db.backends')
+def utc_tzinfo_factory(offset):
+ if offset != 0:
+ raise AssertionError("database connection isn't set to UTC")
+ return utc
+
class CursorWrapper(object):
"""
A thin wrapper around psycopg2's normal cursor class so that we can catch
@@ -144,11 +150,9 @@ def _get_pg_version(self):
def _cursor(self):
new_connection = False
- set_tz = False
settings_dict = self.settings_dict
if self.connection is None:
new_connection = True
- set_tz = settings_dict.get('TIME_ZONE')
if settings_dict['NAME'] == '':
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured("You need to specify NAME in your Django settings file.")
@@ -171,10 +175,11 @@ def _cursor(self):
self.connection.set_isolation_level(self.isolation_level)
connection_created.send(sender=self.__class__, connection=self)
cursor = self.connection.cursor()
- cursor.tzinfo_factory = None
+ cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
if new_connection:
- if set_tz:
- cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']])
+ tz = 'UTC' if settings.USE_TZ else settings_dict.get('TIME_ZONE')
+ if tz:
+ cursor.execute("SET TIME ZONE %s", [tz])
self._get_pg_version()
return CursorWrapper(cursor)
View
56 django/db/backends/sqlite3/base.py
@@ -10,13 +10,16 @@
import re
import sys
+from django.conf import settings
from django.db import utils
from django.db.backends import *
from django.db.backends.signals import connection_created
from django.db.backends.sqlite3.client import DatabaseClient
from django.db.backends.sqlite3.creation import DatabaseCreation
from django.db.backends.sqlite3.introspection import DatabaseIntrospection
+from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.safestring import SafeString
+from django.utils.timezone import is_aware, is_naive, utc
try:
try:
@@ -31,22 +34,29 @@
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
+def parse_datetime_with_timezone_support(value):
+ dt = parse_datetime(value)
+ # Confirm that dt is naive before overwriting its tzinfo.
+ if dt is not None and settings.USE_TZ and is_naive(dt):
+ dt = dt.replace(tzinfo=utc)
+ return dt
+
Database.register_converter("bool", lambda s: str(s) == '1')
-Database.register_converter("time", util.typecast_time)
-Database.register_converter("date", util.typecast_date)
-Database.register_converter("datetime", util.typecast_timestamp)
-Database.register_converter("timestamp", util.typecast_timestamp)
-Database.register_converter("TIMESTAMP", util.typecast_timestamp)
+Database.register_converter("time", parse_time)
+Database.register_converter("date", parse_date)
+Database.register_converter("datetime", parse_datetime_with_timezone_support)
+Database.register_converter("timestamp", parse_datetime_with_timezone_support)
+Database.register_converter("TIMESTAMP", parse_datetime_with_timezone_support)
Database.register_converter("decimal", util.typecast_decimal)
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
-if Database.version_info >= (2,4,1):
+if Database.version_info >= (2, 4, 1):
# Starting in 2.4.1, the str type is not accepted anymore, therefore,
# we convert all str objects to Unicode
# As registering a adapter for a primitive type causes a small
# slow-down, this adapter is only registered for sqlite3 versions
# needing it.
- Database.register_adapter(str, lambda s:s.decode('utf-8'))
- Database.register_adapter(SafeString, lambda s:s.decode('utf-8'))
+ Database.register_adapter(str, lambda s: s.decode('utf-8'))
+ Database.register_adapter(SafeString, lambda s: s.decode('utf-8'))
class DatabaseFeatures(BaseDatabaseFeatures):
# SQLite cannot handle us only partially reading from a cursor's result set
@@ -56,6 +66,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
can_use_chunked_reads = False
test_db_allows_multiple_connections = False
supports_unspecified_pk = True
+ supports_timezones = False
supports_1000_query_parameters = False
supports_mixed_date_datetime_comparisons = False
has_bulk_insert = True
@@ -131,6 +142,29 @@ def sql_flush(self, style, tables, sequences):
# sql_flush() implementations). Just return SQL at this point
return sql
+ def value_to_db_datetime(self, value):
+ if value is None:
+ return None
+
+ # SQLite doesn't support tz-aware datetimes
+ if is_aware(value):
+ if settings.USE_TZ:
+ value = value.astimezone(utc).replace(tzinfo=None)
+ else:
+ raise ValueError("SQLite backend does not support timezone-aware datetimes when USE_TZ is False.")
+
+ return unicode(value)
+
+ def value_to_db_time(self, value):
+ if value is None:
+ return None
+
+ # SQLite doesn't support tz-aware datetimes
+ if is_aware(value):
+ raise ValueError("SQLite backend does not support timezone-aware times.")
+
+ return unicode(value)
+
def year_lookup_bounds(self, value):
first = '%s-01-01'
second = '%s-12-31 23:59:59.999999'
@@ -147,11 +181,11 @@ def convert_values(self, value, field):
elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField':
return int(value)
elif internal_type == 'DateField':
- return util.typecast_date(value)
+ return parse_date(value)
elif internal_type == 'DateTimeField':
- return util.typecast_timestamp(value)
+ return parse_datetime_with_timezone_support(value)
elif internal_type == 'TimeField':
- return util.typecast_time(value)
+ return parse_time(value)
# No field, or the field isn't known to be a decimal or integer
return value
View
6 django/db/backends/util.py
@@ -3,7 +3,9 @@
import hashlib
from time import time
+from django.conf import settings
from django.utils.log import getLogger
+from django.utils.timezone import utc
logger = getLogger('django.db.backends')
@@ -99,8 +101,10 @@ def typecast_timestamp(s): # does NOT store time zone information
seconds, microseconds = seconds.split('.')
else:
microseconds = '0'
+ tzinfo = utc if settings.USE_TZ else None
return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]),
- int(times[0]), int(times[1]), int(seconds), int((microseconds + '000000')[:6]))
+ int(times[0]), int(times[1]), int(seconds),
+ int((microseconds + '000000')[:6]), tzinfo)
def typecast_decimal(s):
if s is None or s == '':
View
168 django/db/models/fields/__init__.py
@@ -1,8 +1,6 @@
import copy
import datetime
import decimal
-import re
-import time
import math
from itertools import tee
@@ -12,8 +10,10 @@
from django import forms
from django.core import exceptions, validators
from django.utils.datastructures import DictWrapper
+from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import curry
from django.utils.text import capfirst
+from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode, force_unicode, smart_str
from django.utils.ipv6 import clean_ipv6_address
@@ -180,8 +180,8 @@ def validate(self, value, model_instance):
return
elif value == option_key:
return
- raise exceptions.ValidationError(
- self.error_messages['invalid_choice'] % value)
+ msg = self.error_messages['invalid_choice'] % value
+ raise exceptions.ValidationError(msg)
if value is None and not self.null:
raise exceptions.ValidationError(self.error_messages['null'])
@@ -638,11 +638,7 @@ def formfield(self, **kwargs):
defaults.update(kwargs)
return super(CommaSeparatedIntegerField, self).formfield(**defaults)
-ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
-
class DateField(Field):
- description = _("Date (without time)")
-
empty_strings_allowed = False
default_error_messages = {
'invalid': _(u"'%s' value has an invalid date format. It must be "
@@ -650,11 +646,11 @@ class DateField(Field):
'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) "
u"but it is an invalid date."),
}
+ description = _("Date (without time)")
+
def __init__(self, verbose_name=None, name=None, auto_now=False,
auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add
- # HACKs : auto_now_add/auto_now should be done as a default or a
- # pre_save.
if auto_now or auto_now_add:
kwargs['editable'] = False
kwargs['blank'] = True
@@ -671,20 +667,19 @@ def to_python(self, value):
if isinstance(value, datetime.date):
return value
- if not ansi_date_re.search(value):
- msg = self.error_messages['invalid'] % str(value)
- raise exceptions.ValidationError(msg)
- # Now that we have the date string in YYYY-MM-DD format, check to make
- # sure it's a valid date.
- # We could use time.strptime here and catch errors, but datetime.date
- # produces much friendlier error messages.
- year, month, day = map(int, value.split('-'))
+ value = smart_str(value)
+
try:
- return datetime.date(year, month, day)
- except ValueError, e:
- msg = self.error_messages['invalid_date'] % str(value)
+ parsed = parse_date(value)
+ if parsed is not None:
+ return parsed
+ except ValueError:
+ msg = self.error_messages['invalid_date'] % value
raise exceptions.ValidationError(msg)
+ msg = self.error_messages['invalid'] % value
+ raise exceptions.ValidationError(msg)
+
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.date.today()
@@ -721,11 +716,7 @@ def get_db_prep_value(self, value, connection, prepared=False):
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
- if val is None:
- data = ''
- else:
- data = str(val)
- return data
+ return '' if val is None else val.isoformat()
def formfield(self, **kwargs):
defaults = {'form_class': forms.DateField}
@@ -733,13 +724,20 @@ def formfield(self, **kwargs):
return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField):
+ empty_strings_allowed = False
default_error_messages = {
- 'invalid': _(u"'%s' value either has an invalid valid format (The "
- u"format must be YYYY-MM-DD HH:MM[:ss[.uuuuuu]]) or is "
- u"an invalid date/time."),
+ 'invalid': _(u"'%s' value has an invalid format. It must be in "
+ u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."),
+ 'invalid_date': _(u"'%s' value has the correct format "
+ u"(YYYY-MM-DD) but it is an invalid date."),
+ 'invalid_datetime': _(u"'%s' value has the correct format "
+ u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
+ u"but it is an invalid date/time."),
}
description = _("Date (with time)")
+ # __init__ is inherited from DateField
+
def get_internal_type(self):
return "DateTimeField"
@@ -751,59 +749,59 @@ def to_python(self, value):
if isinstance(value, datetime.date):
return datetime.datetime(value.year, value.month, value.day)
- # Attempt to parse a datetime:
value = smart_str(value)
- # split usecs, because they are not recognized by strptime.
- if '.' in value:
- try:
- value, usecs = value.split('.')
- usecs = int(usecs)
- except ValueError:
- raise exceptions.ValidationError(
- self.error_messages['invalid'] % str(value))
- else:
- usecs = 0
- kwargs = {'microsecond': usecs}
- try: # Seconds are optional, so try converting seconds first.
- return datetime.datetime(
- *time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6], **kwargs)
+ try:
+ parsed = parse_datetime(value)
+ if parsed is not None:
+ return parsed
+ except ValueError:
+ msg = self.error_messages['invalid_datetime'] % value
+ raise exceptions.ValidationError(msg)
+
+ try:
+ parsed = parse_date(value)
+ if parsed is not None:
+ return datetime.datetime(parsed.year, parsed.month, parsed.day)
except ValueError:
- try: # Try without seconds.
- return datetime.datetime(
- *time.strptime(value, '%Y-%m-%d %H:%M')[:5], **kwargs)
- except ValueError: # Try without hour/minutes/seconds.
- try:
- return datetime.datetime(
- *time.strptime(value, '%Y-%m-%d')[:3], **kwargs)
- except ValueError:
- raise exceptions.ValidationError(
- self.error_messages['invalid'] % str(value))
+ msg = self.error_messages['invalid_date'] % value
+ raise exceptions.ValidationError(msg)
+
+ msg = self.error_messages['invalid'] % value
+ raise exceptions.ValidationError(msg)
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
- value = datetime.datetime.now()
+ value = timezone.now()
setattr(model_instance, self.attname, value)
return value
else:
return super(DateTimeField, self).pre_save(model_instance, add)
+ # contribute_to_class is inherited from DateField, it registers
+ # get_next_by_FOO and get_prev_by_FOO
+
+ # get_prep_lookup is inherited from DateField
+
def get_prep_value(self, value):
- return self.to_python(value)
+ value = self.to_python(value)
+ if settings.USE_TZ and timezone.is_naive(value):
+ # For backwards compatibility, interpret naive datetimes in local
+ # time. This won't work during DST change, but we can't do much
+ # about it, so we let the exceptions percolate up the call stack.
+ default_timezone = timezone.get_default_timezone()
+ value = timezone.make_aware(value, default_timezone)
+ return value
def get_db_prep_value(self, value, connection, prepared=False):
- # Casts dates into the format expected by the backend
+ # Casts datetimes into the format expected by the backend
if not prepared:
value = self.get_prep_value(value)
return connection.ops.value_to_db_datetime(value)
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
- if val is None:
- data = ''
- else:
- data = str(val.replace(microsecond=0, tzinfo=None))
- return data
+ return '' if val is None else val.isoformat()
def formfield(self, **kwargs):
defaults = {'form_class': forms.DateTimeField}
@@ -1158,17 +1156,21 @@ def formfield(self, **kwargs):
return super(TextField, self).formfield(**defaults)
class TimeField(Field):
- description = _("Time")
-
empty_strings_allowed = False
default_error_messages = {
- 'invalid': _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'),
+ 'invalid': _(u"'%s' value has an invalid format. It must be in "
+ u"HH:MM[:ss[.uuuuuu]] format."),
+ 'invalid_time': _(u"'%s' value has the correct format "
+ u"(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."),
}
+ description = _("Time")
+
def __init__(self, verbose_name=None, name=None, auto_now=False,
auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add:
kwargs['editable'] = False
+ kwargs['blank'] = True
Field.__init__(self, verbose_name, name, **kwargs)
def get_internal_type(self):
@@ -1185,30 +1187,18 @@ def to_python(self, value):
# database backend (e.g. Oracle), so we'll be accommodating.
return value.time()
- # Attempt to parse a datetime:
value = smart_str(value)
- # split usecs, because they are not recognized by strptime.
- if '.' in value:
- try:
- value, usecs = value.split('.')
- usecs = int(usecs)
- except ValueError:
- raise exceptions.ValidationError(
- self.error_messages['invalid'])
- else:
- usecs = 0
- kwargs = {'microsecond': usecs}
- try: # Seconds are optional, so try converting seconds first.
- return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6],
- **kwargs)
+ try:
+ parsed = parse_time(value)
+ if parsed is not None:
+ return parsed
except ValueError:
- try: # Try without seconds.
- return datetime.time(*time.strptime(value, '%H:%M')[3:5],
- **kwargs)
- except ValueError:
- raise exceptions.ValidationError(
- self.error_messages['invalid'])
+ msg = self.error_messages['invalid_time'] % value
+ raise exceptions.ValidationError(msg)
+
+ msg = self.error_messages['invalid'] % value
+ raise exceptions.ValidationError(msg)
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
@@ -1229,11 +1219,7 @@ def get_db_prep_value(self, value, connection, prepared=False):
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
- if val is None:
- data = ''
- else:
- data = str(val.replace(microsecond=0))
- return data
+ return '' if val is None else val.isoformat()
def formfield(self, **kwargs):
defaults = {'form_class': forms.TimeField}
View
2  django/db/utils.py
@@ -66,7 +66,7 @@ def ensure_defaults(self, alias):
if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']:
conn['ENGINE'] = 'django.db.backends.dummy'
conn.setdefault('OPTIONS', {})
- conn.setdefault('TIME_ZONE', settings.TIME_ZONE)
+ conn.setdefault('TIME_ZONE', 'UTC' if settings.USE_TZ else settings.TIME_ZONE)
for setting in ['NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']:
conn.setdefault(setting, '')
for setting in ['TEST_CHARSET', 'TEST_COLLATION', 'TEST_NAME', 'TEST_MIRROR']:
View
18 django/forms/fields.py
@@ -17,7 +17,7 @@
from django.core import validators
from django.core.exceptions import ValidationError
-from django.forms.util import ErrorList
+from django.forms.util import ErrorList, from_current_timezone, to_current_timezone
from django.forms.widgets import (TextInput, PasswordInput, HiddenInput,
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
@@ -409,6 +409,11 @@ class DateTimeField(BaseTemporalField):
'invalid': _(u'Enter a valid date/time.'),
}
+ def prepare_value(self, value):
+ if isinstance(value, datetime.datetime):
+ value = to_current_timezone(value)
+ return value
+
def to_python(self, value):
"""
Validates that the input can be converted to a datetime. Returns a
@@ -417,9 +422,10 @@ def to_python(self, value):
if value in validators.EMPTY_VALUES:
return None
if isinstance(value, datetime.datetime):
- return value
+ return from_current_timezone(value)
if isinstance(value, datetime.date):
- return datetime.datetime(value.year, value.month, value.day)
+ result = datetime.datetime(value.year, value.month, value.day)
+ return from_current_timezone(result)
if isinstance(value, list):
# Input comes from a SplitDateTimeWidget, for example. So, it's two
# components: date and time.
@@ -428,7 +434,8 @@ def to_python(self, value):
if value[0] in validators.EMPTY_VALUES and value[1] in validators.EMPTY_VALUES:
return None
value = '%s %s' % tuple(value)
- return super(DateTimeField, self).to_python(value)
+ result = super(DateTimeField, self).to_python(value)
+ return from_current_timezone(result)
def strptime(self, value, format):
return datetime.datetime.strptime(value, format)
@@ -979,7 +986,8 @@ def compress(self, data_list):
raise ValidationError(self.error_messages['invalid_date'])
if data_list[1] in validators.EMPTY_VALUES:
raise ValidationError(self.error_messages['invalid_time'])
- return datetime.datetime.combine(*data_list)
+ result = datetime.datetime.combine(*data_list)
+ return from_current_timezone(result)
return None
View
31 django/forms/util.py
@@ -1,6 +1,9 @@
+from django.conf import settings
from django.utils.html import conditional_escape
from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.safestring import mark_safe
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
# Import ValidationError so that it can be imported from this
# module to maintain backwards compatibility.
@@ -52,3 +55,31 @@ def as_text(self):
def __repr__(self):
return repr([force_unicode(e) for e in self])
+# Utilities for time zone support in DateTimeField et al.
+
+def from_current_timezone(value):
+ """
+ When time zone support is enabled, convert naive datetimes
+ entered in the current time zone to aware datetimes.
+ """
+ if settings.USE_TZ and value is not None and timezone.is_naive(value):
+ current_timezone = timezone.get_current_timezone()
+ try:
+ return timezone.make_aware(value, current_timezone)
+ except Exception, e:
+ raise ValidationError(_('%(datetime)s couldn\'t be interpreted '
+ 'in time zone %(current_timezone)s; it '
+ 'may be ambiguous or it may not exist.')
+ % {'datetime': value,
+ 'current_timezone': current_timezone})
+ return value
+
+def to_current_timezone(value):
+ """
+ When time zone support is enabled, convert aware datetimes
+ to naive dateimes in the current time zone for display.
+ """
+ if settings.USE_TZ and value is not None and timezone.is_aware(value):
+ current_timezone = timezone.get_current_timezone()
+ return timezone.make_naive(value, current_timezone)
+ return value
View
3  django/forms/widgets.py
@@ -10,7 +10,7 @@
from urlparse import urljoin
from django.conf import settings
-from django.forms.util import flatatt
+from django.forms.util import flatatt, to_current_timezone
from django.utils.datastructures import MultiValueDict, MergeDict
from django.utils.html import escape, conditional_escape
from django.utils.translation import ugettext, ugettext_lazy
@@ -847,6 +847,7 @@ def __init__(self, attrs=None, date_format=None, time_format=None):
def decompress(self, value):
if value:
+ value = to_current_timezone(value)
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
View
7 django/template/base.py
@@ -18,6 +18,7 @@
from django.utils.formats import localize
from django.utils.html import escape
from django.utils.module_loading import module_has_submodule
+from django.utils.timezone import aslocaltime
TOKEN_TEXT = 0
@@ -593,6 +594,8 @@ def resolve(self, context, ignore_failures=False):
arg_vals.append(mark_safe(arg))
else:
arg_vals.append(arg.resolve(context))
+ if getattr(func, 'expects_localtime', False):
+ obj = aslocaltime(obj, context.use_tz)
if getattr(func, 'needs_autoescape', False):
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
else:
@@ -853,6 +856,7 @@ def _render_value_in_context(value, context):
means escaping, if required, and conversion to a unicode object. If value
is a string, it is expected to have already been translated.
"""
+ value = aslocaltime(value, use_tz=context.use_tz)
value = localize(value, use_l10n=context.use_l10n)
value = force_unicode(value)
if ((context.autoescape and not isinstance(value, SafeData)) or
@@ -1077,7 +1081,7 @@ def dec(func):
elif name is not None and filter_func is not None:
# register.filter('somename', somefunc)
self.filters[name] = filter_func
- for attr in ('is_safe', 'needs_autoescape'):
+ for attr in ('expects_localtime', 'is_safe', 'needs_autoescape'):
if attr in flags:
value = flags[attr]
# set the flag on the filter for FilterExpression.resolve
@@ -1189,6 +1193,7 @@ def render(self, context):
'autoescape': context.autoescape,
'current_app': context.current_app,
'use_l10n': context.use_l10n,
+ 'use_tz': context.use_tz,
})
# Copy across the CSRF token, if present, because
# inclusion tags are often used for forms, and we need
View
12 django/template/context.py
@@ -83,10 +83,12 @@ def new(self, values=None):
class Context(BaseContext):
"A stack container for variable context"
- def __init__(self, dict_=None, autoescape=True, current_app=None, use_l10n=None):
+ def __init__(self, dict_=None, autoescape=True, current_app=None,
+ use_l10n=None, use_tz=None):
self.autoescape = autoescape
- self.use_l10n = use_l10n
self.current_app = current_app
+ self.use_l10n = use_l10n
+ self.use_tz = use_tz
self.render_context = RenderContext()
super(Context, self).__init__(dict_)
@@ -162,8 +164,10 @@ class RequestContext(Context):
Additional processors can be specified as a list of callables
using the "processors" keyword argument.
"""
- def __init__(self, request, dict=None, processors=None, current_app=None, use_l10n=None):
- Context.__init__(self, dict, current_app=current_app, use_l10n=use_l10n)
+ def __init__(self, request, dict_=None, processors=None, current_app=None,
+ use_l10n=None, use_tz=None):
+ Context.__init__(self, dict_, current_app=current_app,
+ use_l10n=use_l10n, use_tz=use_tz)
if processors is None:
processors = ()
else:
View
2  django/template/debug.py
@@ -3,6 +3,7 @@
from django.utils.html import escape
from django.utils.safestring import SafeData, EscapeData
from django.utils.formats import localize
+from django.utils.timezone import aslocaltime
class DebugLexer(Lexer):
@@ -81,6 +82,7 @@ class DebugVariableNode(VariableNode):
def render(self, context):
try:
output = self.filter_expression.resolve(context)
+ output = aslocaltime(output, use_tz=context.use_tz)
output = localize(output, use_l10n=context.use_l10n)
output = force_unicode(output)
except UnicodeDecodeError:
View
4 django/template/defaultfilters.py
@@ -692,7 +692,7 @@ def get_digit(value, arg):
# DATES #
###################
-@register.filter(is_safe=False)
+@register.filter(expects_localtime=True, is_safe=False)
def date(value, arg=None):
"""Formats a date according to the given format."""
if not value:
@@ -707,7 +707,7 @@ def date(value, arg=None):
except AttributeError:
return ''
-@register.filter(is_safe=False)
+@register.filter(expects_localtime=True, is_safe=False)
def time(value, arg=None):
"""Formats a time according to the given format."""
if value in (None, u''):
View
191 django/templatetags/tz.py
@@ -0,0 +1,191 @@
+from __future__ import with_statement
+
+from datetime import datetime, tzinfo
+
+try:
+ import pytz
+except ImportError:
+ pytz = None
+
+from django.template import Node
+from django.template import TemplateSyntaxError, Library
+from django.utils import timezone
+
+register = Library()
+
+# HACK: datetime is an old-style class, create a new-style equivalent
+# so we can define additional attributes.
+class datetimeobject(datetime, object):
+ pass
+
+
+# Template filters
+
+@register.filter
+def aslocaltime(value):
+ """
+ Converts a datetime to local time in the active time zone.
+
+ This only makes sense within a {% localtime off %} block.
+ """
+ return astimezone(value, timezone.get_current_timezone())
+
+@register.filter
+def asutc(value):
+ """
+ Converts a datetime to UTC.
+ """
+ return astimezone(value, timezone.utc)
+
+@register.filter
+def astimezone(value, arg):
+ """
+ Converts a datetime to local time in a given time zone.
+
+ The argument must be an instance of a tzinfo subclass or a time zone name.
+ If it is a time zone name, pytz is required.
+
+ Naive datetimes are assumed to be in local time in the default time zone.
+ """
+ if not isinstance(value, datetime):
+ return ''
+
+ # Obtain a timezone-aware datetime
+ try:
+ if timezone.is_naive(value):
+ default_timezone = timezone.get_default_timezone()
+ value = timezone.make_aware(value, default_timezone)
+ # Filters must never raise exceptions, and pytz' exceptions inherit
+ # Exception directly, not a specific subclass. So catch everything.
+ except Exception:
+ return ''
+
+ # Obtain a tzinfo instance
+ if isinstance(arg, tzinfo):
+ tz = arg
+ elif isinstance(arg, basestring) and pytz is not None:
+ try:
+ tz = pytz.timezone(arg)
+ except pytz.UnknownTimeZoneError:
+ return ''
+ else:
+ return ''
+
+ # Convert and prevent further conversion
+ result = value.astimezone(tz)
+ if hasattr(tz, 'normalize'):
+ # available for pytz time zones
+ result = tz.normalize(result)
+
+ # HACK: the convert_to_local_time flag will prevent
+ # automatic conversion of the value to local time.
+ result = datetimeobject(result.year, result.month, result.day,
+ result.hour, result.minute, result.second,
+ result.microsecond, result.tzinfo)
+ result.convert_to_local_time = False
+ return result
+
+
+# Template tags
+
+class LocalTimeNode(Node):
+ """
+ Template node class used by ``localtime_tag``.
+ """
+ def __init__(self, nodelist, use_tz):
+ self.nodelist = nodelist
+ self.use_tz = use_tz
+
+ def render(self, context):
+ old_setting = context.use_tz
+ context.use_tz = self.use_tz
+ output = self.nodelist.render(context)
+ context.use_tz = old_setting
+ return output
+
+class TimezoneNode(Node):
+ """
+ Template node class used by ``timezone_tag``.
+ """
+ def __init__(self, nodelist, tz):
+ self.nodelist = nodelist
+ self.tz = tz
+
+ def render(self, context):
+ with timezone.override(self.tz.resolve(context)):
+ output = self.nodelist.render(context)
+ return output
+
+class GetCurrentTimezoneNode(Node):
+ """
+ Template node class used by ``get_current_timezone_tag``.
+ """
+ def __init__(self, variable):
+ self.variable = variable
+
+ def render(self, context):
+ context[self.variable] = timezone.get_current_timezone_name()
+ return ''
+
+@register.tag('localtime')
+def localtime_tag(parser, token):
+ """
+ Forces or prevents conversion of datetime objects to local time,
+ regardless of the value of ``settings.USE_TZ``.
+
+ Sample usage::
+
+ {% localtime off %}{{ value_in_utc }}{% endlocaltime %}
+
+ """
+ bits = token.split_contents()
+ if len(bits) == 1:
+ use_tz = True
+ elif len(bits) > 2 or bits[1] not in ('on', 'off'):
+ raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % bits[0])
+ else:
+ use_tz = bits[1] == 'on'
+ nodelist = parser.parse(('endlocaltime',))
+ parser.delete_first_token()
+ return LocalTimeNode(nodelist, use_tz)
+
+@register.tag('timezone')
+def timezone_tag(parser, token):
+ """
+ Enables a given time zone just for this block.
+
+ The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
+ time zone name, or ``None``. If is it a time zone name, pytz is required.
+ If it is ``None``, the default time zone is used within the block.
+
+ Sample usage::
+
+ {% timezone "Europe/Paris" %}
+ It is {{ now }} in Paris.
+ {% endtimezone %}
+
+ """
+ bits = token.split_contents()
+ if len(bits) != 2:
+ raise TemplateSyntaxError("'%s' takes one argument (timezone)" % bits[0])
+ tz = parser.compile_filter(bits[1])
+ nodelist = parser.parse(('endtimezone',))
+ parser.delete_first_token()
+ return TimezoneNode(nodelist, tz)
+
+@register.tag("get_current_timezone")
+def get_current_timezone_tag(parser, token):
+ """
+ Stores the name of the current time zone in the context.
+
+ Usage::
+
+ {% get_current_timezone as TIME_ZONE %}
+
+ This will fetch the currently active time zone and put its name
+ into the ``TIME_ZONE`` context variable.
+ """
+ args = token.contents.split()
+ if len(args) != 3 or args[1] != 'as':
+ raise TemplateSyntaxError("'get_current_timezone' requires 'as variable' (got %r)" % args)
+ return GetCurrentTimezoneNode(args[2])
View
5 django/utils/cache.py
@@ -25,6 +25,7 @@
from django.core.cache import get_cache
from django.utils.encoding import smart_str, iri_to_uri
from django.utils.http import http_date
+from django.utils.timezone import get_current_timezone_name
from django.utils.translation import get_language
cc_delim_re = re.compile(r'\s*,\s*')
@@ -157,12 +158,14 @@ def has_vary_header(response, header_query):
return header_query.lower() in existing_headers
def _i18n_cache_key_suffix(request, cache_key):
- """If enabled, returns the cache key ending with a locale."""
+ """If necessary, adds the current locale or time zone to the cache key."""
if settings.USE_I18N or settings.USE_L10N:
# first check if LocaleMiddleware or another middleware added
# LANGUAGE_CODE to request, then fall back to the active language
# which in turn can also fall back to settings.LANGUAGE_CODE
cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
+ if settings.USE_TZ:
+ cache_key += '.%s' % get_current_timezone_name()
return cache_key
def _generate_cache_key(request, method, headerlist, key_prefix):
View
14 django/utils/dateformat.py
@@ -14,10 +14,13 @@
import re
import time
import calendar
+import datetime
+
from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR
from django.utils.tzinfo import LocalTimezone
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
+from django.utils.timezone import is_aware, is_naive
re_formatchars = re.compile(r'(?<!\\)([aAbBcdDEfFgGhHiIjlLmMnNOPrsStTUuwWyYzZ])')
re_escaped = re.compile(r'\\(.)')
@@ -115,9 +118,12 @@ class DateFormat(TimeFormat):
def __init__(self, dt):
# Accepts either a datetime or date object.
self.data = dt
- self.timezone = getattr(dt, 'tzinfo', None)
- if hasattr(self.data, 'hour') and not self.timezone:
- self.timezone = LocalTimezone(dt)
+ self.timezone = None
+ if isinstance(dt, datetime.datetime):
+ if is_naive(dt):
+ self.timezone = LocalTimezone(dt)
+ else:
+ self.timezone = dt.tzinfo
def b(self):
"Month, textual, 3 letters, lowercase; e.g. 'jan'"
@@ -218,7 +224,7 @@ def T(self):
def U(self):
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
- if getattr(self.data, 'tzinfo', None):
+ if isinstance(self.data, datetime.datetime) and is_aware(self.data):
return int(calendar.timegm(self.data.utctimetuple()))
else:
return int(time.mktime(self.data.timetuple()))
View
93 django/utils/dateparse.py
@@ -0,0 +1,93 @@
+"""Functions to parse datetime objects."""
+
+# We're using regular expressions rather than time.strptime because:
+# - they provide both validation and parsing,
+# - they're more flexible for datetimes,
+# - the date/datetime/time constructors produce friendlier error messages.
+
+
+import datetime
+import re
+
+from django.utils.timezone import utc
+from django.utils.tzinfo import FixedOffset
+
+
+date_re = re.compile(
+ r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
+)
+
+
+datetime_re = re.compile(
+ r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
+ r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+ r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
+ r'(?P<tzinfo>Z|[+-]\d{1,2}:\d{1,2})?$'
+)
+
+
+time_re = re.compile(
+ r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+ r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
+)
+
+
+def parse_date(value):
+ """Parse a string and return a datetime.date.
+
+ Raise ValueError if the input is well formatted but not a valid date.
+ Return None if the input isn't well formatted.
+ """
+ match = date_re.match(value)
+ if match:
+ kw = dict((k, int(v)) for k, v in match.groupdict().iteritems())
+ return datetime.date(**kw)
+
+
+def parse_time(value):
+ """Parse a string and return a datetime.time.
+
+ This function doesn't support time zone offsets.
+
+ Sub-microsecond precision is accepted, but ignored.
+
+ Raise ValueError if the input is well formatted but not a valid time.
+ Return None if the input isn't well formatted, in particular if it
+ contains an offset.
+ """
+ match = time_re.match(value)
+ if match:
+ kw = match.groupdict()
+ if kw['microsecond']:
+ kw['microsecond'] = kw['microsecond'].ljust(6, '0')
+ kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None)
+ return datetime.time(**kw)
+
+
+def parse_datetime(value):
+ """Parse a string and return a datetime.datetime.
+
+ This function supports time zone offsets. When the input contains one,
+ the output uses an instance of FixedOffset as tzinfo.
+
+ Sub-microsecond precision is accepted, but ignored.
+
+ Raise ValueError if the input is well formatted but not a valid datetime.
+ Return None if the input isn't well formatted.
+ """
+ match = datetime_re.match(value)
+ if match:
+ kw = match.groupdict()
+ if kw['microsecond']:
+ kw['microsecond'] = kw['microsecond'].ljust(6, '0')
+ tzinfo = kw.pop('tzinfo')
+ if tzinfo == 'Z':
+ tzinfo = utc
+ elif tzinfo is not None:
+ offset = 60 * int(tzinfo[1:3]) + int(tzinfo[4:6])
+ if tzinfo[0] == '-':
+ offset = -offset
+ tzinfo = FixedOffset(offset)
+ kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None)
+ kw['tzinfo'] = tzinfo
+ return datetime.datetime(**kw)
View
5 django/utils/feedgenerator.py
@@ -28,6 +28,7 @@
from django.utils.xmlutils import SimplerXMLGenerator
from django.utils.encoding import force_unicode, iri_to_uri
from django.utils import datetime_safe
+from django.utils.timezone import is_aware
def rfc2822_date(date):
# We can't use strftime() because it produces locale-dependant results, so
@@ -40,7 +41,7 @@ def rfc2822_date(date):
dow = days[date.weekday()]
month = months[date.month - 1]
time_str = date.strftime('%s, %%d %s %%Y %%H:%%M:%%S ' % (dow, month))
- if date.tzinfo:
+ if is_aware(date):
offset = date.tzinfo.utcoffset(date)
timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
hour, minute = divmod(timezone, 60)
@@ -51,7 +52,7 @@ def rfc2822_date(date):
def rfc3339_date(date):
# Support datetime objects older than 1900
date = datetime_safe.new_datetime(date)
- if date.tzinfo:
+ if is_aware(date):
time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
offset = date.tzinfo.utcoffset(date)
timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
View
16 django/utils/timesince.py
@@ -1,6 +1,6 @@
import datetime
-from django.utils.tzinfo import LocalTimezone
+from django.utils.timezone import is_aware, utc
from django.utils.translation import ungettext, ugettext
def timesince(d, now=None):
@@ -31,13 +31,10 @@ def timesince(d, now=None):
now = datetime.datetime(now.year, now.month, now.day)
if not now:
- if d.tzinfo:
- now = datetime.datetime.now(LocalTimezone(d))
- else:
- now = datetime.datetime.now()
+ now = datetime.datetime.now(utc if is_aware(d) else None)
- # ignore microsecond part of 'd' since we removed it from 'now'
- delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
+ delta = now - d
+ # ignore microseconds
since = delta.days * 24 * 60 * 60 + delta.seconds
if since <= 0:
# d is in the future compared to now, stop processing.
@@ -61,8 +58,5 @@ def timeuntil(d, now=None):
the given time.
"""
if not now:
- if getattr(d, 'tzinfo', None):
- now = datetime.datetime.now(LocalTimezone(d))
- else:
- now = datetime.datetime.now()
+ now = datetime.datetime.now(utc if is_aware(d) else None)
return timesince(now, d)
View
266 django/utils/timezone.py
@@ -0,0 +1,266 @@
+"""Timezone helper functions.
+
+This module uses pytz when it's available and fallbacks when it isn't.
+"""
+
+from datetime import datetime, timedelta, tzinfo
+from threading import local
+import time as _time
+
+try:
+ import pytz
+except ImportError:
+ pytz = None
+
+from django.conf import settings
+
+__all__ = [
+ 'utc', 'get_default_timezone', 'get_current_timezone',
+ 'activate', 'deactivate', 'override',
+ 'aslocaltime', 'isnaive',
+]
+
+
+# UTC and local time zones
+
+ZERO = timedelta(0)
+
+class UTC(tzinfo):
+ """
+ UTC implementation taken from Python's docs.
+
+ Used only when pytz isn't available.
+ """
+
+ def utcoffset(self, dt):
+ return ZERO
+
+ def tzname(self, dt):
+ return "UTC"
+
+ def dst(self, dt):
+ return ZERO
+
+class LocalTimezone(tzinfo):
+ """
+ Local time implementation taken from Python's docs.
+
+ Used only when pytz isn't available, and most likely inaccurate. If you're
+ having trouble with this class, don't waste your time, just install pytz.
+ """
+
+ def __init__(self):
+ # This code is moved in __init__ to execute it as late as possible
+ # See get_default_timezone().
+ self.STDOFFSET = timedelta(seconds=-_time.timezone)
+ if _time.daylight:
+ self.DSTOFFSET = timedelta(seconds=-_time.altzone)
+ else:
+ self.DSTOFFSET = self.STDOFFSET
+ self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
+ tzinfo.__init__(self)
+
+ def utcoffset(self, dt):
+ if self._isdst(dt):
+ return self.DSTOFFSET
+ else:
+ return self.STDOFFSET
+
+ def dst(self, dt):
+ if self._isdst(dt):
+ return self.DSTDIFF
+ else:
+ return ZERO
+
+ def tzname(self, dt):
+ return _time.tzname[self._isdst(dt)]
+
+ def _isdst(self, dt):
+ tt = (dt.year, dt.month, dt.day,
+ dt.hour, dt.minute, dt.second,
+ dt.weekday(), 0, 0)
+ stamp = _time.mktime(tt)
+ tt = _time.localtime(stamp)
+ return tt.tm_isdst > 0
+
+
+utc = pytz.utc if pytz else UTC()
+"""UTC time zone as a tzinfo instance."""
+
+# In order to avoid accessing the settings at compile time,
+# wrap the expression in a function and cache the result.
+# If you change settings.TIME_ZONE in tests, reset _localtime to None.
+_localtime = None
+
+def get_default_timezone():
+ """
+ Returns the default time zone as a tzinfo instance.
+
+ This is the time zone defined by settings.TIME_ZONE.
+
+ See also :func:`get_current_timezone`.
+ """
+ global _localtime
+ if _localtime is None:
+ tz = settings.TIME_ZONE
+ _localtime = pytz.timezone(tz) if pytz else LocalTimezone()
+ return _localtime
+
+# This function exists for consistency with get_current_timezone_name
+def get_default_timezone_name():
+ """
+ Returns the name of the default time zone.
+ """
+ return _get_timezone_name(get_default_timezone())
+
+_active = local()
+
+def get_current_timezone():
+ """
+ Returns the currently active time zone as a tzinfo instance.
+ """
+ return getattr(_active, "value", get_default_timezone())
+
+def get_current_timezone_name():
+ """
+ Returns the name of the currently active time zone.
+ """
+ return _get_timezone_name(get_current_timezone())
+
+def _get_timezone_name(timezone):
+ """
+ Returns the name of ``timezone``.
+ """
+ try:
+ # for pytz timezones
+ return timezone.zone
+ except AttributeError:
+ # for regular tzinfo objects
+ local_now = datetime.now(timezone)
+ return timezone.tzname(local_now)
+
+# Timezone selection functions.
+
+# These functions don't change os.environ['TZ'] and call time.tzset()
+# because it isn't thread safe.
+
+def activate(timezone):
+ """
+ Sets the time zone for the current thread.
+
+ The ``timezone`` argument must be an instance of a tzinfo subclass or a
+ time zone name. If it is a time zone name, pytz is required.
+ """
+ if isinstance(timezone, tzinfo):
+ _active.value = timezone
+ elif isinstance(timezone, basestring) and pytz is not None:
+ _active.value = pytz.timezone(timezone)
+ else:
+ raise ValueError("Invalid timezone: %r" % timezone)
+
+def deactivate():
+ """
+ Unsets the time zone for the current thread.
+
+ Django will then use the time zone defined by settings.TIME_ZONE.
+ """
+ if hasattr(_active, "value"):
+ del _active.value
+
+class override(object):
+ """
+ Temporarily set the time zone for the current thread.
+
+ This is a context manager that uses ``~django.utils.timezone.activate()``
+ to set the timezone on entry, and restores the previously active timezone
+ on exit.
+
+ The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
+ time zone name, or ``None``. If is it a time zone name, pytz is required.
+ If it is ``None``, Django enables the default time zone.
+ """
+ def __init__(self, timezone):
+ self.timezone = timezone
+ self.old_timezone = getattr(_active, 'value', None)
+
+ def __enter__(self):
+ if self.timezone is None:
+ deactivate()
+ else:
+ activate(self.timezone)
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ if self.old_timezone is not None:
+ _active.value = self.old_timezone
+ else:
+ del _active.value
+
+
+# Utilities
+
+def aslocaltime(value, use_tz=None):
+ """
+ Checks if value is a datetime and converts it to local time if necessary.
+
+ If use_tz is provided and is not None, that will force the value to
+ be converted (or not), overriding the value of settings.USE_TZ.
+ """
+ if (isinstance(value, datetime)
+ and (settings.USE_TZ if use_tz is None else use_tz)
+ and not is_naive(value)
+ and getattr(value, 'convert_to_local_time', True)):
+ timezone = get_current_timezone()
+ value = value.astimezone(timezone)
+ if hasattr(timezone, 'normalize'):
+ # available for pytz time zones
+ value = timezone.normalize(value)
+ return value
+
+def now():
+ """
+ Returns an aware or naive datetime.datetime, depending on settings.USE_TZ.
+ """
+ if settings.USE_TZ:
+ # timeit shows that datetime.now(tz=utc) is 24% slower
+ return datetime.utcnow().replace(tzinfo=utc)
+ else:
+ return datetime.now()
+
+def is_aware(value):
+ """
+ Determines if a given datetime.datetime is aware.
+
+ The logic is described in Python's docs:
+ http://docs.python.org/library/datetime.html#datetime.tzinfo
+ """
+ return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None
+
+def is_naive(value):
+ """
+ Determines if a given datetime.datetime is naive.
+
+ The logic is described in Python's docs:
+ http://docs.python.org/library/datetime.html#datetime.tzinfo
+ """
+ return value.tzinfo is None or value.tzinfo.utcoffset(value) is None
+
+def make_aware(value, timezone):
+ """
+ Makes a naive datetime.datetime in a given time zone aware.
+ """
+ if hasattr(timezone, 'localize'):
+ # available for pytz time zones
+ return timezone.localize(value, is_dst=None)
+ else:
+ # may be wrong around DST changes
+ return value.replace(tzinfo=timezone)
+
+def make_naive(value, timezone):
+ """
+ Makes an aware datetime.datetime naive in a given time zone.
+ """
+ value = value.astimezone(timezone)
+ if hasattr(timezone, 'normalize'):
+ # available for pytz time zones
+ return timezone.normalize(value)
+ return value.replace(tzinfo=None)
View
19 django/utils/tzinfo.py
@@ -2,8 +2,14 @@
import time
from datetime import timedelta, tzinfo
+
from django.utils.encoding import smart_unicode, smart_str, DEFAULT_LOCALE_ENCODING
+# Python's doc say: "A tzinfo subclass must have an __init__() method that can
+# be called with no arguments". FixedOffset and LocalTimezone don't honor this
+# requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as
+# well as pickling/unpickling.
+
class FixedOffset(tzinfo):
"Fixed offset in minutes east from UTC."
def __init__(self, offset):
@@ -19,6 +25,9 @@ def __init__(self, offset):
def __repr__(self):
return self.__name
+ def __getinitargs__(self):
+ return self.__offset,
+
def utcoffset(self, dt):
return self.__offset
@@ -28,15 +37,25 @@ def tzname(self, dt):
def dst(self, dt):
return timedelta(0)
+# This implementation is used for display purposes. It uses an approximation
+# for DST computations on dates >= 2038.
+
+# A similar implementation exists in django.utils.timezone. It's used for
+# timezone support (when USE_TZ = True) and focuses on correctness.
+
class LocalTimezone(tzinfo):
"Proxy timezone information from time module."
def __init__(self, dt):
tzinfo.__init__(self)
+ self.__dt = dt
self._tzname = self.tzname(dt)
def __repr__(self):
return smart_str(self._tzname)
+ def __getinitargs__(self):
+ return self.__dt,
+
def utcoffset(self, dt):
if self._isdst(dt):
return timedelta(seconds=-time.altzone)
View
25 docs/howto/custom-template-tags.txt
@@ -347,6 +347,31 @@ function; this syntax is deprecated.
return mark_safe(result)
initial_letter_filter.needs_autoescape = True
+.. _filters-timezones:
+
+Filters and time zones
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.4
+
+If you write a custom filter that operates on :class:`~datetime.datetime`
+objects, you'll usually register it with the ``expects_localtime`` flag set to
+``True``:
+
+.. code-block:: python
+
+ @register.filter(expects_localtime=True)
+ def businesshours(value):
+ try:
+ return 9 <= value.hour < 17
+ except AttributeError:
+ return ''
+
+When this flag is set, if the first argument to your filter is a time zone
+aware datetime, Django will convert it to the current time zone before passing
+to your filter when appropriate, according to :ref:`rules for time zones
+conversions in templates <time-zones-in-templates>`.
+
Writing custom template tags
----------------------------
View
13 docs/ref/models/querysets.txt
@@ -546,6 +546,12 @@ Examples::
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[datetime.datetime(2005, 3, 20)]
+.. warning::
+
+ When :doc:`time zone support </topics/i18n/timezones>` is enabled, Django
+ uses UTC in the database connection, which means the aggregation is
+ performed in UTC. This is a known limitation of the current implementation.
+
none
~~~~
@@ -1953,6 +1959,13 @@ Note this will match any record with a ``pub_date`` that falls on a Monday (day
2 of the week), regardless of the month or year in which it occurs. Week days
are indexed with day 1 being Sunday and day 7 being Saturday.
+.. warning::
+
+ When :doc:`time zone support </topics/i18n/timezones>` is enabled, Django
+ uses UTC in the database connection, which means the ``year``, ``month``,
+ ``day`` and ``week_day`` lookups are performed in UTC. This is a known
+ limitation of the current implementation.
+
.. fieldlookup:: isnull
isnull
View
54 docs/ref/settings.txt
@@ -1810,6 +1810,7 @@ Default::
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
+ "django.core.context_processors.tz",
"django.contrib.messages.context_processors.messages")
A tuple of callables that are used to populate the context in ``RequestContext``.
@@ -1830,6 +1831,10 @@ of items to be merged into the context.
The ``django.core.context_processors.static`` context processor
was added in this release.
+.. versionadded:: 1.4
+ The ``django.core.context_processors.tz`` context processor
+ was added in this release.
+
.. setting:: TEMPLATE_DEBUG
TEMPLATE_DEBUG
@@ -1971,6 +1976,9 @@ Default: ``'America/Chicago'``
.. versionchanged:: 1.2
``None`` was added as an allowed value.
+.. versionchanged:: 1.4
+ The meaning of this setting now depends on the value of :setting:`USE_TZ`.
+
A string representing the time zone for this installation, or
``None``. `See available choices`_. (Note that list of available
choices lists more than one on the same line; you'll want to use just
@@ -1978,16 +1986,19 @@ one of the choices for a given time zone. For instance, one line says
``'Europe/London GB GB-Eire'``, but you should use the first bit of
that -- ``'Europe/London'`` -- as your :setting:`TIME_ZONE` setting.)
-Note that this is the time zone to which Django will convert all
-dates/times -- not necessarily the timezone of the server. For
-example, one server may serve multiple Django-powered sites, each with
-a separate time-zone setting.
+Note that this isn't necessarily the timezone of the server. For example, one
+server may serve multiple Django-powered sites, each with a separate time zone
+setting.
+
+When :setting:`USE_TZ` is ``False``, this is the time zone in which Django will
+store all datetimes. When :setting:`USE_TZ` is ``True``, this is the default
+time zone that Django will use to display datetimes in templates and to
+interpret datetimes entered in forms.
-Normally, Django sets the ``os.environ['TZ']`` variable to the time
-zone you specify in the :setting:`TIME_ZONE` setting. Thus, all your views
-and models will automatically operate in the correct time zone.
-However, Django won't set the ``TZ`` environment variable under the
-following conditions:
+Django sets the ``os.environ['TZ']`` variable to the time zone you specify in
+the :setting:`TIME_ZONE` setting. Thus, all your views and models will
+automatically operate in this time zone. However, Django won't set the ``TZ``
+environment variable under the following conditions:
* If you're using the manual configuration option as described in
:ref:`manually configuring settings
@@ -2004,7 +2015,6 @@ to ensure your processes are running in the correct environment.
environment. If you're running Django on Windows, this variable
must be set to match the system timezone.
-
.. _See available choices: http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
.. setting:: URL_VALIDATOR_USER_AGENT
@@ -2043,7 +2053,7 @@ This provides an easy way to turn it off, for performance. If this is set to
``False``, Django will make some optimizations so as not to load the
translation machinery.
-See also :setting:`USE_L10N`
+See also :setting:`LANGUAGE_CODE`, :setting:`USE_L10N` and :setting:`USE_TZ`.
.. setting:: USE_L10N
@@ -2058,7 +2068,7 @@ A boolean that specifies if localized formatting of data will be enabled by
default or not. If this is set to ``True``, e.g. Django will display numbers and
dates using the format of the current locale.
-See also :setting:`USE_I18N` and :setting:`LANGUAGE_CODE`
+See also :setting:`LANGUAGE_CODE`, :setting:`USE_I18N` and :setting:`USE_TZ`.
.. note::