tests.py 56 KB
Newer Older
1
import datetime
2
import re
3
import sys
4
import warnings
5
from contextlib import contextmanager
6
from unittest import SkipTest, skipIf
7
from xml.dom.minidom import parseString
8

9 10
import pytz

11
from django.contrib.auth.models import User
12
from django.core import serializers
13 14
from django.core.exceptions import ImproperlyConfigured
from django.db import connection, connections
15
from django.db.models import F, Max, Min
16
from django.http import HttpRequest
17
from django.template import (
18 19
    Context, RequestContext, Template, TemplateSyntaxError, context_processors,
)
20
from django.test import (
21 22
    SimpleTestCase, TestCase, TransactionTestCase, override_settings,
    skipIfDBFeature, skipUnlessDBFeature,
23
)
24
from django.test.utils import requires_tz_support
25
from django.urls import reverse
26
from django.utils import timezone
27
from django.utils.timezone import timedelta
28

29 30 31 32 33 34 35
from .forms import (
    EventForm, EventLocalizedForm, EventLocalizedModelForm, EventModelForm,
    EventSplitForm,
)
from .models import (
    AllDayEvent, Event, MaybeEvent, Session, SessionEvent, Timestamp,
)
36 37 38 39 40 41 42 43 44 45

# 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
46 47
EAT = timezone.get_fixed_timezone(180)      # Africa/Nairobi
ICT = timezone.get_fixed_timezone(420)      # Asia/Bangkok
48 49


50 51
@override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=False)
class LegacyDatabaseTests(TestCase):
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

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

    @skipUnlessDBFeature('supports_timezones')
    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)

    @skipIfDBFeature('supports_timezones')
124
    def test_aware_datetime_unsupported(self):
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
        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)

150
    def test_query_datetime_lookups(self):
151 152 153 154 155 156
        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)
157 158 159
        self.assertEqual(Event.objects.filter(dt__hour=1).count(), 1)
        self.assertEqual(Event.objects.filter(dt__minute=30).count(), 2)
        self.assertEqual(Event.objects.filter(dt__second=0).count(), 2)
160 161 162 163 164 165 166 167 168 169 170 171

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

172 173 174 175 176 177 178 179 180 181
    def test_query_annotation(self):
        # Only min and max make sense for datetimes.
        morning = Session.objects.create(name='morning')
        afternoon = Session.objects.create(name='afternoon')
        SessionEvent.objects.create(dt=datetime.datetime(2011, 9, 1, 23, 20, 20), session=afternoon)
        SessionEvent.objects.create(dt=datetime.datetime(2011, 9, 1, 13, 20, 30), session=afternoon)
        SessionEvent.objects.create(dt=datetime.datetime(2011, 9, 1, 3, 20, 40), session=morning)
        morning_min_dt = datetime.datetime(2011, 9, 1, 3, 20, 40)
        afternoon_min_dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
        self.assertQuerysetEqual(
182 183 184
            Session.objects.annotate(dt=Min('events__dt')).order_by('dt'),
            [morning_min_dt, afternoon_min_dt],
            transform=lambda d: d.dt)
185
        self.assertQuerysetEqual(
186 187 188
            Session.objects.annotate(dt=Min('events__dt')).filter(dt__lt=afternoon_min_dt),
            [morning_min_dt],
            transform=lambda d: d.dt)
189
        self.assertQuerysetEqual(
190 191 192
            Session.objects.annotate(dt=Min('events__dt')).filter(dt__gte=afternoon_min_dt),
            [afternoon_min_dt],
            transform=lambda d: d.dt)
193

194
    def test_query_datetimes(self):
195 196
        Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0))
        Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0))
197 198 199 200
        self.assertSequenceEqual(Event.objects.datetimes('dt', 'year'), [datetime.datetime(2011, 1, 1, 0, 0, 0)])
        self.assertSequenceEqual(Event.objects.datetimes('dt', 'month'), [datetime.datetime(2011, 1, 1, 0, 0, 0)])
        self.assertSequenceEqual(Event.objects.datetimes('dt', 'day'), [datetime.datetime(2011, 1, 1, 0, 0, 0)])
        self.assertSequenceEqual(
201 202
            Event.objects.datetimes('dt', 'hour'),
            [datetime.datetime(2011, 1, 1, 1, 0, 0),
203 204 205
             datetime.datetime(2011, 1, 1, 4, 0, 0)]
        )
        self.assertSequenceEqual(
206 207
            Event.objects.datetimes('dt', 'minute'),
            [datetime.datetime(2011, 1, 1, 1, 30, 0),
208 209 210
             datetime.datetime(2011, 1, 1, 4, 30, 0)]
        )
        self.assertSequenceEqual(
211 212
            Event.objects.datetimes('dt', 'second'),
            [datetime.datetime(2011, 1, 1, 1, 30, 0),
213 214
             datetime.datetime(2011, 1, 1, 4, 30, 0)]
        )
215

216 217 218 219
    def test_raw_sql(self):
        # Regression test for #17755
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
        event = Event.objects.create(dt=dt)
220
        self.assertEqual(list(Event.objects.raw('SELECT * FROM timezones_event WHERE dt = %s', [dt])), [event])
221

