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:
Aymeric Augustin 2011-11-18 13:01:06 +00:00
parent 01f70349c9
commit 9b1cb755a2
58 changed files with 2720 additions and 284 deletions

View File

@ -31,9 +31,13 @@ INTERNAL_IPS = ()
# Local time zone for this installation. All choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
# systems may support all possibilities).
# systems may support all possibilities). When USE_TZ is True, this is
# interpreted as the default user time zone.
TIME_ZONE = 'America/Chicago'
# If you set this to True, Django will use timezone-aware datetimes.
USE_TZ = False
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
@ -119,7 +123,7 @@ LOCALE_PATHS = ()
LANGUAGE_COOKIE_NAME = 'django_language'
# If you set this to True, Django will format dates, numbers and calendars
# according to user current locale
# according to user current locale.
USE_L10N = False
# Not-necessarily-technical managers of the site. They get broken link
@ -192,6 +196,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
# 'django.core.context_processors.request',
'django.contrib.messages.context_processors.messages',
)

View File

@ -40,9 +40,12 @@ SITE_ID = 1
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''

View File

@ -7,6 +7,7 @@ from django.utils import formats
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils import timezone
from django.utils.encoding import force_unicode, smart_unicode, smart_str
from django.utils.translation import ungettext
from django.core.urlresolvers import reverse
@ -293,6 +294,8 @@ def display_for_field(value, field):
return _boolean_icon(value)
elif value is None:
return EMPTY_CHANGELIST_VALUE
elif isinstance(field, models.DateTimeField):
return formats.localize(timezone.aslocaltime(value))
elif isinstance(field, models.DateField) or isinstance(field, models.TimeField):
return formats.localize(value)
elif isinstance(field, models.DecimalField):

View File

@ -7,7 +7,7 @@ from django.template import defaultfilters
from django.utils.encoding import force_unicode
from django.utils.formats import number_format
from django.utils.translation import pgettext, ungettext, ugettext as _
from django.utils.tzinfo import LocalTimezone
from django.utils.timezone import is_aware, utc
register = template.Library()
@ -158,8 +158,8 @@ def naturalday(value, arg=None):
except ValueError:
# Date arguments out of range
return value
today = datetime.now(tzinfo).replace(microsecond=0, second=0, minute=0, hour=0)
delta = value - today.date()
today = datetime.now(tzinfo).date()
delta = value - today
if delta.days == 0:
return _(u'today')
elif delta.days == 1:
@ -174,18 +174,10 @@ def naturaltime(value):
For date and time values shows how many seconds, minutes or hours ago
compared to current timestamp returns representing string.
"""
try:
value = datetime(value.year, value.month, value.day, value.hour, value.minute, value.second)
except AttributeError:
return value
except ValueError:
if not isinstance(value, date): # datetime is a subclass of date
return value
if getattr(value, 'tzinfo', None):
now = datetime.now(LocalTimezone(value))
else:
now = datetime.now()
now = now - timedelta(0, 0, now.microsecond)
now = datetime.now(utc if is_aware(value) else None)
if value < now:
delta = now - value
if delta.days != 0:

View File

@ -1,11 +1,12 @@
from __future__ import with_statement
from datetime import timedelta, date, datetime
import datetime
from django.template import Template, Context, defaultfilters
from django.test import TestCase
from django.utils import translation, tzinfo
from django.utils.translation import ugettext as _
from django.utils.html import escape
from django.utils.timezone import utc
class HumanizeTests(TestCase):
@ -88,10 +89,10 @@ class HumanizeTests(TestCase):
self.humanize_tester(test_list, result_list, 'apnumber')
def test_naturalday(self):
today = date.today()
yesterday = today - timedelta(days=1)
tomorrow = today + timedelta(days=1)
someday = today - timedelta(days=10)
today = datetime.date.today()
yesterday = today - datetime.timedelta(days=1)
tomorrow = today + datetime.timedelta(days=1)
someday = today - datetime.timedelta(days=10)
notdate = u"I'm not a date value"
test_list = (today, yesterday, tomorrow, someday, notdate, None)
@ -103,41 +104,46 @@ class HumanizeTests(TestCase):
def test_naturalday_tz(self):
from django.contrib.humanize.templatetags.humanize import naturalday
today = date.today()
tz_one = tzinfo.FixedOffset(timedelta(hours=-12))
tz_two = tzinfo.FixedOffset(timedelta(hours=12))
today = datetime.date.today()
tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12))
tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12))
# Can be today or yesterday
date_one = datetime(today.year, today.month, today.day, tzinfo=tz_one)
date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one)
naturalday_one = naturalday(date_one)
# Can be today or tomorrow
date_two = datetime(today.year, today.month, today.day, tzinfo=tz_two)
date_two = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_two)
naturalday_two = naturalday(date_two)
# As 24h of difference they will never be the same
self.assertNotEqual(naturalday_one, naturalday_two)
def test_naturaltime(self):
class naive(datetime.tzinfo):
def utcoffset(self, dt):
return None
# we're going to mock datetime.datetime, so use a fixed datetime
now = datetime(2011, 8, 15)
now = datetime.datetime(2011, 8, 15)
test_list = [
now,
now - timedelta(seconds=1),
now - timedelta(seconds=30),
now - timedelta(minutes=1, seconds=30),
now - timedelta(minutes=2),
now - timedelta(hours=1, minutes=30, seconds=30),
now - timedelta(hours=23, minutes=50, seconds=50),
now - timedelta(days=1),
now - timedelta(days=500),
now + timedelta(seconds=1),
now + timedelta(seconds=30),
now + timedelta(minutes=1, seconds=30),
now + timedelta(minutes=2),
now + timedelta(hours=1, minutes=30, seconds=30),
now + timedelta(hours=23, minutes=50, seconds=50),
now + timedelta(days=1),
now + timedelta(days=500),
now - datetime.timedelta(seconds=1),
now - datetime.timedelta(seconds=30),
now - datetime.timedelta(minutes=1, seconds=30),
now - datetime.timedelta(minutes=2),
now - datetime.timedelta(hours=1, minutes=30, seconds=30),
now - datetime.timedelta(hours=23, minutes=50, seconds=50),
now - datetime.timedelta(days=1),
now - datetime.timedelta(days=500),
now + datetime.timedelta(seconds=1),
now + datetime.timedelta(seconds=30),
now + datetime.timedelta(minutes=1, seconds=30),
now + datetime.timedelta(minutes=2),
now + datetime.timedelta(hours=1, minutes=30, seconds=30),
now + datetime.timedelta(hours=23, minutes=50, seconds=50),
now + datetime.timedelta(days=1),
now + datetime.timedelta(days=500),
now.replace(tzinfo=naive()),
now.replace(tzinfo=utc),
]
result_list = [
'now',
@ -157,14 +163,20 @@ class HumanizeTests(TestCase):
'23 hours from now',
'1 day from now',
'1 year, 4 months from now',
'now',
'now',
]
# mock out datetime so these tests don't fail occasionally when the
# test runs too slow
class MockDateTime(datetime):
class MockDateTime(datetime.datetime):
@classmethod
def now(self):
def now(self, tz=None):
if tz is None or tz.utcoffset(now) is None:
return now
else:
# equals now.replace(tzinfo=utc)
return now.replace(tzinfo=tz) + tz.utcoffset(now)
# naturaltime also calls timesince/timeuntil
from django.contrib.humanize.templatetags import humanize

View File

@ -6,6 +6,7 @@ from django.template import loader, TemplateDoesNotExist, RequestContext
from django.utils import feedgenerator, tzinfo
from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode
from django.utils.html import escape
from django.utils.timezone import is_naive
def add_domain(domain, url, secure=False):
if not (url.startswith('http://')
@ -164,7 +165,7 @@ class Feed(object):
author_email = author_link = None
pubdate = self.__get_dynamic_attr('item_pubdate', item)
if pubdate and not pubdate.tzinfo:
if pubdate and is_naive(pubdate):
ltz = tzinfo.LocalTimezone(pubdate)
pubdate = pubdate.replace(tzinfo=ltz)

View File

@ -48,6 +48,11 @@ def i18n(request):
return context_extras
def tz(request):
from django.utils import timezone
return {'TIME_ZONE': timezone.get_current_timezone_name()}
def static(request):
"""
Adds static-related context variables to the context.

