diff options
| author | Aymeric Augustin <aymeric.augustin@m4x.org> | 2011-11-18 13:01:06 +0000 |
|---|---|---|
| committer | Aymeric Augustin <aymeric.augustin@m4x.org> | 2011-11-18 13:01:06 +0000 |
| commit | 9b1cb755a28f020e27d4268c214b25315d4de42e (patch) | |
| tree | 2ff0827176f0eb49defa4ce7ce10164f2fc26e86 /tests | |
| parent | 01f70349c9ef23d6751437dcd57d2efc193b2661 (diff) | |
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
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/modeltests/fixtures/tests.py | 22 | ||||
| -rw-r--r-- | tests/modeltests/serializers/tests.py | 6 | ||||
| -rw-r--r-- | tests/modeltests/timezones/__init__.py | 0 | ||||
| -rw-r--r-- | tests/modeltests/timezones/admin.py | 15 | ||||
| -rw-r--r-- | tests/modeltests/timezones/fixtures/users.xml | 17 | ||||
| -rw-r--r-- | tests/modeltests/timezones/forms.py | 13 | ||||
| -rw-r--r-- | tests/modeltests/timezones/models.py | 8 | ||||
| -rw-r--r-- | tests/modeltests/timezones/tests.py | 871 | ||||
| -rw-r--r-- | tests/modeltests/timezones/urls.py | 10 | ||||
| -rw-r--r-- | tests/modeltests/validation/test_error_messages.py | 43 | ||||
| -rw-r--r-- | tests/regressiontests/cache/tests.py | 18 | ||||
| -rw-r--r-- | tests/regressiontests/datatypes/tests.py | 4 | ||||
| -rw-r--r-- | tests/regressiontests/defaultfilters/tests.py | 2 | ||||
| -rw-r--r-- | tests/regressiontests/utils/dateformat.py | 2 | ||||
| -rw-r--r-- | tests/regressiontests/utils/tests.py | 1 | ||||
| -rw-r--r-- | tests/regressiontests/utils/timesince.py | 9 | ||||
| -rw-r--r-- | tests/regressiontests/utils/timezone.py | 18 | ||||
| -rw-r--r-- | tests/regressiontests/utils/tzinfo.py | 17 |
18 files changed, 1047 insertions, 29 deletions
diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py index 2dca7ced9c..e7daf2d861 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/modeltests/fixtures/tests.py @@ -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) diff --git a/tests/modeltests/serializers/tests.py b/tests/modeltests/serializers/tests.py index 99c065aeaf..721ca09282 100644 --- a/tests/modeltests/serializers/tests.py +++ b/tests/modeltests/serializers/tests.py @@ -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 } diff --git a/tests/modeltests/timezones/__init__.py b/tests/modeltests/timezones/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/timezones/__init__.py diff --git a/tests/modeltests/timezones/admin.py b/tests/modeltests/timezones/admin.py new file mode 100644 index 0000000000..4c199813e2 --- /dev/null +++ b/tests/modeltests/timezones/admin.py @@ -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) diff --git a/tests/modeltests/timezones/fixtures/users.xml b/tests/modeltests/timezones/fixtures/users.xml new file mode 100644 index 0000000000..6cf441f01e --- /dev/null +++ b/tests/modeltests/timezones/fixtures/users.xml @@ -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>
\ No newline at end of file diff --git a/tests/modeltests/timezones/forms.py b/tests/modeltests/timezones/forms.py new file mode 100644 index 0000000000..3c9c31167e --- /dev/null +++ b/tests/modeltests/timezones/forms.py @@ -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 diff --git a/tests/modeltests/timezones/models.py b/tests/modeltests/timezones/models.py new file mode 100644 index 0000000000..bd488d1132 --- /dev/null +++ b/tests/modeltests/timezones/models.py @@ -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) diff --git a/tests/modeltests/timezones/tests.py b/tests/modeltests/timezones/tests.py new file mode 100644 index 0000000000..e33d4c61f8 --- /dev/null +++ b/tests/modeltests/timezones/tests.py @@ -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) diff --git a/tests/modeltests/timezones/urls.py b/tests/modeltests/timezones/urls.py new file mode 100644 index 0000000000..e4f42972db --- /dev/null +++ b/tests/modeltests/timezones/urls.py @@ -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)), +) diff --git a/tests/modeltests/validation/test_error_messages.py b/tests/modeltests/validation/test_error_messages.py index b042c2ee7c..4a2ad1fffa 100644 --- a/tests/modeltests/validation/test_error_messages.py +++ b/tests/modeltests/validation/test_error_messages.py @@ -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."])
\ No newline at end of file + 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."]) diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index e6c03837d4..f1590d1121 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -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", diff --git a/tests/regressiontests/datatypes/tests.py b/tests/regressiontests/datatypes/tests.py index 3cf11024fd..fb94e831ba 100644 --- a/tests/regressiontests/datatypes/tests.py +++ b/tests/regressiontests/datatypes/tests.py @@ -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. diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index b18334d011..6da4f75a67 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -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( diff --git a/tests/regressiontests/utils/dateformat.py b/tests/regressiontests/utils/dateformat.py index f1ef720b20..313a76ba4b 100644 --- a/tests/regressiontests/utils/dateformat.py +++ b/tests/regressiontests/utils/dateformat.py @@ -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') diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 4db894d6ad..b72a88b33d 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -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 diff --git a/tests/regressiontests/utils/timesince.py b/tests/regressiontests/utils/timesince.py index c0fe590028..b0f94d182b 100644 --- a/tests/regressiontests/utils/timesince.py +++ b/tests/regressiontests/utils/timesince.py @@ -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') diff --git a/tests/regressiontests/utils/timezone.py b/tests/regressiontests/utils/timezone.py new file mode 100644 index 0000000000..870997db65 --- /dev/null +++ b/tests/regressiontests/utils/timezone.py @@ -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) diff --git a/tests/regressiontests/utils/tzinfo.py b/tests/regressiontests/utils/tzinfo.py index a60a4574de..cec92652cd 100644 --- a/tests/regressiontests/utils/tzinfo.py +++ b/tests/regressiontests/utils/tzinfo.py @@ -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) |