222 223 224 225 226 227 228
    def test_cursor_execute_accepts_naive_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
        with connection.cursor() as cursor:
            cursor.execute('INSERT INTO timezones_event (dt) VALUES (%s)', [dt])
        event = Event.objects.get()
        self.assertEqual(event.dt, dt)

229 230 231 232 233 234 235
    def test_cursor_execute_returns_naive_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
        Event.objects.create(dt=dt)
        with connection.cursor() as cursor:
            cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [dt])
            self.assertEqual(cursor.fetchall()[0][0], dt)

236 237 238
    def test_filter_date_field_with_aware_datetime(self):
        # Regression test for #17742
        day = datetime.date(2011, 9, 1)
239
        AllDayEvent.objects.create(day=day)
240 241 242 243
        # This is 2011-09-02T01:30:00+03:00 in EAT
        dt = datetime.datetime(2011, 9, 1, 22, 30, 0, tzinfo=UTC)
        self.assertTrue(AllDayEvent.objects.filter(day__gte=dt).exists())

244

245 246
@override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=True)
class NewDatabaseTests(TestCase):
247

248
    @requires_tz_support
249 250
    def test_naive_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
251
        with warnings.catch_warnings(record=True) as recorded:
252
            warnings.simplefilter('always')
253 254 255
            Event.objects.create(dt=dt)
            self.assertEqual(len(recorded), 1)
            msg = str(recorded[0].message)
256 257
            self.assertTrue(msg.startswith("DateTimeField Event.dt received "
                                           "a naive datetime"))
258 259 260 261
        event = Event.objects.get()
        # naive datetimes are interpreted in local time
        self.assertEqual(event.dt, dt.replace(tzinfo=EAT))

262 263 264
    @requires_tz_support
    def test_datetime_from_date(self):
        dt = datetime.date(2011, 9, 1)
265 266 267 268 269
        with warnings.catch_warnings(record=True) as recorded:
            warnings.simplefilter('always')
            Event.objects.create(dt=dt)
            self.assertEqual(len(recorded), 1)
            msg = str(recorded[0].message)
270 271
            self.assertTrue(msg.startswith("DateTimeField Event.dt received "
                                           "a naive datetime"))
272 273 274
        event = Event.objects.get()
        self.assertEqual(event.dt, datetime.datetime(2011, 9, 1, tzinfo=EAT))

275
    @requires_tz_support
276 277 278
    @skipUnlessDBFeature('supports_microsecond_precision')
    def test_naive_datetime_with_microsecond(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
279
        with warnings.catch_warnings(record=True) as recorded:
280
            warnings.simplefilter('always')
281 282 283
            Event.objects.create(dt=dt)
            self.assertEqual(len(recorded), 1)
            msg = str(recorded[0].message)
284 285
            self.assertTrue(msg.startswith("DateTimeField Event.dt received "
                                           "a naive datetime"))
286 287 288 289
        event = Event.objects.get()
        # naive datetimes are interpreted in local time
        self.assertEqual(event.dt, dt.replace(tzinfo=EAT))

290
    @requires_tz_support
291 292 293
    @skipIfDBFeature('supports_microsecond_precision')
    def test_naive_datetime_with_microsecond_unsupported(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
294
        with warnings.catch_warnings(record=True) as recorded:
295
            warnings.simplefilter('always')
296 297 298
            Event.objects.create(dt=dt)
            self.assertEqual(len(recorded), 1)
            msg = str(recorded[0].message)
299 300
            self.assertTrue(msg.startswith("DateTimeField Event.dt received "
                                           "a naive datetime"))
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
        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):
340
        now = timezone.now()
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
        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)

    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)

372
    @requires_tz_support
373 374 375 376 377 378 379 380 381 382 383 384 385
    def test_query_filter_with_naive_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=EAT)
        Event.objects.create(dt=dt)
        dt = dt.replace(tzinfo=None)
        with warnings.catch_warnings(record=True) as recorded:
            warnings.simplefilter('always')
            # naive datetimes are interpreted in local time
            self.assertEqual(Event.objects.filter(dt__exact=dt).count(), 1)
            self.assertEqual(Event.objects.filter(dt__lte=dt).count(), 1)
            self.assertEqual(Event.objects.filter(dt__gt=dt).count(), 0)
            self.assertEqual(len(recorded), 3)
            for warning in recorded:
                msg = str(warning.message)
386 387
                self.assertTrue(msg.startswith("DateTimeField Event.dt "
                                               "received a naive datetime"))
388

389 390
    @skipUnlessDBFeature('has_zoneinfo_database')
    def test_query_datetime_lookups(self):
391 392
        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))
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
        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)
        self.assertEqual(Event.objects.filter(dt__hour=1).count(), 1)
        self.assertEqual(Event.objects.filter(dt__minute=30).count(), 2)
        self.assertEqual(Event.objects.filter(dt__second=0).count(), 2)

    @skipUnlessDBFeature('has_zoneinfo_database')
    def test_query_datetime_lookups_in_other_timezone(self):
        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))
        with timezone.override(UTC):
            # These two dates fall in the same day in EAT, but in different days,
            # years and months in UTC.
            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)
            self.assertEqual(Event.objects.filter(dt__hour=22).count(), 1)
            self.assertEqual(Event.objects.filter(dt__minute=30).count(), 2)
            self.assertEqual(Event.objects.filter(dt__second=0).count(), 2)