View File

@ -8,8 +8,8 @@ from StringIO import StringIO
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
from django.utils import datetime_safe
from django.utils import simplejson
from django.utils.timezone import is_aware
class Serializer(PythonSerializer):
"""
@ -39,19 +39,24 @@ class DjangoJSONEncoder(simplejson.JSONEncoder):
"""
JSONEncoder subclass that knows how to encode date/time and decimal types.
"""
DATE_FORMAT = "%Y-%m-%d"
TIME_FORMAT = "%H:%M:%S"
def default(self, o):
# See "Date Time String Format" in the ECMA-262 specification.
if isinstance(o, datetime.datetime):
d = datetime_safe.new_datetime(o)
return d.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
r = o.isoformat()
if o.microsecond:
r = r[:23] + r[26:]
if r.endswith('+00:00'):
r = r[:-6] + 'Z'
return r
elif isinstance(o, datetime.date):
d = datetime_safe.new_date(o)
return d.strftime(self.DATE_FORMAT)
return o.isoformat()
elif isinstance(o, datetime.time):
return o.strftime(self.TIME_FORMAT)
if is_aware(o):
raise ValueError("JSON can't represent timezone-aware times.")
r = o.isoformat()
if o.microsecond:
r = r[:12]
return r
elif isinstance(o, decimal.Decimal):
return str(o)
else:

View File

@ -10,6 +10,7 @@ from django.db import DEFAULT_DB_ALIAS
from django.db.backends import util
from django.db.transaction import TransactionManagementError
from django.utils.importlib import import_module
from django.utils.timezone import is_aware
class BaseDatabaseWrapper(local):
@ -743,6 +744,8 @@ class BaseDatabaseOperations(object):
"""
if value is None:
return None
if is_aware(value):
raise ValueError("Django does not support timezone-aware times.")
return unicode(value)
def value_to_db_decimal(self, value, max_digits, decimal_places):

View File

@ -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.validation import DatabaseValidation
from django.utils.safestring import SafeString, SafeUnicode
from django.utils.timezone import is_aware, is_naive, utc
# Raise exceptions for database warnings if DEBUG is on
from django.conf import settings
@ -43,16 +44,29 @@ if settings.DEBUG:
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
# It's impossible to import datetime_or_None directly from MySQLdb.times
datetime_or_None = conversions[FIELD_TYPE.DATETIME]
def datetime_or_None_with_timezone_support(value):
dt = datetime_or_None(value)
# Confirm that dt is naive before overwriting its tzinfo.
if dt is not None and settings.USE_TZ and is_naive(dt):
dt = dt.replace(tzinfo=utc)
return dt
# MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
# timedelta in terms of actual behavior as they are signed and include days --
# and Django expects time, so we still need to override that. We also need to
# add special handling for SafeUnicode and SafeString as MySQLdb's type
# checking is too tight to catch those (see Django ticket #6052).
# Finally, MySQLdb always returns naive datetime objects. However, when
# timezone support is active, Django expects timezone-aware datetime objects.
django_conversions = conversions.copy()
django_conversions.update({
FIELD_TYPE.TIME: util.typecast_time,
FIELD_TYPE.DECIMAL: util.typecast_decimal,
FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
FIELD_TYPE.DATETIME: datetime_or_None_with_timezone_support,
})
# This should match the numerical portion of the version numbers (we can treat
@ -238,8 +252,11 @@ class DatabaseOperations(BaseDatabaseOperations):
return None
# MySQL doesn't support tz-aware datetimes
if value.tzinfo is not None:
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
if is_aware(value):
if settings.USE_TZ:
value = value.astimezone(utc).replace(tzinfo=None)
else:
raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
# MySQL doesn't support microseconds
return unicode(value.replace(microsecond=0))
@ -248,9 +265,9 @@ class DatabaseOperations(BaseDatabaseOperations):
if value is None:
return None
# MySQL doesn't support tz-aware datetimes
if value.tzinfo is not None:
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
# MySQL doesn't support tz-aware times
if is_aware(value):
raise ValueError("MySQL backend does not support timezone-aware times.")
# MySQL doesn't support microseconds
return unicode(value.replace(microsecond=0))

View File

