mirror of
https://github.com/django/django.git
synced 2025-08-09 11:29:13 +00:00
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
This commit is contained in:
parent
01f70349c9
commit
9b1cb755a2
@ -31,9 +31,13 @@ INTERNAL_IPS = ()
|
|||||||
|
|
||||||
# Local time zone for this installation. All choices can be found here:
|
# 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
|
# 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'
|
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:
|
# Language code for this installation. All choices can be found here:
|
||||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
@ -119,7 +123,7 @@ LOCALE_PATHS = ()
|
|||||||
LANGUAGE_COOKIE_NAME = 'django_language'
|
LANGUAGE_COOKIE_NAME = 'django_language'
|
||||||
|
|
||||||
# If you set this to True, Django will format dates, numbers and calendars
|
# 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
|
USE_L10N = False
|
||||||
|
|
||||||
# Not-necessarily-technical managers of the site. They get broken link
|
# Not-necessarily-technical managers of the site. They get broken link
|
||||||
@ -192,6 +196,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
|||||||
'django.core.context_processors.i18n',
|
'django.core.context_processors.i18n',
|
||||||
'django.core.context_processors.media',
|
'django.core.context_processors.media',
|
||||||
'django.core.context_processors.static',
|
'django.core.context_processors.static',
|
||||||
|
'django.core.context_processors.tz',
|
||||||
# 'django.core.context_processors.request',
|
# 'django.core.context_processors.request',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
)
|
)
|
||||||
|
@ -40,9 +40,12 @@ SITE_ID = 1
|
|||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
# If you set this to False, Django will not format dates, numbers and
|
# 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
|
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.
|
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||||
# Example: "/home/media/media.lawrence.com/media/"
|
# Example: "/home/media/media.lawrence.com/media/"
|
||||||
MEDIA_ROOT = ''
|
MEDIA_ROOT = ''
|
||||||
|
@ -7,6 +7,7 @@ from django.utils import formats
|
|||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.text import capfirst
|
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.encoding import force_unicode, smart_unicode, smart_str
|
||||||
from django.utils.translation import ungettext
|
from django.utils.translation import ungettext
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
@ -293,6 +294,8 @@ def display_for_field(value, field):
|
|||||||
return _boolean_icon(value)
|
return _boolean_icon(value)
|
||||||
elif value is None:
|
elif value is None:
|
||||||
return EMPTY_CHANGELIST_VALUE
|
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):
|
elif isinstance(field, models.DateField) or isinstance(field, models.TimeField):
|
||||||
return formats.localize(value)
|
return formats.localize(value)
|
||||||
elif isinstance(field, models.DecimalField):
|
elif isinstance(field, models.DecimalField):
|
||||||
|
@ -7,7 +7,7 @@ from django.template import defaultfilters
|
|||||||
from django.utils.encoding import force_unicode
|
from django.utils.encoding import force_unicode
|
||||||
from django.utils.formats import number_format
|
from django.utils.formats import number_format
|
||||||
from django.utils.translation import pgettext, ungettext, ugettext as _
|
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()
|
register = template.Library()
|
||||||
|
|
||||||
@ -158,8 +158,8 @@ def naturalday(value, arg=None):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
# Date arguments out of range
|
# Date arguments out of range
|
||||||
return value
|
return value
|
||||||
today = datetime.now(tzinfo).replace(microsecond=0, second=0, minute=0, hour=0)
|
today = datetime.now(tzinfo).date()
|
||||||
delta = value - today.date()
|
delta = value - today
|
||||||
if delta.days == 0:
|
if delta.days == 0:
|
||||||
return _(u'today')
|
return _(u'today')
|
||||||
elif delta.days == 1:
|
elif delta.days == 1:
|
||||||
@ -174,18 +174,10 @@ def naturaltime(value):
|
|||||||
For date and time values shows how many seconds, minutes or hours ago
|
For date and time values shows how many seconds, minutes or hours ago
|
||||||
compared to current timestamp returns representing string.
|
compared to current timestamp returns representing string.
|
||||||
"""
|
"""
|
||||||
try:
|
if not isinstance(value, date): # datetime is a subclass of date
|
||||||
value = datetime(value.year, value.month, value.day, value.hour, value.minute, value.second)
|
|
||||||
except AttributeError:
|
|
||||||
return value
|
|
||||||
except ValueError:
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
if getattr(value, 'tzinfo', None):
|
now = datetime.now(utc if is_aware(value) else None)
|
||||||
now = datetime.now(LocalTimezone(value))
|
|
||||||
else:
|
|
||||||
now = datetime.now()
|
|
||||||
now = now - timedelta(0, 0, now.microsecond)
|
|
||||||
if value < now:
|
if value < now:
|
||||||
delta = now - value
|
delta = now - value
|
||||||
if delta.days != 0:
|
if delta.days != 0:
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
from datetime import timedelta, date, datetime
|
import datetime
|
||||||
|
|
||||||
from django.template import Template, Context, defaultfilters
|
from django.template import Template, Context, defaultfilters
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import translation, tzinfo
|
from django.utils import translation, tzinfo
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
from django.utils.timezone import utc
|
||||||
|
|
||||||
|
|
||||||
class HumanizeTests(TestCase):
|
class HumanizeTests(TestCase):
|
||||||
@ -88,10 +89,10 @@ class HumanizeTests(TestCase):
|
|||||||
self.humanize_tester(test_list, result_list, 'apnumber')
|
self.humanize_tester(test_list, result_list, 'apnumber')
|
||||||
|
|
||||||
def test_naturalday(self):
|
def test_naturalday(self):
|
||||||
today = date.today()
|
today = datetime.date.today()
|
||||||
yesterday = today - timedelta(days=1)
|
yesterday = today - datetime.timedelta(days=1)
|
||||||
tomorrow = today + timedelta(days=1)
|
tomorrow = today + datetime.timedelta(days=1)
|
||||||
someday = today - timedelta(days=10)
|
someday = today - datetime.timedelta(days=10)
|
||||||
notdate = u"I'm not a date value"
|
notdate = u"I'm not a date value"
|
||||||
|
|
||||||
test_list = (today, yesterday, tomorrow, someday, notdate, None)
|
test_list = (today, yesterday, tomorrow, someday, notdate, None)
|
||||||
@ -103,41 +104,46 @@ class HumanizeTests(TestCase):
|
|||||||
def test_naturalday_tz(self):
|
def test_naturalday_tz(self):
|
||||||
from django.contrib.humanize.templatetags.humanize import naturalday
|
from django.contrib.humanize.templatetags.humanize import naturalday
|
||||||
|
|
||||||
today = date.today()
|
today = datetime.date.today()
|
||||||
tz_one = tzinfo.FixedOffset(timedelta(hours=-12))
|
tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12))
|
||||||
tz_two = tzinfo.FixedOffset(timedelta(hours=12))
|
tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12))
|
||||||
|
|
||||||
# Can be today or yesterday
|
# 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)
|
naturalday_one = naturalday(date_one)
|
||||||
# Can be today or tomorrow
|
# 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)
|
naturalday_two = naturalday(date_two)
|
||||||
|
|
||||||
# As 24h of difference they will never be the same
|
# As 24h of difference they will never be the same
|
||||||
self.assertNotEqual(naturalday_one, naturalday_two)
|
self.assertNotEqual(naturalday_one, naturalday_two)
|
||||||
|
|
||||||
def test_naturaltime(self):
|
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
|
# 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 = [
|
test_list = [
|
||||||
now,
|
now,
|
||||||
now - timedelta(seconds=1),
|
now - datetime.timedelta(seconds=1),
|
||||||
now - timedelta(seconds=30),
|
now - datetime.timedelta(seconds=30),
|
||||||
now - timedelta(minutes=1, seconds=30),
|
now - datetime.timedelta(minutes=1, seconds=30),
|
||||||
now - timedelta(minutes=2),
|
now - datetime.timedelta(minutes=2),
|
||||||
now - timedelta(hours=1, minutes=30, seconds=30),
|
now - datetime.timedelta(hours=1, minutes=30, seconds=30),
|
||||||
now - timedelta(hours=23, minutes=50, seconds=50),
|
now - datetime.timedelta(hours=23, minutes=50, seconds=50),
|
||||||
now - timedelta(days=1),
|
now - datetime.timedelta(days=1),
|
||||||
now - timedelta(days=500),
|
now - datetime.timedelta(days=500),
|
||||||
now + timedelta(seconds=1),
|
now + datetime.timedelta(seconds=1),
|
||||||
now + timedelta(seconds=30),
|
now + datetime.timedelta(seconds=30),
|
||||||
now + timedelta(minutes=1, seconds=30),
|
now + datetime.timedelta(minutes=1, seconds=30),
|
||||||
now + timedelta(minutes=2),
|
now + datetime.timedelta(minutes=2),
|
||||||
now + timedelta(hours=1, minutes=30, seconds=30),
|
now + datetime.timedelta(hours=1, minutes=30, seconds=30),
|
||||||
now + timedelta(hours=23, minutes=50, seconds=50),
|
now + datetime.timedelta(hours=23, minutes=50, seconds=50),
|
||||||
now + timedelta(days=1),
|
now + datetime.timedelta(days=1),
|
||||||
now + timedelta(days=500),
|
now + datetime.timedelta(days=500),
|
||||||
|
now.replace(tzinfo=naive()),
|
||||||
|
now.replace(tzinfo=utc),
|
||||||
]
|
]
|
||||||
result_list = [
|
result_list = [
|
||||||
'now',
|
'now',
|
||||||
@ -157,14 +163,20 @@ class HumanizeTests(TestCase):
|
|||||||
'23 hours from now',
|
'23 hours from now',
|
||||||
'1 day from now',
|
'1 day from now',
|
||||||
'1 year, 4 months from now',
|
'1 year, 4 months from now',
|
||||||
|
'now',
|
||||||
|
'now',
|
||||||
]
|
]
|
||||||
|
|
||||||
# mock out datetime so these tests don't fail occasionally when the
|
# mock out datetime so these tests don't fail occasionally when the
|
||||||
# test runs too slow
|
# test runs too slow
|
||||||
class MockDateTime(datetime):
|
class MockDateTime(datetime.datetime):
|
||||||
@classmethod
|
@classmethod
|
||||||
def now(self):
|
def now(self, tz=None):
|
||||||
return now
|
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
|
# naturaltime also calls timesince/timeuntil
|
||||||
from django.contrib.humanize.templatetags import humanize
|
from django.contrib.humanize.templatetags import humanize
|
||||||
|
@ -6,6 +6,7 @@ from django.template import loader, TemplateDoesNotExist, RequestContext
|
|||||||
from django.utils import feedgenerator, tzinfo
|
from django.utils import feedgenerator, tzinfo
|
||||||
from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode
|
from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
from django.utils.timezone import is_naive
|
||||||
|
|
||||||
def add_domain(domain, url, secure=False):
|
def add_domain(domain, url, secure=False):
|
||||||
if not (url.startswith('http://')
|
if not (url.startswith('http://')
|
||||||
@ -164,7 +165,7 @@ class Feed(object):
|
|||||||
author_email = author_link = None
|
author_email = author_link = None
|
||||||
|
|
||||||
pubdate = self.__get_dynamic_attr('item_pubdate', item)
|
pubdate = self.__get_dynamic_attr('item_pubdate', item)
|
||||||
if pubdate and not pubdate.tzinfo:
|
if pubdate and is_naive(pubdate):
|
||||||
ltz = tzinfo.LocalTimezone(pubdate)
|
ltz = tzinfo.LocalTimezone(pubdate)
|
||||||
pubdate = pubdate.replace(tzinfo=ltz)
|
pubdate = pubdate.replace(tzinfo=ltz)
|
||||||
|
|
||||||
|
@ -48,6 +48,11 @@ def i18n(request):
|
|||||||
|
|
||||||
return context_extras
|
return context_extras
|
||||||
|
|
||||||
|
def tz(request):
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
return {'TIME_ZONE': timezone.get_current_timezone_name()}
|
||||||
|
|
||||||
def static(request):
|
def static(request):
|
||||||
"""
|
"""
|
||||||
Adds static-related context variables to the context.
|
Adds static-related context variables to the context.
|
||||||
|
@ -8,8 +8,8 @@ from StringIO import StringIO
|
|||||||
|
|
||||||
from django.core.serializers.python import Serializer as PythonSerializer
|
from django.core.serializers.python import Serializer as PythonSerializer
|
||||||
from django.core.serializers.python import Deserializer as PythonDeserializer
|
from django.core.serializers.python import Deserializer as PythonDeserializer
|
||||||
from django.utils import datetime_safe
|
|
||||||
from django.utils import simplejson
|
from django.utils import simplejson
|
||||||
|
from django.utils.timezone import is_aware
|
||||||
|
|
||||||
class Serializer(PythonSerializer):
|
class Serializer(PythonSerializer):
|
||||||
"""
|
"""
|
||||||
@ -39,19 +39,24 @@ class DjangoJSONEncoder(simplejson.JSONEncoder):
|
|||||||
"""
|
"""
|
||||||
JSONEncoder subclass that knows how to encode date/time and decimal types.
|
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):
|
def default(self, o):
|
||||||
|
# See "Date Time String Format" in the ECMA-262 specification.
|
||||||
if isinstance(o, datetime.datetime):
|
if isinstance(o, datetime.datetime):
|
||||||
d = datetime_safe.new_datetime(o)
|
r = o.isoformat()
|
||||||
return d.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
|
if o.microsecond:
|
||||||
|
r = r[:23] + r[26:]
|
||||||
|
if r.endswith('+00:00'):
|
||||||
|
r = r[:-6] + 'Z'
|
||||||
|
return r
|
||||||
elif isinstance(o, datetime.date):
|
elif isinstance(o, datetime.date):
|
||||||
d = datetime_safe.new_date(o)
|
return o.isoformat()
|
||||||
return d.strftime(self.DATE_FORMAT)
|
|
||||||
elif isinstance(o, datetime.time):
|
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):
|
elif isinstance(o, decimal.Decimal):
|
||||||
return str(o)
|
return str(o)
|
||||||
else:
|
else:
|
||||||
|
@ -10,6 +10,7 @@ from django.db import DEFAULT_DB_ALIAS
|
|||||||
from django.db.backends import util
|
from django.db.backends import util
|
||||||
from django.db.transaction import TransactionManagementError
|
from django.db.transaction import TransactionManagementError
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
from django.utils.timezone import is_aware
|
||||||
|
|
||||||
|
|
||||||
class BaseDatabaseWrapper(local):
|
class BaseDatabaseWrapper(local):
|
||||||
@ -743,6 +744,8 @@ class BaseDatabaseOperations(object):
|
|||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
if is_aware(value):
|
||||||
|
raise ValueError("Django does not support timezone-aware times.")
|
||||||
return unicode(value)
|
return unicode(value)
|
||||||
|
|
||||||
def value_to_db_decimal(self, value, max_digits, decimal_places):
|
def value_to_db_decimal(self, value, max_digits, decimal_places):
|
||||||
|
@ -33,6 +33,7 @@ from django.db.backends.mysql.creation import DatabaseCreation
|
|||||||
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
||||||
from django.db.backends.mysql.validation import DatabaseValidation
|
from django.db.backends.mysql.validation import DatabaseValidation
|
||||||
from django.utils.safestring import SafeString, SafeUnicode
|
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
|
# Raise exceptions for database warnings if DEBUG is on
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -43,16 +44,29 @@ if settings.DEBUG:
|
|||||||
DatabaseError = Database.DatabaseError
|
DatabaseError = Database.DatabaseError
|
||||||
IntegrityError = Database.IntegrityError
|
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
|
# 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 --
|
# 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
|
# 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
|
# add special handling for SafeUnicode and SafeString as MySQLdb's type
|
||||||
# checking is too tight to catch those (see Django ticket #6052).
|
# 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 = conversions.copy()
|
||||||
django_conversions.update({
|
django_conversions.update({
|
||||||
FIELD_TYPE.TIME: util.typecast_time,
|
FIELD_TYPE.TIME: util.typecast_time,
|
||||||
FIELD_TYPE.DECIMAL: util.typecast_decimal,
|
FIELD_TYPE.DECIMAL: util.typecast_decimal,
|
||||||
FIELD_TYPE.NEWDECIMAL: 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
|
# This should match the numerical portion of the version numbers (we can treat
|
||||||
@ -238,8 +252,11 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# MySQL doesn't support tz-aware datetimes
|
# MySQL doesn't support tz-aware datetimes
|
||||||
if value.tzinfo is not None:
|
if is_aware(value):
|
||||||
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
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
|
# MySQL doesn't support microseconds
|
||||||
return unicode(value.replace(microsecond=0))
|
return unicode(value.replace(microsecond=0))
|
||||||
@ -248,9 +265,9 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# MySQL doesn't support tz-aware datetimes
|
# MySQL doesn't support tz-aware times
|
||||||
if value.tzinfo is not None:
|
if is_aware(value):
|
||||||
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
raise ValueError("MySQL backend does not support timezone-aware times.")
|
||||||
|
|
||||||
# MySQL doesn't support microseconds
|
# MySQL doesn't support microseconds
|
||||||
return unicode(value.replace(microsecond=0))
|
return unicode(value.replace(microsecond=0))
|
||||||
|
@ -44,6 +44,7 @@ except ImportError, e:
|
|||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e)
|
raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e)
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db import utils
|
from django.db import utils
|
||||||
from django.db.backends import *
|
from django.db.backends import *
|
||||||
from django.db.backends.signals import connection_created
|
from django.db.backends.signals import connection_created
|
||||||
@ -51,6 +52,7 @@ from django.db.backends.oracle.client import DatabaseClient
|
|||||||
from django.db.backends.oracle.creation import DatabaseCreation
|
from django.db.backends.oracle.creation import DatabaseCreation
|
||||||
from django.db.backends.oracle.introspection import DatabaseIntrospection
|
from django.db.backends.oracle.introspection import DatabaseIntrospection
|
||||||
from django.utils.encoding import smart_str, force_unicode
|
from django.utils.encoding import smart_str, force_unicode
|
||||||
|
from django.utils.timezone import is_aware, is_naive, utc
|
||||||
|
|
||||||
DatabaseError = Database.DatabaseError
|
DatabaseError = Database.DatabaseError
|
||||||
IntegrityError = Database.IntegrityError
|
IntegrityError = Database.IntegrityError
|
||||||
@ -333,11 +335,17 @@ WHEN (new.%(col_name)s IS NULL)
|
|||||||
return "TABLESPACE %s" % self.quote_name(tablespace)
|
return "TABLESPACE %s" % self.quote_name(tablespace)
|
||||||
|
|
||||||
def value_to_db_datetime(self, value):
|
def value_to_db_datetime(self, value):
|
||||||
# Oracle doesn't support tz-aware datetimes
|
if value is None:
|
||||||
if getattr(value, 'tzinfo', None) is not None:
|
return None
|
||||||
raise ValueError("Oracle backend does not support timezone-aware datetimes.")
|
|
||||||
|
|
||||||
return super(DatabaseOperations, self).value_to_db_datetime(value)
|
# Oracle doesn't support tz-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 unicode(value)
|
||||||
|
|
||||||
def value_to_db_time(self, value):
|
def value_to_db_time(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
@ -346,9 +354,9 @@ WHEN (new.%(col_name)s IS NULL)
|
|||||||
if isinstance(value, basestring):
|
if isinstance(value, basestring):
|
||||||
return datetime.datetime.strptime(value, '%H:%M:%S')
|
return datetime.datetime.strptime(value, '%H:%M:%S')
|
||||||
|
|
||||||
# Oracle doesn't support tz-aware datetimes
|
# Oracle doesn't support tz-aware times
|
||||||
if value.tzinfo is not None:
|
if is_aware(value):
|
||||||
raise ValueError("Oracle backend does not support timezone-aware datetimes.")
|
raise ValueError("Oracle backend does not support timezone-aware times.")
|
||||||
|
|
||||||
return datetime.datetime(1900, 1, 1, value.hour, value.minute,
|
return datetime.datetime(1900, 1, 1, value.hour, value.minute,
|
||||||
value.second, value.microsecond)
|
value.second, value.microsecond)
|
||||||
@ -472,9 +480,28 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
# Set oracle date to ansi date format. This only needs to execute
|
# Set oracle date to ansi date format. This only needs to execute
|
||||||
# once when we create a new connection. We also set the Territory
|
# 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().
|
# 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' "
|
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_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'"
|
||||||
"NLS_TERRITORY = 'AMERICA'")
|
" 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__:
|
if 'operators' not in self.__dict__:
|
||||||
# Ticket #14149: Check whether our LIKE implementation will
|
# Ticket #14149: Check whether our LIKE implementation will
|
||||||
|
@ -13,8 +13,9 @@ from django.db.backends.postgresql_psycopg2.client import DatabaseClient
|
|||||||
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
|
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.version import get_version
|
||||||
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
|
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.log import getLogger
|
||||||
|
from django.utils.safestring import SafeUnicode, SafeString
|
||||||
|
from django.utils.timezone import utc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psycopg2 as Database
|
import psycopg2 as Database
|
||||||
@ -32,6 +33,11 @@ psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedStri
|
|||||||
|
|
||||||
logger = getLogger('django.db.backends')
|
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):
|
class CursorWrapper(object):
|
||||||
"""
|
"""
|
||||||
A thin wrapper around psycopg2's normal cursor class so that we can catch
|
A thin wrapper around psycopg2's normal cursor class so that we can catch
|
||||||
@ -144,11 +150,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
|
|
||||||
def _cursor(self):
|
def _cursor(self):
|
||||||
new_connection = False
|
new_connection = False
|
||||||
set_tz = False
|
|
||||||
settings_dict = self.settings_dict
|
settings_dict = self.settings_dict
|
||||||
if self.connection is None:
|
if self.connection is None:
|
||||||
new_connection = True
|
new_connection = True
|
||||||
set_tz = settings_dict.get('TIME_ZONE')
|
|
||||||
if settings_dict['NAME'] == '':
|
if settings_dict['NAME'] == '':
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
raise ImproperlyConfigured("You need to specify NAME in your Django settings file.")
|
raise ImproperlyConfigured("You need to specify NAME in your Django settings file.")
|
||||||
@ -171,10 +175,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
self.connection.set_isolation_level(self.isolation_level)
|
self.connection.set_isolation_level(self.isolation_level)
|
||||||
connection_created.send(sender=self.__class__, connection=self)
|
connection_created.send(sender=self.__class__, connection=self)
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
cursor.tzinfo_factory = None
|
cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
|
||||||
if new_connection:
|
if new_connection:
|
||||||
if set_tz:
|
tz = 'UTC' if settings.USE_TZ else settings_dict.get('TIME_ZONE')
|
||||||
cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']])
|
if tz:
|
||||||
|
cursor.execute("SET TIME ZONE %s", [tz])
|
||||||
self._get_pg_version()
|
self._get_pg_version()
|
||||||
return CursorWrapper(cursor)
|
return CursorWrapper(cursor)
|
||||||
|
|
||||||
|
@ -10,13 +10,16 @@ import decimal
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db import utils
|
from django.db import utils
|
||||||
from django.db.backends import *
|
from django.db.backends import *
|
||||||
from django.db.backends.signals import connection_created
|
from django.db.backends.signals import connection_created
|
||||||
from django.db.backends.sqlite3.client import DatabaseClient
|
from django.db.backends.sqlite3.client import DatabaseClient
|
||||||
from django.db.backends.sqlite3.creation import DatabaseCreation
|
from django.db.backends.sqlite3.creation import DatabaseCreation
|
||||||
from django.db.backends.sqlite3.introspection import DatabaseIntrospection
|
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.safestring import SafeString
|
||||||
|
from django.utils.timezone import is_aware, is_naive, utc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@ -31,22 +34,29 @@ except ImportError, exc:
|
|||||||
DatabaseError = Database.DatabaseError
|
DatabaseError = Database.DatabaseError
|
||||||
IntegrityError = Database.IntegrityError
|
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("bool", lambda s: str(s) == '1')
|
||||||
Database.register_converter("time", util.typecast_time)
|
Database.register_converter("time", parse_time)
|
||||||
Database.register_converter("date", util.typecast_date)
|
Database.register_converter("date", parse_date)
|
||||||
Database.register_converter("datetime", util.typecast_timestamp)
|
Database.register_converter("datetime", parse_datetime_with_timezone_support)
|
||||||
Database.register_converter("timestamp", util.typecast_timestamp)
|
Database.register_converter("timestamp", parse_datetime_with_timezone_support)
|
||||||
Database.register_converter("TIMESTAMP", util.typecast_timestamp)
|
Database.register_converter("TIMESTAMP", parse_datetime_with_timezone_support)
|
||||||
Database.register_converter("decimal", util.typecast_decimal)
|
Database.register_converter("decimal", util.typecast_decimal)
|
||||||
Database.register_adapter(decimal.Decimal, util.rev_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,
|
# Starting in 2.4.1, the str type is not accepted anymore, therefore,
|
||||||
# we convert all str objects to Unicode
|
# we convert all str objects to Unicode
|
||||||
# As registering a adapter for a primitive type causes a small
|
# As registering a adapter for a primitive type causes a small
|
||||||
# slow-down, this adapter is only registered for sqlite3 versions
|
# slow-down, this adapter is only registered for sqlite3 versions
|
||||||
# needing it.
|
# needing it.
|
||||||
Database.register_adapter(str, 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'))
|
Database.register_adapter(SafeString, lambda s: s.decode('utf-8'))
|
||||||
|
|
||||||
class DatabaseFeatures(BaseDatabaseFeatures):
|
class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
# SQLite cannot handle us only partially reading from a cursor's result set
|
# 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
|
can_use_chunked_reads = False
|
||||||
test_db_allows_multiple_connections = False
|
test_db_allows_multiple_connections = False
|
||||||
supports_unspecified_pk = True
|
supports_unspecified_pk = True
|
||||||
|
supports_timezones = False
|
||||||
supports_1000_query_parameters = False
|
supports_1000_query_parameters = False
|
||||||
supports_mixed_date_datetime_comparisons = False
|
supports_mixed_date_datetime_comparisons = False
|
||||||
has_bulk_insert = True
|
has_bulk_insert = True
|
||||||
@ -131,6 +142,29 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||||||
# sql_flush() implementations). Just return SQL at this point
|
# sql_flush() implementations). Just return SQL at this point
|
||||||
return sql
|
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):
|
def year_lookup_bounds(self, value):
|
||||||
first = '%s-01-01'
|
first = '%s-01-01'
|
||||||
second = '%s-12-31 23:59:59.999999'
|
second = '%s-12-31 23:59:59.999999'
|
||||||
@ -147,11 +181,11 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||||||
elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField':
|
elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField':
|
||||||
return int(value)
|
return int(value)
|
||||||
elif internal_type == 'DateField':
|
elif internal_type == 'DateField':
|
||||||
return util.typecast_date(value)
|
return parse_date(value)
|
||||||
elif internal_type == 'DateTimeField':
|
elif internal_type == 'DateTimeField':
|
||||||
return util.typecast_timestamp(value)
|
return parse_datetime_with_timezone_support(value)
|
||||||
elif internal_type == 'TimeField':
|
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
|
# No field, or the field isn't known to be a decimal or integer
|
||||||
return value
|
return value
|
||||||
|
@ -3,7 +3,9 @@ import decimal
|
|||||||
import hashlib
|
import hashlib
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.utils.log import getLogger
|
from django.utils.log import getLogger
|
||||||
|
from django.utils.timezone import utc
|
||||||
|
|
||||||
|
|
||||||
logger = getLogger('django.db.backends')
|
logger = getLogger('django.db.backends')
|
||||||
@ -99,8 +101,10 @@ def typecast_timestamp(s): # does NOT store time zone information
|
|||||||
seconds, microseconds = seconds.split('.')
|
seconds, microseconds = seconds.split('.')
|
||||||
else:
|
else:
|
||||||
microseconds = '0'
|
microseconds = '0'
|
||||||
|
tzinfo = utc if settings.USE_TZ else None
|
||||||
return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]),
|
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):
|
def typecast_decimal(s):
|
||||||
if s is None or s == '':
|
if s is None or s == '':
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import math
|
import math
|
||||||
from itertools import tee
|
from itertools import tee
|
||||||
|
|
||||||
@ -12,8 +10,10 @@ from django.conf import settings
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.core import exceptions, validators
|
from django.core import exceptions, validators
|
||||||
from django.utils.datastructures import DictWrapper
|
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.functional import curry
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.encoding import smart_unicode, force_unicode, smart_str
|
from django.utils.encoding import smart_unicode, force_unicode, smart_str
|
||||||
from django.utils.ipv6 import clean_ipv6_address
|
from django.utils.ipv6 import clean_ipv6_address
|
||||||
@ -180,8 +180,8 @@ class Field(object):
|
|||||||
return
|
return
|
||||||
elif value == option_key:
|
elif value == option_key:
|
||||||
return
|
return
|
||||||
raise exceptions.ValidationError(
|
msg = self.error_messages['invalid_choice'] % value
|
||||||
self.error_messages['invalid_choice'] % value)
|
raise exceptions.ValidationError(msg)
|
||||||
|
|
||||||
if value is None and not self.null:
|
if value is None and not self.null:
|
||||||
raise exceptions.ValidationError(self.error_messages['null'])
|
raise exceptions.ValidationError(self.error_messages['null'])
|
||||||
@ -638,11 +638,7 @@ class CommaSeparatedIntegerField(CharField):
|
|||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(CommaSeparatedIntegerField, self).formfield(**defaults)
|
return super(CommaSeparatedIntegerField, self).formfield(**defaults)
|
||||||
|
|
||||||
ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
|
|
||||||
|
|
||||||
class DateField(Field):
|
class DateField(Field):
|
||||||
description = _("Date (without time)")
|
|
||||||
|
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _(u"'%s' value has an invalid date format. It must be "
|
'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) "
|
'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) "
|
||||||
u"but it is an invalid date."),
|
u"but it is an invalid date."),
|
||||||
}
|
}
|
||||||
|
description = _("Date (without time)")
|
||||||
|
|
||||||
def __init__(self, verbose_name=None, name=None, auto_now=False,
|
def __init__(self, verbose_name=None, name=None, auto_now=False,
|
||||||
auto_now_add=False, **kwargs):
|
auto_now_add=False, **kwargs):
|
||||||
self.auto_now, self.auto_now_add = auto_now, auto_now_add
|
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:
|
if auto_now or auto_now_add:
|
||||||
kwargs['editable'] = False
|
kwargs['editable'] = False
|
||||||
kwargs['blank'] = True
|
kwargs['blank'] = True
|
||||||
@ -671,20 +667,19 @@ class DateField(Field):
|
|||||||
if isinstance(value, datetime.date):
|
if isinstance(value, datetime.date):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
if not ansi_date_re.search(value):
|
value = smart_str(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('-'))
|
|
||||||
try:
|
try:
|
||||||
return datetime.date(year, month, day)
|
parsed = parse_date(value)
|
||||||
except ValueError, e:
|
if parsed is not None:
|
||||||
msg = self.error_messages['invalid_date'] % str(value)
|
return parsed
|
||||||
|
except ValueError:
|
||||||
|
msg = self.error_messages['invalid_date'] % value
|
||||||
raise exceptions.ValidationError(msg)
|
raise exceptions.ValidationError(msg)
|
||||||
|
|
||||||
|
msg = self.error_messages['invalid'] % value
|
||||||
|
raise exceptions.ValidationError(msg)
|
||||||
|
|
||||||
def pre_save(self, model_instance, add):
|
def pre_save(self, model_instance, add):
|
||||||
if self.auto_now or (self.auto_now_add and add):
|
if self.auto_now or (self.auto_now_add and add):
|
||||||
value = datetime.date.today()
|
value = datetime.date.today()
|
||||||
@ -721,11 +716,7 @@ class DateField(Field):
|
|||||||
|
|
||||||
def value_to_string(self, obj):
|
def value_to_string(self, obj):
|
||||||
val = self._get_val_from_obj(obj)
|
val = self._get_val_from_obj(obj)
|
||||||
if val is None:
|
return '' if val is None else val.isoformat()
|
||||||
data = ''
|
|
||||||
else:
|
|
||||||
data = str(val)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.DateField}
|
defaults = {'form_class': forms.DateField}
|
||||||
@ -733,13 +724,20 @@ class DateField(Field):
|
|||||||
return super(DateField, self).formfield(**defaults)
|
return super(DateField, self).formfield(**defaults)
|
||||||
|
|
||||||
class DateTimeField(DateField):
|
class DateTimeField(DateField):
|
||||||
|
empty_strings_allowed = False
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _(u"'%s' value either has an invalid valid format (The "
|
'invalid': _(u"'%s' value has an invalid format. It must be in "
|
||||||
u"format must be YYYY-MM-DD HH:MM[:ss[.uuuuuu]]) or is "
|
u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."),
|
||||||
u"an invalid date/time."),
|
'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)")
|
description = _("Date (with time)")
|
||||||
|
|
||||||
|
# __init__ is inherited from DateField
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "DateTimeField"
|
return "DateTimeField"
|
||||||
|
|
||||||
@ -751,59 +749,59 @@ class DateTimeField(DateField):
|
|||||||
if isinstance(value, datetime.date):
|
if isinstance(value, datetime.date):
|
||||||
return datetime.datetime(value.year, value.month, value.day)
|
return datetime.datetime(value.year, value.month, value.day)
|
||||||
|
|
||||||
# Attempt to parse a datetime:
|
|
||||||
value = smart_str(value)
|
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:
|
except ValueError:
|
||||||
try: # Try without seconds.
|
msg = self.error_messages['invalid_datetime'] % value
|
||||||
return datetime.datetime(
|
raise exceptions.ValidationError(msg)
|
||||||
*time.strptime(value, '%Y-%m-%d %H:%M')[:5], **kwargs)
|
|
||||||
except ValueError: # Try without hour/minutes/seconds.
|
try:
|
||||||
try:
|
parsed = parse_date(value)
|
||||||
return datetime.datetime(
|
if parsed is not None:
|
||||||
*time.strptime(value, '%Y-%m-%d')[:3], **kwargs)
|
return datetime.datetime(parsed.year, parsed.month, parsed.day)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise exceptions.ValidationError(
|
msg = self.error_messages['invalid_date'] % value
|
||||||
self.error_messages['invalid'] % str(value))
|
raise exceptions.ValidationError(msg)
|
||||||
|
|
||||||
|
msg = self.error_messages['invalid'] % value
|
||||||
|
raise exceptions.ValidationError(msg)
|
||||||
|
|
||||||
def pre_save(self, model_instance, add):
|
def pre_save(self, model_instance, add):
|
||||||
if self.auto_now or (self.auto_now_add and 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)
|
setattr(model_instance, self.attname, value)
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
return super(DateTimeField, self).pre_save(model_instance, add)
|
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):
|
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):
|
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:
|
if not prepared:
|
||||||
value = self.get_prep_value(value)
|
value = self.get_prep_value(value)
|
||||||
return connection.ops.value_to_db_datetime(value)
|
return connection.ops.value_to_db_datetime(value)
|
||||||
|
|
||||||
def value_to_string(self, obj):
|
def value_to_string(self, obj):
|
||||||
val = self._get_val_from_obj(obj)
|
val = self._get_val_from_obj(obj)
|
||||||
if val is None:
|
return '' if val is None else val.isoformat()
|
||||||
data = ''
|
|
||||||
else:
|
|
||||||
data = str(val.replace(microsecond=0, tzinfo=None))
|
|
||||||
return data
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.DateTimeField}
|
defaults = {'form_class': forms.DateTimeField}
|
||||||
@ -1158,17 +1156,21 @@ class TextField(Field):
|
|||||||
return super(TextField, self).formfield(**defaults)
|
return super(TextField, self).formfield(**defaults)
|
||||||
|
|
||||||
class TimeField(Field):
|
class TimeField(Field):
|
||||||
description = _("Time")
|
|
||||||
|
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
default_error_messages = {
|
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,
|
def __init__(self, verbose_name=None, name=None, auto_now=False,
|
||||||
auto_now_add=False, **kwargs):
|
auto_now_add=False, **kwargs):
|
||||||
self.auto_now, self.auto_now_add = auto_now, auto_now_add
|
self.auto_now, self.auto_now_add = auto_now, auto_now_add
|
||||||
if auto_now or auto_now_add:
|
if auto_now or auto_now_add:
|
||||||
kwargs['editable'] = False
|
kwargs['editable'] = False
|
||||||
|
kwargs['blank'] = True
|
||||||
Field.__init__(self, verbose_name, name, **kwargs)
|
Field.__init__(self, verbose_name, name, **kwargs)
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
@ -1185,30 +1187,18 @@ class TimeField(Field):
|
|||||||
# database backend (e.g. Oracle), so we'll be accommodating.
|
# database backend (e.g. Oracle), so we'll be accommodating.
|
||||||
return value.time()
|
return value.time()
|
||||||
|
|
||||||
# Attempt to parse a datetime:
|
|
||||||
value = smart_str(value)
|
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.
|
try:
|
||||||
return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6],
|
parsed = parse_time(value)
|
||||||
**kwargs)
|
if parsed is not None:
|
||||||
|
return parsed
|
||||||
except ValueError:
|
except ValueError:
|
||||||
try: # Try without seconds.
|
msg = self.error_messages['invalid_time'] % value
|
||||||
return datetime.time(*time.strptime(value, '%H:%M')[3:5],
|
raise exceptions.ValidationError(msg)
|
||||||
**kwargs)
|
|
||||||
except ValueError:
|
msg = self.error_messages['invalid'] % value
|
||||||
raise exceptions.ValidationError(
|
raise exceptions.ValidationError(msg)
|
||||||
self.error_messages['invalid'])
|
|
||||||
|
|
||||||
def pre_save(self, model_instance, add):
|
def pre_save(self, model_instance, add):
|
||||||
if self.auto_now or (self.auto_now_add and add):
|
if self.auto_now or (self.auto_now_add and add):
|
||||||
@ -1229,11 +1219,7 @@ class TimeField(Field):
|
|||||||
|
|
||||||
def value_to_string(self, obj):
|
def value_to_string(self, obj):
|
||||||
val = self._get_val_from_obj(obj)
|
val = self._get_val_from_obj(obj)
|
||||||
if val is None:
|
return '' if val is None else val.isoformat()
|
||||||
data = ''
|
|
||||||
else:
|
|
||||||
data = str(val.replace(microsecond=0))
|
|
||||||
return data
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.TimeField}
|
defaults = {'form_class': forms.TimeField}
|
||||||
|
@ -66,7 +66,7 @@ class ConnectionHandler(object):
|
|||||||
if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']:
|
if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']:
|
||||||
conn['ENGINE'] = 'django.db.backends.dummy'
|
conn['ENGINE'] = 'django.db.backends.dummy'
|
||||||
conn.setdefault('OPTIONS', {})
|
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']:
|
for setting in ['NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']:
|
||||||
conn.setdefault(setting, '')
|
conn.setdefault(setting, '')
|
||||||
for setting in ['TEST_CHARSET', 'TEST_COLLATION', 'TEST_NAME', 'TEST_MIRROR']:
|
for setting in ['TEST_CHARSET', 'TEST_COLLATION', 'TEST_NAME', 'TEST_MIRROR']:
|
||||||
|
@ -17,7 +17,7 @@ except ImportError:
|
|||||||
|
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
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,
|
from django.forms.widgets import (TextInput, PasswordInput, HiddenInput,
|
||||||
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
|
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
|
||||||
NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
|
NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
|
||||||
@ -409,6 +409,11 @@ class DateTimeField(BaseTemporalField):
|
|||||||
'invalid': _(u'Enter a valid date/time.'),
|
'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):
|
def to_python(self, value):
|
||||||
"""
|
"""
|
||||||
Validates that the input can be converted to a datetime. Returns a
|
Validates that the input can be converted to a datetime. Returns a
|
||||||
@ -417,9 +422,10 @@ class DateTimeField(BaseTemporalField):
|
|||||||
if value in validators.EMPTY_VALUES:
|
if value in validators.EMPTY_VALUES:
|
||||||
return None
|
return None
|
||||||
if isinstance(value, datetime.datetime):
|
if isinstance(value, datetime.datetime):
|
||||||
return value
|
return from_current_timezone(value)
|
||||||
if isinstance(value, datetime.date):
|
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):
|
if isinstance(value, list):
|
||||||
# Input comes from a SplitDateTimeWidget, for example. So, it's two
|
# Input comes from a SplitDateTimeWidget, for example. So, it's two
|
||||||
# components: date and time.
|
# components: date and time.
|
||||||
@ -428,7 +434,8 @@ class DateTimeField(BaseTemporalField):
|
|||||||
if value[0] in validators.EMPTY_VALUES and value[1] in validators.EMPTY_VALUES:
|
if value[0] in validators.EMPTY_VALUES and value[1] in validators.EMPTY_VALUES:
|
||||||
return None
|
return None
|
||||||
value = '%s %s' % tuple(value)
|
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):
|
def strptime(self, value, format):
|
||||||
return datetime.datetime.strptime(value, format)
|
return datetime.datetime.strptime(value, format)
|
||||||
@ -979,7 +986,8 @@ class SplitDateTimeField(MultiValueField):
|
|||||||
raise ValidationError(self.error_messages['invalid_date'])
|
raise ValidationError(self.error_messages['invalid_date'])
|
||||||
if data_list[1] in validators.EMPTY_VALUES:
|
if data_list[1] in validators.EMPTY_VALUES:
|
||||||
raise ValidationError(self.error_messages['invalid_time'])
|
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
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
from django.conf import settings
|
||||||
from django.utils.html import conditional_escape
|
from django.utils.html import conditional_escape
|
||||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
from django.utils.encoding import StrAndUnicode, force_unicode
|
||||||
from django.utils.safestring import mark_safe
|
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
|
# Import ValidationError so that it can be imported from this
|
||||||
# module to maintain backwards compatibility.
|
# module to maintain backwards compatibility.
|
||||||
@ -52,3 +55,31 @@ class ErrorList(list, StrAndUnicode):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr([force_unicode(e) for e in 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
|
||||||
|
@ -10,7 +10,7 @@ from itertools import chain
|
|||||||
from urlparse import urljoin
|
from urlparse import urljoin
|
||||||
|
|
||||||
from django.conf import settings
|
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.datastructures import MultiValueDict, MergeDict
|
||||||
from django.utils.html import escape, conditional_escape
|
from django.utils.html import escape, conditional_escape
|
||||||
from django.utils.translation import ugettext, ugettext_lazy
|
from django.utils.translation import ugettext, ugettext_lazy
|
||||||
@ -847,6 +847,7 @@ class SplitDateTimeWidget(MultiWidget):
|
|||||||
|
|
||||||
def decompress(self, value):
|
def decompress(self, value):
|
||||||
if value:
|
if value:
|
||||||
|
value = to_current_timezone(value)
|
||||||
return [value.date(), value.time().replace(microsecond=0)]
|
return [value.date(), value.time().replace(microsecond=0)]
|
||||||
return [None, None]
|
return [None, None]
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ from django.utils.safestring import (SafeData, EscapeData, mark_safe,
|
|||||||
from django.utils.formats import localize
|
from django.utils.formats import localize
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.module_loading import module_has_submodule
|
from django.utils.module_loading import module_has_submodule
|
||||||
|
from django.utils.timezone import aslocaltime
|
||||||
|
|
||||||
|
|
||||||
TOKEN_TEXT = 0
|
TOKEN_TEXT = 0
|
||||||
@ -593,6 +594,8 @@ class FilterExpression(object):
|
|||||||
arg_vals.append(mark_safe(arg))
|
arg_vals.append(mark_safe(arg))
|
||||||
else:
|
else:
|
||||||
arg_vals.append(arg.resolve(context))
|
arg_vals.append(arg.resolve(context))
|
||||||
|
if getattr(func, 'expects_localtime', False):
|
||||||
|
obj = aslocaltime(obj, context.use_tz)
|
||||||
if getattr(func, 'needs_autoescape', False):
|
if getattr(func, 'needs_autoescape', False):
|
||||||
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
|
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
|
||||||
else:
|
else:
|
||||||
@ -853,6 +856,7 @@ def _render_value_in_context(value, context):
|
|||||||
means escaping, if required, and conversion to a unicode object. If value
|
means escaping, if required, and conversion to a unicode object. If value
|
||||||
is a string, it is expected to have already been translated.
|
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 = localize(value, use_l10n=context.use_l10n)
|
||||||
value = force_unicode(value)
|
value = force_unicode(value)
|
||||||
if ((context.autoescape and not isinstance(value, SafeData)) or
|
if ((context.autoescape and not isinstance(value, SafeData)) or
|
||||||
@ -1077,7 +1081,7 @@ class Library(object):
|
|||||||
elif name is not None and filter_func is not None:
|
elif name is not None and filter_func is not None:
|
||||||
# register.filter('somename', somefunc)
|
# register.filter('somename', somefunc)
|
||||||
self.filters[name] = filter_func
|
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:
|
if attr in flags:
|
||||||
value = flags[attr]
|
value = flags[attr]
|
||||||
# set the flag on the filter for FilterExpression.resolve
|
# set the flag on the filter for FilterExpression.resolve
|
||||||
@ -1189,6 +1193,7 @@ class Library(object):
|
|||||||
'autoescape': context.autoescape,
|
'autoescape': context.autoescape,
|
||||||
'current_app': context.current_app,
|
'current_app': context.current_app,
|
||||||
'use_l10n': context.use_l10n,
|
'use_l10n': context.use_l10n,
|
||||||
|
'use_tz': context.use_tz,
|
||||||
})
|
})
|
||||||
# Copy across the CSRF token, if present, because
|
# Copy across the CSRF token, if present, because
|
||||||
# inclusion tags are often used for forms, and we need
|
# inclusion tags are often used for forms, and we need
|
||||||
|
@ -83,10 +83,12 @@ class BaseContext(object):
|
|||||||
|
|
||||||
class Context(BaseContext):
|
class Context(BaseContext):
|
||||||
"A stack container for variable context"
|
"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.autoescape = autoescape
|
||||||
self.use_l10n = use_l10n
|
|
||||||
self.current_app = current_app
|
self.current_app = current_app
|
||||||
|
self.use_l10n = use_l10n
|
||||||
|
self.use_tz = use_tz
|
||||||
self.render_context = RenderContext()
|
self.render_context = RenderContext()
|
||||||
super(Context, self).__init__(dict_)
|
super(Context, self).__init__(dict_)
|
||||||
|
|
||||||
@ -162,8 +164,10 @@ class RequestContext(Context):
|
|||||||
Additional processors can be specified as a list of callables
|
Additional processors can be specified as a list of callables
|
||||||
using the "processors" keyword argument.
|
using the "processors" keyword argument.
|
||||||
"""
|
"""
|
||||||
def __init__(self, request, dict=None, processors=None, current_app=None, use_l10n=None):
|
def __init__(self, request, dict_=None, processors=None, current_app=None,
|
||||||
Context.__init__(self, dict, current_app=current_app, use_l10n=use_l10n)
|
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:
|
if processors is None:
|
||||||
processors = ()
|
processors = ()
|
||||||
else:
|
else:
|
||||||
|
@ -3,6 +3,7 @@ from django.utils.encoding import force_unicode
|
|||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import SafeData, EscapeData
|
from django.utils.safestring import SafeData, EscapeData
|
||||||
from django.utils.formats import localize
|
from django.utils.formats import localize
|
||||||
|
from django.utils.timezone import aslocaltime
|
||||||
|
|
||||||
|
|
||||||
class DebugLexer(Lexer):
|
class DebugLexer(Lexer):
|
||||||
@ -81,6 +82,7 @@ class DebugVariableNode(VariableNode):
|
|||||||
def render(self, context):
|
def render(self, context):
|
||||||
try:
|
try:
|
||||||
output = self.filter_expression.resolve(context)
|
output = self.filter_expression.resolve(context)
|
||||||
|
output = aslocaltime(output, use_tz=context.use_tz)
|
||||||
output = localize(output, use_l10n=context.use_l10n)
|
output = localize(output, use_l10n=context.use_l10n)
|
||||||
output = force_unicode(output)
|
output = force_unicode(output)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
|
@ -692,7 +692,7 @@ def get_digit(value, arg):
|
|||||||
# DATES #
|
# DATES #
|
||||||
###################
|
###################
|
||||||
|
|
||||||
@register.filter(is_safe=False)
|
@register.filter(expects_localtime=True, is_safe=False)
|
||||||
def date(value, arg=None):
|
def date(value, arg=None):
|
||||||
"""Formats a date according to the given format."""
|
"""Formats a date according to the given format."""
|
||||||
if not value:
|
if not value:
|
||||||
@ -707,7 +707,7 @@ def date(value, arg=None):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@register.filter(is_safe=False)
|
@register.filter(expects_localtime=True, is_safe=False)
|
||||||
def time(value, arg=None):
|
def time(value, arg=None):
|
||||||
"""Formats a time according to the given format."""
|
"""Formats a time according to the given format."""
|
||||||
if value in (None, u''):
|
if value in (None, u''):
|
||||||
|
191
django/templatetags/tz.py
Normal file
191
django/templatetags/tz.py
Normal file
@ -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])
|
@ -25,6 +25,7 @@ from django.conf import settings
|
|||||||
from django.core.cache import get_cache
|
from django.core.cache import get_cache
|
||||||
from django.utils.encoding import smart_str, iri_to_uri
|
from django.utils.encoding import smart_str, iri_to_uri
|
||||||
from django.utils.http import http_date
|
from django.utils.http import http_date
|
||||||
|
from django.utils.timezone import get_current_timezone_name
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
|
|
||||||
cc_delim_re = re.compile(r'\s*,\s*')
|
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
|
return header_query.lower() in existing_headers
|
||||||
|
|
||||||
def _i18n_cache_key_suffix(request, cache_key):
|
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:
|
if settings.USE_I18N or settings.USE_L10N:
|
||||||
# first check if LocaleMiddleware or another middleware added
|
# first check if LocaleMiddleware or another middleware added
|
||||||
# LANGUAGE_CODE to request, then fall back to the active language
|
# LANGUAGE_CODE to request, then fall back to the active language
|
||||||
# which in turn can also fall back to settings.LANGUAGE_CODE
|
# which in turn can also fall back to settings.LANGUAGE_CODE
|
||||||
cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
|
cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
|
||||||
|
if settings.USE_TZ:
|
||||||
|
cache_key += '.%s' % get_current_timezone_name()
|
||||||
return cache_key
|
return cache_key
|
||||||
|
|
||||||
def _generate_cache_key(request, method, headerlist, key_prefix):
|
def _generate_cache_key(request, method, headerlist, key_prefix):
|
||||||
|
@ -14,10 +14,13 @@ Usage:
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import calendar
|
import calendar
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR
|
from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR
|
||||||
from django.utils.tzinfo import LocalTimezone
|
from django.utils.tzinfo import LocalTimezone
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.encoding import force_unicode
|
from django.utils.encoding import force_unicode
|
||||||
|
from django.utils.timezone import is_aware, is_naive
|
||||||
|
|
||||||
re_formatchars = re.compile(r'(?<!\\)([aAbBcdDEfFgGhHiIjlLmMnNOPrsStTUuwWyYzZ])')
|
re_formatchars = re.compile(r'(?<!\\)([aAbBcdDEfFgGhHiIjlLmMnNOPrsStTUuwWyYzZ])')
|
||||||
re_escaped = re.compile(r'\\(.)')
|
re_escaped = re.compile(r'\\(.)')
|
||||||
@ -115,9 +118,12 @@ class DateFormat(TimeFormat):
|
|||||||
def __init__(self, dt):
|
def __init__(self, dt):
|
||||||
# Accepts either a datetime or date object.
|
# Accepts either a datetime or date object.
|
||||||
self.data = dt
|
self.data = dt
|
||||||
self.timezone = getattr(dt, 'tzinfo', None)
|
self.timezone = None
|
||||||
if hasattr(self.data, 'hour') and not self.timezone:
|
if isinstance(dt, datetime.datetime):
|
||||||
self.timezone = LocalTimezone(dt)
|
if is_naive(dt):
|
||||||
|
self.timezone = LocalTimezone(dt)
|
||||||
|
else:
|
||||||
|
self.timezone = dt.tzinfo
|
||||||
|
|
||||||
def b(self):
|
def b(self):
|
||||||
"Month, textual, 3 letters, lowercase; e.g. 'jan'"
|
"Month, textual, 3 letters, lowercase; e.g. 'jan'"
|
||||||
@ -218,7 +224,7 @@ class DateFormat(TimeFormat):
|
|||||||
|
|
||||||
def U(self):
|
def U(self):
|
||||||
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
|
"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()))
|
return int(calendar.timegm(self.data.utctimetuple()))
|
||||||
else:
|
else:
|
||||||
return int(time.mktime(self.data.timetuple()))
|
return int(time.mktime(self.data.timetuple()))
|
||||||
|
93
django/utils/dateparse.py
Normal file
93
django/utils/dateparse.py
Normal file
@ -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)
|
@ -28,6 +28,7 @@ import urlparse
|
|||||||
from django.utils.xmlutils import SimplerXMLGenerator
|
from django.utils.xmlutils import SimplerXMLGenerator
|
||||||
from django.utils.encoding import force_unicode, iri_to_uri
|
from django.utils.encoding import force_unicode, iri_to_uri
|
||||||
from django.utils import datetime_safe
|
from django.utils import datetime_safe
|
||||||
|
from django.utils.timezone import is_aware
|
||||||
|
|
||||||
def rfc2822_date(date):
|
def rfc2822_date(date):
|
||||||
# We can't use strftime() because it produces locale-dependant results, so
|
# We can't use strftime() because it produces locale-dependant results, so
|
||||||
@ -40,7 +41,7 @@ def rfc2822_date(date):
|
|||||||
dow = days[date.weekday()]
|
dow = days[date.weekday()]
|
||||||
month = months[date.month - 1]
|
month = months[date.month - 1]
|
||||||
time_str = date.strftime('%s, %%d %s %%Y %%H:%%M:%%S ' % (dow, month))
|
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)
|
offset = date.tzinfo.utcoffset(date)
|
||||||
timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
|
timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
|
||||||
hour, minute = divmod(timezone, 60)
|
hour, minute = divmod(timezone, 60)
|
||||||
@ -51,7 +52,7 @@ def rfc2822_date(date):
|
|||||||
def rfc3339_date(date):
|
def rfc3339_date(date):
|
||||||
# Support datetime objects older than 1900
|
# Support datetime objects older than 1900
|
||||||
date = datetime_safe.new_datetime(date)
|
date = datetime_safe.new_datetime(date)
|
||||||
if date.tzinfo:
|
if is_aware(date):
|
||||||
time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
|
time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
|
||||||
offset = date.tzinfo.utcoffset(date)
|
offset = date.tzinfo.utcoffset(date)
|
||||||
timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
|
timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.utils.tzinfo import LocalTimezone
|
from django.utils.timezone import is_aware, utc
|
||||||
from django.utils.translation import ungettext, ugettext
|
from django.utils.translation import ungettext, ugettext
|
||||||
|
|
||||||
def timesince(d, now=None):
|
def timesince(d, now=None):
|
||||||
@ -31,13 +31,10 @@ def timesince(d, now=None):
|
|||||||
now = datetime.datetime(now.year, now.month, now.day)
|
now = datetime.datetime(now.year, now.month, now.day)
|
||||||
|
|
||||||
if not now:
|
if not now:
|
||||||
if d.tzinfo:
|
now = datetime.datetime.now(utc if is_aware(d) else None)
|
||||||
now = datetime.datetime.now(LocalTimezone(d))
|
|
||||||
else:
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
|
|
||||||
# ignore microsecond part of 'd' since we removed it from 'now'
|
delta = now - d
|
||||||
delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
|
# ignore microseconds
|
||||||
since = delta.days * 24 * 60 * 60 + delta.seconds
|
since = delta.days * 24 * 60 * 60 + delta.seconds
|
||||||
if since <= 0:
|
if since <= 0:
|
||||||
# d is in the future compared to now, stop processing.
|
# d is in the future compared to now, stop processing.
|
||||||
@ -61,8 +58,5 @@ def timeuntil(d, now=None):
|
|||||||
the given time.
|
the given time.
|
||||||
"""
|
"""
|
||||||
if not now:
|
if not now:
|
||||||
if getattr(d, 'tzinfo', None):
|
now = datetime.datetime.now(utc if is_aware(d) else None)
|
||||||
now = datetime.datetime.now(LocalTimezone(d))
|
|
||||||
else:
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
return timesince(now, d)
|
return timesince(now, d)
|
||||||
|
266
django/utils/timezone.py
Normal file
266
django/utils/timezone.py
Normal file
@ -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)
|
@ -2,8 +2,14 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from datetime import timedelta, tzinfo
|
from datetime import timedelta, tzinfo
|
||||||
|
|
||||||
from django.utils.encoding import smart_unicode, smart_str, DEFAULT_LOCALE_ENCODING
|
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):
|
class FixedOffset(tzinfo):
|
||||||
"Fixed offset in minutes east from UTC."
|
"Fixed offset in minutes east from UTC."
|
||||||
def __init__(self, offset):
|
def __init__(self, offset):
|
||||||
@ -19,6 +25,9 @@ class FixedOffset(tzinfo):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__name
|
return self.__name
|
||||||
|
|
||||||
|
def __getinitargs__(self):
|
||||||
|
return self.__offset,
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
return self.__offset
|
return self.__offset
|
||||||
|
|
||||||
@ -28,15 +37,25 @@ class FixedOffset(tzinfo):
|
|||||||
def dst(self, dt):
|
def dst(self, dt):
|
||||||
return timedelta(0)
|
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):
|
class LocalTimezone(tzinfo):
|
||||||
"Proxy timezone information from time module."
|
"Proxy timezone information from time module."
|
||||||
def __init__(self, dt):
|
def __init__(self, dt):
|
||||||
tzinfo.__init__(self)
|
tzinfo.__init__(self)
|
||||||
|
self.__dt = dt
|
||||||
self._tzname = self.tzname(dt)
|
self._tzname = self.tzname(dt)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return smart_str(self._tzname)
|
return smart_str(self._tzname)
|
||||||
|
|
||||||
|
def __getinitargs__(self):
|
||||||
|
return self.__dt,
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
if self._isdst(dt):
|
if self._isdst(dt):
|
||||||
return timedelta(seconds=-time.altzone)
|
return timedelta(seconds=-time.altzone)
|
||||||
|
@ -347,6 +347,31 @@ function; this syntax is deprecated.
|
|||||||
return mark_safe(result)
|
return mark_safe(result)
|
||||||
initial_letter_filter.needs_autoescape = True
|
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
|
Writing custom template tags
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
@ -546,6 +546,12 @@ Examples::
|
|||||||
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
|
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
|
||||||
[datetime.datetime(2005, 3, 20)]
|
[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
|
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
|
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.
|
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
|
.. fieldlookup:: isnull
|
||||||
|
|
||||||
isnull
|
isnull
|
||||||
|
@ -1810,6 +1810,7 @@ Default::
|
|||||||
"django.core.context_processors.i18n",
|
"django.core.context_processors.i18n",
|
||||||
"django.core.context_processors.media",
|
"django.core.context_processors.media",
|
||||||
"django.core.context_processors.static",
|
"django.core.context_processors.static",
|
||||||
|
"django.core.context_processors.tz",
|
||||||
"django.contrib.messages.context_processors.messages")
|
"django.contrib.messages.context_processors.messages")
|
||||||
|
|
||||||
A tuple of callables that are used to populate the context in ``RequestContext``.
|
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
|
The ``django.core.context_processors.static`` context processor
|
||||||
was added in this release.
|
was added in this release.
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
The ``django.core.context_processors.tz`` context processor
|
||||||
|
was added in this release.
|
||||||
|
|
||||||
.. setting:: TEMPLATE_DEBUG
|
.. setting:: TEMPLATE_DEBUG
|
||||||
|
|
||||||
TEMPLATE_DEBUG
|
TEMPLATE_DEBUG
|
||||||
@ -1971,6 +1976,9 @@ Default: ``'America/Chicago'``
|
|||||||
.. versionchanged:: 1.2
|
.. versionchanged:: 1.2
|
||||||
``None`` was added as an allowed value.
|
``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
|
A string representing the time zone for this installation, or
|
||||||
``None``. `See available choices`_. (Note that list of available
|
``None``. `See available choices`_. (Note that list of available
|
||||||
choices lists more than one on the same line; you'll want to use just
|
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
|
``'Europe/London GB GB-Eire'``, but you should use the first bit of
|
||||||
that -- ``'Europe/London'`` -- as your :setting:`TIME_ZONE` setting.)
|
that -- ``'Europe/London'`` -- as your :setting:`TIME_ZONE` setting.)
|
||||||
|
|
||||||
Note that this is the time zone to which Django will convert all
|
Note that this isn't necessarily the timezone of the server. For example, one
|
||||||
dates/times -- not necessarily the timezone of the server. For
|
server may serve multiple Django-powered sites, each with a separate time zone
|
||||||
example, one server may serve multiple Django-powered sites, each with
|
setting.
|
||||||
a separate time-zone setting.
|
|
||||||
|
|
||||||
Normally, Django sets the ``os.environ['TZ']`` variable to the time
|
When :setting:`USE_TZ` is ``False``, this is the time zone in which Django will
|
||||||
zone you specify in the :setting:`TIME_ZONE` setting. Thus, all your views
|
store all datetimes. When :setting:`USE_TZ` is ``True``, this is the default
|
||||||
and models will automatically operate in the correct time zone.
|
time zone that Django will use to display datetimes in templates and to
|
||||||
However, Django won't set the ``TZ`` environment variable under the
|
interpret datetimes entered in forms.
|
||||||
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
|
* If you're using the manual configuration option as described in
|
||||||
:ref:`manually configuring settings
|
: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
|
environment. If you're running Django on Windows, this variable
|
||||||
must be set to match the system timezone.
|
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
|
.. _See available choices: http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
||||||
|
|
||||||
.. setting:: URL_VALIDATOR_USER_AGENT
|
.. 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
|
``False``, Django will make some optimizations so as not to load the
|
||||||
translation machinery.
|
translation machinery.
|
||||||
|
|
||||||
See also :setting:`USE_L10N`
|
See also :setting:`LANGUAGE_CODE`, :setting:`USE_L10N` and :setting:`USE_TZ`.
|
||||||
|
|
||||||
.. setting:: USE_L10N
|
.. 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
|
default or not. If this is set to ``True``, e.g. Django will display numbers and
|
||||||
dates using the format of the current locale.
|
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::
|
.. note::
|
||||||
|
|
||||||
@ -2082,6 +2092,26 @@ When :setting:`USE_L10N` is set to ``True`` and if this is also set to
|
|||||||
See also :setting:`DECIMAL_SEPARATOR`, :setting:`NUMBER_GROUPING` and
|
See also :setting:`DECIMAL_SEPARATOR`, :setting:`NUMBER_GROUPING` and
|
||||||
:setting:`THOUSAND_SEPARATOR`.
|
:setting:`THOUSAND_SEPARATOR`.
|
||||||
|
|
||||||
|
.. setting:: USE_TZ
|
||||||
|
|
||||||
|
USE_TZ
|
||||||
|
------
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
A boolean that specifies if datetimes will be timezone-aware by default or not.
|
||||||
|
If this is set to ``True``, Django will use timezone-aware datetimes internally.
|
||||||
|
Otherwise, Django will use naive datetimes in local time.
|
||||||
|
|
||||||
|
See also :setting:`TIME_ZONE`, :setting:`USE_I18N` and :setting:`USE_L10N`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The default :file:`settings.py` file created by
|
||||||
|
:djadmin:`django-admin.py startproject <startproject>` includes
|
||||||
|
``USE_TZ = True`` for convenience.
|
||||||
|
|
||||||
.. setting:: USE_X_FORWARDED_HOST
|
.. setting:: USE_X_FORWARDED_HOST
|
||||||
|
|
||||||
USE_X_FORWARDED_HOST
|
USE_X_FORWARDED_HOST
|
||||||
|
@ -2318,8 +2318,45 @@ Value Argument Outputs
|
|||||||
if no mapping for None is given)
|
if no mapping for None is given)
|
||||||
========== ====================== ==================================
|
========== ====================== ==================================
|
||||||
|
|
||||||
Other tags and filter libraries
|
Internationalization tags and filters
|
||||||
-------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
Django provides template tags and filters to control each aspect of
|
||||||
|
`internationalization </topics/i18n/index>`_ in templates. They allow for
|
||||||
|
granular control of translations, formatting, and time zone conversions.
|
||||||
|
|
||||||
|
i18n
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
This library allows specifying translatable text in templates.
|
||||||
|
To enable it, set :setting:`USE_I18N` to ``True``, then load it with
|
||||||
|
``{% load i18n %}``.
|
||||||
|
|
||||||
|
See :ref:`specifying-translation-strings-in-template-code`.
|
||||||
|
|
||||||
|
l10n
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
This library provides control over the localization of values in templates.
|
||||||
|
You only need to load the library using ``{% load l10n %}``, but you'll often
|
||||||
|
set :setting:`USE_L10N` to ``True`` so that localization is active by default.
|
||||||
|
|
||||||
|
See :ref:`topic-l10n-templates`.
|
||||||
|
|
||||||
|
tz
|
||||||
|
^^
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
|
This library provides control over time zone conversions in templates.
|
||||||
|
Like ``l10n``, you only need to load the library using ``{% load tz %}``,
|
||||||
|
but you'll usually also set :setting:`USE_TZ` to ``True`` so that conversion
|
||||||
|
to local time happens by default.
|
||||||
|
|
||||||
|
See :ref:`time-zones-in-templates`.
|
||||||
|
|
||||||
|
Other tags and filters libraries
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
Django comes with a couple of other template-tag libraries that you have to
|
Django comes with a couple of other template-tag libraries that you have to
|
||||||
enable explicitly in your :setting:`INSTALLED_APPS` setting and enable in your
|
enable explicitly in your :setting:`INSTALLED_APPS` setting and enable in your
|
||||||
@ -2348,28 +2385,6 @@ django.contrib.webdesign
|
|||||||
A collection of template tags that can be useful while designing a Web site,
|
A collection of template tags that can be useful while designing a Web site,
|
||||||
such as a generator of Lorem Ipsum text. See :doc:`/ref/contrib/webdesign`.
|
such as a generator of Lorem Ipsum text. See :doc:`/ref/contrib/webdesign`.
|
||||||
|
|
||||||
i18n
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
Provides a couple of templatetags that allow specifying translatable text in
|
|
||||||
Django templates. It is slightly different from the libraries described
|
|
||||||
above because you don't need to add any application to the
|
|
||||||
:setting:`INSTALLED_APPS` setting but rather set :setting:`USE_I18N` to True,
|
|
||||||
then loading it with ``{% load i18n %}``.
|
|
||||||
|
|
||||||
See :ref:`specifying-translation-strings-in-template-code`.
|
|
||||||
|
|
||||||
l10n
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
Provides a couple of templatetags that allow control over the localization of
|
|
||||||
values in Django templates. It is slightly different from the libraries
|
|
||||||
described above because you don't need to add any application to the
|
|
||||||
:setting:`INSTALLED_APPS`; you only need to load the library using
|
|
||||||
``{% load l10n %}``.
|
|
||||||
|
|
||||||
See :ref:`topic-l10n-templates`.
|
|
||||||
|
|
||||||
static
|
static
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
|
@ -131,6 +131,41 @@ results. Instead do::
|
|||||||
|
|
||||||
SortedDict([('b', 1), ('a', 2), ('c', 3)])
|
SortedDict([('b', 1), ('a', 2), ('c', 3)])
|
||||||
|
|
||||||
|
``django.utils.dateparse``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
|
.. module:: django.utils.dateparse
|
||||||
|
:synopsis: Functions to parse datetime objects.
|
||||||
|
|
||||||
|
The functions defined in this module share the following properties:
|
||||||
|
|
||||||
|
- They raise :exc:`ValueError` if their input is well formatted but isn't a
|
||||||
|
valid date or time.
|
||||||
|
- They return ``None`` if it isn't well formatted at all.
|
||||||
|
- They accept up to picosecond resolution in input, but they truncate it to
|
||||||
|
microseconds, since that's what Python supports.
|
||||||
|
|
||||||
|
.. function:: parse_date(value)
|
||||||
|
|
||||||
|
Parses a string and returns a :class:`datetime.date`.
|
||||||
|
|
||||||
|
.. function:: parse_time(value)
|
||||||
|
|
||||||
|
Parses a string and returns a :class:`datetime.time`.
|
||||||
|
|
||||||
|
UTC offsets aren't supported; if ``value`` describes one, the result is
|
||||||
|
``None``.
|
||||||
|
|
||||||
|
.. function:: parse_datetime(value)
|
||||||
|
|
||||||
|
Parses a string and returns a :class:`datetime.datetime`.
|
||||||
|
|
||||||
|
UTC offsets are supported; if ``value`` describes one, the result's
|
||||||
|
``tzinfo`` attribute is a :class:`~django.utils.tzinfo.FixedOffset`
|
||||||
|
instance.
|
||||||
|
|
||||||
``django.utils.encoding``
|
``django.utils.encoding``
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
@ -573,6 +608,96 @@ For a complete discussion on the usage of the following see the
|
|||||||
so by translating the Django translation tags into standard gettext function
|
so by translating the Django translation tags into standard gettext function
|
||||||
invocations.
|
invocations.
|
||||||
|
|
||||||
|
.. _time-zone-selection-functions:
|
||||||
|
|
||||||
|
``django.utils.timezone``
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
|
.. module:: django.utils.timezone
|
||||||
|
:synopsis: Timezone support.
|
||||||
|
|
||||||
|
.. data:: utc
|
||||||
|
|
||||||
|
:class:`~datetime.tzinfo` instance that represents UTC.
|
||||||
|
|
||||||
|
.. function:: get_default_timezone()
|
||||||
|
|
||||||
|
Returns a :class:`~datetime.tzinfo` instance that represents the
|
||||||
|
:ref:`default time zone <default-current-time-zone>`.
|
||||||
|
|
||||||
|
.. function:: get_default_timezone_name()
|
||||||
|
|
||||||
|
Returns the name of the :ref:`default time zone
|
||||||
|
<default-current-time-zone>`.
|
||||||
|
|
||||||
|
.. function:: get_current_timezone()
|
||||||
|
|
||||||
|
Returns a :class:`~datetime.tzinfo` instance that represents the
|
||||||
|
:ref:`current time zone <default-current-time-zone>`.
|
||||||
|
|
||||||
|
.. function:: get_current_timezone_name()
|
||||||
|
|
||||||
|
Returns the name of the :ref:`current time zone
|
||||||
|
<default-current-time-zone>`.
|
||||||
|
|
||||||
|
.. function:: activate(timezone)
|
||||||
|
|
||||||
|
Sets the :ref:`current time zone <default-current-time-zone>`. The
|
||||||
|
``timezone`` argument must be an instance of a :class:`~datetime.tzinfo`
|
||||||
|
subclass or, if pytz_ is available, a time zone name.
|
||||||
|
|
||||||
|
.. function:: deactivate()
|
||||||
|
|
||||||
|
Unsets the :ref:`current time zone <default-current-time-zone>`.
|
||||||
|
|
||||||
|
.. function:: override(timezone)
|
||||||
|
|
||||||
|
This is a Python context manager that sets the :ref:`current time zone
|
||||||
|
<default-current-time-zone>` on entry with :func:`activate()`, and restores
|
||||||
|
the previously active time zone on exit. If the ``timezone`` argument is
|
||||||
|
``None``, the :ref:`current time zone <default-current-time-zone>` is unset
|
||||||
|
on entry with :func:`deactivate()` instead.
|
||||||
|
|
||||||
|
.. function:: aslocaltime(value, use_tz=None)
|
||||||
|
|
||||||
|
This function is used by the template engine to convert datetimes to local
|
||||||
|
time where appropriate.
|
||||||
|
|
||||||
|
.. function:: now()
|
||||||
|
|
||||||
|
Returns an aware or naive :class:`~datetime.datetime` that represents the
|
||||||
|
current point in time when :setting:`USE_TZ` is ``True`` or ``False``
|
||||||
|
respectively.
|
||||||
|
|
||||||
|
.. function:: is_aware(value)
|
||||||
|
|
||||||
|
Returns ``True`` if ``value`` is aware, ``False`` if it is naive. This
|
||||||
|
function assumes that ``value`` is a :class:`~datetime.datetime`.
|
||||||
|
|
||||||
|
.. function:: is_naive(value)
|
||||||
|
|
||||||
|
Returns ``True`` if ``value`` is naive, ``False`` if it is aware. This
|
||||||
|
function assumes that ``value`` is a :class:`~datetime.datetime`.
|
||||||
|
|
||||||
|
.. function:: make_aware(value, timezone)
|
||||||
|
|
||||||
|
Returns an aware :class:`~datetime.datetime` that represents the same
|
||||||
|
point in time as ``value`` in ``timezone``, ``value`` being a naive
|
||||||
|
:class:`~datetime.datetime`.
|
||||||
|
|
||||||
|
This function can raise an exception if ``value`` doesn't exist or is
|
||||||
|
ambiguous because of DST transitions.
|
||||||
|
|
||||||
|
.. function:: make_naive(value, timezone)
|
||||||
|
|
||||||
|
Returns an naive :class:`~datetime.datetime` that represents in
|
||||||
|
``timezone`` the same point in time as ``value``, ``value`` being an
|
||||||
|
aware :class:`~datetime.datetime`
|
||||||
|
|
||||||
|
.. _pytz: http://pytz.sourceforge.net/
|
||||||
|
|
||||||
``django.utils.tzinfo``
|
``django.utils.tzinfo``
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
@ -409,7 +409,6 @@ If the same code is imported inconsistently (some places with the project
|
|||||||
prefix, some places without it), the imports will need to be cleaned up when
|
prefix, some places without it), the imports will need to be cleaned up when
|
||||||
switching to the new ``manage.py``.
|
switching to the new ``manage.py``.
|
||||||
|
|
||||||
|
|
||||||
Improved WSGI support
|
Improved WSGI support
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -427,6 +426,25 @@ callable :djadmin:`runserver` uses.
|
|||||||
(The :djadmin:`runfcgi` management command also internally wraps the WSGI
|
(The :djadmin:`runfcgi` management command also internally wraps the WSGI
|
||||||
callable configured via :setting:`WSGI_APPLICATION`.)
|
callable configured via :setting:`WSGI_APPLICATION`.)
|
||||||
|
|
||||||
|
Support for time zones
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Django 1.4 adds :ref:`support for time zones <time-zones>`. When it's enabled,
|
||||||
|
Django stores date and time information in UTC in the database, uses time
|
||||||
|
zone-aware datetime objects internally, and translates them to the end user's
|
||||||
|
time zone in templates and forms.
|
||||||
|
|
||||||
|
Reasons for using this feature include:
|
||||||
|
|
||||||
|
- Customizing date and time display for users around the world.
|
||||||
|
- Storing datetimes in UTC for database portability and interoperability.
|
||||||
|
(This argument doesn't apply to PostgreSQL, because it already stores
|
||||||
|
timestamps with time zone information in Django 1.3.)
|
||||||
|
- Avoiding data corruption problems around DST transitions.
|
||||||
|
|
||||||
|
Time zone support in enabled by default in new projects created with
|
||||||
|
:djadmin:`startproject`. If you want to use this feature in an existing
|
||||||
|
project, there is a :ref:`migration guide <time-zones-migration-guide>`.
|
||||||
|
|
||||||
Minor features
|
Minor features
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
@ -616,6 +634,39 @@ immediately raise a 404. Additionally redirects returned by flatpages are now
|
|||||||
permanent (301 status code) to match the behavior of the
|
permanent (301 status code) to match the behavior of the
|
||||||
:class:`~django.middleware.common.CommonMiddleware`.
|
:class:`~django.middleware.common.CommonMiddleware`.
|
||||||
|
|
||||||
|
Serialization of :class:`~datetime.datetime` and :class:`~datetime.time`
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
As a consequence of time zone support, and according to the ECMA-262
|
||||||
|
specification, some changes were made to the JSON serializer:
|
||||||
|
|
||||||
|
- It includes the time zone for aware datetime objects. It raises an exception
|
||||||
|
for aware time objects.
|
||||||
|
- It includes milliseconds for datetime and time objects. There is still
|
||||||
|
some precision loss, because Python stores microseconds (6 digits) and JSON
|
||||||
|
only supports milliseconds (3 digits). However, it's better than discarding
|
||||||
|
microseconds entirely.
|
||||||
|
|
||||||
|
The XML serializer was also changed to use ISO8601 for datetimes. The letter
|
||||||
|
``T`` is used to separate the date part from the time part, instead of a
|
||||||
|
space. Time zone information is included in the ``[+-]HH:MM`` format.
|
||||||
|
|
||||||
|
The serializers will dump datetimes in fixtures with these new formats. They
|
||||||
|
can still load fixtures that use the old format.
|
||||||
|
|
||||||
|
``supports_timezone`` changed to ``False`` for SQLite
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The database feature ``supports_timezone`` used to be ``True`` for SQLite.
|
||||||
|
Indeed, if you saved an aware datetime object, SQLite stored a string that
|
||||||
|
included an UTC offset. However, this offset was ignored when loading the value
|
||||||
|
back from the database, which could corrupt the data.
|
||||||
|
|
||||||
|
In the context of time zone support, this flag was changed to ``False``, and
|
||||||
|
datetimes are now stored without time zone information in SQLite. When
|
||||||
|
:setting:`USE_TZ` is ``False``, if you attempt to save an aware datetime
|
||||||
|
object, Django raises an exception.
|
||||||
|
|
||||||
`COMMENTS_BANNED_USERS_GROUP` setting
|
`COMMENTS_BANNED_USERS_GROUP` setting
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -502,7 +502,9 @@ cache multilingual sites without having to create the cache key yourself.
|
|||||||
|
|
||||||
.. versionchanged:: 1.4
|
.. versionchanged:: 1.4
|
||||||
|
|
||||||
This also happens when :setting:`USE_L10N` is set to ``True``.
|
Cache keys also include the active :term:`language <language code>` when
|
||||||
|
:setting:`USE_L10N` is set to ``True`` and the :ref:`current time zone
|
||||||
|
<default-current-time-zone>` when :setting:`USE_TZ` is set to ``True``.
|
||||||
|
|
||||||
__ `Controlling cache: Using other headers`_
|
__ `Controlling cache: Using other headers`_
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ Internationalization and localization
|
|||||||
|
|
||||||
translation
|
translation
|
||||||
formatting
|
formatting
|
||||||
|
timezones
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
========
|
========
|
||||||
@ -17,8 +18,8 @@ application to offer its content in languages and formats tailored to the
|
|||||||
audience.
|
audience.
|
||||||
|
|
||||||
Django has full support for :doc:`translation of text
|
Django has full support for :doc:`translation of text
|
||||||
</topics/i18n/translation>` and :doc:`formatting of dates, times and numbers
|
</topics/i18n/translation>`, :doc:`formatting of dates, times and numbers
|
||||||
</topics/i18n/formatting>`.
|
</topics/i18n/formatting>`, and :doc:`time zones </topics/i18n/timezones>`.
|
||||||
|
|
||||||
Essentially, Django does two things:
|
Essentially, Django does two things:
|
||||||
|
|
||||||
@ -27,8 +28,9 @@ Essentially, Django does two things:
|
|||||||
* It uses these hooks to localize Web apps for particular users according to
|
* It uses these hooks to localize Web apps for particular users according to
|
||||||
their preferences.
|
their preferences.
|
||||||
|
|
||||||
Obviously, translation depends on the target language. Formatting usually
|
Obviously, translation depends on the target language, and formatting usually
|
||||||
depends on the target country.
|
depends on the target country. These informations are provided by browsers in
|
||||||
|
the ``Accept-Language`` header. However, the time zone isn't readily available.
|
||||||
|
|
||||||
Definitions
|
Definitions
|
||||||
===========
|
===========
|
||||||
|
429
docs/topics/i18n/timezones.txt
Normal file
429
docs/topics/i18n/timezones.txt
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
.. _time-zones:
|
||||||
|
|
||||||
|
==========
|
||||||
|
Time zones
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
When support for time zones is enabled, Django stores date and time
|
||||||
|
information in UTC in the database, uses time zone-aware datetime objects
|
||||||
|
internally, and translates them to the end user's time zone in templates and
|
||||||
|
forms.
|
||||||
|
|
||||||
|
This is handy if your users live in more than one time zone and you want to
|
||||||
|
display date and time information according to each user's wall clock. Even if
|
||||||
|
your website is available in only one time zone, it's still a good practice to
|
||||||
|
store data in UTC in your database. Here is why.
|
||||||
|
|
||||||
|
Many countries have a system of daylight saving time (DST), where clocks are
|
||||||
|
moved forwards in spring and backwards in autumn. If you're working in local
|
||||||
|
time, you're likely to encounter errors twice a year, when the transitions
|
||||||
|
happen. pytz' docs discuss `these issues`_ in greater detail. It probably
|
||||||
|
doesn't matter for your blog, but it's more annoying if you over-bill or
|
||||||
|
under-bill your customers by one hour, twice a year, every year. The solution
|
||||||
|
to this problem is to use UTC in the code and local time only when
|
||||||
|
interacting with end users.
|
||||||
|
|
||||||
|
Time zone support is disabled by default. To enable it, set :setting:`USE_TZ =
|
||||||
|
True <USE_TZ>` in your settings file. Installing pytz_ is highly recommended,
|
||||||
|
but not mandatory.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The default :file:`settings.py` file created by :djadmin:`django-admin.py
|
||||||
|
startproject <startproject>` includes :setting:`USE_TZ = True <USE_TZ>`
|
||||||
|
for convenience.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
There is also an independent but related :setting:`USE_L10N` setting that
|
||||||
|
controls if Django should activate format localization. See
|
||||||
|
:doc:`/topics/i18n/formatting` for more details.
|
||||||
|
|
||||||
|
Concepts
|
||||||
|
========
|
||||||
|
|
||||||
|
Naive and aware datetime objects
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Python's :class:`datetime.datetime` objects have a ``tzinfo`` attribute that
|
||||||
|
can be used to store time zone information, represented as an instance of a
|
||||||
|
subclass of :class:`datetime.tzinfo`. When this attribute is set and describes
|
||||||
|
an offset, a datetime object is **aware**; otherwise, it's **naive**.
|
||||||
|
|
||||||
|
You can use :func:`~django.utils.timezone.is_aware` and
|
||||||
|
:func:`~django.utils.timezone.is_naive` to determine if datetimes are aware or
|
||||||
|
naive.
|
||||||
|
|
||||||
|
When time zone support is disabled, Django uses naive datetime objects in local
|
||||||
|
time. This is simple and sufficient for many use cases. In this mode, to obtain
|
||||||
|
the current time, you would write::
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
|
When time zone support is enabled, Django uses time zone aware datetime
|
||||||
|
objects. If your code creates datetime objects, they should be aware too. In
|
||||||
|
this mode, the example above becomes::
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.utils.timezone import utc
|
||||||
|
|
||||||
|
now = datetime.datetime.utcnow().replace(tzinfo=utc)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
:mod:`django.utils.timezone` provides a
|
||||||
|
:func:`~django.utils.timezone.now()` function that returns a naive or
|
||||||
|
aware datetime object according to the value of :setting:`USE_TZ`.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Dealing with aware datetime objects isn't always intuitive. For instance,
|
||||||
|
the ``tzinfo`` argument of the standard datetime constructor doesn't work
|
||||||
|
reliably for time zones with DST. Using UTC is generally safe; if you're
|
||||||
|
using other time zones, you should review `pytz' documentation <pytz>`_
|
||||||
|
carefully.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Python's :class:`datetime.time` objects also feature a ``tzinfo``
|
||||||
|
attribute, and PostgreSQL has a matching ``time with time zone`` type.
|
||||||
|
However, as PostgreSQL's docs put it, this type "exhibits properties which
|
||||||
|
lead to questionable usefulness".
|
||||||
|
|
||||||
|
Django only supports naive time objects and will raise an exception if you
|
||||||
|
attempt to save an aware time object.
|
||||||
|
|
||||||
|
.. _naive-datetime-objects:
|
||||||
|
|
||||||
|
Interpretation of naive datetime objects
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
When :setting:`USE_TZ` is ``True``, Django still accepts naive datetime
|
||||||
|
objects, in order to preserve backwards-compatibility. It attempts to make them
|
||||||
|
aware by interpreting them in the :ref:`default time zone
|
||||||
|
<default-current-time-zone>`.
|
||||||
|
|
||||||
|
Unfortunately, during DST transitions, some datetimes don't exist or are
|
||||||
|
ambiguous. In such situations, pytz_ raises an exception. Other
|
||||||
|
:class:`~datetime.tzinfo` implementations, such as the local time zone used as
|
||||||
|
a fallback when pytz_ isn't installed, may raise an exception or return
|
||||||
|
inaccurate results. That's why you should always create aware datetime objects
|
||||||
|
when time zone support is enabled.
|
||||||
|
|
||||||
|
In practice, this is rarely an issue. Django gives you aware datetime objects
|
||||||
|
in the models and forms, and most often, new datetime objects are created from
|
||||||
|
existing ones through :class:`~datetime.timedelta` arithmetic. The only
|
||||||
|
datetime that's often created in application code is the current time, and
|
||||||
|
:func:`timezone.now() <django.utils.timezone.now>` automatically does the
|
||||||
|
right thing.
|
||||||
|
|
||||||
|
.. _default-current-time-zone:
|
||||||
|
|
||||||
|
Default time zone and current time zone
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
The **default time zone** is the time zone defined by the :setting:`TIME_ZONE`
|
||||||
|
setting.
|
||||||
|
|
||||||
|
When pytz_ is available, Django loads the definition of the default time zone
|
||||||
|
from the `tz database`_. This is the most accurate solution. Otherwise, it
|
||||||
|
relies on the difference between local time and UTC, as reported by the
|
||||||
|
operating system, to compute conversions. This is less reliable, especially
|
||||||
|
around DST transitions.
|
||||||
|
|
||||||
|
The **current time zone** is the time zone that's used for rendering.
|
||||||
|
|
||||||
|
You should set it to the end user's actual time zone with
|
||||||
|
:func:`~django.utils.timezone.activate`. Otherwise, the default time zone is
|
||||||
|
used.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
As explained in the documentation of :setting:`TIME_ZONE`, Django sets
|
||||||
|
environment variables so that its process runs in the default time zone.
|
||||||
|
This happens regardless of the value of :setting:`USE_TZ` and of the
|
||||||
|
current time zone.
|
||||||
|
|
||||||
|
When :setting:`USE_TZ` is ``True``, this is useful to preserve
|
||||||
|
backwards-compatibility with applications that still rely on local time.
|
||||||
|
However, :ref:`as explained above <naive-datetime-objects>`, this isn't
|
||||||
|
entirely reliable, and you should always work with aware datetimes in UTC
|
||||||
|
in your own code. For instance, use
|
||||||
|
:meth:`~datetime.datetime.utcfromtimestamp` instead of
|
||||||
|
:meth:`~datetime.datetime.fromtimestamp` -- and don't forget to set
|
||||||
|
``tzinfo`` to :data:`~django.utils.timezone.utc`.
|
||||||
|
|
||||||
|
Selecting the current time zone
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
The current time zone is the equivalent of the current :term:`locale <locale
|
||||||
|
name>` for translations. However, there's no equivalent of the
|
||||||
|
``Accept-Language`` HTTP header that Django could use to determine the user's
|
||||||
|
time zone automatically. Instead, Django provides :ref:`time zone selection
|
||||||
|
functions <time-zone-selection-functions>`. Use them to build the time zone
|
||||||
|
selection logic that makes sense for you.
|
||||||
|
|
||||||
|
Most websites who care about time zones just ask users in which time zone they
|
||||||
|
live and store this information in the user's profile. For anonymous users,
|
||||||
|
they use the time zone of their primary audience or UTC. pytz_ provides
|
||||||
|
helpers, like a list of time zones per country, that you can use to pre-select
|
||||||
|
the most likely choices.
|
||||||
|
|
||||||
|
Here's an example that stores the current timezone in the session. (It skips
|
||||||
|
error handling entirely for the sake of simplicity.)
|
||||||
|
|
||||||
|
Add the following middleware to :setting:`MIDDLEWARE_CLASSES`::
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
class TimezoneMiddleware(object):
|
||||||
|
def process_request(self, request):
|
||||||
|
tz = request.session.get('django_timezone')
|
||||||
|
if tz:
|
||||||
|
timezone.activate(tz)
|
||||||
|
|
||||||
|
Create a view that can set the current timezone::
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
|
def set_timezone(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
request.session[session_key] = pytz.timezone(request.POST['timezone'])
|
||||||
|
return redirect('/')
|
||||||
|
else:
|
||||||
|
return render(request, 'template.html', {'timezones': pytz.common_timezones})
|
||||||
|
|
||||||
|
Include in :file:`template.html` a form that will ``POST`` to this view:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% load tz %}{% load url from future %}
|
||||||
|
<form action="{% url 'set_timezone' %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<label for="timezone">Time zone:</label>
|
||||||
|
<select name="timezone">
|
||||||
|
{% for tz in timezones %}
|
||||||
|
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected="selected"{% endif %}>{{ tz }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Set" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
Time zone aware input in forms
|
||||||
|
==============================
|
||||||
|
|
||||||
|
When you enable time zone support, Django interprets datetimes entered in
|
||||||
|
forms in the :ref:`current time zone <default-current-time-zone>` and returns
|
||||||
|
aware datetime objects in ``cleaned_data``.
|
||||||
|
|
||||||
|
If the current time zone raises an exception for datetimes that don't exist or
|
||||||
|
are ambiguous because they fall in a DST transition (the timezones provided by
|
||||||
|
pytz_ do this), such datetimes will be reported as invalid values.
|
||||||
|
|
||||||
|
.. _time-zones-in-templates:
|
||||||
|
|
||||||
|
Time zone aware output in templates
|
||||||
|
===================================
|
||||||
|
|
||||||
|
When you enable time zone support, Django converts aware datetime objects to
|
||||||
|
the :ref:`current time zone <default-current-time-zone>` when they're rendered
|
||||||
|
in templates. This behaves very much like :doc:`format localization
|
||||||
|
</topics/i18n/formatting>`.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Django doesn't convert naive datetime objects, because they could be
|
||||||
|
ambiguous, and because your code should never produce naive datetimes when
|
||||||
|
time zone support is enabled. However, you can force conversion with the
|
||||||
|
template filters described below.
|
||||||
|
|
||||||
|
Conversion to local time isn't always appropriate -- you may be generating
|
||||||
|
output for computers rather than for humans. The following filters and tags,
|
||||||
|
provided the ``tz`` template library, allow you to control the time zone
|
||||||
|
conversions.
|
||||||
|
|
||||||
|
Template tags
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. templatetag:: localtime
|
||||||
|
|
||||||
|
localtime
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
Enables or disables conversion of aware datetime objects to the current time
|
||||||
|
zone in the contained block.
|
||||||
|
|
||||||
|
This tag has exactly the same effects as the :setting:`USE_TZ` setting as far
|
||||||
|
as the template engine is concerned. It allows a more fine grained control of
|
||||||
|
conversion.
|
||||||
|
|
||||||
|
To activate or deactivate conversion for a template block, use::
|
||||||
|
|
||||||
|
{% load tz %}
|
||||||
|
|
||||||
|
{% localtime on %}
|
||||||
|
{{ value }}
|
||||||
|
{% endlocaltime %}
|
||||||
|
|
||||||
|
{% localtime off %}
|
||||||
|
{{ value }}
|
||||||
|
{% endlocaltime %}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The value of :setting:`USE_TZ` isn't respected inside of a
|
||||||
|
``{% localtime %}`` block.
|
||||||
|
|
||||||
|
.. templatetag:: timezone
|
||||||
|
|
||||||
|
timezone
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Sets or unsets the current time zone in the contained block. When the current
|
||||||
|
time zone is unset, the default time zone applies.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{% load tz %}
|
||||||
|
|
||||||
|
{% timezone "Europe/Paris" %}
|
||||||
|
Paris time: {{ value }}
|
||||||
|
{% endtimezone %}
|
||||||
|
|
||||||
|
{% timezone None %}
|
||||||
|
Server time: {{ value }}
|
||||||
|
{% endtimezone %}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
In the second block, ``None`` resolves to the Python object ``None``
|
||||||
|
because isn't defined in the template context, not because it's the string
|
||||||
|
``None``.
|
||||||
|
|
||||||
|
.. templatetag:: get_current_timezone
|
||||||
|
|
||||||
|
get_current_timezone
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When the :func:`django.core.context_processors.tz` context processor is
|
||||||
|
enabled -- by default, it is -- each :class:`~django.template.RequestContext`
|
||||||
|
contains a ``TIME_ZONE`` variable that provides the name of the current time
|
||||||
|
zone.
|
||||||
|
|
||||||
|
If you don't use a :class:`~django.template.RequestContext`, you can obtain
|
||||||
|
this value with the ``get_current_timezone`` tag::
|
||||||
|
|
||||||
|
{% get_current_timezone as TIME_ZONE %}
|
||||||
|
|
||||||
|
Template filters
|
||||||
|
----------------
|
||||||
|
|
||||||
|
These filters accept both aware and naive datetimes. For conversion purposes,
|
||||||
|
they assume that naive datetimes are in the default time zone. They always
|
||||||
|
return aware datetimes.
|
||||||
|
|
||||||
|
.. templatefilter:: aslocaltime
|
||||||
|
|
||||||
|
aslocaltime
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
Forces conversion of a single value to the current time zone.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
{% load tz %}
|
||||||
|
|
||||||
|
{{ value|aslocaltime }}
|
||||||
|
|
||||||
|
.. templatefilter:: asutc
|
||||||
|
|
||||||
|
asutc
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
Forces conversion of a single value to UTC.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
{% load tz %}
|
||||||
|
|
||||||
|
{{ value|asutc }}
|
||||||
|
|
||||||
|
astimezone
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Forces conversion of a single value to an arbitrary timezone.
|
||||||
|
|
||||||
|
The argument must be an instance of a :class:`~datetime.tzinfo` subclass or a
|
||||||
|
time zone name. If it is a time zone name, pytz_ is required.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
{% load tz %}
|
||||||
|
|
||||||
|
{{ value|astimezone:"Europe/Paris" }}
|
||||||
|
|
||||||
|
.. _time-zones-migration-guide:
|
||||||
|
|
||||||
|
Migration guide
|
||||||
|
===============
|
||||||
|
|
||||||
|
Here's how to migrate a project that was started before Django supported time
|
||||||
|
zones.
|
||||||
|
|
||||||
|
Data
|
||||||
|
----
|
||||||
|
|
||||||
|
PostgreSQL
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
The PostgreSQL backend stores datetimes as ``timestamp with time zone``. In
|
||||||
|
practice, this means it converts datetimes from the connection's time zone to
|
||||||
|
UTC on storage, and from UTC to the connection's time zone on retrieval.
|
||||||
|
|
||||||
|
As a consequence, if you're using PostgreSQL, you can switch between ``USE_TZ
|
||||||
|
= False`` and ``USE_TZ = True`` freely. The database connection's time zone
|
||||||
|
will be set to :setting:`TIME_ZONE` or ``UTC`` respectively, so that Django
|
||||||
|
obtains correct datetimes in all cases. You don't need to perform any data
|
||||||
|
conversions.
|
||||||
|
|
||||||
|
Other databases
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Other backends store datetimes without time zone information. If you switch
|
||||||
|
from ``USE_TZ = False`` to ``USE_TZ = True``, you must convert your data from
|
||||||
|
local time to UTC -- which isn't deterministic if your local time has DST.
|
||||||
|
|
||||||
|
Code
|
||||||
|
----
|
||||||
|
|
||||||
|
The first step is to add :setting:`USE_TZ = True <USE_TZ>` to your settings
|
||||||
|
file and install pytz_ (if possible). At this point, things should mostly
|
||||||
|
work. If you create naive datetime objects in your code, Django makes them
|
||||||
|
aware when necessary.
|
||||||
|
|
||||||
|
However, these conversions may fail around DST transitions, which means you
|
||||||
|
aren't getting the full benefits of time zone support yet. Also, you're likely
|
||||||
|
to run into a few problems because it's impossible to compare a naive datetime
|
||||||
|
with an aware datetime. Since Django now gives you aware datetimes, you'll get
|
||||||
|
exceptions wherever you compare a datetime that comes from a model or a form
|
||||||
|
with a naive datetime that you've created in your code.
|
||||||
|
|
||||||
|
So the second step is to refactor your code wherever you instanciate datetime
|
||||||
|
objects to make them aware. This can be done incrementally.
|
||||||
|
:mod:`django.utils.timezone` defines some handy helpers for compatibility
|
||||||
|
code: :func:`~django.utils.timezone.is_aware`,
|
||||||
|
:func:`~django.utils.timezone.is_naive`,
|
||||||
|
:func:`~django.utils.timezone.make_aware`, and
|
||||||
|
:func:`~django.utils.timezone.make_naive`.
|
||||||
|
|
||||||
|
.. _pytz: http://pytz.sourceforge.net/
|
||||||
|
.. _these issues: http://pytz.sourceforge.net/#problems-with-localtime
|
||||||
|
.. _tz database: http://en.wikipedia.org/wiki/Tz_database
|
@ -56,25 +56,25 @@ class FixtureLoadingTests(TestCase):
|
|||||||
])
|
])
|
||||||
|
|
||||||
# Dump the current contents of the database as a JSON fixture
|
# Dump the current contents of the database as a JSON fixture
|
||||||
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
|
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}]')
|
||||||
|
|
||||||
# Try just dumping the contents of fixtures.Category
|
# Try just dumping the contents of fixtures.Category
|
||||||
self._dumpdata_assert(['fixtures.Category'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}]')
|
self._dumpdata_assert(['fixtures.Category'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}]')
|
||||||
|
|
||||||
# ...and just fixtures.Article
|
# ...and just fixtures.Article
|
||||||
self._dumpdata_assert(['fixtures.Article'], '[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
|
self._dumpdata_assert(['fixtures.Article'], '[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}]')
|
||||||
|
|
||||||
# ...and both
|
# ...and both
|
||||||
self._dumpdata_assert(['fixtures.Category', 'fixtures.Article'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
|
self._dumpdata_assert(['fixtures.Category', 'fixtures.Article'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}]')
|
||||||
|
|
||||||
# Specify a specific model twice
|
# Specify a specific model twice
|
||||||
self._dumpdata_assert(['fixtures.Article', 'fixtures.Article'], '[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
|
self._dumpdata_assert(['fixtures.Article', 'fixtures.Article'], '[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}]')
|
||||||
|
|
||||||
# Specify a dump that specifies Article both explicitly and implicitly
|
# Specify a dump that specifies Article both explicitly and implicitly
|
||||||
self._dumpdata_assert(['fixtures.Article', 'fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
|
self._dumpdata_assert(['fixtures.Article', 'fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}]')
|
||||||
|
|
||||||
# Same again, but specify in the reverse order
|
# Same again, but specify in the reverse order
|
||||||
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
|
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}]')
|
||||||
|
|
||||||
# Specify one model from one application, and an entire other application.
|
# Specify one model from one application, and an entire other application.
|
||||||
self._dumpdata_assert(['fixtures.Category', 'sites'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}]')
|
self._dumpdata_assert(['fixtures.Category', 'sites'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}]')
|
||||||
@ -153,11 +153,11 @@ class FixtureLoadingTests(TestCase):
|
|||||||
self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True)
|
self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True)
|
||||||
|
|
||||||
# Dump the current contents of the database as a JSON fixture
|
# Dump the current contents of the database as a JSON fixture
|
||||||
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16 16:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16 15:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16 14:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": ["Django Reinhardt"], "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": ["Stephane Grappelli"], "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": ["Artist formerly known as \\"Prince\\""], "permissions": [["change_user", "auth", "user"]]}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True)
|
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16T16:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16T15:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16T14:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16T11:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": ["Django Reinhardt"], "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": ["Stephane Grappelli"], "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": ["Artist formerly known as \\"Prince\\""], "permissions": [["change_user", "auth", "user"]]}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True)
|
||||||
|
|
||||||
# Dump the current contents of the database as an XML fixture
|
# Dump the current contents of the database as an XML fixture
|
||||||
self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
|
self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
|
||||||
<django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="5" model="fixtures.article"><field type="CharField" name="headline">XML identified as leading cause of cancer</field><field type="DateTimeField" name="pub_date">2006-06-16 16:00:00</field></object><object pk="4" model="fixtures.article"><field type="CharField" name="headline">Django conquers world!</field><field type="DateTimeField" name="pub_date">2006-06-16 15:00:00</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Copyright is fine the way it is</field><field type="DateTimeField" name="pub_date">2006-06-16 14:00:00</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker on TV is great!</field><field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field></object><object pk="1" model="fixtures.article"><field type="CharField" name="headline">Python program becomes self aware</field><field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">legal</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="3" model="fixtures.tag"><field type="CharField" name="name">django</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">4</field></object><object pk="4" model="fixtures.tag"><field type="CharField" name="name">world domination</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">4</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Artist formerly known as "Prince"</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object><object pk="1" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel"><natural>Django Reinhardt</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>add_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>change_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>delete_user</natural><natural>auth</natural><natural>user</natural></object></field></object><object pk="2" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel"><natural>Stephane Grappelli</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>add_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>delete_user</natural><natural>auth</natural><natural>user</natural></object></field></object><object pk="3" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel"><natural>Artist formerly known as "Prince"</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>change_user</natural><natural>auth</natural><natural>user</natural></object></field></object><object pk="1" model="fixtures.book"><field type="CharField" name="name">Music for all ages</field><field to="fixtures.person" name="authors" rel="ManyToManyRel"><object><natural>Artist formerly known as "Prince"</natural></object><object><natural>Django Reinhardt</natural></object></field></object></django-objects>""", format='xml', natural_keys=True)
|
<django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="5" model="fixtures.article"><field type="CharField" name="headline">XML identified as leading cause of cancer</field><field type="DateTimeField" name="pub_date">2006-06-16T16:00:00</field></object><object pk="4" model="fixtures.article"><field type="CharField" name="headline">Django conquers world!</field><field type="DateTimeField" name="pub_date">2006-06-16T15:00:00</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Copyright is fine the way it is</field><field type="DateTimeField" name="pub_date">2006-06-16T14:00:00</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker on TV is great!</field><field type="DateTimeField" name="pub_date">2006-06-16T11:00:00</field></object><object pk="1" model="fixtures.article"><field type="CharField" name="headline">Python program becomes self aware</field><field type="DateTimeField" name="pub_date">2006-06-16T11:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">legal</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="3" model="fixtures.tag"><field type="CharField" name="name">django</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">4</field></object><object pk="4" model="fixtures.tag"><field type="CharField" name="name">world domination</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">4</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Artist formerly known as "Prince"</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object><object pk="1" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel"><natural>Django Reinhardt</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>add_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>change_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>delete_user</natural><natural>auth</natural><natural>user</natural></object></field></object><object pk="2" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel"><natural>Stephane Grappelli</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>add_user</natural><natural>auth</natural><natural>user</natural></object><object><natural>delete_user</natural><natural>auth</natural><natural>user</natural></object></field></object><object pk="3" model="fixtures.visa"><field to="fixtures.person" name="person" rel="ManyToOneRel"><natural>Artist formerly known as "Prince"</natural></field><field to="auth.permission" name="permissions" rel="ManyToManyRel"><object><natural>change_user</natural><natural>auth</natural><natural>user</natural></object></field></object><object pk="1" model="fixtures.book"><field type="CharField" name="name">Music for all ages</field><field to="fixtures.person" name="authors" rel="ManyToManyRel"><object><natural>Artist formerly known as "Prince"</natural></object><object><natural>Django Reinhardt</natural></object></field></object></django-objects>""", format='xml', natural_keys=True)
|
||||||
|
|
||||||
def test_dumpdata_with_excludes(self):
|
def test_dumpdata_with_excludes(self):
|
||||||
# Load fixture1 which has a site, two articles, and a category
|
# Load fixture1 which has a site, two articles, and a category
|
||||||
@ -305,11 +305,11 @@ class FixtureLoadingTests(TestCase):
|
|||||||
])
|
])
|
||||||
|
|
||||||
# Dump the current contents of the database as a JSON fixture
|
# Dump the current contents of the database as a JSON fixture
|
||||||
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}]', natural_keys=True)
|
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}]', natural_keys=True)
|
||||||
|
|
||||||
# Dump the current contents of the database as an XML fixture
|
# Dump the current contents of the database as an XML fixture
|
||||||
self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
|
self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
|
||||||
<django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Time to reform copyright</field><field type="DateTimeField" name="pub_date">2006-06-16 13:00:00</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker has no place on ESPN</field><field type="DateTimeField" name="pub_date">2006-06-16 12:00:00</field></object><object pk="1" model="fixtures.article"><field type="CharField" name="headline">Python program becomes self aware</field><field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">law</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Prince</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object></django-objects>""", format='xml', natural_keys=True)
|
<django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Time to reform copyright</field><field type="DateTimeField" name="pub_date">2006-06-16T13:00:00</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker has no place on ESPN</field><field type="DateTimeField" name="pub_date">2006-06-16T12:00:00</field></object><object pk="1" model="fixtures.article"><field type="CharField" name="headline">Python program becomes self aware</field><field type="DateTimeField" name="pub_date">2006-06-16T11:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">law</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Prince</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object></django-objects>""", format='xml', natural_keys=True)
|
||||||
|
|
||||||
class FixtureTransactionTests(TransactionTestCase):
|
class FixtureTransactionTests(TransactionTestCase):
|
||||||
def _dumpdata_assert(self, args, output, format='json'):
|
def _dumpdata_assert(self, args, output, format='json'):
|
||||||
@ -344,7 +344,7 @@ class FixtureTransactionTests(TransactionTestCase):
|
|||||||
])
|
])
|
||||||
|
|
||||||
# Dump the current contents of the database as a JSON fixture
|
# Dump the current contents of the database as a JSON fixture
|
||||||
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
|
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16T11:00:00"}}]')
|
||||||
|
|
||||||
# Load fixture 4 (compressed), using format discovery
|
# Load fixture 4 (compressed), using format discovery
|
||||||
management.call_command('loaddata', 'fixture4', verbosity=0, commit=False)
|
management.call_command('loaddata', 'fixture4', verbosity=0, commit=False)
|
||||||
|
@ -230,7 +230,7 @@ class SerializersTestBase(object):
|
|||||||
|
|
||||||
serial_str = serializers.serialize(self.serializer_name, [a])
|
serial_str = serializers.serialize(self.serializer_name, [a])
|
||||||
date_values = self._get_field_values(serial_str, "pub_date")
|
date_values = self._get_field_values(serial_str, "pub_date")
|
||||||
self.assertEqual(date_values[0], "0001-02-03 04:05:06")
|
self.assertEqual(date_values[0].replace('T', ' '), "0001-02-03 04:05:06")
|
||||||
|
|
||||||
def test_pkless_serialized_strings(self):
|
def test_pkless_serialized_strings(self):
|
||||||
"""
|
"""
|
||||||
@ -323,7 +323,7 @@ class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, Transacti
|
|||||||
<object pk="1" model="serializers.article">
|
<object pk="1" model="serializers.article">
|
||||||
<field to="serializers.author" name="author" rel="ManyToOneRel">1</field>
|
<field to="serializers.author" name="author" rel="ManyToOneRel">1</field>
|
||||||
<field type="CharField" name="headline">Forward references pose no problem</field>
|
<field type="CharField" name="headline">Forward references pose no problem</field>
|
||||||
<field type="DateTimeField" name="pub_date">2006-06-16 15:00:00</field>
|
<field type="DateTimeField" name="pub_date">2006-06-16T15:00:00</field>
|
||||||
<field to="serializers.category" name="categories" rel="ManyToManyRel">
|
<field to="serializers.category" name="categories" rel="ManyToManyRel">
|
||||||
<object pk="1"></object>
|
<object pk="1"></object>
|
||||||
</field>
|
</field>
|
||||||
@ -374,7 +374,7 @@ class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, Transact
|
|||||||
"model": "serializers.article",
|
"model": "serializers.article",
|
||||||
"fields": {
|
"fields": {
|
||||||
"headline": "Forward references pose no problem",
|
"headline": "Forward references pose no problem",
|
||||||
"pub_date": "2006-06-16 15:00:00",
|
"pub_date": "2006-06-16T15:00:00",
|
||||||
"categories": [1],
|
"categories": [1],
|
||||||
"author": 1
|
"author": 1
|
||||||
}
|
}
|
||||||
|
0
tests/modeltests/timezones/__init__.py
Normal file
0
tests/modeltests/timezones/__init__.py
Normal file
15
tests/modeltests/timezones/admin.py
Normal file
15
tests/modeltests/timezones/admin.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Event, Timestamp
|
||||||
|
|
||||||
|
class EventAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('dt',)
|
||||||
|
|
||||||
|
admin.site.register(Event, EventAdmin)
|
||||||
|
|
||||||
|
class TimestampAdmin(admin.ModelAdmin):
|
||||||
|
readonly_fields = ('created', 'updated')
|
||||||
|
|
||||||
|
admin.site.register(Timestamp, TimestampAdmin)
|
17
tests/modeltests/timezones/fixtures/users.xml
Normal file
17
tests/modeltests/timezones/fixtures/users.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<django-objects version="1.0">
|
||||||
|
<object pk="100" model="auth.user">
|
||||||
|
<field type="CharField" name="username">super</field>
|
||||||
|
<field type="CharField" name="first_name">Super</field>
|
||||||
|
<field type="CharField" name="last_name">User</field>
|
||||||
|
<field type="CharField" name="email">super@example.com</field>
|
||||||
|
<field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
|
||||||
|
<field type="BooleanField" name="is_staff">True</field>
|
||||||
|
<field type="BooleanField" name="is_active">True</field>
|
||||||
|
<field type="BooleanField" name="is_superuser">True</field>
|
||||||
|
<field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
|
||||||
|
<field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
|
||||||
|
<field to="auth.group" name="groups" rel="ManyToManyRel"></field>
|
||||||
|
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
|
||||||
|
</object>
|
||||||
|
</django-objects>
|
13
tests/modeltests/timezones/forms.py
Normal file
13
tests/modeltests/timezones/forms.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django import forms
|
||||||
|
|
||||||
|
from .models import Event
|
||||||
|
|
||||||
|
class EventForm(forms.Form):
|
||||||
|
dt = forms.DateTimeField()
|
||||||
|
|
||||||
|
class EventSplitForm(forms.Form):
|
||||||
|
dt = forms.SplitDateTimeField()
|
||||||
|
|
||||||
|
class EventModelForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Event
|
8
tests/modeltests/timezones/models.py
Normal file
8
tests/modeltests/timezones/models.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Event(models.Model):
|
||||||
|
dt = models.DateTimeField()
|
||||||
|
|
||||||
|
class Timestamp(models.Model):
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated = models.DateTimeField(auto_now=True)
|
871
tests/modeltests/timezones/tests.py
Normal file
871
tests/modeltests/timezones/tests.py
Normal file
@ -0,0 +1,871 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pytz
|
||||||
|
except ImportError:
|
||||||
|
pytz = None
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core import serializers
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db import connection
|
||||||
|
from django.db.models import Min, Max
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.template import Context, RequestContext, Template, TemplateSyntaxError
|
||||||
|
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.tzinfo import FixedOffset
|
||||||
|
from django.utils.unittest import skipIf
|
||||||
|
|
||||||
|
from .forms import EventForm, EventSplitForm, EventModelForm
|
||||||
|
from .models import Event, Timestamp
|
||||||
|
|
||||||
|
|
||||||
|
# These tests use the EAT (Eastern Africa Time) and ICT (Indochina Time)
|
||||||
|
# who don't have Daylight Saving Time, so we can represent them easily
|
||||||
|
# with FixedOffset, and use them directly as tzinfo in the constructors.
|
||||||
|
|
||||||
|
# settings.TIME_ZONE is forced to EAT. Most tests use a variant of
|
||||||
|
# datetime.datetime(2011, 9, 1, 13, 20, 30), which translates to
|
||||||
|
# 10:20:30 in UTC and 17:20:30 in ICT.
|
||||||
|
|
||||||
|
UTC = timezone.utc
|
||||||
|
EAT = FixedOffset(180) # Africa/Nairobi
|
||||||
|
ICT = FixedOffset(420) # Asia/Bangkok
|
||||||
|
|
||||||
|
TZ_SUPPORT = hasattr(time, 'tzset')
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDateTimeTests(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(self):
|
||||||
|
self._old_time_zone = settings.TIME_ZONE
|
||||||
|
settings.TIME_ZONE = connection.settings_dict['TIME_ZONE'] = 'Africa/Nairobi'
|
||||||
|
timezone._localtime = None
|
||||||
|
if TZ_SUPPORT:
|
||||||
|
self._old_tz = os.environ.get('TZ')
|
||||||
|
os.environ['TZ'] = 'Africa/Nairobi'
|
||||||
|
time.tzset()
|
||||||
|
# Create a new cursor, for test cases that change the value of USE_TZ.
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(self):
|
||||||
|
settings.TIME_ZONE = connection.settings_dict['TIME_ZONE'] = self._old_time_zone
|
||||||
|
timezone._localtime = None
|
||||||
|
if TZ_SUPPORT:
|
||||||
|
if self._old_tz is None:
|
||||||
|
del os.environ['TZ']
|
||||||
|
else:
|
||||||
|
os.environ['TZ'] = self._old_tz
|
||||||
|
time.tzset()
|
||||||
|
|
||||||
|
|
||||||
|
#@override_settings(USE_TZ=False)
|
||||||
|
class LegacyDatabaseTests(BaseDateTimeTests):
|
||||||
|
|
||||||
|
def test_naive_datetime(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertEqual(event.dt, dt)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_microsecond_precision')
|
||||||
|
def test_naive_datetime_with_microsecond(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertEqual(event.dt, dt)
|
||||||
|
|
||||||
|
@skipIfDBFeature('supports_microsecond_precision')
|
||||||
|
def test_naive_datetime_with_microsecond_unsupported(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
# microseconds are lost during a round-trip in the database
|
||||||
|
self.assertEqual(event.dt, dt.replace(microsecond=0))
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
|
def test_aware_datetime_in_local_timezone(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertIsNone(event.dt.tzinfo)
|
||||||
|
# interpret the naive datetime in local time to get the correct value
|
||||||
|
self.assertEqual(event.dt.replace(tzinfo=EAT), dt)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
|
@skipUnlessDBFeature('supports_microsecond_precision')
|
||||||
|
def test_aware_datetime_in_local_timezone_with_microsecond(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertIsNone(event.dt.tzinfo)
|
||||||
|
# interpret the naive datetime in local time to get the correct value
|
||||||
|
self.assertEqual(event.dt.replace(tzinfo=EAT), dt)
|
||||||
|
|
||||||
|
# This combination actually never happens.
|
||||||
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
|
@skipIfDBFeature('supports_microsecond_precision')
|
||||||
|
def test_aware_datetime_in_local_timezone_with_microsecond_unsupported(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertIsNone(event.dt.tzinfo)
|
||||||
|
# interpret the naive datetime in local time to get the correct value
|
||||||
|
# microseconds are lost during a round-trip in the database
|
||||||
|
self.assertEqual(event.dt.replace(tzinfo=EAT), dt.replace(microsecond=0))
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
|
@skipIfDBFeature('needs_datetime_string_cast')
|
||||||
|
def test_aware_datetime_in_utc(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertIsNone(event.dt.tzinfo)
|
||||||
|
# interpret the naive datetime in local time to get the correct value
|
||||||
|
self.assertEqual(event.dt.replace(tzinfo=EAT), dt)
|
||||||
|
|
||||||
|
# This combination is no longer possible since timezone support
|
||||||
|
# was removed from the SQLite backend -- it didn't work.
|
||||||
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
|
@skipUnlessDBFeature('needs_datetime_string_cast')
|
||||||
|
def test_aware_datetime_in_utc_unsupported(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertIsNone(event.dt.tzinfo)
|
||||||
|
# django.db.backend.utils.typecast_dt will just drop the
|
||||||
|
# timezone, so a round-trip in the database alters the data (!)
|
||||||
|
# interpret the naive datetime in local time and you get a wrong value
|
||||||
|
self.assertNotEqual(event.dt.replace(tzinfo=EAT), dt)
|
||||||
|
# interpret the naive datetime in original time to get the correct value
|
||||||
|
self.assertEqual(event.dt.replace(tzinfo=UTC), dt)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
|
@skipIfDBFeature('needs_datetime_string_cast')
|
||||||
|
def test_aware_datetime_in_other_timezone(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=ICT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertIsNone(event.dt.tzinfo)
|
||||||
|
# interpret the naive datetime in local time to get the correct value
|
||||||
|
self.assertEqual(event.dt.replace(tzinfo=EAT), dt)
|
||||||
|
|
||||||
|
# This combination is no longer possible since timezone support
|
||||||
|
# was removed from the SQLite backend -- it didn't work.
|
||||||
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
|
@skipUnlessDBFeature('needs_datetime_string_cast')
|
||||||
|
def test_aware_datetime_in_other_timezone_unsupported(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=ICT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertIsNone(event.dt.tzinfo)
|
||||||
|
# django.db.backend.utils.typecast_dt will just drop the
|
||||||
|
# timezone, so a round-trip in the database alters the data (!)
|
||||||
|
# interpret the naive datetime in local time and you get a wrong value
|
||||||
|
self.assertNotEqual(event.dt.replace(tzinfo=EAT), dt)
|
||||||
|
# interpret the naive datetime in original time to get the correct value
|
||||||
|
self.assertEqual(event.dt.replace(tzinfo=ICT), dt)
|
||||||
|
|
||||||
|
@skipIfDBFeature('supports_timezones')
|
||||||
|
def test_aware_datetime_unspported(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
|
||||||
|
def test_auto_now_and_auto_now_add(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
past = now - datetime.timedelta(seconds=2)
|
||||||
|
future = now + datetime.timedelta(seconds=2)
|
||||||
|
Timestamp.objects.create()
|
||||||
|
ts = Timestamp.objects.get()
|
||||||
|
self.assertLess(past, ts.created)
|
||||||
|
self.assertLess(past, ts.updated)
|
||||||
|
self.assertGreater(future, ts.updated)
|
||||||
|
self.assertGreater(future, ts.updated)
|
||||||
|
|
||||||
|
def test_query_filter(self):
|
||||||
|
dt1 = datetime.datetime(2011, 9, 1, 12, 20, 30)
|
||||||
|
dt2 = datetime.datetime(2011, 9, 1, 14, 20, 30)
|
||||||
|
Event.objects.create(dt=dt1)
|
||||||
|
Event.objects.create(dt=dt2)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__gte=dt1).count(), 2)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__gt=dt1).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__gte=dt2).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__gt=dt2).count(), 0)
|
||||||
|
|
||||||
|
def test_query_date_related_filters(self):
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0))
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0))
|
||||||
|
self.assertEqual(Event.objects.filter(dt__year=2011).count(), 2)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__month=1).count(), 2)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__day=1).count(), 2)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__week_day=7).count(), 2)
|
||||||
|
|
||||||
|
def test_query_aggregation(self):
|
||||||
|
# Only min and max make sense for datetimes.
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 9, 1, 23, 20, 20))
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 9, 1, 13, 20, 30))
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 9, 1, 3, 20, 40))
|
||||||
|
result = Event.objects.all().aggregate(Min('dt'), Max('dt'))
|
||||||
|
self.assertEqual(result, {
|
||||||
|
'dt__min': datetime.datetime(2011, 9, 1, 3, 20, 40),
|
||||||
|
'dt__max': datetime.datetime(2011, 9, 1, 23, 20, 20),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_query_dates(self):
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0))
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0))
|
||||||
|
self.assertQuerysetEqual(Event.objects.dates('dt', 'year'),
|
||||||
|
[datetime.datetime(2011, 1, 1)], transform=lambda d: d)
|
||||||
|
self.assertQuerysetEqual(Event.objects.dates('dt', 'month'),
|
||||||
|
[datetime.datetime(2011, 1, 1)], transform=lambda d: d)
|
||||||
|
self.assertQuerysetEqual(Event.objects.dates('dt', 'day'),
|
||||||
|
[datetime.datetime(2011, 1, 1)], transform=lambda d: d)
|
||||||
|
|
||||||
|
LegacyDatabaseTests = override_settings(USE_TZ=False)(LegacyDatabaseTests)
|
||||||
|
|
||||||
|
|
||||||
|
#@override_settings(USE_TZ=True)
|
||||||
|
class NewDatabaseTests(BaseDateTimeTests):
|
||||||
|
|
||||||
|
def test_naive_datetime(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
# naive datetimes are interpreted in local time
|
||||||
|
self.assertEqual(event.dt, dt.replace(tzinfo=EAT))
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_microsecond_precision')
|
||||||
|
def test_naive_datetime_with_microsecond(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
# naive datetimes are interpreted in local time
|
||||||
|
self.assertEqual(event.dt, dt.replace(tzinfo=EAT))
|
||||||
|
|
||||||
|
@skipIfDBFeature('supports_microsecond_precision')
|
||||||
|
def test_naive_datetime_with_microsecond_unsupported(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
# microseconds are lost during a round-trip in the database
|
||||||
|
# naive datetimes are interpreted in local time
|
||||||
|
self.assertEqual(event.dt, dt.replace(microsecond=0, tzinfo=EAT))
|
||||||
|
|
||||||
|
def test_aware_datetime_in_local_timezone(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertEqual(event.dt, dt)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_microsecond_precision')
|
||||||
|
def test_aware_datetime_in_local_timezone_with_microsecond(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertEqual(event.dt, dt)
|
||||||
|
|
||||||
|
@skipIfDBFeature('supports_microsecond_precision')
|
||||||
|
def test_aware_datetime_in_local_timezone_with_microsecond_unsupported(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
# microseconds are lost during a round-trip in the database
|
||||||
|
self.assertEqual(event.dt, dt.replace(microsecond=0))
|
||||||
|
|
||||||
|
def test_aware_datetime_in_utc(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertEqual(event.dt, dt)
|
||||||
|
|
||||||
|
def test_aware_datetime_in_other_timezone(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=ICT)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
event = Event.objects.get()
|
||||||
|
self.assertEqual(event.dt, dt)
|
||||||
|
|
||||||
|
def test_auto_now_and_auto_now_add(self):
|
||||||
|
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
|
||||||
|
past = now - datetime.timedelta(seconds=2)
|
||||||
|
future = now + datetime.timedelta(seconds=2)
|
||||||
|
Timestamp.objects.create()
|
||||||
|
ts = Timestamp.objects.get()
|
||||||
|
self.assertLess(past, ts.created)
|
||||||
|
self.assertLess(past, ts.updated)
|
||||||
|
self.assertGreater(future, ts.updated)
|
||||||
|
self.assertGreater(future, ts.updated)
|
||||||
|
|
||||||
|
def test_query_filter(self):
|
||||||
|
dt1 = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=EAT)
|
||||||
|
dt2 = datetime.datetime(2011, 9, 1, 14, 20, 30, tzinfo=EAT)
|
||||||
|
Event.objects.create(dt=dt1)
|
||||||
|
Event.objects.create(dt=dt2)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__gte=dt1).count(), 2)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__gt=dt1).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__gte=dt2).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__gt=dt2).count(), 0)
|
||||||
|
|
||||||
|
@skipIf(pytz is None, "this test requires pytz")
|
||||||
|
def test_query_filter_with_pytz_timezones(self):
|
||||||
|
tz = pytz.timezone('Europe/Paris')
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=tz)
|
||||||
|
Event.objects.create(dt=dt)
|
||||||
|
next = dt + datetime.timedelta(seconds=3)
|
||||||
|
prev = dt - datetime.timedelta(seconds=3)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__exact=dt).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__exact=next).count(), 0)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__in=(prev, next)).count(), 0)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__in=(prev, dt, next)).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__range=(prev, next)).count(), 1)
|
||||||
|
|
||||||
|
def test_query_date_related_filters(self):
|
||||||
|
# These two dates fall in the same day in EAT, but in different days,
|
||||||
|
# years and months in UTC, and aggregation is performed in UTC when
|
||||||
|
# time zone support is enabled. This test could be changed if the
|
||||||
|
# implementation is changed to perform the aggregation is local time.
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT))
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT))
|
||||||
|
self.assertEqual(Event.objects.filter(dt__year=2011).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__month=1).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__day=1).count(), 1)
|
||||||
|
self.assertEqual(Event.objects.filter(dt__week_day=7).count(), 1)
|
||||||
|
|
||||||
|
def test_query_aggregation(self):
|
||||||
|
# Only min and max make sense for datetimes.
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 9, 1, 23, 20, 20, tzinfo=EAT))
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT))
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 9, 1, 3, 20, 40, tzinfo=EAT))
|
||||||
|
result = Event.objects.all().aggregate(Min('dt'), Max('dt'))
|
||||||
|
self.assertEqual(result, {
|
||||||
|
'dt__min': datetime.datetime(2011, 9, 1, 3, 20, 40, tzinfo=EAT),
|
||||||
|
'dt__max': datetime.datetime(2011, 9, 1, 23, 20, 20, tzinfo=EAT),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_query_dates(self):
|
||||||
|
# Same comment as in test_query_date_related_filters.
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT))
|
||||||
|
Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT))
|
||||||
|
self.assertQuerysetEqual(Event.objects.dates('dt', 'year'),
|
||||||
|
[datetime.datetime(2010, 1, 1, tzinfo=UTC),
|
||||||
|
datetime.datetime(2011, 1, 1, tzinfo=UTC)],
|
||||||
|
transform=lambda d: d)
|
||||||
|
self.assertQuerysetEqual(Event.objects.dates('dt', 'month'),
|
||||||
|
[datetime.datetime(2010, 12, 1, tzinfo=UTC),
|
||||||
|
datetime.datetime(2011, 1, 1, tzinfo=UTC)],
|
||||||
|
transform=lambda d: d)
|
||||||
|
self.assertQuerysetEqual(Event.objects.dates('dt', 'day'),
|
||||||
|
[datetime.datetime(2010, 12, 31, tzinfo=UTC),
|
||||||
|
datetime.datetime(2011, 1, 1, tzinfo=UTC)],
|
||||||
|
transform=lambda d: d)
|
||||||
|
|
||||||
|
NewDatabaseTests = override_settings(USE_TZ=True)(NewDatabaseTests)
|
||||||
|
|
||||||
|
|
||||||
|
class SerializationTests(BaseDateTimeTests):
|
||||||
|
|
||||||
|
# Backend-specific notes:
|
||||||
|
# - JSON supports only milliseconds, microseconds will be truncated.
|
||||||
|
# - PyYAML dumps the UTC offset correctly for timezone-aware datetimes,
|
||||||
|
# but when it loads this representation, it substracts the offset and
|
||||||
|
# returns a naive datetime object in UTC (http://pyyaml.org/ticket/202).
|
||||||
|
# Tests are adapted to take these quirks into account.
|
||||||
|
|
||||||
|
def test_naive_datetime(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
|
||||||
|
|
||||||
|
data = serializers.serialize('python', [Event(dt=dt)])
|
||||||
|
self.assertEqual(data[0]['fields']['dt'], dt)
|
||||||
|
obj = serializers.deserialize('python', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('json', [Event(dt=dt)])
|
||||||
|
self.assertIn('"fields": {"dt": "2011-09-01T13:20:30"}', data)
|
||||||
|
obj = serializers.deserialize('json', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('xml', [Event(dt=dt)])
|
||||||
|
self.assertIn('<field type="DateTimeField" name="dt">2011-09-01T13:20:30</field>', data)
|
||||||
|
obj = serializers.deserialize('xml', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
if 'yaml' in serializers.get_serializer_formats():
|
||||||
|
data = serializers.serialize('yaml', [Event(dt=dt)])
|
||||||
|
self.assertIn("- fields: {dt: !!timestamp '2011-09-01 13:20:30'}", data)
|
||||||
|
obj = serializers.deserialize('yaml', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
def test_naive_datetime_with_microsecond(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
|
||||||
|
|
||||||
|
data = serializers.serialize('python', [Event(dt=dt)])
|
||||||
|
self.assertEqual(data[0]['fields']['dt'], dt)
|
||||||
|
obj = serializers.deserialize('python', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('json', [Event(dt=dt)])
|
||||||
|
self.assertIn('"fields": {"dt": "2011-09-01T13:20:30.405"}', data)
|
||||||
|
obj = serializers.deserialize('json', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt.replace(microsecond=405000))
|
||||||
|
|
||||||
|
data = serializers.serialize('xml', [Event(dt=dt)])
|
||||||
|
self.assertIn('<field type="DateTimeField" name="dt">2011-09-01T13:20:30.405060</field>', data)
|
||||||
|
obj = serializers.deserialize('xml', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
if 'yaml' in serializers.get_serializer_formats():
|
||||||
|
data = serializers.serialize('yaml', [Event(dt=dt)])
|
||||||
|
self.assertIn("- fields: {dt: !!timestamp '2011-09-01 13:20:30.405060'}", data)
|
||||||
|
obj = serializers.deserialize('yaml', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
def test_aware_datetime_with_microsecond(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 17, 20, 30, 405060, tzinfo=ICT)
|
||||||
|
|
||||||
|
data = serializers.serialize('python', [Event(dt=dt)])
|
||||||
|
self.assertEqual(data[0]['fields']['dt'], dt)
|
||||||
|
obj = serializers.deserialize('python', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('json', [Event(dt=dt)])
|
||||||
|
self.assertIn('"fields": {"dt": "2011-09-01T17:20:30.405+07:00"}', data)
|
||||||
|
obj = serializers.deserialize('json', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt.replace(microsecond=405000))
|
||||||
|
|
||||||
|
data = serializers.serialize('xml', [Event(dt=dt)])
|
||||||
|
self.assertIn('<field type="DateTimeField" name="dt">2011-09-01T17:20:30.405060+07:00</field>', data)
|
||||||
|
obj = serializers.deserialize('xml', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
if 'yaml' in serializers.get_serializer_formats():
|
||||||
|
data = serializers.serialize('yaml', [Event(dt=dt)])
|
||||||
|
self.assertIn("- fields: {dt: !!timestamp '2011-09-01 17:20:30.405060+07:00'}", data)
|
||||||
|
obj = serializers.deserialize('yaml', data).next().object
|
||||||
|
self.assertEqual(obj.dt.replace(tzinfo=UTC), dt)
|
||||||
|
|
||||||
|
def test_aware_datetime_in_utc(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)
|
||||||
|
|
||||||
|
data = serializers.serialize('python', [Event(dt=dt)])
|
||||||
|
self.assertEqual(data[0]['fields']['dt'], dt)
|
||||||
|
obj = serializers.deserialize('python', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('json', [Event(dt=dt)])
|
||||||
|
self.assertIn('"fields": {"dt": "2011-09-01T10:20:30Z"}', data)
|
||||||
|
obj = serializers.deserialize('json', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('xml', [Event(dt=dt)])
|
||||||
|
self.assertIn('<field type="DateTimeField" name="dt">2011-09-01T10:20:30+00:00</field>', data)
|
||||||
|
obj = serializers.deserialize('xml', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
if 'yaml' in serializers.get_serializer_formats():
|
||||||
|
data = serializers.serialize('yaml', [Event(dt=dt)])
|
||||||
|
self.assertIn("- fields: {dt: !!timestamp '2011-09-01 10:20:30+00:00'}", data)
|
||||||
|
obj = serializers.deserialize('yaml', data).next().object
|
||||||
|
self.assertEqual(obj.dt.replace(tzinfo=UTC), dt)
|
||||||
|
|
||||||
|
def test_aware_datetime_in_local_timezone(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
|
||||||
|
|
||||||
|
data = serializers.serialize('python', [Event(dt=dt)])
|
||||||
|
self.assertEqual(data[0]['fields']['dt'], dt)
|
||||||
|
obj = serializers.deserialize('python', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('json', [Event(dt=dt)])
|
||||||
|
self.assertIn('"fields": {"dt": "2011-09-01T13:20:30+03:00"}', data)
|
||||||
|
obj = serializers.deserialize('json', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('xml', [Event(dt=dt)])
|
||||||
|
self.assertIn('<field type="DateTimeField" name="dt">2011-09-01T13:20:30+03:00</field>', data)
|
||||||
|
obj = serializers.deserialize('xml', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
if 'yaml' in serializers.get_serializer_formats():
|
||||||
|
data = serializers.serialize('yaml', [Event(dt=dt)])
|
||||||
|
self.assertIn("- fields: {dt: !!timestamp '2011-09-01 13:20:30+03:00'}", data)
|
||||||
|
obj = serializers.deserialize('yaml', data).next().object
|
||||||
|
self.assertEqual(obj.dt.replace(tzinfo=UTC), dt)
|
||||||
|
|
||||||
|
def test_aware_datetime_in_other_timezone(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=ICT)
|
||||||
|
|
||||||
|
data = serializers.serialize('python', [Event(dt=dt)])
|
||||||
|
self.assertEqual(data[0]['fields']['dt'], dt)
|
||||||
|
obj = serializers.deserialize('python', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('json', [Event(dt=dt)])
|
||||||
|
self.assertIn('"fields": {"dt": "2011-09-01T17:20:30+07:00"}', data)
|
||||||
|
obj = serializers.deserialize('json', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
data = serializers.serialize('xml', [Event(dt=dt)])
|
||||||
|
self.assertIn('<field type="DateTimeField" name="dt">2011-09-01T17:20:30+07:00</field>', data)
|
||||||
|
obj = serializers.deserialize('xml', data).next().object
|
||||||
|
self.assertEqual(obj.dt, dt)
|
||||||
|
|
||||||
|
if 'yaml' in serializers.get_serializer_formats():
|
||||||
|
data = serializers.serialize('yaml', [Event(dt=dt)])
|
||||||
|
self.assertIn("- fields: {dt: !!timestamp '2011-09-01 17:20:30+07:00'}", data)
|
||||||
|
obj = serializers.deserialize('yaml', data).next().object
|
||||||
|
self.assertEqual(obj.dt.replace(tzinfo=UTC), dt)
|
||||||
|
|
||||||
|
#@override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=True)
|
||||||
|
class TemplateTests(BaseDateTimeTests):
|
||||||
|
|
||||||
|
def test_localtime_templatetag_and_filters(self):
|
||||||
|
"""
|
||||||
|
Test the {% localtime %} templatetag and related filters.
|
||||||
|
"""
|
||||||
|
datetimes = {
|
||||||
|
'utc': datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC),
|
||||||
|
'eat': datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT),
|
||||||
|
'ict': datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=ICT),
|
||||||
|
'naive': datetime.datetime(2011, 9, 1, 13, 20, 30),
|
||||||
|
}
|
||||||
|
templates = {
|
||||||
|
'notag': Template("{% load tz %}{{ dt }}|{{ dt|aslocaltime }}|{{ dt|asutc }}|{{ dt|astimezone:ICT }}"),
|
||||||
|
'noarg': Template("{% load tz %}{% localtime %}{{ dt }}|{{ dt|aslocaltime }}|{{ dt|asutc }}|{{ dt|astimezone:ICT }}{% endlocaltime %}"),
|
||||||
|
'on': Template("{% load tz %}{% localtime on %}{{ dt }}|{{ dt|aslocaltime }}|{{ dt|asutc }}|{{ dt|astimezone:ICT }}{% endlocaltime %}"),
|
||||||
|
'off': Template("{% load tz %}{% localtime off %}{{ dt }}|{{ dt|aslocaltime }}|{{ dt|asutc }}|{{ dt|astimezone:ICT }}{% endlocaltime %}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Transform a list of keys in 'datetimes' to the expected template
|
||||||
|
# output. This makes the definition of 'results' more readable.
|
||||||
|
def t(*result):
|
||||||
|
return '|'.join(datetimes[key].isoformat() for key in result)
|
||||||
|
|
||||||
|
# Results for USE_TZ = True
|
||||||
|
|
||||||
|
results = {
|
||||||
|
'utc': {
|
||||||
|
'notag': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'noarg': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'on': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'off': t('utc', 'eat', 'utc', 'ict'),
|
||||||
|
},
|
||||||
|
'eat': {
|
||||||
|
'notag': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'noarg': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'on': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'off': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
},
|
||||||
|
'ict': {
|
||||||
|
'notag': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'noarg': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'on': t('eat', 'eat', 'utc', 'ict'),
|
||||||
|
'off': t('ict', 'eat', 'utc', 'ict'),
|
||||||
|
},
|
||||||
|
'naive': {
|
||||||
|
'notag': t('naive', 'eat', 'utc', 'ict'),
|
||||||
|
'noarg': t('naive', 'eat', 'utc', 'ict'),
|
||||||
|
'on': t('naive', 'eat', 'utc', 'ict'),
|
||||||
|
'off': t('naive', 'eat', 'utc', 'ict'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k1, dt in datetimes.iteritems():
|
||||||
|
for k2, tpl in templates.iteritems():
|
||||||
|
ctx = Context({'dt': dt, 'ICT': ICT})
|
||||||
|
actual = tpl.render(ctx)
|
||||||
|
expected = results[k1][k2]
|
||||||
|
self.assertEqual(actual, expected, '%s / %s: %r != %r' % (k1, k2, actual, expected))
|
||||||
|
|
||||||
|
# Changes for USE_TZ = False
|
||||||
|
|
||||||
|
results['utc']['notag'] = t('utc', 'eat', 'utc', 'ict')
|
||||||
|
results['ict']['notag'] = t('ict', 'eat', 'utc', 'ict')
|
||||||
|
|
||||||
|
with self.settings(USE_TZ=False):
|
||||||
|
for k1, dt in datetimes.iteritems():
|
||||||
|
for k2, tpl in templates.iteritems():
|
||||||
|
ctx = Context({'dt': dt, 'ICT': ICT})
|
||||||
|
actual = tpl.render(ctx)
|
||||||
|
expected = results[k1][k2]
|
||||||
|
self.assertEqual(actual, expected, '%s / %s: %r != %r' % (k1, k2, actual, expected))
|
||||||
|
|
||||||
|
@skipIf(pytz is None, "this test requires pytz")
|
||||||
|
def test_localtime_filters_with_pytz(self):
|
||||||
|
"""
|
||||||
|
Test the |aslocaltime, |asutc, and |astimezone filters with pytz.
|
||||||
|
"""
|
||||||
|
# Use a pytz timezone as local time
|
||||||
|
tpl = Template("{% load tz %}{{ dt|aslocaltime }}|{{ dt|asutc }}")
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 12, 20, 30)})
|
||||||
|
|
||||||
|
timezone._localtime = None
|
||||||
|
with self.settings(TIME_ZONE='Europe/Paris'):
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00|2011-09-01T10:20:30+00:00")
|
||||||
|
timezone._localtime = None
|
||||||
|
|
||||||
|
# Use a pytz timezone as argument
|
||||||
|
tpl = Template("{% load tz %}{{ dt|astimezone:tz }}")
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30),
|
||||||
|
'tz': pytz.timezone('Europe/Paris')})
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
|
||||||
|
|
||||||
|
# Use a pytz timezone name as argument
|
||||||
|
tpl = Template("{% load tz %}{{ dt|astimezone:'Europe/Paris' }}")
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30),
|
||||||
|
'tz': pytz.timezone('Europe/Paris')})
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
|
||||||
|
|
||||||
|
def test_localtime_templatetag_invalid_argument(self):
|
||||||
|
with self.assertRaises(TemplateSyntaxError):
|
||||||
|
Template("{% load tz %}{% localtime foo %}{% endlocaltime %}").render()
|
||||||
|
|
||||||
|
def test_localtime_filters_do_not_raise_exceptions(self):
|
||||||
|
"""
|
||||||
|
Test the |aslocaltime, |asutc, and |astimezone filters on bad inputs.
|
||||||
|
"""
|
||||||
|
tpl = Template("{% load tz %}{{ dt }}|{{ dt|aslocaltime }}|{{ dt|asutc }}|{{ dt|astimezone:tz }}")
|
||||||
|
with self.settings(USE_TZ=True):
|
||||||
|
# bad datetime value
|
||||||
|
ctx = Context({'dt': None, 'tz': ICT})
|
||||||
|
self.assertEqual(tpl.render(ctx), "None|||")
|
||||||
|
ctx = Context({'dt': 'not a date', 'tz': ICT})
|
||||||
|
self.assertEqual(tpl.render(ctx), "not a date|||")
|
||||||
|
# bad timezone value
|
||||||
|
tpl = Template("{% load tz %}{{ dt|astimezone:tz }}")
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30), 'tz': None})
|
||||||
|
self.assertEqual(tpl.render(ctx), "")
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30), 'tz': 'not a tz'})
|
||||||
|
self.assertEqual(tpl.render(ctx), "")
|
||||||
|
|
||||||
|
def test_timezone_templatetag(self):
|
||||||
|
"""
|
||||||
|
Test the {% timezone %} templatetag.
|
||||||
|
"""
|
||||||
|
tpl = Template("{% load tz %}"
|
||||||
|
"{{ dt }}|"
|
||||||
|
"{% timezone tz1 %}"
|
||||||
|
"{{ dt }}|"
|
||||||
|
"{% timezone tz2 %}"
|
||||||
|
"{{ dt }}"
|
||||||
|
"{% endtimezone %}"
|
||||||
|
"{% endtimezone %}")
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC),
|
||||||
|
'tz1': ICT, 'tz2': None})
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01T13:20:30+03:00|2011-09-01T17:20:30+07:00|2011-09-01T13:20:30+03:00")
|
||||||
|
|
||||||
|
@skipIf(pytz is None, "this test requires pytz")
|
||||||
|
def test_timezone_templatetag_with_pytz(self):
|
||||||
|
"""
|
||||||
|
Test the {% timezone %} templatetag with pytz.
|
||||||
|
"""
|
||||||
|
tpl = Template("{% load tz %}{% timezone tz %}{{ dt }}{% endtimezone %}")
|
||||||
|
|
||||||
|
# Use a pytz timezone as argument
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT),
|
||||||
|
'tz': pytz.timezone('Europe/Paris')})
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
|
||||||
|
|
||||||
|
# Use a pytz timezone name as argument
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT),
|
||||||
|
'tz': 'Europe/Paris'})
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
|
||||||
|
|
||||||
|
def test_timezone_templatetag_invalid_argument(self):
|
||||||
|
with self.assertRaises(TemplateSyntaxError):
|
||||||
|
Template("{% load tz %}{% timezone %}{% endtimezone %}").render()
|
||||||
|
with self.assertRaises(ValueError if pytz is None else pytz.UnknownTimeZoneError):
|
||||||
|
Template("{% load tz %}{% timezone tz %}{% endtimezone %}").render(Context({'tz': 'foobar'}))
|
||||||
|
|
||||||
|
def test_get_current_timezone_templatetag(self):
|
||||||
|
"""
|
||||||
|
Test the {% get_current_timezone %} templatetag.
|
||||||
|
"""
|
||||||
|
tpl = Template("{% load tz %}{% get_current_timezone as time_zone %}{{ time_zone }}")
|
||||||
|
|
||||||
|
self.assertEqual(tpl.render(Context()), "Africa/Nairobi" if pytz else "EAT")
|
||||||
|
with timezone.override(UTC):
|
||||||
|
self.assertEqual(tpl.render(Context()), "UTC")
|
||||||
|
|
||||||
|
tpl = Template("{% load tz %}{% timezone tz %}{% get_current_timezone as time_zone %}{% endtimezone %}{{ time_zone }}")
|
||||||
|
|
||||||
|
self.assertEqual(tpl.render(Context({'tz': ICT})), "+0700")
|
||||||
|
with timezone.override(UTC):
|
||||||
|
self.assertEqual(tpl.render(Context({'tz': ICT})), "+0700")
|
||||||
|
|
||||||
|
@skipIf(pytz is None, "this test requires pytz")
|
||||||
|
def test_get_current_timezone_templatetag_with_pytz(self):
|
||||||
|
"""
|
||||||
|
Test the {% get_current_timezone %} templatetag with pytz.
|
||||||
|
"""
|
||||||
|
tpl = Template("{% load tz %}{% get_current_timezone as time_zone %}{{ time_zone }}")
|
||||||
|
with timezone.override(pytz.timezone('Europe/Paris')):
|
||||||
|
self.assertEqual(tpl.render(Context()), "Europe/Paris")
|
||||||
|
|
||||||
|
tpl = Template("{% load tz %}{% timezone 'Europe/Paris' %}{% get_current_timezone as time_zone %}{% endtimezone %}{{ time_zone }}")
|
||||||
|
self.assertEqual(tpl.render(Context()), "Europe/Paris")
|
||||||
|
|
||||||
|
def test_get_current_timezone_templatetag_invalid_argument(self):
|
||||||
|
with self.assertRaises(TemplateSyntaxError):
|
||||||
|
Template("{% load tz %}{% get_current_timezone %}").render()
|
||||||
|
|
||||||
|
def test_tz_template_context_processor(self):
|
||||||
|
"""
|
||||||
|
Test the django.core.context_processors.tz template context processor.
|
||||||
|
"""
|
||||||
|
tpl = Template("{{ TIME_ZONE }}")
|
||||||
|
self.assertEqual(tpl.render(Context()), "")
|
||||||
|
self.assertEqual(tpl.render(RequestContext(HttpRequest())), "Africa/Nairobi" if pytz else "EAT")
|
||||||
|
|
||||||
|
def test_date_and_time_template_filters(self):
|
||||||
|
tpl = Template("{{ dt|date:'Y-m-d' }} at {{ dt|time:'H:i:s' }}")
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 20, 20, 20, tzinfo=UTC)})
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01 at 23:20:20")
|
||||||
|
with timezone.override(ICT):
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-02 at 03:20:20")
|
||||||
|
|
||||||
|
def test_date_and_time_template_filters_honor_localtime(self):
|
||||||
|
tpl = Template("{% load tz %}{% localtime off %}{{ dt|date:'Y-m-d' }} at {{ dt|time:'H:i:s' }}{% endlocaltime %}")
|
||||||
|
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 20, 20, 20, tzinfo=UTC)})
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01 at 20:20:20")
|
||||||
|
with timezone.override(ICT):
|
||||||
|
self.assertEqual(tpl.render(ctx), "2011-09-01 at 20:20:20")
|
||||||
|
|
||||||
|
TemplateTests = override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=True)(TemplateTests)
|
||||||
|
|
||||||
|
#@override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=False)
|
||||||
|
class LegacyFormsTests(BaseDateTimeTests):
|
||||||
|
|
||||||
|
def test_form(self):
|
||||||
|
form = EventForm({'dt': u'2011-09-01 13:20:30'})
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30))
|
||||||
|
|
||||||
|
@skipIf(pytz is None, "this test requires pytz")
|
||||||
|
def test_form_with_non_existent_time(self):
|
||||||
|
form = EventForm({'dt': u'2011-03-27 02:30:00'})
|
||||||
|
with timezone.override(pytz.timezone('Europe/Paris')):
|
||||||
|
# this is obviously a bug
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 3, 27, 2, 30, 0))
|
||||||
|
|
||||||
|
@skipIf(pytz is None, "this test requires pytz")
|
||||||
|
def test_form_with_ambiguous_time(self):
|
||||||
|
form = EventForm({'dt': u'2011-10-30 02:30:00'})
|
||||||
|
with timezone.override(pytz.timezone('Europe/Paris')):
|
||||||
|
# this is obviously a bug
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 10, 30, 2, 30, 0))
|
||||||
|
|
||||||
|
def test_split_form(self):
|
||||||
|
form = EventSplitForm({'dt_0': u'2011-09-01', 'dt_1': u'13:20:30'})
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30))
|
||||||
|
|
||||||
|
def test_model_form(self):
|
||||||
|
EventModelForm({'dt': u'2011-09-01 13:20:30'}).save()
|
||||||
|
e = Event.objects.get()
|
||||||
|
self.assertEqual(e.dt, datetime.datetime(2011, 9, 1, 13, 20, 30))
|
||||||
|
|
||||||
|
LegacyFormsTests = override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=False)(LegacyFormsTests)
|
||||||
|
|
||||||
|
#@override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=True)
|
||||||
|
class NewFormsTests(BaseDateTimeTests):
|
||||||
|
|
||||||
|
def test_form(self):
|
||||||
|
form = EventForm({'dt': u'2011-09-01 13:20:30'})
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
|
||||||
|
|
||||||
|
def test_form_with_other_timezone(self):
|
||||||
|
form = EventForm({'dt': u'2011-09-01 17:20:30'})
|
||||||
|
with timezone.override(ICT):
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
|
||||||
|
|
||||||
|
@skipIf(pytz is None, "this test requires pytz")
|
||||||
|
def test_form_with_non_existent_time(self):
|
||||||
|
with timezone.override(pytz.timezone('Europe/Paris')):
|
||||||
|
form = EventForm({'dt': u'2011-03-27 02:30:00'})
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
|
self.assertEqual(form.errors['dt'],
|
||||||
|
[u"2011-03-27 02:30:00 couldn't be interpreted in time zone "
|
||||||
|
u"Europe/Paris; it may be ambiguous or it may not exist."])
|
||||||
|
|
||||||
|
@skipIf(pytz is None, "this test requires pytz")
|
||||||
|
def test_form_with_ambiguous_time(self):
|
||||||
|
with timezone.override(pytz.timezone('Europe/Paris')):
|
||||||
|
form = EventForm({'dt': u'2011-10-30 02:30:00'})
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
|
self.assertEqual(form.errors['dt'],
|
||||||
|
[u"2011-10-30 02:30:00 couldn't be interpreted in time zone "
|
||||||
|
u"Europe/Paris; it may be ambiguous or it may not exist."])
|
||||||
|
|
||||||
|
def test_split_form(self):
|
||||||
|
form = EventSplitForm({'dt_0': u'2011-09-01', 'dt_1': u'13:20:30'})
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
|
||||||
|
|
||||||
|
def test_model_form(self):
|
||||||
|
EventModelForm({'dt': u'2011-09-01 13:20:30'}).save()
|
||||||
|
e = Event.objects.get()
|
||||||
|
self.assertEqual(e.dt, datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
|
||||||
|
|
||||||
|
NewFormsTests = override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=True)(NewFormsTests)
|
||||||
|
|
||||||
|
#@override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=True)
|
||||||
|
class AdminTests(BaseDateTimeTests):
|
||||||
|
|
||||||
|
urls = 'modeltests.timezones.urls'
|
||||||
|
fixtures = ['users.xml']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client.login(username='super', password='secret')
|
||||||
|
|
||||||
|
def test_changelist(self):
|
||||||
|
e = Event.objects.create(dt=datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
|
||||||
|
response = self.client.get(reverse('admin:timezones_event_changelist'))
|
||||||
|
self.assertContains(response, e.dt.astimezone(EAT).isoformat())
|
||||||
|
|
||||||
|
def test_changelist_in_other_timezone(self):
|
||||||
|
e = Event.objects.create(dt=datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
|
||||||
|
with timezone.override(ICT):
|
||||||
|
response = self.client.get(reverse('admin:timezones_event_changelist'))
|
||||||
|
self.assertContains(response, e.dt.astimezone(ICT).isoformat())
|
||||||
|
|
||||||
|
def test_change_editable(self):
|
||||||
|
e = Event.objects.create(dt=datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
|
||||||
|
response = self.client.get(reverse('admin:timezones_event_change', args=(e.pk,)))
|
||||||
|
self.assertContains(response, e.dt.astimezone(EAT).date().isoformat())
|
||||||
|
self.assertContains(response, e.dt.astimezone(EAT).time().isoformat())
|
||||||
|
|
||||||
|
def test_change_editable_in_other_timezone(self):
|
||||||
|
e = Event.objects.create(dt=datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
|
||||||
|
with timezone.override(ICT):
|
||||||
|
response = self.client.get(reverse('admin:timezones_event_change', args=(e.pk,)))
|
||||||
|
self.assertContains(response, e.dt.astimezone(ICT).date().isoformat())
|
||||||
|
self.assertContains(response, e.dt.astimezone(ICT).time().isoformat())
|
||||||
|
|
||||||
|
def test_change_readonly(self):
|
||||||
|
Timestamp.objects.create()
|
||||||
|
# re-fetch the object for backends that lose microseconds (MySQL)
|
||||||
|
t = Timestamp.objects.get()
|
||||||
|
response = self.client.get(reverse('admin:timezones_timestamp_change', args=(t.pk,)))
|
||||||
|
self.assertContains(response, t.created.astimezone(EAT).isoformat())
|
||||||
|
|
||||||
|
def test_change_readonly_in_other_timezone(self):
|
||||||
|
Timestamp.objects.create()
|
||||||
|
# re-fetch the object for backends that lose microseconds (MySQL)
|
||||||
|
t = Timestamp.objects.get()
|
||||||
|
with timezone.override(ICT):
|
||||||
|
response = self.client.get(reverse('admin:timezones_timestamp_change', args=(t.pk,)))
|
||||||
|
self.assertContains(response, t.created.astimezone(ICT).isoformat())
|
||||||
|
|
||||||
|
AdminTests = override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=True)(AdminTests)
|
10
tests/modeltests/timezones/urls.py
Normal file
10
tests/modeltests/timezones/urls.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from django.conf.urls import patterns, include
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from . import admin as tz_admin
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^admin/', include(admin.site.urls)),
|
||||||
|
)
|
@ -104,16 +104,43 @@ class ValidationMessagesTest(TestCase):
|
|||||||
f.clean('foo', None)
|
f.clean('foo', None)
|
||||||
except ValidationError, e:
|
except ValidationError, e:
|
||||||
self.assertEqual(e.messages, [
|
self.assertEqual(e.messages, [
|
||||||
u"'foo' value either has an invalid valid format "
|
u"'foo' value has an invalid format. It must be "
|
||||||
u"(The format must be YYYY-MM-DD HH:MM[:ss[.uuuuuu]]) "
|
u"in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."])
|
||||||
u"or is an invalid date/time."])
|
|
||||||
self.assertRaises(ValidationError, f.clean,
|
# Correct format but invalid date
|
||||||
'2011-10-32 10:10', None)
|
self.assertRaises(ValidationError, f.clean, '2011-10-32', None)
|
||||||
|
try:
|
||||||
|
f.clean('2011-10-32', None)
|
||||||
|
except ValidationError, e:
|
||||||
|
self.assertEqual(e.messages, [
|
||||||
|
u"'2011-10-32' value has the correct format "
|
||||||
|
u"(YYYY-MM-DD) but it is an invalid date."])
|
||||||
|
|
||||||
# Correct format but invalid date/time
|
# Correct format but invalid date/time
|
||||||
|
self.assertRaises(ValidationError, f.clean, '2011-10-32 10:10', None)
|
||||||
try:
|
try:
|
||||||
f.clean('2011-10-32 10:10', None)
|
f.clean('2011-10-32 10:10', None)
|
||||||
except ValidationError, e:
|
except ValidationError, e:
|
||||||
self.assertEqual(e.messages, [
|
self.assertEqual(e.messages, [
|
||||||
u"'2011-10-32 10:10' value either has an invalid valid format "
|
u"'2011-10-32 10:10' value has the correct format "
|
||||||
u"(The format must be YYYY-MM-DD HH:MM[:ss[.uuuuuu]]) "
|
u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
|
||||||
u"or is an invalid date/time."])
|
u"but it is an invalid date/time."])
|
||||||
|
|
||||||
|
def test_time_field_raises_error_message(self):
|
||||||
|
f = models.TimeField()
|
||||||
|
# Wrong format
|
||||||
|
self.assertRaises(ValidationError, f.clean, 'foo', None)
|
||||||
|
try:
|
||||||
|
f.clean('foo', None)
|
||||||
|
except ValidationError, e:
|
||||||
|
self.assertEqual(e.messages, [
|
||||||
|
u"'foo' value has an invalid format. It must be in "
|
||||||
|
u"HH:MM[:ss[.uuuuuu]] format."])
|
||||||
|
# Correct format but invalid time
|
||||||
|
self.assertRaises(ValidationError, f.clean, '25:50', None)
|
||||||
|
try:
|
||||||
|
f.clean('25:50', None)
|
||||||
|
except ValidationError, e:
|
||||||
|
self.assertEqual(e.messages, [
|
||||||
|
u"'25:50' value has the correct format "
|
||||||
|
u"(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."])
|
||||||
|
18
tests/regressiontests/cache/tests.py
vendored
18
tests/regressiontests/cache/tests.py
vendored
@ -24,7 +24,7 @@ from django.template.response import TemplateResponse
|
|||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
from django.test.utils import (get_warnings_state, restore_warnings_state,
|
from django.test.utils import (get_warnings_state, restore_warnings_state,
|
||||||
override_settings)
|
override_settings)
|
||||||
from django.utils import translation, unittest
|
from django.utils import timezone, translation, unittest
|
||||||
from django.utils.cache import (patch_vary_headers, get_cache_key,
|
from django.utils.cache import (patch_vary_headers, get_cache_key,
|
||||||
learn_cache_key, patch_cache_control, patch_response_headers)
|
learn_cache_key, patch_cache_control, patch_response_headers)
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
@ -1154,7 +1154,7 @@ class CacheI18nTest(TestCase):
|
|||||||
request.session = {}
|
request.session = {}
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@override_settings(USE_I18N=True, USE_L10N=False)
|
@override_settings(USE_I18N=True, USE_L10N=False, USE_TZ=False)
|
||||||
def test_cache_key_i18n_translation(self):
|
def test_cache_key_i18n_translation(self):
|
||||||
request = self._get_request()
|
request = self._get_request()
|
||||||
lang = translation.get_language()
|
lang = translation.get_language()
|
||||||
@ -1164,7 +1164,7 @@ class CacheI18nTest(TestCase):
|
|||||||
key2 = get_cache_key(request)
|
key2 = get_cache_key(request)
|
||||||
self.assertEqual(key, key2)
|
self.assertEqual(key, key2)
|
||||||
|
|
||||||
@override_settings(USE_I18N=False, USE_L10N=True)
|
@override_settings(USE_I18N=False, USE_L10N=True, USE_TZ=False)
|
||||||
def test_cache_key_i18n_formatting(self):
|
def test_cache_key_i18n_formatting(self):
|
||||||
request = self._get_request()
|
request = self._get_request()
|
||||||
lang = translation.get_language()
|
lang = translation.get_language()
|
||||||
@ -1174,13 +1174,25 @@ class CacheI18nTest(TestCase):
|
|||||||
key2 = get_cache_key(request)
|
key2 = get_cache_key(request)
|
||||||
self.assertEqual(key, key2)
|
self.assertEqual(key, key2)
|
||||||
|
|
||||||
|
@override_settings(USE_I18N=False, USE_L10N=False, USE_TZ=True)
|
||||||
|
def test_cache_key_i18n_timezone(self):
|
||||||
|
request = self._get_request()
|
||||||
|
tz = timezone.get_current_timezone_name()
|
||||||
|
response = HttpResponse()
|
||||||
|
key = learn_cache_key(request, response)
|
||||||
|
self.assertIn(tz, key, "Cache keys should include the time zone name when time zones are active")
|
||||||
|
key2 = get_cache_key(request)
|
||||||
|
self.assertEqual(key, key2)
|
||||||
|
|
||||||
@override_settings(USE_I18N=False, USE_L10N=False)
|
@override_settings(USE_I18N=False, USE_L10N=False)
|
||||||
def test_cache_key_no_i18n (self):
|
def test_cache_key_no_i18n (self):
|
||||||
request = self._get_request()
|
request = self._get_request()
|
||||||
lang = translation.get_language()
|
lang = translation.get_language()
|
||||||
|
tz = timezone.get_current_timezone_name()
|
||||||
response = HttpResponse()
|
response = HttpResponse()
|
||||||
key = learn_cache_key(request, response)
|
key = learn_cache_key(request, response)
|
||||||
self.assertNotIn(lang, key, "Cache keys shouldn't include the language name when i18n isn't active")
|
self.assertNotIn(lang, key, "Cache keys shouldn't include the language name when i18n isn't active")
|
||||||
|
self.assertNotIn(tz, key, "Cache keys shouldn't include the time zone name when i18n isn't active")
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
CACHE_MIDDLEWARE_KEY_PREFIX="test",
|
CACHE_MIDDLEWARE_KEY_PREFIX="test",
|
||||||
|
@ -3,7 +3,7 @@ from __future__ import absolute_import
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.test import TestCase, skipIfDBFeature
|
from django.test import TestCase, skipIfDBFeature
|
||||||
from django.utils import tzinfo
|
from django.utils.timezone import utc
|
||||||
|
|
||||||
from .models import Donut, RumBaba
|
from .models import Donut, RumBaba
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ class DataTypesTestCase(TestCase):
|
|||||||
def test_error_on_timezone(self):
|
def test_error_on_timezone(self):
|
||||||
"""Regression test for #8354: the MySQL and Oracle backends should raise
|
"""Regression test for #8354: the MySQL and Oracle backends should raise
|
||||||
an error if given a timezone-aware datetime object."""
|
an error if given a timezone-aware datetime object."""
|
||||||
dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0))
|
dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=utc)
|
||||||
d = Donut(name='Bear claw', consumed_at=dt)
|
d = Donut(name='Bear claw', consumed_at=dt)
|
||||||
self.assertRaises(ValueError, d.save)
|
self.assertRaises(ValueError, d.save)
|
||||||
# ValueError: MySQL backend does not support timezone-aware datetimes.
|
# ValueError: MySQL backend does not support timezone-aware datetimes.
|
||||||
|
@ -421,7 +421,7 @@ class DefaultFiltersTests(TestCase):
|
|||||||
|
|
||||||
def test_timeuntil(self):
|
def test_timeuntil(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
timeuntil_filter(datetime.datetime.now() + datetime.timedelta(1)),
|
timeuntil_filter(datetime.datetime.now() + datetime.timedelta(1, 1)),
|
||||||
u'1 day')
|
u'1 day')
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -4,6 +4,7 @@ import time
|
|||||||
|
|
||||||
from django.utils.dateformat import format
|
from django.utils.dateformat import format
|
||||||
from django.utils import dateformat, translation, unittest
|
from django.utils import dateformat, translation, unittest
|
||||||
|
from django.utils.timezone import utc
|
||||||
from django.utils.tzinfo import FixedOffset, LocalTimezone
|
from django.utils.tzinfo import FixedOffset, LocalTimezone
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +57,6 @@ class DateFormatTests(unittest.TestCase):
|
|||||||
self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple())
|
self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple())
|
||||||
|
|
||||||
def test_epoch(self):
|
def test_epoch(self):
|
||||||
utc = FixedOffset(0)
|
|
||||||
udt = datetime(1970, 1, 1, tzinfo=utc)
|
udt = datetime(1970, 1, 1, tzinfo=utc)
|
||||||
self.assertEqual(format(udt, 'U'), u'0')
|
self.assertEqual(format(udt, 'U'), u'0')
|
||||||
|
|
||||||
|
@ -23,3 +23,4 @@ from .datetime_safe import DatetimeTests
|
|||||||
from .baseconv import TestBaseConv
|
from .baseconv import TestBaseConv
|
||||||
from .jslex import JsTokensTest, JsToCForGettextTest
|
from .jslex import JsTokensTest, JsToCForGettextTest
|
||||||
from .ipv6 import TestUtilsIPv6
|
from .ipv6 import TestUtilsIPv6
|
||||||
|
from .timezone import TimezoneTests
|
||||||
|
@ -105,3 +105,12 @@ class TimesinceTests(unittest.TestCase):
|
|||||||
self.assertEqual(timeuntil(today+self.oneday, today), u'1 day')
|
self.assertEqual(timeuntil(today+self.oneday, today), u'1 day')
|
||||||
self.assertEqual(timeuntil(today-self.oneday, today), u'0 minutes')
|
self.assertEqual(timeuntil(today-self.oneday, today), u'0 minutes')
|
||||||
self.assertEqual(timeuntil(today+self.oneweek, today), u'1 week')
|
self.assertEqual(timeuntil(today+self.oneweek, today), u'1 week')
|
||||||
|
|
||||||
|
def test_naive_datetime_with_tzinfo_attribute(self):
|
||||||
|
class naive(datetime.tzinfo):
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return None
|
||||||
|
future = datetime.datetime(2080, 1, 1, tzinfo=naive())
|
||||||
|
self.assertEqual(timesince(future), u'0 minutes')
|
||||||
|
past = datetime.datetime(1980, 1, 1, tzinfo=naive())
|
||||||
|
self.assertEqual(timeuntil(past), u'0 minutes')
|
||||||
|
18
tests/regressiontests/utils/timezone.py
Normal file
18
tests/regressiontests/utils/timezone.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import copy
|
||||||
|
import pickle
|
||||||
|
from django.utils.timezone import UTC, LocalTimezone
|
||||||
|
from django.utils import unittest
|
||||||
|
|
||||||
|
class TimezoneTests(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_copy(self):
|
||||||
|
self.assertIsInstance(copy.copy(UTC()), UTC)
|
||||||
|
self.assertIsInstance(copy.copy(LocalTimezone()), LocalTimezone)
|
||||||
|
|
||||||
|
def test_deepcopy(self):
|
||||||
|
self.assertIsInstance(copy.deepcopy(UTC()), UTC)
|
||||||
|
self.assertIsInstance(copy.deepcopy(LocalTimezone()), LocalTimezone)
|
||||||
|
|
||||||
|
def test_pickling_unpickling(self):
|
||||||
|
self.assertIsInstance(pickle.loads(pickle.dumps(UTC())), UTC)
|
||||||
|
self.assertIsInstance(pickle.loads(pickle.dumps(LocalTimezone())), LocalTimezone)
|
@ -1,5 +1,7 @@
|
|||||||
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
import pickle
|
||||||
import time
|
import time
|
||||||
from django.utils.tzinfo import FixedOffset, LocalTimezone
|
from django.utils.tzinfo import FixedOffset, LocalTimezone
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
@ -60,3 +62,18 @@ class TzinfoTests(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
repr(datetime.datetime.fromtimestamp(ts + 3600, tz)),
|
repr(datetime.datetime.fromtimestamp(ts + 3600, tz)),
|
||||||
'datetime.datetime(2010, 11, 7, 1, 0, tzinfo=EST)')
|
'datetime.datetime(2010, 11, 7, 1, 0, tzinfo=EST)')
|
||||||
|
|
||||||
|
def test_copy(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
self.assertIsInstance(copy.copy(FixedOffset(90)), FixedOffset)
|
||||||
|
self.assertIsInstance(copy.copy(LocalTimezone(now)), LocalTimezone)
|
||||||
|
|
||||||
|
def test_deepcopy(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
self.assertIsInstance(copy.deepcopy(FixedOffset(90)), FixedOffset)
|
||||||
|
self.assertIsInstance(copy.deepcopy(LocalTimezone(now)), LocalTimezone)
|
||||||
|
|
||||||
|
def test_pickling_unpickling(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
self.assertIsInstance(pickle.loads(pickle.dumps(FixedOffset(90))), FixedOffset)
|
||||||
|
self.assertIsInstance(pickle.loads(pickle.dumps(LocalTimezone(now))), LocalTimezone)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user