415 416 417 418 419 420 421 422 423 424 425 426

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

427 428 429 430 431 432 433 434 435 436
    def test_query_annotation(self):
        # Only min and max make sense for datetimes.
        morning = Session.objects.create(name='morning')
        afternoon = Session.objects.create(name='afternoon')
        SessionEvent.objects.create(dt=datetime.datetime(2011, 9, 1, 23, 20, 20, tzinfo=EAT), session=afternoon)
        SessionEvent.objects.create(dt=datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT), session=afternoon)
        SessionEvent.objects.create(dt=datetime.datetime(2011, 9, 1, 3, 20, 40, tzinfo=EAT), session=morning)
        morning_min_dt = datetime.datetime(2011, 9, 1, 3, 20, 40, tzinfo=EAT)
        afternoon_min_dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
        self.assertQuerysetEqual(
437 438 439
            Session.objects.annotate(dt=Min('events__dt')).order_by('dt'),
            [morning_min_dt, afternoon_min_dt],
            transform=lambda d: d.dt)
440
        self.assertQuerysetEqual(
441 442 443
            Session.objects.annotate(dt=Min('events__dt')).filter(dt__lt=afternoon_min_dt),
            [morning_min_dt],
            transform=lambda d: d.dt)
444
        self.assertQuerysetEqual(
445 446 447
            Session.objects.annotate(dt=Min('events__dt')).filter(dt__gte=afternoon_min_dt),
            [afternoon_min_dt],
            transform=lambda d: d.dt)
448

449 450
    @skipUnlessDBFeature('has_zoneinfo_database')
    def test_query_datetimes(self):
451 452
        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))
453
        self.assertSequenceEqual(
454
            Event.objects.datetimes('dt', 'year'),
455 456 457
            [datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=EAT)]
        )
        self.assertSequenceEqual(
458
            Event.objects.datetimes('dt', 'month'),
459 460 461
            [datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=EAT)]
        )
        self.assertSequenceEqual(
462
            Event.objects.datetimes('dt', 'day'),
463 464 465
            [datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=EAT)]
        )
        self.assertSequenceEqual(
466 467
            Event.objects.datetimes('dt', 'hour'),
            [datetime.datetime(2011, 1, 1, 1, 0, 0, tzinfo=EAT),
468 469 470
             datetime.datetime(2011, 1, 1, 4, 0, 0, tzinfo=EAT)]
        )
        self.assertSequenceEqual(
471 472
            Event.objects.datetimes('dt', 'minute'),
            [datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT),
473 474 475
             datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT)]
        )
        self.assertSequenceEqual(
476 477
            Event.objects.datetimes('dt', 'second'),
            [datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT),
478 479
             datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT)]
        )
480 481 482 483 484 485

    @skipUnlessDBFeature('has_zoneinfo_database')
    def test_query_datetimes_in_other_timezone(self):
        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))
        with timezone.override(UTC):
486
            self.assertSequenceEqual(
487 488
                Event.objects.datetimes('dt', 'year'),
                [datetime.datetime(2010, 1, 1, 0, 0, 0, tzinfo=UTC),
489 490 491
                 datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=UTC)]
            )
            self.assertSequenceEqual(
492 493
                Event.objects.datetimes('dt', 'month'),
                [datetime.datetime(2010, 12, 1, 0, 0, 0, tzinfo=UTC),
494 495 496
                 datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=UTC)]
            )
            self.assertSequenceEqual(
497 498
                Event.objects.datetimes('dt', 'day'),
                [datetime.datetime(2010, 12, 31, 0, 0, 0, tzinfo=UTC),
499 500 501
                 datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=UTC)]
            )
            self.assertSequenceEqual(
502 503
                Event.objects.datetimes('dt', 'hour'),
                [datetime.datetime(2010, 12, 31, 22, 0, 0, tzinfo=UTC),
504 505 506
                 datetime.datetime(2011, 1, 1, 1, 0, 0, tzinfo=UTC)]
            )
            self.assertSequenceEqual(
507 508
                Event.objects.datetimes('dt', 'minute'),
                [datetime.datetime(2010, 12, 31, 22, 30, 0, tzinfo=UTC),
509 510 511
                 datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=UTC)]
            )
            self.assertSequenceEqual(
512 513
                Event.objects.datetimes('dt', 'second'),
                [datetime.datetime(2010, 12, 31, 22, 30, 0, tzinfo=UTC),
514 515
                 datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=UTC)]
            )
516

517 518 519 520
    def test_raw_sql(self):
        # Regression test for #17755
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
        event = Event.objects.create(dt=dt)
521
        self.assertSequenceEqual(list(Event.objects.raw('SELECT * FROM timezones_event WHERE dt = %s', [dt])), [event])
522