@ -44,6 +44,7 @@ except ImportError, e:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e)
from django.conf import settings
from django.db import utils
from django.db.backends import *
from django.db.backends.signals import connection_created
@ -51,6 +52,7 @@ from django.db.backends.oracle.client import DatabaseClient
from django.db.backends.oracle.creation import DatabaseCreation
from django.db.backends.oracle.introspection import DatabaseIntrospection
from django.utils.encoding import smart_str, force_unicode
from django.utils.timezone import is_aware, is_naive, utc
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
@ -333,11 +335,17 @@ WHEN (new.%(col_name)s IS NULL)
return "TABLESPACE %s" % self.quote_name(tablespace)
def value_to_db_datetime(self, value):
# Oracle doesn't support tz-aware datetimes
if getattr(value, 'tzinfo', None) is not None:
raise ValueError("Oracle backend does not support timezone-aware datetimes.")
if value is None:
return None
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):
if value is None:
@ -346,9 +354,9 @@ WHEN (new.%(col_name)s IS NULL)
if isinstance(value, basestring):
return datetime.datetime.strptime(value, '%H:%M:%S')
# Oracle doesn't support tz-aware datetimes
if value.tzinfo is not None:
raise ValueError("Oracle backend does not support timezone-aware datetimes.")
# Oracle doesn't support tz-aware times
if is_aware(value):
raise ValueError("Oracle backend does not support timezone-aware times.")
return datetime.datetime(1900, 1, 1, value.hour, value.minute,
value.second, value.microsecond)
@ -474,7 +482,26 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# to 'AMERICA' which forces Sunday to evaluate to a '1' in TO_CHAR().
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
" NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'"
"NLS_TERRITORY = 'AMERICA'")
" NLS_TERRITORY = 'AMERICA'"
+ (" TIME_ZONE = 'UTC'" if settings.USE_TZ else ''))
def datetime_converter(dt):
# Confirm that dt is naive before overwriting its tzinfo.
if dt is not None and is_naive(dt):
dt = dt.replace(tzinfo=utc)
return dt
def output_type_handler(cursor, name, default_type,
size, precision, scale):
# datetimes are returned as TIMESTAMP, except the results
# of "dates" queries, which are returned as DATETIME.
if settings.USE_TZ and default_type in (Database.TIMESTAMP,
Database.DATETIME):
return cursor.var(default_type,
arraysize=cursor.arraysize,
outconverter=datetime_converter)
self.connection.outputtypehandler = output_type_handler
if 'operators' not in self.__dict__:
# Ticket #14149: Check whether our LIKE implementation will

View File

