Added support for time zones. Thanks Luke Plant for the review. Fixed #2626.
For more information on this project, see this thread: http://groups.google.com/group/django-developers/browse_thread/thread/cf0423bbb85b1bbf git-svn-id: http://code.djangoproject.com/svn/django/trunk@17106 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
01f70349c9
commit
9b1cb755a2
|
@ -31,9 +31,13 @@ INTERNAL_IPS = ()
|
|||
|
||||
# Local time zone for this installation. All choices can be found here:
|
||||
# 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',
|
||||
)
|
||||
|
|
|
@ -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 = ''
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
return now
|
||||
def now(self, tz=None):
|
||||
if tz is None or tz.utcoffset(now) is None:
|
||||
return now
|
||||
else:
|
||||
# equals now.replace(tzinfo=utc)
|
||||
return now.replace(tzinfo=tz) + tz.utcoffset(now)
|
||||
|
||||
# naturaltime also calls timesince/timeuntil
|
||||
from django.contrib.humanize.templatetags import humanize
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
@ -472,9 +480,28 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
# Set oracle date to ansi date format. This only needs to execute
|
||||
# once when we create a new connection. We also set the Territory
|
||||
# to 'AMERICA' which forces Sunday to evaluate to a '1' in TO_CHAR().
|
||||
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' "
|
||||
"NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF' "
|
||||
"NLS_TERRITORY = 'AMERICA'")
|
||||
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
|
||||
" NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'"
|
||||
" NLS_TERRITORY = 'AMERICA'"
|
||||
+ (" TIME_ZONE = 'UTC'" if settings.USE_TZ else ''))
|
||||
|
||||
def datetime_converter(dt):
|
||||
# Confirm that dt is naive before overwriting its tzinfo.
|
||||
if dt is not None and is_naive(dt):
|
||||
dt = dt.replace(tzinfo=utc)
|
||||
return dt
|
||||
|
||||
def output_type_handler(cursor, name, default_type,
|
||||
size, precision, scale):
|
||||
# datetimes are returned as TIMESTAMP, except the results
|
||||
# of "dates" queries, which are returned as DATETIME.
|
||||
if settings.USE_TZ and default_type in (Database.TIMESTAMP,
|
||||
Database.DATETIME):
|
||||
return cursor.var(default_type,
|
||||
arraysize=cursor.arraysize,
|
||||
outconverter=datetime_converter)
|
||||
|
||||
self.connection.outputtypehandler = output_type_handler
|
||||
|
||||
if 'operators' not in self.__dict__:
|
||||
# Ticket #14149: Check whether our LIKE implementation will
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,22 +34,29 @@ 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):
|
||||
if Database.version_info >= (2, 4, 1):
|
||||
# Starting in 2.4.1, the str type is not accepted anymore, therefore,
|
||||
# we convert all str objects to Unicode
|
||||
# As registering a adapter for a primitive type causes a small
|
||||
# slow-down, this adapter is only registered for sqlite3 versions
|
||||
# needing it.
|
||||
Database.register_adapter(str, lambda s:s.decode('utf-8'))
|
||||
Database.register_adapter(SafeString, lambda s:s.decode('utf-8'))
|
||||
Database.register_adapter(str, lambda s: s.decode('utf-8'))
|
||||
Database.register_adapter(SafeString, lambda s: s.decode('utf-8'))
|
||||
|
||||
class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
# SQLite cannot handle us only partially reading from a cursor's result set
|
||||
|
@ -56,6 +66,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||
can_use_chunked_reads = False
|
||||
test_db_allows_multiple_connections = False
|
||||
supports_unspecified_pk = True
|
||||
supports_timezones = False
|
||||
supports_1000_query_parameters = False
|
||||
supports_mixed_date_datetime_comparisons = False
|
||||
has_bulk_insert = True
|
||||
|
@ -131,6 +142,29 @@ 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
|
||||
|
|
|
@ -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 == '':
|
||||
|
|
|
@ -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,20 +667,19 @@ 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):
|
||||
if self.auto_now or (self.auto_now_add and add):
|
||||
value = datetime.date.today()
|
||||
|
@ -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)
|
||||
|
||||
try:
|
||||
parsed = parse_datetime(value)
|
||||
if parsed is not None:
|
||||
return parsed
|
||||
except ValueError:
|
||||
try: # Try without seconds.
|
||||
return datetime.datetime(
|
||||
*time.strptime(value, '%Y-%m-%d %H:%M')[:5], **kwargs)
|
||||
except ValueError: # Try without hour/minutes/seconds.
|
||||
try:
|
||||
return datetime.datetime(
|
||||
*time.strptime(value, '%Y-%m-%d')[:3], **kwargs)
|
||||
except ValueError:
|
||||
raise exceptions.ValidationError(
|
||||
self.error_messages['invalid'] % str(value))
|
||||
msg = self.error_messages['invalid_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}
|
||||
|
|
|
@ -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']:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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''):
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
from __future__ import with_statement
|
||||
|
||||
from datetime import datetime, tzinfo
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
|
||||
from django.template import Node
|
||||
from django.template import TemplateSyntaxError, Library
|
||||
from django.utils import timezone
|
||||
|
||||
register = Library()
|
||||
|
||||
# HACK: datetime is an old-style class, create a new-style equivalent
|
||||
# so we can define additional attributes.
|
||||
class datetimeobject(datetime, object):
|
||||
pass
|
||||
|
||||
|
||||
# Template filters
|
||||
|
||||
@register.filter
|
||||
def aslocaltime(value):
|
||||
"""
|
||||
Converts a datetime to local time in the active time zone.
|
||||
|
||||
This only makes sense within a {% localtime off %} block.
|
||||
"""
|
||||
return astimezone(value, timezone.get_current_timezone())
|
||||
|
||||
@register.filter
|
||||
def asutc(value):
|
||||
"""
|
||||
Converts a datetime to UTC.
|
||||
"""
|
||||
return astimezone(value, timezone.utc)
|
||||
|
||||
@register.filter
|
||||
def astimezone(value, arg):
|
||||
"""
|
||||
Converts a datetime to local time in a given time zone.
|
||||
|
||||
The argument must be an instance of a tzinfo subclass or a time zone name.
|
||||
If it is a time zone name, pytz is required.
|
||||
|
||||
Naive datetimes are assumed to be in local time in the default time zone.
|
||||
"""
|
||||
if not isinstance(value, datetime):
|
||||
return ''
|
||||
|
||||
# Obtain a timezone-aware datetime
|
||||
try:
|
||||
if timezone.is_naive(value):
|
||||
default_timezone = timezone.get_default_timezone()
|
||||
value = timezone.make_aware(value, default_timezone)
|
||||
# Filters must never raise exceptions, and pytz' exceptions inherit
|
||||
# Exception directly, not a specific subclass. So catch everything.
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
# Obtain a tzinfo instance
|
||||
if isinstance(arg, tzinfo):
|
||||
tz = arg
|
||||
elif isinstance(arg, basestring) and pytz is not None:
|
||||
try:
|
||||
tz = pytz.timezone(arg)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
return ''
|
||||
else:
|
||||
return ''
|
||||
|
||||
# Convert and prevent further conversion
|
||||
result = value.astimezone(tz)
|
||||
if hasattr(tz, 'normalize'):
|
||||
# available for pytz time zones
|
||||
result = tz.normalize(result)
|
||||
|
||||
# HACK: the convert_to_local_time flag will prevent
|
||||
# automatic conversion of the value to local time.
|
||||
result = datetimeobject(result.year, result.month, result.day,
|
||||
result.hour, result.minute, result.second,
|
||||
result.microsecond, result.tzinfo)
|
||||
result.convert_to_local_time = False
|
||||
return result
|
||||
|
||||
|
||||
# Template tags
|
||||
|
||||
class LocalTimeNode(Node):
|
||||
"""
|
||||
Template node class used by ``localtime_tag``.
|
||||
"""
|
||||
def __init__(self, nodelist, use_tz):
|
||||
self.nodelist = nodelist
|
||||
self.use_tz = use_tz
|
||||
|
||||
def render(self, context):
|
||||
old_setting = context.use_tz
|
||||
context.use_tz = self.use_tz
|
||||
output = self.nodelist.render(context)
|
||||
context.use_tz = old_setting
|
||||
return output
|
||||
|
||||
class TimezoneNode(Node):
|
||||
"""
|
||||
Template node class used by ``timezone_tag``.
|
||||
"""
|
||||
def __init__(self, nodelist, tz):
|
||||
self.nodelist = nodelist
|
||||
self.tz = tz
|
||||
|
||||
def render(self, context):
|
||||
with timezone.override(self.tz.resolve(context)):
|
||||
output = self.nodelist.render(context)
|
||||
return output
|
||||
|
||||
class GetCurrentTimezoneNode(Node):
|
||||
"""
|
||||
Template node class used by ``get_current_timezone_tag``.
|
||||
"""
|
||||
def __init__(self, variable):
|
||||
self.variable = variable
|
||||
|
||||
def render(self, context):
|
||||
context[self.variable] = timezone.get_current_timezone_name()
|
||||
return ''
|
||||
|
||||
@register.tag('localtime')
|
||||
def localtime_tag(parser, token):
|
||||
"""
|
||||
Forces or prevents conversion of datetime objects to local time,
|
||||
regardless of the value of ``settings.USE_TZ``.
|
||||
|
||||
Sample usage::
|
||||
|
||||
{% localtime off %}{{ value_in_utc }}{% endlocaltime %}
|
||||
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) == 1:
|
||||
use_tz = True
|
||||
elif len(bits) > 2 or bits[1] not in ('on', 'off'):
|
||||
raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % bits[0])
|
||||
else:
|
||||
use_tz = bits[1] == 'on'
|
||||
nodelist = parser.parse(('endlocaltime',))
|
||||
parser.delete_first_token()
|
||||
return LocalTimeNode(nodelist, use_tz)
|
||||
|
||||
@register.tag('timezone')
|
||||
def timezone_tag(parser, token):
|
||||
"""
|
||||
Enables a given time zone just for this block.
|
||||
|
||||
The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
|
||||
time zone name, or ``None``. If is it a time zone name, pytz is required.
|
||||
If it is ``None``, the default time zone is used within the block.
|
||||
|
||||
Sample usage::
|
||||
|
||||
{% timezone "Europe/Paris" %}
|
||||
It is {{ now }} in Paris.
|
||||
{% endtimezone %}
|
||||
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) != 2:
|
||||
raise TemplateSyntaxError("'%s' takes one argument (timezone)" % bits[0])
|
||||
tz = parser.compile_filter(bits[1])
|
||||
nodelist = parser.parse(('endtimezone',))
|
||||
parser.delete_first_token()
|
||||
return TimezoneNode(nodelist, tz)
|
||||
|
||||
@register.tag("get_current_timezone")
|
||||
def get_current_timezone_tag(parser, token):
|
||||
"""
|
||||
Stores the name of the current time zone in the context.
|
||||
|
||||
Usage::
|
||||
|
||||
{% get_current_timezone as TIME_ZONE %}
|
||||
|
||||
This will fetch the currently active time zone and put its name
|
||||
into the ``TIME_ZONE`` context variable.
|
||||
"""
|
||||
args = token.contents.split()
|
||||
if len(args) != 3 or args[1] != 'as':
|
||||
raise TemplateSyntaxError("'get_current_timezone' requires 'as variable' (got %r)" % args)
|
||||
return GetCurrentTimezoneNode(args[2])
|
|
@ -25,6 +25,7 @@ from django.conf import settings
|
|||
from django.core.cache import get_cache
|
||||
from django.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):
|
||||
|
|
|
@ -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 = LocalTimezone(dt)
|
||||
self.timezone = None
|
||||
if isinstance(dt, datetime.datetime):
|
||||
if is_naive(dt):
|
||||
self.timezone = LocalTimezone(dt)
|
||||
else:
|
||||
self.timezone = dt.tzinfo
|
||||
|
||||
def b(self):
|
||||
"Month, textual, 3 letters, lowercase; e.g. 'jan'"
|
||||
|
@ -218,7 +224,7 @@ 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()))
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
"""Functions to parse datetime objects."""
|
||||
|
||||
# We're using regular expressions rather than time.strptime because:
|
||||
# - they provide both validation and parsing,
|
||||
# - they're more flexible for datetimes,
|
||||
# - the date/datetime/time constructors produce friendlier error messages.
|
||||
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from django.utils.timezone import utc
|
||||
from django.utils.tzinfo import FixedOffset
|
||||
|
||||
|
||||
date_re = re.compile(
|
||||
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
|
||||
)
|
||||
|
||||
|
||||
datetime_re = re.compile(
|
||||
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
|
||||
r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
|
||||
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
|
||||
r'(?P<tzinfo>Z|[+-]\d{1,2}:\d{1,2})?$'
|
||||
)
|
||||
|
||||
|
||||
time_re = re.compile(
|
||||
r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
|
||||
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
|
||||
)
|
||||
|
||||
|
||||
def parse_date(value):
|
||||
"""Parse a string and return a datetime.date.
|
||||
|
||||
Raise ValueError if the input is well formatted but not a valid date.
|
||||
Return None if the input isn't well formatted.
|
||||
"""
|
||||
match = date_re.match(value)
|
||||
if match:
|
||||
kw = dict((k, int(v)) for k, v in match.groupdict().iteritems())
|
||||
return datetime.date(**kw)
|
||||
|
||||
|
||||
def parse_time(value):
|
||||
"""Parse a string and return a datetime.time.
|
||||
|
||||
This function doesn't support time zone offsets.
|
||||
|
||||
Sub-microsecond precision is accepted, but ignored.
|
||||
|
||||
Raise ValueError if the input is well formatted but not a valid time.
|
||||
Return None if the input isn't well formatted, in particular if it
|
||||
contains an offset.
|
||||
"""
|
||||
match = time_re.match(value)
|
||||
if match:
|
||||
kw = match.groupdict()
|
||||
if kw['microsecond']:
|
||||
kw['microsecond'] = kw['microsecond'].ljust(6, '0')
|
||||
kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None)
|
||||
return datetime.time(**kw)
|
||||
|
||||
|
||||
def parse_datetime(value):
|
||||
"""Parse a string and return a datetime.datetime.
|
||||
|
||||
This function supports time zone offsets. When the input contains one,
|
||||
the output uses an instance of FixedOffset as tzinfo.
|
||||
|
||||
Sub-microsecond precision is accepted, but ignored.
|
||||
|
||||
Raise ValueError if the input is well formatted but not a valid datetime.
|
||||
Return None if the input isn't well formatted.
|
||||
"""
|
||||
match = datetime_re.match(value)
|
||||
if match:
|
||||
kw = match.groupdict()
|
||||
if kw['microsecond']:
|
||||
kw['microsecond'] = kw['microsecond'].ljust(6, '0')
|
||||
tzinfo = kw.pop('tzinfo')
|
||||
if tzinfo == 'Z':
|
||||
tzinfo = utc
|
||||
elif tzinfo is not None:
|
||||
offset = 60 * int(tzinfo[1:3]) + int(tzinfo[4:6])
|
||||
if tzinfo[0] == '-':
|
||||
offset = -offset
|
||||
tzinfo = FixedOffset(offset)
|
||||
kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None)
|
||||
kw['tzinfo'] = tzinfo
|
||||
return datetime.datetime(**kw)
|
|
@ -28,6 +28,7 @@ import urlparse
|
|||
from django.utils.xmlutils import SimplerXMLGenerator
|
||||
from django.utils.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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
"""Timezone helper functions.
|
||||
|
||||
This module uses pytz when it's available and fallbacks when it isn't.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
from threading import local
|
||||
import time as _time
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
__all__ = [
|
||||
'utc', 'get_default_timezone', 'get_current_timezone',
|
||||
'activate', 'deactivate', 'override',
|
||||
'aslocaltime', 'isnaive',
|
||||
]
|
||||
|
||||
|
||||
# UTC and local time zones
|
||||
|
||||
ZERO = timedelta(0)
|
||||
|
||||
class UTC(tzinfo):
|
||||
"""
|
||||
UTC implementation taken from Python's docs.
|
||||
|
||||
Used only when pytz isn't available.
|
||||
"""
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return "UTC"
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
class LocalTimezone(tzinfo):
|
||||
"""
|
||||
Local time implementation taken from Python's docs.
|
||||
|
||||
Used only when pytz isn't available, and most likely inaccurate. If you're
|
||||
having trouble with this class, don't waste your time, just install pytz.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# This code is moved in __init__ to execute it as late as possible
|
||||
# See get_default_timezone().
|
||||
self.STDOFFSET = timedelta(seconds=-_time.timezone)
|
||||
if _time.daylight:
|
||||
self.DSTOFFSET = timedelta(seconds=-_time.altzone)
|
||||
else:
|
||||
self.DSTOFFSET = self.STDOFFSET
|
||||
self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
|
||||
tzinfo.__init__(self)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self.DSTOFFSET
|
||||
else:
|
||||
return self.STDOFFSET
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self.DSTDIFF
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return _time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
tt = (dt.year, dt.month, dt.day,
|
||||
dt.hour, dt.minute, dt.second,
|
||||
dt.weekday(), 0, 0)
|
||||
stamp = _time.mktime(tt)
|
||||
tt = _time.localtime(stamp)
|
||||
return tt.tm_isdst > 0
|
||||
|
||||
|
||||
utc = pytz.utc if pytz else UTC()
|
||||
"""UTC time zone as a tzinfo instance."""
|
||||
|
||||
# In order to avoid accessing the settings at compile time,
|
||||
# wrap the expression in a function and cache the result.
|
||||
# If you change settings.TIME_ZONE in tests, reset _localtime to None.
|
||||
_localtime = None
|
||||
|
||||
def get_default_timezone():
|
||||
"""
|
||||
Returns the default time zone as a tzinfo instance.
|
||||
|
||||
This is the time zone defined by settings.TIME_ZONE.
|
||||
|
||||
See also :func:`get_current_timezone`.
|
||||
"""
|
||||
global _localtime
|
||||
if _localtime is None:
|
||||
tz = settings.TIME_ZONE
|
||||
_localtime = pytz.timezone(tz) if pytz else LocalTimezone()
|
||||
return _localtime
|
||||
|
||||
# This function exists for consistency with get_current_timezone_name
|
||||
def get_default_timezone_name():
|
||||
"""
|
||||
Returns the name of the default time zone.
|
||||
"""
|
||||
return _get_timezone_name(get_default_timezone())
|
||||
|
||||
_active = local()
|
||||
|
||||
def get_current_timezone():
|
||||
"""
|
||||
Returns the currently active time zone as a tzinfo instance.
|
||||
"""
|
||||
return getattr(_active, "value", get_default_timezone())
|
||||
|
||||
def get_current_timezone_name():
|
||||
"""
|
||||
Returns the name of the currently active time zone.
|
||||
"""
|
||||
return _get_timezone_name(get_current_timezone())
|
||||
|
||||
def _get_timezone_name(timezone):
|
||||
"""
|
||||
Returns the name of ``timezone``.
|
||||
"""
|
||||
try:
|
||||
# for pytz timezones
|
||||
return timezone.zone
|
||||
except AttributeError:
|
||||
# for regular tzinfo objects
|
||||
local_now = datetime.now(timezone)
|
||||
return timezone.tzname(local_now)
|
||||
|
||||
# Timezone selection functions.
|
||||
|
||||
# These functions don't change os.environ['TZ'] and call time.tzset()
|
||||
# because it isn't thread safe.
|
||||
|
||||
def activate(timezone):
|
||||
"""
|
||||
Sets the time zone for the current thread.
|
||||
|
||||
The ``timezone`` argument must be an instance of a tzinfo subclass or a
|
||||
time zone name. If it is a time zone name, pytz is required.
|
||||
"""
|
||||
if isinstance(timezone, tzinfo):
|
||||
_active.value = timezone
|
||||
elif isinstance(timezone, basestring) and pytz is not None:
|
||||
_active.value = pytz.timezone(timezone)
|
||||
else:
|
||||
raise ValueError("Invalid timezone: %r" % timezone)
|
||||
|
||||
def deactivate():
|
||||
"""
|
||||
Unsets the time zone for the current thread.
|
||||
|
||||
Django will then use the time zone defined by settings.TIME_ZONE.
|
||||
"""
|
||||
if hasattr(_active, "value"):
|
||||
del _active.value
|
||||
|
||||
class override(object):
|
||||
"""
|
||||
Temporarily set the time zone for the current thread.
|
||||
|
||||
This is a context manager that uses ``~django.utils.timezone.activate()``
|
||||
to set the timezone on entry, and restores the previously active timezone
|
||||
on exit.
|
||||
|
||||
The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
|
||||
time zone name, or ``None``. If is it a time zone name, pytz is required.
|
||||
If it is ``None``, Django enables the default time zone.
|
||||
"""
|
||||
def __init__(self, timezone):
|
||||
self.timezone = timezone
|
||||
self.old_timezone = getattr(_active, 'value', None)
|
||||
|
||||
def __enter__(self):
|
||||
if self.timezone is None:
|
||||
deactivate()
|
||||
else:
|
||||
activate(self.timezone)
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.old_timezone is not None:
|
||||
_active.value = self.old_timezone
|
||||
else:
|
||||
del _active.value
|
||||
|
||||
|
||||
# Utilities
|
||||
|
||||
def aslocaltime(value, use_tz=None):
|
||||
"""
|
||||
Checks if value is a datetime and converts it to local time if necessary.
|
||||
|
||||
If use_tz is provided and is not None, that will force the value to
|
||||
be converted (or not), overriding the value of settings.USE_TZ.
|
||||
"""
|
||||
if (isinstance(value, datetime)
|
||||
and (settings.USE_TZ if use_tz is None else use_tz)
|
||||
and not is_naive(value)
|
||||
and getattr(value, 'convert_to_local_time', True)):
|
||||
timezone = get_current_timezone()
|
||||
value = value.astimezone(timezone)
|
||||
if hasattr(timezone, 'normalize'):
|
||||
# available for pytz time zones
|
||||
value = timezone.normalize(value)
|
||||
return value
|
||||
|
||||
def now():
|
||||
"""
|
||||
Returns an aware or naive datetime.datetime, depending on settings.USE_TZ.
|
||||
"""
|
||||
if settings.USE_TZ:
|
||||
# timeit shows that datetime.now(tz=utc) is 24% slower
|
||||
return datetime.utcnow().replace(tzinfo=utc)
|
||||
else:
|
||||
return datetime.now()
|
||||
|
||||
def is_aware(value):
|
||||
"""
|
||||
Determines if a given datetime.datetime is aware.
|
||||
|
||||
The logic is described in Python's docs:
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo
|
||||
"""
|
||||
return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None
|
||||
|
||||
def is_naive(value):
|
||||
"""
|
||||
Determines if a given datetime.datetime is naive.
|
||||
|
||||
The logic is described in Python's docs:
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo
|
||||
"""
|
||||
return value.tzinfo is None or value.tzinfo.utcoffset(value) is None
|
||||
|
||||
def make_aware(value, timezone):
|
||||
"""
|
||||
Makes a naive datetime.datetime in a given time zone aware.
|
||||
"""
|
||||
if hasattr(timezone, 'localize'):
|
||||
# available for pytz time zones
|
||||
return timezone.localize(value, is_dst=None)
|
||||
else:
|
||||
# may be wrong around DST changes
|
||||
return value.replace(tzinfo=timezone)
|
||||
|
||||
def make_naive(value, timezone):
|
||||
"""
|
||||
Makes an aware datetime.datetime naive in a given time zone.
|
||||
"""
|
||||
value = value.astimezone(timezone)
|
||||
if hasattr(timezone, 'normalize'):
|
||||
# available for pytz time zones
|
||||
return timezone.normalize(value)
|
||||
return value.replace(tzinfo=None)
|
|
@ -2,8 +2,14 @@
|
|||
|
||||
import time
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
----------------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
^^^^^^
|
||||
|
||||
|
|
|
@ -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``
|
||||
=======================
|
||||
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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`_
|
||||
|
||||
|
|
|
@ -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
|
||||
===========
|
||||
|
|
|
@ -0,0 +1,429 @@
|
|||
.. _time-zones:
|
||||
|
||||
==========
|
||||
Time zones
|
||||
==========
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
When support for time zones is enabled, Django stores date and time
|
||||
information in UTC in the database, uses time zone-aware datetime objects
|
||||
internally, and translates them to the end user's time zone in templates and
|
||||
forms.
|
||||
|
||||
This is handy if your users live in more than one time zone and you want to
|
||||
display date and time information according to each user's wall clock. Even if
|
||||
your website is available in only one time zone, it's still a good practice to
|
||||
store data in UTC in your database. Here is why.
|
||||
|
||||
Many countries have a system of daylight saving time (DST), where clocks are
|
||||
moved forwards in spring and backwards in autumn. If you're working in local
|
||||
time, you're likely to encounter errors twice a year, when the transitions
|
||||
happen. pytz' docs discuss `these issues`_ in greater detail. It probably
|
||||
doesn't matter for your blog, but it's more annoying if you over-bill or
|
||||
under-bill your customers by one hour, twice a year, every year. The solution
|
||||
to this problem is to use UTC in the code and local time only when
|
||||
interacting with end users.
|
||||
|
||||
Time zone support is disabled by default. To enable it, set :setting:`USE_TZ =
|
||||
True <USE_TZ>` in your settings file. Installing pytz_ is highly recommended,
|
||||
but not mandatory.
|
||||
|
||||
.. note::
|
||||
|
||||
The default :file:`settings.py` file created by :djadmin:`django-admin.py
|
||||
startproject <startproject>` includes :setting:`USE_TZ = True <USE_TZ>`
|
||||
for convenience.
|
||||
|
||||
.. note::
|
||||
|
||||
There is also an independent but related :setting:`USE_L10N` setting that
|
||||
controls if Django should activate format localization. See
|
||||
:doc:`/topics/i18n/formatting` for more details.
|
||||
|
||||
Concepts
|
||||
========
|
||||
|
||||
Naive and aware datetime objects
|
||||
--------------------------------
|
||||
|
||||
Python's :class:`datetime.datetime` objects have a ``tzinfo`` attribute that
|
||||
can be used to store time zone information, represented as an instance of a
|
||||
subclass of :class:`datetime.tzinfo`. When this attribute is set and describes
|
||||
an offset, a datetime object is **aware**; otherwise, it's **naive**.
|
||||
|
||||
You can use :func:`~django.utils.timezone.is_aware` and
|
||||
:func:`~django.utils.timezone.is_naive` to determine if datetimes are aware or
|
||||
naive.
|
||||
|
||||
When time zone support is disabled, Django uses naive datetime objects in local
|
||||
time. This is simple and sufficient for many use cases. In this mode, to obtain
|
||||
the current time, you would write::
|
||||
|
||||
import datetime
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
When time zone support is enabled, Django uses time zone aware datetime
|
||||
objects. If your code creates datetime objects, they should be aware too. In
|
||||
this mode, the example above becomes::
|
||||
|
||||
import datetime
|
||||
from django.utils.timezone import utc
|
||||
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=utc)
|
||||
|
||||
.. note::
|
||||
|
||||
:mod:`django.utils.timezone` provides a
|
||||
:func:`~django.utils.timezone.now()` function that returns a naive or
|
||||
aware datetime object according to the value of :setting:`USE_TZ`.
|
||||
|
||||
.. warning::
|
||||
|
||||
Dealing with aware datetime objects isn't always intuitive. For instance,
|
||||
the ``tzinfo`` argument of the standard datetime constructor doesn't work
|
||||
reliably for time zones with DST. Using UTC is generally safe; if you're
|
||||
using other time zones, you should review `pytz' documentation <pytz>`_
|
||||
carefully.
|
||||
|
||||
.. note::
|
||||
|
||||
Python's :class:`datetime.time` objects also feature a ``tzinfo``
|
||||
attribute, and PostgreSQL has a matching ``time with time zone`` type.
|
||||
However, as PostgreSQL's docs put it, this type "exhibits properties which
|
||||
lead to questionable usefulness".
|
||||
|
||||
Django only supports naive time objects and will raise an exception if you
|
||||
attempt to save an aware time object.
|
||||
|
||||
.. _naive-datetime-objects:
|
||||
|
||||
Interpretation of naive datetime objects
|
||||
----------------------------------------
|
||||
|
||||
When :setting:`USE_TZ` is ``True``, Django still accepts naive datetime
|
||||
objects, in order to preserve backwards-compatibility. It attempts to make them
|
||||
aware by interpreting them in the :ref:`default time zone
|
||||
<default-current-time-zone>`.
|
||||
|
||||
Unfortunately, during DST transitions, some datetimes don't exist or are
|
||||
ambiguous. In such situations, pytz_ raises an exception. Other
|
||||
:class:`~datetime.tzinfo` implementations, such as the local time zone used as
|
||||
a fallback when pytz_ isn't installed, may raise an exception or return
|
||||
inaccurate results. That's why you should always create aware datetime objects
|
||||
when time zone support is enabled.
|
||||
|
||||
In practice, this is rarely an issue. Django gives you aware datetime objects
|
||||
in the models and forms, and most often, new datetime objects are created from
|
||||
existing ones through :class:`~datetime.timedelta` arithmetic. The only
|
||||
datetime that's often created in application code is the current time, and
|
||||
:func:`timezone.now() <django.utils.timezone.now>` automatically does the
|
||||
right thing.
|
||||
|
||||
.. _default-current-time-zone:
|
||||
|
||||
Default time zone and current time zone
|
||||
---------------------------------------
|
||||
|
||||
The **default time zone** is the time zone defined by the :setting:`TIME_ZONE`
|
||||
setting.
|
||||
|
||||
When pytz_ is available, Django loads the definition of the default time zone
|
||||
from the `tz database`_. This is the most accurate solution. Otherwise, it
|
||||
relies on the difference between local time and UTC, as reported by the
|
||||
operating system, to compute conversions. This is less reliable, especially
|
||||
around DST transitions.
|
||||
|
||||
The **current time zone** is the time zone that's used for rendering.
|
||||
|
||||
You should set it to the end user's actual time zone with
|
||||
:func:`~django.utils.timezone.activate`. Otherwise, the default time zone is
|
||||
used.
|
||||
|
||||
.. note::
|
||||
|
||||
As explained in the documentation of :setting:`TIME_ZONE`, Django sets
|
||||
environment variables so that its process runs in the default time zone.
|
||||
This happens regardless of the value of :setting:`USE_TZ` and of the
|
||||
current time zone.
|
||||
|
||||
When :setting:`USE_TZ` is ``True``, this is useful to preserve
|
||||
backwards-compatibility with applications that still rely on local time.
|
||||
However, :ref:`as explained above <naive-datetime-objects>`, this isn't
|
||||
entirely reliable, and you should always work with aware datetimes in UTC
|
||||
in your own code. For instance, use
|
||||
:meth:`~datetime.datetime.utcfromtimestamp` instead of
|
||||
:meth:`~datetime.datetime.fromtimestamp` -- and don't forget to set
|
||||
``tzinfo`` to :data:`~django.utils.timezone.utc`.
|
||||
|
||||
Selecting the current time zone
|
||||
-------------------------------
|
||||
|
||||
The current time zone is the equivalent of the current :term:`locale <locale
|
||||
name>` for translations. However, there's no equivalent of the
|
||||
``Accept-Language`` HTTP header that Django could use to determine the user's
|
||||
time zone automatically. Instead, Django provides :ref:`time zone selection
|
||||
functions <time-zone-selection-functions>`. Use them to build the time zone
|
||||
selection logic that makes sense for you.
|
||||
|
||||
Most websites who care about time zones just ask users in which time zone they
|
||||
live and store this information in the user's profile. For anonymous users,
|
||||
they use the time zone of their primary audience or UTC. pytz_ provides
|
||||
helpers, like a list of time zones per country, that you can use to pre-select
|
||||
the most likely choices.
|
||||
|
||||
Here's an example that stores the current timezone in the session. (It skips
|
||||
error handling entirely for the sake of simplicity.)
|
||||
|
||||
Add the following middleware to :setting:`MIDDLEWARE_CLASSES`::
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
class TimezoneMiddleware(object):
|
||||
def process_request(self, request):
|
||||
tz = request.session.get('django_timezone')
|
||||
if tz:
|
||||
timezone.activate(tz)
|
||||
|
||||
Create a view that can set the current timezone::
|
||||
|
||||
import pytz
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
def set_timezone(request):
|
||||
if request.method == 'POST':
|
||||
request.session[session_key] = pytz.timezone(request.POST['timezone'])
|
||||
return redirect('/')
|
||||
else:
|
||||
return render(request, 'template.html', {'timezones': pytz.common_timezones})
|
||||
|
||||
Include in :file:`template.html` a form that will ``POST`` to this view:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% load tz %}{% load url from future %}
|
||||
<form action="{% url 'set_timezone' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<label for="timezone">Time zone:</label>
|
||||
<select name="timezone">
|
||||
{% for tz in timezones %}
|
||||
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected="selected"{% endif %}>{{ tz }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Set" />
|
||||
</form>
|
||||
|
||||
Time zone aware input in forms
|
||||
==============================
|
||||
|
||||
When you enable time zone support, Django interprets datetimes entered in
|
||||
forms in the :ref:`current time zone <default-current-time-zone>` and returns
|
||||
aware datetime objects in ``cleaned_data``.
|
||||
|
||||
If the current time zone raises an exception for datetimes that don't exist or
|
||||
are ambiguous because they fall in a DST transition (the timezones provided by
|
||||
pytz_ do this), such datetimes will be reported as invalid values.
|
||||
|
||||
.. _time-zones-in-templates:
|
||||
|
||||
Time zone aware output in templates
|
||||
===================================
|
||||
|
||||
When you enable time zone support, Django converts aware datetime objects to
|
||||
the :ref:`current time zone <default-current-time-zone>` when they're rendered
|
||||
in templates. This behaves very much like :doc:`format localization
|
||||
</topics/i18n/formatting>`.
|
||||
|
||||
.. warning::
|
||||
|
||||
Django doesn't convert naive datetime objects, because they could be
|
||||
ambiguous, and because your code should never produce naive datetimes when
|
||||
time zone support is enabled. However, you can force conversion with the
|
||||
template filters described below.
|
||||
|
||||
Conversion to local time isn't always appropriate -- you may be generating
|
||||
output for computers rather than for humans. The following filters and tags,
|
||||
provided the ``tz`` template library, allow you to control the time zone
|
||||
conversions.
|
||||
|
||||
Template tags
|
||||
-------------
|
||||
|
||||
.. templatetag:: localtime
|
||||
|
||||
localtime
|
||||
~~~~~~~~~
|
||||
|
||||
Enables or disables conversion of aware datetime objects to the current time
|
||||
zone in the contained block.
|
||||
|
||||
This tag has exactly the same effects as the :setting:`USE_TZ` setting as far
|
||||
as the template engine is concerned. It allows a more fine grained control of
|
||||
conversion.
|
||||
|
||||
To activate or deactivate conversion for a template block, use::
|
||||
|
||||
{% load tz %}
|
||||
|
||||
{% localtime on %}
|
||||
{{ value }}
|
||||
{% endlocaltime %}
|
||||
|
||||
{% localtime off %}
|
||||
{{ value }}
|
||||
{% endlocaltime %}
|
||||
|
||||
.. note::
|
||||
|
||||
The value of :setting:`USE_TZ` isn't respected inside of a
|
||||
``{% localtime %}`` block.
|
||||
|
||||
.. templatetag:: timezone
|
||||
|
||||
timezone
|
||||
~~~~~~~~
|
||||
|
||||
Sets or unsets the current time zone in the contained block. When the current
|
||||
time zone is unset, the default time zone applies.
|
||||
|
||||
::
|
||||
|
||||
{% load tz %}
|
||||
|
||||
{% timezone "Europe/Paris" %}
|
||||
Paris time: {{ value }}
|
||||
{% endtimezone %}
|
||||
|
||||
{% timezone None %}
|
||||
Server time: {{ value }}
|
||||
{% endtimezone %}
|
||||
|
||||
.. note::
|
||||
|
||||
In the second block, ``None`` resolves to the Python object ``None``
|
||||
because isn't defined in the template context, not because it's the string
|
||||
``None``.
|
||||
|
||||
.. templatetag:: get_current_timezone
|
||||
|
||||
get_current_timezone
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When the :func:`django.core.context_processors.tz` context processor is
|
||||
enabled -- by default, it is -- each :class:`~django.template.RequestContext`
|
||||
contains a ``TIME_ZONE`` variable that provides the name of the current time
|
||||
zone.
|
||||
|
||||
If you don't use a :class:`~django.template.RequestContext`, you can obtain
|
||||
this value with the ``get_current_timezone`` tag::
|
||||
|
||||
{% get_current_timezone as TIME_ZONE %}
|
||||
|
||||
Template filters
|
||||
----------------
|
||||
|
||||
These filters accept both aware and naive datetimes. For conversion purposes,
|
||||
they assume that naive datetimes are in the default time zone. They always
|
||||
return aware datetimes.
|
||||
|
||||
.. templatefilter:: aslocaltime
|
||||
|
||||
aslocaltime
|
||||
~~~~~~~~~~~
|
||||
|
||||
Forces conversion of a single value to the current time zone.
|
||||
|
||||
For example::
|
||||
|
||||
{% load tz %}
|
||||
|
||||
{{ value|aslocaltime }}
|
||||
|
||||
.. templatefilter:: asutc
|
||||
|
||||
asutc
|
||||
~~~~~
|
||||
|
||||
Forces conversion of a single value to UTC.
|
||||
|
||||
For example::
|
||||
|
||||
{% load tz %}
|
||||
|
||||
{{ value|asutc }}
|
||||
|
||||
astimezone
|
||||
~~~~~~~~~~
|
||||
|
||||
Forces conversion of a single value to an arbitrary timezone.
|
||||
|
||||
The argument must be an instance of a :class:`~datetime.tzinfo` subclass or a
|
||||
time zone name. If it is a time zone name, pytz_ is required.
|
||||
|
||||
For example::
|
||||
|
||||
{% load tz %}
|
||||
|
||||
{{ value|astimezone:"Europe/Paris" }}
|
||||
|
||||
.. _time-zones-migration-guide:
|
||||
|
||||
Migration guide
|
||||
===============
|
||||
|
||||
Here's how to migrate a project that was started before Django supported time
|
||||
zones.
|
||||
|
||||
Data
|
||||
----
|
||||
|
||||
PostgreSQL
|
||||
~~~~~~~~~~
|
||||
|
||||
The PostgreSQL backend stores datetimes as ``timestamp with time zone``. In
|
||||
practice, this means it converts datetimes from the connection's time zone to
|
||||
UTC on storage, and from UTC to the connection's time zone on retrieval.
|
||||
|
||||
As a consequence, if you're using PostgreSQL, you can switch between ``USE_TZ
|
||||
= False`` and ``USE_TZ = True`` freely. The database connection's time zone
|
||||
will be set to :setting:`TIME_ZONE` or ``UTC`` respectively, so that Django
|
||||
obtains correct datetimes in all cases. You don't need to perform any data
|
||||
conversions.
|
||||
|
||||
Other databases
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Other backends store datetimes without time zone information. If you switch
|
||||
from ``USE_TZ = False`` to ``USE_TZ = True``, you must convert your data from
|
||||
local time to UTC -- which isn't deterministic if your local time has DST.
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
The first step is to add :setting:`USE_TZ = True <USE_TZ>` to your settings
|
||||
file and install pytz_ (if possible). At this point, things should mostly
|
||||
work. If you create naive datetime objects in your code, Django makes them
|
||||
aware when necessary.
|
||||
|
||||
However, these conversions may fail around DST transitions, which means you
|
||||
aren't getting the full benefits of time zone support yet. Also, you're likely
|
||||
to run into a few problems because it's impossible to compare a naive datetime
|
||||
with an aware datetime. Since Django now gives you aware datetimes, you'll get
|
||||
exceptions wherever you compare a datetime that comes from a model or a form
|
||||
with a naive datetime that you've created in your code.
|
||||
|
||||
So the second step is to refactor your code wherever you instanciate datetime
|
||||
objects to make them aware. This can be done incrementally.
|
||||
:mod:`django.utils.timezone` defines some handy helpers for compatibility
|
||||
code: :func:`~django.utils.timezone.is_aware`,
|
||||
:func:`~django.utils.timezone.is_naive`,
|
||||
:func:`~django.utils.timezone.make_aware`, and
|
||||
:func:`~django.utils.timezone.make_naive`.
|
||||
|
||||
.. _pytz: http://pytz.sourceforge.net/
|
||||
.. _these issues: http://pytz.sourceforge.net/#problems-with-localtime
|
||||
.. _tz database: http://en.wikipedia.org/wiki/Tz_database
|
|
@ -56,25 +56,25 @@ class FixtureLoadingTests(TestCase):
|
|||
])
|
||||
|
||||
# Dump the current contents of the database as a JSON fixture
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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>
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -0,0 +1,10 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import patterns, include
|
||||
from django.contrib import admin
|
||||
|
||||
from . import admin as tz_admin
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^admin/', include(admin.site.urls)),
|
||||
)
|
|
@ -104,16 +104,43 @@ class ValidationMessagesTest(TestCase):
|
|||
f.clean('foo', None)
|
||||
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."])
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import copy
|
||||
import pickle
|
||||
from django.utils.timezone import UTC, LocalTimezone
|
||||
from django.utils import unittest
|
||||
|
||||
class TimezoneTests(unittest.TestCase):
|
||||
|
||||
def test_copy(self):
|
||||
self.assertIsInstance(copy.copy(UTC()), UTC)
|
||||
self.assertIsInstance(copy.copy(LocalTimezone()), LocalTimezone)
|
||||
|
||||
def test_deepcopy(self):
|
||||
self.assertIsInstance(copy.deepcopy(UTC()), UTC)
|
||||
self.assertIsInstance(copy.deepcopy(LocalTimezone()), LocalTimezone)
|
||||
|
||||
def test_pickling_unpickling(self):
|
||||
self.assertIsInstance(pickle.loads(pickle.dumps(UTC())), UTC)
|
||||
self.assertIsInstance(pickle.loads(pickle.dumps(LocalTimezone())), LocalTimezone)
|
|
@ -1,5 +1,7 @@
|
|||
import copy
|
||||
import datetime
|
||||
import 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)
|
||||
|
|
Loading…
Reference in New Issue