523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
    @skipUnlessDBFeature('supports_timezones')
    def test_cursor_execute_accepts_aware_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
        with connection.cursor() as cursor:
            cursor.execute('INSERT INTO timezones_event (dt) VALUES (%s)', [dt])
        event = Event.objects.get()
        self.assertEqual(event.dt, dt)

    @skipIfDBFeature('supports_timezones')
    def test_cursor_execute_accepts_naive_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
        utc_naive_dt = timezone.make_naive(dt, timezone.utc)
        with connection.cursor() as cursor:
            cursor.execute('INSERT INTO timezones_event (dt) VALUES (%s)', [utc_naive_dt])
        event = Event.objects.get()
        self.assertEqual(event.dt, dt)

540 541 542 543 544 545 546 547 548 549 550 551 552 553
    @skipUnlessDBFeature('supports_timezones')
    def test_cursor_execute_returns_aware_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
        Event.objects.create(dt=dt)
        with connection.cursor() as cursor:
            cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [dt])
            self.assertEqual(cursor.fetchall()[0][0], dt)

    @skipIfDBFeature('supports_timezones')
    def test_cursor_execute_returns_naive_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
        utc_naive_dt = timezone.make_naive(dt, timezone.utc)
        Event.objects.create(dt=dt)
        with connection.cursor() as cursor:
554
            cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [utc_naive_dt])
555 556
            self.assertEqual(cursor.fetchall()[0][0], utc_naive_dt)

557
    @requires_tz_support
558 559 560
    def test_filter_date_field_with_aware_datetime(self):
        # Regression test for #17742
        day = datetime.date(2011, 9, 1)
561
        AllDayEvent.objects.create(day=day)
562 563 564 565
        # This is 2011-09-02T01:30:00+03:00 in EAT
        dt = datetime.datetime(2011, 9, 1, 22, 30, 0, tzinfo=UTC)
        self.assertFalse(AllDayEvent.objects.filter(day__gte=dt).exists())

566
    def test_null_datetime(self):
567
        # Regression test for #17294
568
        e = MaybeEvent.objects.create()
569
        self.assertIsNone(e.dt)
570

571 572 573 574 575 576 577
    def test_update_with_timedelta(self):
        initial_dt = timezone.now().replace(microsecond=0)
        event = Event.objects.create(dt=initial_dt)
        Event.objects.update(dt=F('dt') + timedelta(hours=2))
        event.refresh_from_db()
        self.assertEqual(event.dt, initial_dt + timedelta(hours=2))

578

579 580 581 582 583 584 585 586 587 588 589 590 591
@override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=True)
class ForcedTimeZoneDatabaseTests(TransactionTestCase):
    """
    Test the TIME_ZONE database configuration parameter.

    Since this involves reading and writing to the same database through two
    connections, this is a TransactionTestCase.
    """

    available_apps = ['timezones']

    @classmethod
    def setUpClass(cls):
592 593 594 595 596 597 598
        # @skipIfDBFeature and @skipUnlessDBFeature cannot be chained. The
        # outermost takes precedence. Handle skipping manually instead.
        if connection.features.supports_timezones:
            raise SkipTest("Database has feature(s) supports_timezones")
        if not connection.features.test_db_allows_multiple_connections:
            raise SkipTest("Database doesn't support feature(s): test_db_allows_multiple_connections")

599
        super().setUpClass()
600

601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
    @contextmanager
    def override_database_connection_timezone(self, timezone):
        try:
            orig_timezone = connection.settings_dict['TIME_ZONE']
            connection.settings_dict['TIME_ZONE'] = timezone
            # Clear cached properties, after first accessing them to ensure they exist.
            connection.timezone
            del connection.timezone
            connection.timezone_name
            del connection.timezone_name

            yield

        finally:
            connection.settings_dict['TIME_ZONE'] = orig_timezone
            # Clear cached properties, after first accessing them to ensure they exist.
            connection.timezone
            del connection.timezone
            connection.timezone_name
            del connection.timezone_name
621 622 623 624 625

    def test_read_datetime(self):
        fake_dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=UTC)
        Event.objects.create(dt=fake_dt)

626 627 628
        with self.override_database_connection_timezone('Asia/Bangkok'):
            event = Event.objects.get()
            dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)
629 630 631 632
        self.assertEqual(event.dt, dt)

    def test_write_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)
633 634
        with self.override_database_connection_timezone('Asia/Bangkok'):
            Event.objects.create(dt=dt)
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657

        event = Event.objects.get()
        fake_dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=UTC)
        self.assertEqual(event.dt, fake_dt)


@skipUnlessDBFeature('supports_timezones')
@override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=True)
class UnsupportedTimeZoneDatabaseTests(TestCase):

    def test_time_zone_parameter_not_supported_if_database_supports_timezone(self):
        connections.databases['tz'] = connections.databases['default'].copy()
        connections.databases['tz']['TIME_ZONE'] = 'Asia/Bangkok'
        tz_conn = connections['tz']
        try:
            with self.assertRaises(ImproperlyConfigured):
                tz_conn.cursor()
        finally:
            connections['tz'].close()       # in case the test fails
            del connections['tz']
            del connections.databases['tz']


658
@override_settings(TIME_ZONE='Africa/Nairobi')
659
class SerializationTests(SimpleTestCase):
660 661 662 663

    # Backend-specific notes:
    # - JSON supports only milliseconds, microseconds will be truncated.
    # - PyYAML dumps the UTC offset correctly for timezone-aware datetimes,