@ -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.version import get_version
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
from django.utils.safestring import SafeUnicode, SafeString
from django.utils.log import getLogger
from django.utils.safestring import SafeUnicode, SafeString
from django.utils.timezone import utc
try:
import psycopg2 as Database
@ -32,6 +33,11 @@ psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedStri
logger = getLogger('django.db.backends')
def utc_tzinfo_factory(offset):
if offset != 0:
raise AssertionError("database connection isn't set to UTC")
return utc
class CursorWrapper(object):
"""
A thin wrapper around psycopg2's normal cursor class so that we can catch
@ -144,11 +150,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
def _cursor(self):
new_connection = False
set_tz = False
settings_dict = self.settings_dict
if self.connection is None:
new_connection = True
set_tz = settings_dict.get('TIME_ZONE')
if settings_dict['NAME'] == '':
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured("You need to specify NAME in your Django settings file.")
@ -171,10 +175,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
self.connection.set_isolation_level(self.isolation_level)
connection_created.send(sender=self.__class__, connection=self)
cursor = self.connection.cursor()
cursor.tzinfo_factory = None
cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
if new_connection:
if set_tz:
cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']])
tz = 'UTC' if settings.USE_TZ else settings_dict.get('TIME_ZONE')
if tz:
cursor.execute("SET TIME ZONE %s", [tz])
self._get_pg_version()
return CursorWrapper(cursor)

View File

@ -10,13 +10,16 @@ import decimal
import re
import sys
from django.conf import settings
from django.db import utils
from django.db.backends import *
from django.db.backends.signals import connection_created
from django.db.backends.sqlite3.client import DatabaseClient
from django.db.backends.sqlite3.creation import DatabaseCreation
from django.db.backends.sqlite3.introspection import DatabaseIntrospection
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.safestring import SafeString
from django.utils.timezone import is_aware, is_naive, utc
try:
try:
@ -31,12 +34,19 @@ except ImportError, exc:
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
def parse_datetime_with_timezone_support(value):
dt = parse_datetime(value)
# Confirm that dt is naive before overwriting its tzinfo.
if dt is not None and settings.USE_TZ and is_naive(dt):
dt = dt.replace(tzinfo=utc)
return dt
Database.register_converter("bool", lambda s: str(s) == '1')
Database.register_converter("time", util.typecast_time)
Database.register_converter("date", util.typecast_date)
Database.register_converter("datetime", util.typecast_timestamp)
Database.register_converter("timestamp", util.typecast_timestamp)
Database.register_converter("TIMESTAMP", util.typecast_timestamp)
Database.register_converter("time", parse_time)
Database.register_converter("date", parse_date)
Database.register_converter("datetime", parse_datetime_with_timezone_support)
Database.register_converter("timestamp", parse_datetime_with_timezone_support)
Database.register_converter("TIMESTAMP", parse_datetime_with_timezone_support)
Database.register_converter("decimal", util.typecast_decimal)
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
if Database.version_info >= (2, 4, 1):
@ -56,6 +66,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
can_use_chunked_reads = False
test_db_allows_multiple_connections = False
supports_unspecified_pk = True
supports_timezones = False
supports_1000_query_parameters = False
supports_mixed_date_datetime_comparisons = False
has_bulk_insert = True
@ -131,6 +142,29 @@ class DatabaseOperations(BaseDatabaseOperations):
# sql_flush() implementations). Just return SQL at this point
return sql
def value_to_db_datetime(self, value):
if value is None:
return None
# SQLite doesn't support tz-aware datetimes
if is_aware(value):
if settings.USE_TZ:
value = value.astimezone(utc).replace(tzinfo=None)
else:
raise ValueError("SQLite backend does not support timezone-aware datetimes when USE_TZ is False.")
return unicode(value)
def value_to_db_time(self, value):
if value is None:
return None
# SQLite doesn't support tz-aware datetimes
if is_aware(value):
raise ValueError("SQLite backend does not support timezone-aware times.")
return unicode(value)
def year_lookup_bounds(self, value):
first = '%s-01-01'
second = '%s-12-31 23:59:59.999999'
@ -147,11 +181,11 @@ class DatabaseOperations(BaseDatabaseOperations):
elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField':
return int(value)
elif internal_type == 'DateField':
return util.typecast_date(value)
return parse_date(value)
elif internal_type == 'DateTimeField':
return util.typecast_timestamp(value)
return parse_datetime_with_timezone_support(value)
elif internal_type == 'TimeField':
return util.typecast_time(value)
return parse_time(value)
# No field, or the field isn't known to be a decimal or integer
return value

View File

@ -3,7 +3,9 @@ import decimal
import hashlib
from time import time
from django.conf import settings
from django.utils.log import getLogger
from django.utils.timezone import utc
logger = getLogger('django.db.backends')
@ -99,8 +101,10 @@ def typecast_timestamp(s): # does NOT store time zone information
seconds, microseconds = seconds.split('.')
else:
microseconds = '0'
tzinfo = utc if settings.USE_TZ else None
return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]),
int(times[0]), int(times[1]), int(seconds), int((microseconds + '000000')[:6]))
int(times[0]), int(times[1]), int(seconds),
int((microseconds + '000000')[:6]), tzinfo)
def typecast_decimal(s):
if s is None or s == '':

View File

@ -1,8 +1,6 @@
import copy
import datetime
import decimal
import re
import time
import math
from itertools import tee
@ -12,8 +10,10 @@ from django.conf import settings
from django import forms
from django.core import exceptions, validators
from django.utils.datastructures import DictWrapper
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import curry
from django.utils.text import capfirst
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode, force_unicode, smart_str
from django.utils.ipv6 import clean_ipv6_address
@ -180,8 +180,8 @@ class Field(object):
return
elif value == option_key:
return
raise exceptions.ValidationError(
self.error_messages['invalid_choice'] % value)
msg = self.error_messages['invalid_choice'] % value
raise exceptions.ValidationError(msg)
if value is None and not self.null:
raise exceptions.ValidationError(self.error_messages['null'])
@ -638,11 +638,7 @@ class CommaSeparatedIntegerField(CharField):
defaults.update(kwargs)
return super(CommaSeparatedIntegerField, self).formfield(**defaults)
ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
class DateField(Field):
description = _("Date (without time)")
empty_strings_allowed = False
default_error_messages = {
'invalid': _(u"'%s' value has an invalid date format. It must be "
@ -650,11 +646,11 @@ class DateField(Field):
'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) "
u"but it is an invalid date."),
}
description = _("Date (without time)")
def __init__(self, verbose_name=None, name=None, auto_now=False,
auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add
# HACKs : auto_now_add/auto_now should be done as a default or a
# pre_save.
if auto_now or auto_now_add:
kwargs['editable'] = False
kwargs['blank'] = True
@ -671,18 +667,17 @@ class DateField(Field):
if isinstance(value, datetime.date):
return value
if not ansi_date_re.search(value):
msg = self.error_messages['invalid'] % str(value)
raise exceptions.ValidationError(msg)
# Now that we have the date string in YYYY-MM-DD format, check to make
# sure it's a valid date.
# We could use time.strptime here and catch errors, but datetime.date
# produces much friendlier error messages.
year, month, day = map(int, value.split('-'))
value = smart_str(value)
try:
return datetime.date(year, month, day)
except ValueError, e:
msg = self.error_messages['invalid_date'] % str(value)
parsed = parse_date(value)
if parsed is not None:
return parsed
except ValueError:
msg = self.error_messages['invalid_date'] % value
raise exceptions.ValidationError(msg)
msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg)
def pre_save(self, model_instance, add):
@ -721,11 +716,7 @@ class DateField(Field):
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
if val is None:
data = ''
else:
data = str(val)
return data
return '' if val is None else val.isoformat()
def formfield(self, **kwargs):
defaults = {'form_class': forms.DateField}
@ -733,13 +724,20 @@ class DateField(Field):
return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField):
empty_strings_allowed = False
default_error_messages = {
'invalid': _(u"'%s' value either has an invalid valid format (The "
u"format must be YYYY-MM-DD HH:MM[:ss[.uuuuuu]]) or is "
u"an invalid date/time."),
'invalid': _(u"'%s' value has an invalid format. It must be in "
u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."),
'invalid_date': _(u"'%s' value has the correct format "
u"(YYYY-MM-DD) but it is an invalid date."),
'invalid_datetime': _(u"'%s' value has the correct format "
u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
u"but it is an invalid date/time."),
}
description = _("Date (with time)")
# __init__ is inherited from DateField
def get_internal_type(self):
return "DateTimeField"
@ -751,59 +749,59 @@ class DateTimeField(DateField):
if isinstance(value, datetime.date):
return datetime.datetime(value.year, value.month, value.day)
# Attempt to parse a datetime:
value = smart_str(value)
# split usecs, because they are not recognized by strptime.
if '.' in value:
try:
value, usecs = value.split('.')
usecs = int(usecs)
except ValueError:
raise exceptions.ValidationError(
self.error_messages['invalid'] % str(value))
else:
usecs = 0
kwargs = {'microsecond': usecs}
try: # Seconds are optional, so try converting seconds first.
return datetime.datetime(
*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6], **kwargs)
except ValueError:
try: # Try without seconds.
return datetime.datetime(
*time.strptime(value, '%Y-%m-%d %H:%M')[:5], **kwargs)
except ValueError: # Try without hour/minutes/seconds.
try:
return datetime.datetime(
*time.strptime(value, '%Y-%m-%d')[:3], **kwargs)
parsed = parse_datetime(value)
if parsed is not None:
return parsed
except ValueError:
raise exceptions.ValidationError(
self.error_messages['invalid'] % str(value))
msg = self.error_messages['invalid_datetime'] % value
raise exceptions.ValidationError(msg)
try:
parsed = parse_date(value)
if parsed is not None:
return datetime.datetime(parsed.year, parsed.month, parsed.day)
except ValueError:
msg = self.error_messages['invalid_date'] % value
raise exceptions.ValidationError(msg)
msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg)
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.datetime.now()
value = timezone.now()
setattr(model_instance, self.attname, value)
return value
else:
return super(DateTimeField, self).pre_save(model_instance, add)
# contribute_to_class is inherited from DateField, it registers
# get_next_by_FOO and get_prev_by_FOO
# get_prep_lookup is inherited from DateField
def get_prep_value(self, value):
return self.to_python(value)
value = self.to_python(value)
if settings.USE_TZ and timezone.is_naive(value):
# For backwards compatibility, interpret naive datetimes in local
# time. This won't work during DST change, but we can't do much
# about it, so we let the exceptions percolate up the call stack.
default_timezone = timezone.get_default_timezone()
value = timezone.make_aware(value, default_timezone)
return value
def get_db_prep_value(self, value, connection, prepared=False):
# Casts dates into the format expected by the backend
# Casts datetimes into the format expected by the backend
if not prepared:
value = self.get_prep_value(value)
return connection.ops.value_to_db_datetime(value)
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
if val is None:
data = ''
else:
data = str(val.replace(microsecond=0, tzinfo=None))
return data
return '' if val is None else val.isoformat()
def formfield(self, **kwargs):
defaults = {'form_class': forms.DateTimeField}
@ -1158,17 +1156,21 @@ class TextField(Field):
return super(TextField, self).formfield(**defaults)
class TimeField(Field):
description = _("Time")
empty_strings_allowed = False
default_error_messages = {
'invalid': _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'),
'invalid': _(u"'%s' value has an invalid format. It must be in "
u"HH:MM[:ss[.uuuuuu]] format."),
'invalid_time': _(u"'%s' value has the correct format "
u"(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."),
}
description = _("Time")
def __init__(self, verbose_name=None, name=None, auto_now=False,
auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add:
kwargs['editable'] = False
kwargs['blank'] = True
Field.__init__(self, verbose_name, name, **kwargs)
def get_internal_type(self):
@ -1185,30 +1187,18 @@ class TimeField(Field):
# database backend (e.g. Oracle), so we'll be accommodating.
return value.time()
# Attempt to parse a datetime:
value = smart_str(value)
# split usecs, because they are not recognized by strptime.
if '.' in value:
try:
value, usecs = value.split('.')
usecs = int(usecs)
except ValueError:
raise exceptions.ValidationError(
self.error_messages['invalid'])
else:
usecs = 0
kwargs = {'microsecond': usecs}
try: # Seconds are optional, so try converting seconds first.
return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6],
**kwargs)
try:
parsed = parse_time(value)
if parsed is not None:
return parsed
except ValueError:
try: # Try without seconds.
return datetime.time(*time.strptime(value, '%H:%M')[3:5],
**kwargs)
except ValueError:
raise exceptions.ValidationError(
self.error_messages['invalid'])
msg = self.error_messages['invalid_time'] % value
raise exceptions.ValidationError(msg)
msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg)
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
@ -1229,11 +1219,7 @@ class TimeField(Field):
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
if val is None:
data = ''
else:
data = str(val.replace(microsecond=0))
return data
return '' if val is None else val.isoformat()
def formfield(self, **kwargs):
defaults = {'form_class': forms.TimeField}

View File

@ -66,7 +66,7 @@ class ConnectionHandler(object):
if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']:
conn['ENGINE'] = 'django.db.backends.dummy'
conn.setdefault('OPTIONS', {})
conn.setdefault('TIME_ZONE', settings.TIME_ZONE)
conn.setdefault('TIME_ZONE', 'UTC' if settings.USE_TZ else settings.TIME_ZONE)
for setting in ['NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']:
conn.setdefault(setting, '')
for setting in ['TEST_CHARSET', 'TEST_COLLATION', 'TEST_NAME', 'TEST_MIRROR']:

View File

@ -17,7 +17,7 @@ except ImportError:
from django.core import validators
from django.core.exceptions import ValidationError
from django.forms.util import ErrorList
from django.forms.util import ErrorList, from_current_timezone, to_current_timezone
from django.forms.widgets import (TextInput, PasswordInput, HiddenInput,
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
@ -409,6 +409,11 @@ class DateTimeField(BaseTemporalField):
'invalid': _(u'Enter a valid date/time.'),
}
def prepare_value(self, value):
if isinstance(value, datetime.datetime):
value = to_current_timezone(value)
return value
def to_python(self, value):
"""
Validates that the input can be converted to a datetime. Returns a
@ -417,9 +422,10 @@ class DateTimeField(BaseTemporalField):
if value in validators.EMPTY_VALUES:
return None
if isinstance(value, datetime.datetime):
return value
return from_current_timezone(value)
if isinstance(value, datetime.date):
return datetime.datetime(value.year, value.month, value.day)
result = datetime.datetime(value.year, value.month, value.day)
return from_current_timezone(result)
if isinstance(value, list):
# Input comes from a SplitDateTimeWidget, for example. So, it's two
# components: date and time.
@ -428,7 +434,8 @@ class DateTimeField(BaseTemporalField):
if value[0] in validators.EMPTY_VALUES and value[1] in validators.EMPTY_VALUES:
return None
value = '%s %s' % tuple(value)
return super(DateTimeField, self).to_python(value)
result = super(DateTimeField, self).to_python(value)
return from_current_timezone(result)
def strptime(self, value, format):
return datetime.datetime.strptime(value, format)
@ -979,7 +986,8 @@ class SplitDateTimeField(MultiValueField):
raise ValidationError(self.error_messages['invalid_date'])
if data_list[1] in validators.EMPTY_VALUES:
raise ValidationError(self.error_messages['invalid_time'])
return datetime.datetime.combine(*data_list)
result = datetime.datetime.combine(*data_list)
return from_current_timezone(result)
return None

View File

@ -1,6 +1,9 @@
from django.conf import settings
from django.utils.html import conditional_escape
from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.safestring import mark_safe
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
# Import ValidationError so that it can be imported from this
# module to maintain backwards compatibility.
@ -52,3 +55,31 @@ class ErrorList(list, StrAndUnicode):
def __repr__(self):
return repr([force_unicode(e) for e in self])
# Utilities for time zone support in DateTimeField et al.
def from_current_timezone(value):
"""
When time zone support is enabled, convert naive datetimes
entered in the current time zone to aware datetimes.
"""
if settings.USE_TZ and value is not None and timezone.is_naive(value):
current_timezone = timezone.get_current_timezone()
try:
return timezone.make_aware(value, current_timezone)
except Exception, e:
raise ValidationError(_('%(datetime)s couldn\'t be interpreted '
'in time zone %(current_timezone)s; it '
'may be ambiguous or it may not exist.')
% {'datetime': value,
'current_timezone': current_timezone})
return value
def to_current_timezone(value):
"""
When time zone support is enabled, convert aware datetimes
to naive dateimes in the current time zone for display.
"""
if settings.USE_TZ and value is not None and timezone.is_aware(value):
current_timezone = timezone.get_current_timezone()
return timezone.make_naive(value, current_timezone)
return value

View File

@ -10,7 +10,7 @@ from itertools import chain
from urlparse import urljoin
from django.conf import settings
from django.forms.util import flatatt
from django.forms.util import flatatt, to_current_timezone
from django.utils.datastructures import MultiValueDict, MergeDict
from django.utils.html import escape, conditional_escape
from django.utils.translation import ugettext, ugettext_lazy
@ -847,6 +847,7 @@ class SplitDateTimeWidget(MultiWidget):
def decompress(self, value):
if value:
value = to_current_timezone(value)
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]

View File