664
    #   but when it loads this representation, it subtracts the offset and
665 666 667
    #   returns a naive datetime object in UTC (http://pyyaml.org/ticket/202).
    # Tests are adapted to take these quirks into account.

668 669 670 671 672 673 674 675 676 677 678
    def assert_python_contains_datetime(self, objects, dt):
        self.assertEqual(objects[0]['fields']['dt'], dt)

    def assert_json_contains_datetime(self, json, dt):
        self.assertIn('"fields": {"dt": "%s"}' % dt, json)

    def assert_xml_contains_datetime(self, xml, dt):
        field = parseString(xml).getElementsByTagName('field')[0]
        self.assertXMLEqual(field.childNodes[0].wholeText, dt)

    def assert_yaml_contains_datetime(self, yaml, dt):
679
        # Depending on the yaml dumper, '!timestamp' might be absent
680
        self.assertRegex(yaml, r"\n  fields: {dt: !(!timestamp)? '%s'}" % re.escape(dt))
681

682 683 684 685
    def test_naive_datetime(self):
        dt = datetime.datetime(2011, 9, 1, 13, 20, 30)

        data = serializers.serialize('python', [Event(dt=dt)])
686
        self.assert_python_contains_datetime(data, dt)
687
        obj = next(serializers.deserialize('python', data)).object
688 689 690
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('json', [Event(dt=dt)])
691
        self.assert_json_contains_datetime(data, "2011-09-01T13:20:30")
692
        obj = next(serializers.deserialize('json', data)).object
693 694 695
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('xml', [Event(dt=dt)])
696
        self.assert_xml_contains_datetime(data, "2011-09-01T13:20:30")
697
        obj = next(serializers.deserialize('xml', data)).object
698 699
        self.assertEqual(obj.dt, dt)

700
        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
701
            data = serializers.serialize('yaml', [Event(dt=dt)])
702
            self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30")
703
            obj = next(serializers.deserialize('yaml', data)).object
704 705 706 707 708 709
            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)])
710
        self.assert_python_contains_datetime(data, dt)
711
        obj = next(serializers.deserialize('python', data)).object
712 713 714
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('json', [Event(dt=dt)])
715
        self.assert_json_contains_datetime(data, "2011-09-01T13:20:30.405")
716
        obj = next(serializers.deserialize('json', data)).object
717 718 719
        self.assertEqual(obj.dt, dt.replace(microsecond=405000))

        data = serializers.serialize('xml', [Event(dt=dt)])
720
        self.assert_xml_contains_datetime(data, "2011-09-01T13:20:30.405060")
721
        obj = next(serializers.deserialize('xml', data)).object
722 723
        self.assertEqual(obj.dt, dt)

724
        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
725
            data = serializers.serialize('yaml', [Event(dt=dt)])
726
            self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30.405060")
727
            obj = next(serializers.deserialize('yaml', data)).object
728 729 730 731 732 733
            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)])
734
        self.assert_python_contains_datetime(data, dt)
735
        obj = next(serializers.deserialize('python', data)).object
736 737 738
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('json', [Event(dt=dt)])
739
        self.assert_json_contains_datetime(data, "2011-09-01T17:20:30.405+07:00")
740
        obj = next(serializers.deserialize('json', data)).object
741 742 743
        self.assertEqual(obj.dt, dt.replace(microsecond=405000))

        data = serializers.serialize('xml', [Event(dt=dt)])
744
        self.assert_xml_contains_datetime(data, "2011-09-01T17:20:30.405060+07:00")
745
        obj = next(serializers.deserialize('xml', data)).object
746 747
        self.assertEqual(obj.dt, dt)

748
        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
749
            data = serializers.serialize('yaml', [Event(dt=dt)])
750
            self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30.405060+07:00")
751
            obj = next(serializers.deserialize('yaml', data)).object
752 753 754 755 756 757
            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)])
758
        self.assert_python_contains_datetime(data, dt)
759
        obj = next(serializers.deserialize('python', data)).object
760 761 762
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('json', [Event(dt=dt)])
763
        self.assert_json_contains_datetime(data, "2011-09-01T10:20:30Z")
764
        obj = next(serializers.deserialize('json', data)).object
765 766 767
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('xml', [Event(dt=dt)])
768
        self.assert_xml_contains_datetime(data, "2011-09-01T10:20:30+00:00")
769
        obj = next(serializers.deserialize('xml', data)).object
770 771
        self.assertEqual(obj.dt, dt)

772
        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
773
            data = serializers.serialize('yaml', [Event(dt=dt)])
774
            self.assert_yaml_contains_datetime(data, "2011-09-01 10:20:30+00:00")
775
            obj = next(serializers.deserialize('yaml', data)).object
776 777 778 779 780 781
            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)])
782
        self.assert_python_contains_datetime(data, dt)
783
        obj = next(serializers.deserialize('python', data)).object
784 785 786
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('json', [Event(dt=dt)])
787
        self.assert_json_contains_datetime(data, "2011-09-01T13:20:30+03:00")
788
        obj = next(serializers.deserialize('json', data)).object