@ -18,6 +18,7 @@ from django.utils.safestring import (SafeData, EscapeData, mark_safe,
from django.utils.formats import localize
from django.utils.html import escape
from django.utils.module_loading import module_has_submodule
from django.utils.timezone import aslocaltime
TOKEN_TEXT = 0
@ -593,6 +594,8 @@ class FilterExpression(object):
arg_vals.append(mark_safe(arg))
else:
arg_vals.append(arg.resolve(context))
if getattr(func, 'expects_localtime', False):
obj = aslocaltime(obj, context.use_tz)
if getattr(func, 'needs_autoescape', False):
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
else:
@ -853,6 +856,7 @@ def _render_value_in_context(value, context):
means escaping, if required, and conversion to a unicode object. If value
is a string, it is expected to have already been translated.
"""
value = aslocaltime(value, use_tz=context.use_tz)
value = localize(value, use_l10n=context.use_l10n)
value = force_unicode(value)
if ((context.autoescape and not isinstance(value, SafeData)) or
@ -1077,7 +1081,7 @@ class Library(object):
elif name is not None and filter_func is not None:
# register.filter('somename', somefunc)
self.filters[name] = filter_func
for attr in ('is_safe', 'needs_autoescape'):
for attr in ('expects_localtime', 'is_safe', 'needs_autoescape'):
if attr in flags:
value = flags[attr]
# set the flag on the filter for FilterExpression.resolve
@ -1189,6 +1193,7 @@ class Library(object):
'autoescape': context.autoescape,
'current_app': context.current_app,
'use_l10n': context.use_l10n,
'use_tz': context.use_tz,
})
# Copy across the CSRF token, if present, because
# inclusion tags are often used for forms, and we need

View File

@ -83,10 +83,12 @@ class BaseContext(object):
class Context(BaseContext):
"A stack container for variable context"
def __init__(self, dict_=None, autoescape=True, current_app=None, use_l10n=None):
def __init__(self, dict_=None, autoescape=True, current_app=None,
use_l10n=None, use_tz=None):
self.autoescape = autoescape
self.use_l10n = use_l10n
self.current_app = current_app
self.use_l10n = use_l10n
self.use_tz = use_tz
self.render_context = RenderContext()
super(Context, self).__init__(dict_)
@ -162,8 +164,10 @@ class RequestContext(Context):
Additional processors can be specified as a list of callables
using the "processors" keyword argument.
"""
def __init__(self, request, dict=None, processors=None, current_app=None, use_l10n=None):
Context.__init__(self, dict, current_app=current_app, use_l10n=use_l10n)
def __init__(self, request, dict_=None, processors=None, current_app=None,
use_l10n=None, use_tz=None):
Context.__init__(self, dict_, current_app=current_app,
use_l10n=use_l10n, use_tz=use_tz)
if processors is None:
processors = ()
else:

View File

@ -3,6 +3,7 @@ from django.utils.encoding import force_unicode
from django.utils.html import escape
from django.utils.safestring import SafeData, EscapeData
from django.utils.formats import localize
from django.utils.timezone import aslocaltime
class DebugLexer(Lexer):
@ -81,6 +82,7 @@ class DebugVariableNode(VariableNode):
def render(self, context):
try:
output = self.filter_expression.resolve(context)
output = aslocaltime(output, use_tz=context.use_tz)
output = localize(output, use_l10n=context.use_l10n)
output = force_unicode(output)
except UnicodeDecodeError:

View File

@ -692,7 +692,7 @@ def get_digit(value, arg):
# DATES #
###################
@register.filter(is_safe=False)
@register.filter(expects_localtime=True, is_safe=False)
def date(value, arg=None):
"""Formats a date according to the given format."""
if not value:
@ -707,7 +707,7 @@ def date(value, arg=None):
except AttributeError:
return ''
@register.filter(is_safe=False)
@register.filter(expects_localtime=True, is_safe=False)
def time(value, arg=None):
"""Formats a time according to the given format."""
if value in (None, u''):

191
django/templatetags/tz.py Normal file
View 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])

View File

@ -25,6 +25,7 @@ from django.conf import settings
from django.core.cache import get_cache
from django.utils.encoding import smart_str, iri_to_uri
from django.utils.http import http_date
from django.utils.timezone import get_current_timezone_name
from django.utils.translation import get_language
cc_delim_re = re.compile(r'\s*,\s*')
@ -157,12 +158,14 @@ def has_vary_header(response, header_query):
return header_query.lower() in existing_headers
def _i18n_cache_key_suffix(request, cache_key):
"""If enabled, returns the cache key ending with a locale."""
"""If necessary, adds the current locale or time zone to the cache key."""
if settings.USE_I18N or settings.USE_L10N:
# first check if LocaleMiddleware or another middleware added
# LANGUAGE_CODE to request, then fall back to the active language
# which in turn can also fall back to settings.LANGUAGE_CODE
cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
if settings.USE_TZ:
cache_key += '.%s' % get_current_timezone_name()
return cache_key
def _generate_cache_key(request, method, headerlist, key_prefix):

View File

@ -14,10 +14,13 @@ Usage:
import re
import time
import calendar
import datetime
from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR
from django.utils.tzinfo import LocalTimezone
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
from django.utils.timezone import is_aware, is_naive
re_formatchars = re.compile(r'(?<!\\)([aAbBcdDEfFgGhHiIjlLmMnNOPrsStTUuwWyYzZ])')
re_escaped = re.compile(r'\\(.)')
@ -115,9 +118,12 @@ class DateFormat(TimeFormat):
def __init__(self, dt):
# Accepts either a datetime or date object.
self.data = dt
self.timezone = getattr(dt, 'tzinfo', None)
if hasattr(self.data, 'hour') and not self.timezone:
self.timezone = None
if isinstance(dt, datetime.datetime):
if is_naive(dt):
self.timezone = LocalTimezone(dt)
else:
self.timezone = dt.tzinfo
def b(self):
"Month, textual, 3 letters, lowercase; e.g. 'jan'"
@ -218,7 +224,7 @@ class DateFormat(TimeFormat):
def U(self):
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
if getattr(self.data, 'tzinfo', None):
if isinstance(self.data, datetime.datetime) and is_aware(self.data):
return int(calendar.timegm(self.data.utctimetuple()))
else:
return int(time.mktime(self.data.timetuple()))

93
django/utils/dateparse.py Normal file
View 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)

View File

@ -28,6 +28,7 @@ import urlparse
from django.utils.xmlutils import SimplerXMLGenerator
from django.utils.encoding import force_unicode, iri_to_uri
from django.utils import datetime_safe
from django.utils.timezone import is_aware
def rfc2822_date(date):
# We can't use strftime() because it produces locale-dependant results, so
@ -40,7 +41,7 @@ def rfc2822_date(date):
dow = days[date.weekday()]
month = months[date.month - 1]
time_str = date.strftime('%s, %%d %s %%Y %%H:%%M:%%S ' % (dow, month))
if date.tzinfo:
if is_aware(date):
offset = date.tzinfo.utcoffset(date)
timezone = (offset.days * 24 * 60) + (offset.seconds // 60)
hour, minute = divmod(timezone, 60)
@ -51,7 +52,7 @@ def rfc2822_date(date):
def rfc3339_date(date):
# Support datetime objects older than 1900
date = datetime_safe.new_datetime(date)
if date.tzinfo:
if is_aware(date):
time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
offset = date.tzinfo.utcoffset(date)
timezone = (offset.days * 24 * 60) + (offset.seconds // 60)

View File

@ -1,6 +1,6 @@
import datetime
from django.utils.tzinfo import LocalTimezone
from django.utils.timezone import is_aware, utc
from django.utils.translation import ungettext, ugettext
def timesince(d, now=None):
@ -31,13 +31,10 @@ def timesince(d, now=None):
now = datetime.datetime(now.year, now.month, now.day)
if not now:
if d.tzinfo:
now = datetime.datetime.now(LocalTimezone(d))
else:
now = datetime.datetime.now()
now = datetime.datetime.now(utc if is_aware(d) else None)
# ignore microsecond part of 'd' since we removed it from 'now'
delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
delta = now - d
# ignore microseconds
since = delta.days * 24 * 60 * 60 + delta.seconds
if since <= 0:
# d is in the future compared to now, stop processing.
@ -61,8 +58,5 @@ def timeuntil(d, now=None):
the given time.
"""
if not now:
if getattr(d, 'tzinfo', None):
now = datetime.datetime.now(LocalTimezone(d))
else:
now = datetime.datetime.now()
now = datetime.datetime.now(utc if is_aware(d) else None)
return timesince(now, d)

266
django/utils/timezone.py Normal file
View 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)

View File

@ -2,8 +2,14 @@
import time
from datetime import timedelta, tzinfo
from django.utils.encoding import smart_unicode, smart_str, DEFAULT_LOCALE_ENCODING
# Python's doc say: "A tzinfo subclass must have an __init__() method that can
# be called with no arguments". FixedOffset and LocalTimezone don't honor this
# requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as
# well as pickling/unpickling.
class FixedOffset(tzinfo):
"Fixed offset in minutes east from UTC."
def __init__(self, offset):
@ -19,6 +25,9 @@ class FixedOffset(tzinfo):
def __repr__(self):
return self.__name
def __getinitargs__(self):
return self.__offset,
def utcoffset(self, dt):
return self.__offset
@ -28,15 +37,25 @@ class FixedOffset(tzinfo):
def dst(self, dt):
return timedelta(0)
# This implementation is used for display purposes. It uses an approximation
# for DST computations on dates >= 2038.
# A similar implementation exists in django.utils.timezone. It's used for
# timezone support (when USE_TZ = True) and focuses on correctness.
class LocalTimezone(tzinfo):
"Proxy timezone information from time module."
def __init__(self, dt):
tzinfo.__init__(self)
self.__dt = dt
self._tzname = self.tzname(dt)
def __repr__(self):
return smart_str(self._tzname)
def __getinitargs__(self):
return self.__dt,
def utcoffset(self, dt):
if self._isdst(dt):
return timedelta(seconds=-time.altzone)

View File

@ -347,6 +347,31 @@ function; this syntax is deprecated.
return mark_safe(result)
initial_letter_filter.needs_autoescape = True
.. _filters-timezones:
Filters and time zones
~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.4
If you write a custom filter that operates on :class:`~datetime.datetime`
objects, you'll usually register it with the ``expects_localtime`` flag set to
``True``:
.. code-block:: python
@register.filter(expects_localtime=True)
def businesshours(value):
try:
return 9 <= value.hour < 17
except AttributeError:
return ''
When this flag is set, if the first argument to your filter is a time zone
aware datetime, Django will convert it to the current time zone before passing
to your filter when appropriate, according to :ref:`rules for time zones
conversions in templates <time-zones-in-templates>`.
Writing custom template tags
----------------------------

View File

@ -546,6 +546,12 @@ Examples::
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[datetime.datetime(2005, 3, 20)]
.. warning::
When :doc:`time zone support </topics/i18n/timezones>` is enabled, Django
uses UTC in the database connection, which means the aggregation is
performed in UTC. This is a known limitation of the current implementation.
none
~~~~
@ -1953,6 +1959,13 @@ Note this will match any record with a ``pub_date`` that falls on a Monday (day
2 of the week), regardless of the month or year in which it occurs. Week days
are indexed with day 1 being Sunday and day 7 being Saturday.
.. warning::
When :doc:`time zone support </topics/i18n/timezones>` is enabled, Django
uses UTC in the database connection, which means the ``year``, ``month``,
``day`` and ``week_day`` lookups are performed in UTC. This is a known
limitation of the current implementation.
.. fieldlookup:: isnull
isnull

View File

@ -1810,6 +1810,7 @@ Default::
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.contrib.messages.context_processors.messages")
A tuple of callables that are used to populate the context in ``RequestContext``.
@ -1830,6 +1831,10 @@ of items to be merged into the context.
The ``django.core.context_processors.static`` context processor
was added in this release.
.. versionadded:: 1.4
The ``django.core.context_processors.tz`` context processor
was added in this release.
.. setting:: TEMPLATE_DEBUG
TEMPLATE_DEBUG
@ -1971,6 +1976,9 @@ Default: ``'America/Chicago'``
.. versionchanged:: 1.2
``None`` was added as an allowed value.
.. versionchanged:: 1.4
The meaning of this setting now depends on the value of :setting:`USE_TZ`.
A string representing the time zone for this installation, or
``None``. `See available choices`_. (Note that list of available
choices lists more than one on the same line; you'll want to use just
@ -1978,16 +1986,19 @@ one of the choices for a given time zone. For instance, one line says
``'Europe/London GB GB-Eire'``, but you should use the first bit of
that -- ``'Europe/London'`` -- as your :setting:`TIME_ZONE` setting.)
Note that this is the time zone to which Django will convert all
dates/times -- not necessarily the timezone of the server. For
example, one server may serve multiple Django-powered sites, each with
a separate time-zone setting.
Note that this isn't necessarily the timezone of the server. For example, one
server may serve multiple Django-powered sites, each with a separate time zone
setting.
Normally, Django sets the ``os.environ['TZ']`` variable to the time
zone you specify in the :setting:`TIME_ZONE` setting. Thus, all your views
and models will automatically operate in the correct time zone.
However, Django won't set the ``TZ`` environment variable under the
following conditions:
When :setting:`USE_TZ` is ``False``, this is the time zone in which Django will
store all datetimes. When :setting:`USE_TZ` is ``True``, this is the default
time zone that Django will use to display datetimes in templates and to
interpret datetimes entered in forms.
Django sets the ``os.environ['TZ']`` variable to the time zone you specify in
the :setting:`TIME_ZONE` setting. Thus, all your views and models will
automatically operate in this time zone. However, Django won't set the ``TZ``
environment variable under the following conditions:
* If you're using the manual configuration option as described in
:ref:`manually configuring settings
@ -2004,7 +2015,6 @@ to ensure your processes are running in the correct environment.
environment. If you're running Django on Windows, this variable
must be set to match the system timezone.
.. _See available choices: http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
.. setting:: URL_VALIDATOR_USER_AGENT
@ -2043,7 +2053,7 @@ This provides an easy way to turn it off, for performance. If this is set to
``False``, Django will make some optimizations so as not to load the
translation machinery.
See also :setting:`USE_L10N`
See also :setting:`LANGUAGE_CODE`, :setting:`USE_L10N` and :setting:`USE_TZ`.
.. setting:: USE_L10N
@ -2058,7 +2068,7 @@ A boolean that specifies if localized formatting of data will be enabled by
default or not. If this is set to ``True``, e.g. Django will display numbers and
dates using the format of the current locale.
See also :setting:`USE_I18N` and :setting:`LANGUAGE_CODE`
See also :setting:`LANGUAGE_CODE`, :setting:`USE_I18N` and :setting:`USE_TZ`.
.. note::
@ -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
: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
USE_X_FORWARDED_HOST

View File

@ -2318,8 +2318,45 @@ Value Argument Outputs
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
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,
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
^^^^^^

View File

@ -131,6 +131,41 @@ results. Instead do::
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``
=========================
@ -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
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``
=======================

View File

@ -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
switching to the new ``manage.py``.
Improved WSGI support
~~~~~~~~~~~~~~~~~~~~~
@ -427,6 +426,25 @@ callable :djadmin:`runserver` uses.
(The :djadmin:`runfcgi` management command also internally wraps the WSGI
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
~~~~~~~~~~~~~~
@ -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
: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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -502,7 +502,9 @@ cache multilingual sites without having to create the cache key yourself.
.. 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`_

View File

@ -8,6 +8,7 @@ Internationalization and localization
translation
formatting
timezones
Overview
========
@ -17,8 +18,8 @@ application to offer its content in languages and formats tailored to the
audience.
Django has full support for :doc:`translation of text
</topics/i18n/translation>` and :doc:`formatting of dates, times and numbers
</topics/i18n/formatting>`.
</topics/i18n/translation>`, :doc:`formatting of dates, times and numbers
</topics/i18n/formatting>`, and :doc:`time zones </topics/i18n/timezones>`.
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
their preferences.
Obviously, translation depends on the target language. Formatting usually
depends on the target country.
Obviously, translation depends on the target language, and formatting usually
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
===========

View 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

View File

@ -56,25 +56,25 @@ class FixtureLoadingTests(TestCase):
])
# 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
self._dumpdata_assert(['fixtures.Category'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}]')
# ...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
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
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
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
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.
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)
# 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
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):
# 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
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
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):
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
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
management.call_command('loaddata', 'fixture4', verbosity=0, commit=False)

View File

@ -230,7 +230,7 @@ class SerializersTestBase(object):
serial_str = serializers.serialize(self.serializer_name, [a])
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):
"""
@ -323,7 +323,7 @@ class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, Transacti
<object pk="1" model="serializers.article">
<field to="serializers.author" name="author" rel="ManyToOneRel">1</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">
<object pk="1"></object>
</field>
@ -374,7 +374,7 @@ class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, Transact
"model": "serializers.article",
"fields": {
"headline": "Forward references pose no problem",
"pub_date": "2006-06-16 15:00:00",
"pub_date": "2006-06-16T15:00:00",
"categories": [1],
"author": 1
}

View File

View 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)

View 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>

View 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

View 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)