789 790 791
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('xml', [Event(dt=dt)])
792
        self.assert_xml_contains_datetime(data, "2011-09-01T13:20:30+03:00")
793
        obj = next(serializers.deserialize('xml', data)).object
794 795
        self.assertEqual(obj.dt, dt)

796
        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
797
            data = serializers.serialize('yaml', [Event(dt=dt)])
798
            self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30+03:00")
799
            obj = next(serializers.deserialize('yaml', data)).object
800 801 802 803 804 805
            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)])
806
        self.assert_python_contains_datetime(data, dt)
807
        obj = next(serializers.deserialize('python', data)).object
808 809 810
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('json', [Event(dt=dt)])
811
        self.assert_json_contains_datetime(data, "2011-09-01T17:20:30+07:00")
812
        obj = next(serializers.deserialize('json', data)).object
813 814 815
        self.assertEqual(obj.dt, dt)

        data = serializers.serialize('xml', [Event(dt=dt)])
816
        self.assert_xml_contains_datetime(data, "2011-09-01T17:20:30+07:00")
817
        obj = next(serializers.deserialize('xml', data)).object
818 819
        self.assertEqual(obj.dt, dt)

820
        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
821
            data = serializers.serialize('yaml', [Event(dt=dt)])
822
            self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30+07:00")
823
            obj = next(serializers.deserialize('yaml', data)).object
824 825
            self.assertEqual(obj.dt.replace(tzinfo=UTC), dt)

826 827

@override_settings(DATETIME_FORMAT='c', TIME_ZONE='Africa/Nairobi', USE_L10N=False, USE_TZ=True)
828
class TemplateTests(SimpleTestCase):
829

830
    @requires_tz_support
831 832 833 834 835 836 837 838 839 840 841
    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 = {
842
            'notag': Template("{% load tz %}{{ dt }}|{{ dt|localtime }}|{{ dt|utc }}|{{ dt|timezone:ICT }}"),
843 844 845 846 847 848 849 850 851 852 853 854
            'noarg': Template(
                "{% load tz %}{% localtime %}{{ dt }}|{{ dt|localtime }}|"
                "{{ dt|utc }}|{{ dt|timezone:ICT }}{% endlocaltime %}"
            ),
            'on': Template(
                "{% load tz %}{% localtime on %}{{ dt }}|{{ dt|localtime }}|"
                "{{ dt|utc }}|{{ dt|timezone:ICT }}{% endlocaltime %}"
            ),
            'off': Template(
                "{% load tz %}{% localtime off %}{{ dt }}|{{ dt|localtime }}|"
                "{{ dt|utc }}|{{ dt|timezone:ICT }}{% endlocaltime %}"
            ),
855 856 857 858 859 860 861 862 863 864 865 866 867
        }

        # 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'),
Boryslav Larin's avatar
Boryslav Larin committed
868 869
                'on': t('eat', 'eat', 'utc', 'ict'),
                'off': t('utc', 'eat', 'utc', 'ict'),
870 871 872 873
            },
            'eat': {
                'notag': t('eat', 'eat', 'utc', 'ict'),
                'noarg': t('eat', 'eat', 'utc', 'ict'),
Boryslav Larin's avatar
Boryslav Larin committed
874 875
                'on': t('eat', 'eat', 'utc', 'ict'),
                'off': t('eat', 'eat', 'utc', 'ict'),
876 877 878 879
            },
            'ict': {
                'notag': t('eat', 'eat', 'utc', 'ict'),
                'noarg': t('eat', 'eat', 'utc', 'ict'),
Boryslav Larin's avatar
Boryslav Larin committed
880 881
                'on': t('eat', 'eat', 'utc', 'ict'),
                'off': t('ict', 'eat', 'utc', 'ict'),
882 883 884 885
            },
            'naive': {
                'notag': t('naive', 'eat', 'utc', 'ict'),
                'noarg': t('naive', 'eat', 'utc', 'ict'),
Boryslav Larin's avatar
Boryslav Larin committed
886 887
                'on': t('naive', 'eat', 'utc', 'ict'),
                'off': t('naive', 'eat', 'utc', 'ict'),
888 889 890
            }
        }

891 892
        for k1, dt in datetimes.items():
            for k2, tpl in templates.items():
893 894 895 896 897 898 899 900 901 902 903
                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):
904 905
            for k1, dt in datetimes.items():
                for k2, tpl in templates.items():
906 907 908 909 910 911 912
                    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))

    def test_localtime_filters_with_pytz(self):
        """
913
        Test the |localtime, |utc, and |timezone filters with pytz.
914 915
        """
        # Use a pytz timezone as local time
916
        tpl = Template("{% load tz %}{{ dt|localtime }}|{{ dt|utc }}")
917 918 919 920 921 922
        ctx = Context({'dt': datetime.datetime(2011, 9, 1, 12, 20, 30)})

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

        # Use a pytz timezone as argument
923
        tpl = Template("{% load tz %}{{ dt|timezone:tz }}")
924 925 926 927 928
        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
929
        tpl = Template("{% load tz %}{{ dt|timezone:'Europe/Paris' }}")
930 931 932 933 934 935 936 937 938 939
        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):
        """
940
        Test the |localtime, |utc, and |timezone filters on bad inputs.
941
        """