View 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)

View 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)),
)

View File

@ -104,16 +104,43 @@ class ValidationMessagesTest(TestCase):
f.clean('foo', None)
except ValidationError, e:
self.assertEqual(e.messages, [
u"'foo' value either has an invalid valid format "
u"(The format must be YYYY-MM-DD HH:MM[:ss[.uuuuuu]]) "
u"or is an invalid date/time."])
self.assertRaises(ValidationError, f.clean,
'2011-10-32 10:10', None)
u"'foo' value has an invalid format. It must be "
u"in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."])
# Correct format but invalid date
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
self.assertRaises(ValidationError, f.clean, '2011-10-32 10:10', None)
try:
f.clean('2011-10-32 10:10', None)
except ValidationError, e:
self.assertEqual(e.messages, [
u"'2011-10-32 10:10' value either has an invalid valid format "
u"(The format must be YYYY-MM-DD HH:MM[:ss[.uuuuuu]]) "
u"or is an invalid date/time."])
u"'2011-10-32 10:10' value has the correct format "
u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
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."])

View File

@ -24,7 +24,7 @@ from django.template.response import TemplateResponse
from django.test import TestCase, RequestFactory
from django.test.utils import (get_warnings_state, restore_warnings_state,
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,
learn_cache_key, patch_cache_control, patch_response_headers)
from django.views.decorators.cache import cache_page
@ -1154,7 +1154,7 @@ class CacheI18nTest(TestCase):
request.session = {}
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):
request = self._get_request()
lang = translation.get_language()
@ -1164,7 +1164,7 @@ class CacheI18nTest(TestCase):
key2 = get_cache_key(request)
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):
request = self._get_request()
lang = translation.get_language()
@ -1174,13 +1174,25 @@ class CacheI18nTest(TestCase):
key2 = get_cache_key(request)
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)
def test_cache_key_no_i18n (self):
request = self._get_request()
lang = translation.get_language()
tz = timezone.get_current_timezone_name()
response = HttpResponse()
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(tz, key, "Cache keys shouldn't include the time zone name when i18n isn't active")
@override_settings(
CACHE_MIDDLEWARE_KEY_PREFIX="test",

View File

@ -3,7 +3,7 @@ from __future__ import absolute_import
import datetime
from django.test import TestCase, skipIfDBFeature
from django.utils import tzinfo
from django.utils.timezone import utc
from .models import Donut, RumBaba
@ -79,7 +79,7 @@ class DataTypesTestCase(TestCase):
def test_error_on_timezone(self):
"""Regression test for #8354: the MySQL and Oracle backends should raise
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)
self.assertRaises(ValueError, d.save)
# ValueError: MySQL backend does not support timezone-aware datetimes.

View File

@ -421,7 +421,7 @@ class DefaultFiltersTests(TestCase):
def test_timeuntil(self):
self.assertEqual(
timeuntil_filter(datetime.datetime.now() + datetime.timedelta(1)),
timeuntil_filter(datetime.datetime.now() + datetime.timedelta(1, 1)),
u'1 day')
self.assertEqual(

View File

@ -4,6 +4,7 @@ import time
from django.utils.dateformat import format
from django.utils import dateformat, translation, unittest
from django.utils.timezone import utc
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())
def test_epoch(self):
utc = FixedOffset(0)
udt = datetime(1970, 1, 1, tzinfo=utc)
self.assertEqual(format(udt, 'U'), u'0')

View File

@ -23,3 +23,4 @@ from .datetime_safe import DatetimeTests
from .baseconv import TestBaseConv
from .jslex import JsTokensTest, JsToCForGettextTest
from .ipv6 import TestUtilsIPv6
from .timezone import TimezoneTests

View File

@ -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'0 minutes')
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')

View 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)

View File

@ -1,5 +1,7 @@
import copy
import datetime
import os
import pickle
import time
from django.utils.tzinfo import FixedOffset, LocalTimezone
from django.utils import unittest
@ -60,3 +62,18 @@ class TzinfoTests(unittest.TestCase):
self.assertEqual(
repr(datetime.datetime.fromtimestamp(ts + 3600, tz)),
'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)