942
        tpl = Template("{% load tz %}{{ dt }}|{{ dt|localtime }}|{{ dt|utc }}|{{ dt|timezone:tz }}")
943 944 945 946 947 948 949
        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
950
            tpl = Template("{% load tz %}{{ dt|timezone:tz }}")
951 952 953 954 955
            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), "")

956
    @requires_tz_support
957 958 959 960
    def test_timezone_templatetag(self):
        """
        Test the {% timezone %} templatetag.
        """
Loic Bistuer's avatar
Loic Bistuer committed
961 962 963 964 965 966 967 968 969 970
        tpl = Template(
            "{% load tz %}"
            "{{ dt }}|"
            "{% timezone tz1 %}"
            "{{ dt }}|"
            "{% timezone tz2 %}"
            "{{ dt }}"
            "{% endtimezone %}"
            "{% endtimezone %}"
        )
971 972
        ctx = Context({'dt': datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC),
                       'tz1': ICT, 'tz2': None})
973 974 975 976
        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"
        )
977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996

    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()
997
        with self.assertRaises(pytz.UnknownTimeZoneError):
998 999
            Template("{% load tz %}{% timezone tz %}{% endtimezone %}").render(Context({'tz': 'foobar'}))

1000
    @skipIf(sys.platform.startswith('win'), "Windows uses non-standard time zone names")
1001 1002 1003 1004 1005 1006
    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 }}")

1007
        self.assertEqual(tpl.render(Context()), "Africa/Nairobi")
1008 1009 1010
        with timezone.override(UTC):
            self.assertEqual(tpl.render(Context()), "UTC")

1011 1012 1013 1014
        tpl = Template(
            "{% load tz %}{% timezone tz %}{% get_current_timezone as time_zone %}"
            "{% endtimezone %}{{ time_zone }}"
        )
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027

        self.assertEqual(tpl.render(Context({'tz': ICT})), "+0700")
        with timezone.override(UTC):
            self.assertEqual(tpl.render(Context({'tz': ICT})), "+0700")

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

1028 1029 1030 1031 1032
        tpl = Template(
            "{% load tz %}{% timezone 'Europe/Paris' %}"
            "{% get_current_timezone as time_zone %}{% endtimezone %}"
            "{{ time_zone }}"
        )
1033 1034 1035 1036 1037 1038
        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()

1039
    @skipIf(sys.platform.startswith('win'), "Windows uses non-standard time zone names")
1040 1041
    def test_tz_template_context_processor(self):
        """
1042
        Test the django.template.context_processors.tz template context processor.
1043 1044
        """
        tpl = Template("{{ TIME_ZONE }}")
1045 1046 1047
        context = Context()
        self.assertEqual(tpl.render(context), "")
        request_context = RequestContext(HttpRequest(), processors=[context_processors.tz])
1048
        self.assertEqual(tpl.render(request_context), "Africa/Nairobi")
1049

1050
    @requires_tz_support
1051 1052 1053 1054 1055 1056 1057 1058
    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):
1059 1060 1061 1062
        tpl = Template(
            "{% load tz %}{% localtime off %}{{ dt|date:'Y-m-d' }} at "
            "{{ dt|time:'H:i:s' }}{% endlocaltime %}"
        )
1063 1064 1065 1066 1067
        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")

1068
    @requires_tz_support
1069 1070 1071 1072 1073 1074 1075
    def test_now_template_tag_uses_current_time_zone(self):
        # Regression for #17343
        tpl = Template("{% now \"O\" %}")
        self.assertEqual(tpl.render(Context({})), "+0300")
        with timezone.override(ICT):
            self.assertEqual(tpl.render(Context({})), "+0700")

1076

1077 1078
@override_settings(DATETIME_FORMAT='c', TIME_ZONE='Africa/Nairobi', USE_L10N=False, USE_TZ=False)
class LegacyFormsTests(TestCase):
1079 1080

    def test_form(self):
1081
        form = EventForm({'dt': '2011-09-01 13:20:30'})
1082 1083 1084 1085
        self.assertTrue(form.is_valid())
        self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30))

    def test_form_with_non_existent_time(self):
1086
        form = EventForm({'dt': '2011-03-27 02:30:00'})
1087 1088 1089 1090 1091 1092
        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))

    def test_form_with_ambiguous_time(self):
1093
        form = EventForm({'dt': '2011-10-30 02:30:00'})
1094 1095 1096 1097 1098 1099
        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):
1100
        form = EventSplitForm({'dt_0': '2011-09-01', 'dt_1': '13:20:30'})
1101 1102 1103 1104
        self.assertTrue(form.is_valid())
        self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30))

    def test_model_form(self):
1105
        EventModelForm({'dt': '2011-09-01 13:20:30'}).save()
1106 1107 1108 1109
        e = Event.objects.get()
        self.assertEqual(e.dt, datetime.datetime(2011, 9, 1, 13, 20, 30))


1110 1111
@override_settings(DATETIME_FORMAT='c', TIME_ZONE='Africa/Nairobi', USE_L10N=False, USE_TZ=True)
class NewFormsTests(TestCase):
1112

1113
    @requires_tz_support
1114
    def test_form(self):
1115
        form = EventForm({'dt': '2011-09-01 13:20:30'})
1116 1117 1118 1119
        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):
1120
        form = EventForm({'dt': '2011-09-01 17:20:30'})
1121 1122 1123 1124
        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))

1125 1126 1127 1128 1129
    def test_form_with_explicit_timezone(self):
        form = EventForm({'dt': '2011-09-01 17:20:30+07:00'})
        # Datetime inputs formats don't allow providing a time zone.
        self.assertFalse(form.is_valid())

1130 1131
    def test_form_with_non_existent_time(self):
        with timezone.override(pytz.timezone('Europe/Paris')):
1132
            form = EventForm({'dt': '2011-03-27 02:30:00'})
1133
            self.assertFalse(form.is_valid())
1134 1135 1136 1137 1138 1139
            self.assertEqual(
                form.errors['dt'], [
                    "2011-03-27 02:30:00 couldn't be interpreted in time zone "
                    "Europe/Paris; it may be ambiguous or it may not exist."
                ]
            )
1140 1141 1142

    def test_form_with_ambiguous_time(self):
        with timezone.override(pytz.timezone('Europe/Paris')):
1143
            form = EventForm({'dt': '2011-10-30 02:30:00'})
1144
            self.assertFalse(form.is_valid())
1145 1146 1147 1148 1149 1150
            self.assertEqual(
                form.errors['dt'], [
                    "2011-10-30 02:30:00 couldn't be interpreted in time zone "
                    "Europe/Paris; it may be ambiguous or it may not exist."
                ]
            )
1151

1152
    @requires_tz_support
1153
    def test_split_form(self):
1154
        form = EventSplitForm({'dt_0': '2011-09-01', 'dt_1': '13:20:30'})
1155 1156 1157
        self.assertTrue(form.is_valid())
        self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))

1158 1159 1160 1161 1162 1163
    @requires_tz_support
    def test_localized_form(self):
        form = EventLocalizedForm(initial={'dt': datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)})
        with timezone.override(ICT):
            self.assertIn("2011-09-01 17:20:30", str(form))

1164
    @requires_tz_support
1165
    def test_model_form(self):
1166
        EventModelForm({'dt': '2011-09-01 13:20:30'}).save()
1167 1168 1169
        e = Event.objects.get()
        self.assertEqual(e.dt, datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))

1170 1171 1172 1173 1174 1175
    @requires_tz_support
    def test_localized_model_form(self):
        form = EventLocalizedModelForm(instance=Event(dt=datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)))
        with timezone.override(ICT):
            self.assertIn("2011-09-01 17:20:30", str(form))

1176

1177 1178 1179 1180 1181 1182 1183
@override_settings(
    DATETIME_FORMAT='c',
    TIME_ZONE='Africa/Nairobi',
    USE_L10N=False,
    USE_TZ=True,
    ROOT_URLCONF='timezones.urls',
)
1184
class AdminTests(TestCase):
1185

1186 1187
    @classmethod
    def setUpTestData(cls):
1188 1189
        cls.u1 = User.objects.create_user(
            password='secret',
1190 1191 1192 1193
            last_login=datetime.datetime(2007, 5, 30, 13, 20, 10, tzinfo=UTC),
            is_superuser=True, username='super', first_name='Super', last_name='User',
            email='super@example.com', is_staff=True, is_active=True,
            date_joined=datetime.datetime(2007, 5, 30, 13, 20, 10, tzinfo=UTC),
1194
        )
1195 1196

    def setUp(self):
1197
        self.client.force_login(self.u1)
1198

1199
    @requires_tz_support
1200 1201
    def test_changelist(self):
        e = Event.objects.create(dt=datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
1202
        response = self.client.get(reverse('admin_tz:timezones_event_changelist'))
1203 1204 1205 1206 1207
        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):
1208
            response = self.client.get(reverse('admin_tz:timezones_event_changelist'))
1209 1210
        self.assertContains(response, e.dt.astimezone(ICT).isoformat())

1211
    @requires_tz_support
1212 1213
    def test_change_editable(self):
        e = Event.objects.create(dt=datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC))
1214
        response = self.client.get(reverse('admin_tz:timezones_event_change', args=(e.pk,)))
1215 1216 1217 1218 1219 1220
        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):
1221
            response = self.client.get(reverse('admin_tz:timezones_event_change', args=(e.pk,)))
1222 1223 1224
        self.assertContains(response, e.dt.astimezone(ICT).date().isoformat())
        self.assertContains(response, e.dt.astimezone(ICT).time().isoformat())

1225
    @requires_tz_support
1226 1227 1228 1229
    def test_change_readonly(self):
        Timestamp.objects.create()
        # re-fetch the object for backends that lose microseconds (MySQL)
        t = Timestamp.objects.get()
1230
        response = self.client.get(reverse('admin_tz:timezones_timestamp_change', args=(t.pk,)))
1231 1232 1233 1234 1235 1236 1237
        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):
1238
            response = self.client.get(reverse('admin_tz:timezones_timestamp_change', args=(t.pk,)))
1239
        self.assertContains(response, t.created.astimezone(ICT).isoformat())