55import sys
66import uuid
77from decimal import ROUND_DOWN , ROUND_UP , Decimal
8+ from unittest .mock import patch
89
910import pytest
1011import pytz
2122)
2223from tests .models import UUIDForeignKeyTarget
2324
25+ if sys .version_info >= (3 , 9 ):
26+ from zoneinfo import ZoneInfo
27+ else :
28+ from backports .zoneinfo import ZoneInfo
29+
2430utc = datetime .timezone .utc
2531
2632# Tests for helper functions.
@@ -651,15 +657,15 @@ class FieldValues:
651657 """
652658 Base class for testing valid and invalid input values.
653659 """
654- def test_valid_inputs (self ):
660+ def test_valid_inputs (self , * args ):
655661 """
656662 Ensure that valid values return the expected validated data.
657663 """
658664 for input_value , expected_output in get_items (self .valid_inputs ):
659665 assert self .field .run_validation (input_value ) == expected_output , \
660666 'input value: {}' .format (repr (input_value ))
661667
662- def test_invalid_inputs (self ):
668+ def test_invalid_inputs (self , * args ):
663669 """
664670 Ensure that invalid values raise the expected validation error.
665671 """
@@ -669,7 +675,7 @@ def test_invalid_inputs(self):
669675 assert exc_info .value .detail == expected_failure , \
670676 'input value: {}' .format (repr (input_value ))
671677
672- def test_outputs (self ):
678+ def test_outputs (self , * args ):
673679 for output_value , expected_output in get_items (self .outputs ):
674680 assert self .field .to_representation (output_value ) == expected_output , \
675681 'output value: {}' .format (repr (output_value ))
@@ -1505,12 +1511,12 @@ class TestTZWithDateTimeField(FieldValues):
15051511 @classmethod
15061512 def setup_class (cls ):
15071513 # use class setup method, as class-level attribute will still be evaluated even if test is skipped
1508- kolkata = pytz . timezone ('Asia/Kolkata' )
1514+ kolkata = ZoneInfo ('Asia/Kolkata' )
15091515
15101516 cls .valid_inputs = {
1511- '2016-12-19T10:00:00' : kolkata . localize ( datetime .datetime (2016 , 12 , 19 , 10 ) ),
1512- '2016-12-19T10:00:00+05:30' : kolkata . localize ( datetime .datetime (2016 , 12 , 19 , 10 ) ),
1513- datetime .datetime (2016 , 12 , 19 , 10 ): kolkata . localize ( datetime .datetime (2016 , 12 , 19 , 10 ) ),
1517+ '2016-12-19T10:00:00' : datetime .datetime (2016 , 12 , 19 , 10 , tzinfo = kolkata ),
1518+ '2016-12-19T10:00:00+05:30' : datetime .datetime (2016 , 12 , 19 , 10 , tzinfo = kolkata ),
1519+ datetime .datetime (2016 , 12 , 19 , 10 ): datetime .datetime (2016 , 12 , 19 , 10 , tzinfo = kolkata ),
15141520 }
15151521 cls .invalid_inputs = {}
15161522 cls .outputs = {
@@ -1529,7 +1535,7 @@ class TestDefaultTZDateTimeField(TestCase):
15291535 @classmethod
15301536 def setup_class (cls ):
15311537 cls .field = serializers .DateTimeField ()
1532- cls .kolkata = pytz . timezone ('Asia/Kolkata' )
1538+ cls .kolkata = ZoneInfo ('Asia/Kolkata' )
15331539
15341540 def assertUTC (self , tzinfo ):
15351541 """
@@ -1551,18 +1557,17 @@ def test_current_timezone(self):
15511557 self .assertUTC (self .field .default_timezone ())
15521558
15531559
1554- @pytest .mark .skipif (pytz is None , reason = 'pytz not installed' )
15551560@override_settings (TIME_ZONE = 'UTC' , USE_TZ = True )
15561561class TestCustomTimezoneForDateTimeField (TestCase ):
15571562
15581563 @classmethod
15591564 def setup_class (cls ):
1560- cls .kolkata = pytz . timezone ('Asia/Kolkata' )
1565+ cls .kolkata = ZoneInfo ('Asia/Kolkata' )
15611566 cls .date_format = '%d/%m/%Y %H:%M'
15621567
15631568 def test_should_render_date_time_in_default_timezone (self ):
15641569 field = serializers .DateTimeField (default_timezone = self .kolkata , format = self .date_format )
1565- dt = datetime .datetime (2018 , 2 , 8 , 14 , 15 , 16 , tzinfo = pytz . utc )
1570+ dt = datetime .datetime (2018 , 2 , 8 , 14 , 15 , 16 , tzinfo = ZoneInfo ( "UTC" ) )
15661571
15671572 with override (self .kolkata ):
15681573 rendered_date = field .to_representation (dt )
@@ -1572,7 +1577,8 @@ def test_should_render_date_time_in_default_timezone(self):
15721577 assert rendered_date == rendered_date_in_timezone
15731578
15741579
1575- class TestNaiveDayLightSavingTimeTimeZoneDateTimeField (FieldValues ):
1580+ @pytest .mark .skipif (pytz is None , reason = "As Django 4.0 has deprecated pytz, this test should eventually be able to get removed." )
1581+ class TestPytzNaiveDayLightSavingTimeTimeZoneDateTimeField (FieldValues ):
15761582 """
15771583 Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST.
15781584 Timezone America/New_York has DST shift from 2017-03-12T02:00:00 to 2017-03-12T03:00:00 and
@@ -1596,6 +1602,27 @@ def __str__(self):
15961602 field = serializers .DateTimeField (default_timezone = MockTimezone ())
15971603
15981604
1605+ @patch ('rest_framework.utils.timezone.datetime_ambiguous' , return_value = True )
1606+ class TestNaiveDayLightSavingTimeTimeZoneDateTimeField (FieldValues ):
1607+ """
1608+ Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST.
1609+ Timezone America/New_York has DST shift from 2017-03-12T02:00:00 to 2017-03-12T03:00:00 and
1610+ from 2017-11-05T02:00:00 to 2017-11-05T01:00:00 in 2017.
1611+ """
1612+ valid_inputs = {}
1613+ invalid_inputs = {
1614+ '2017-03-12T02:30:00' : ['Invalid datetime for the timezone "America/New_York".' ],
1615+ '2017-11-05T01:30:00' : ['Invalid datetime for the timezone "America/New_York".' ]
1616+ }
1617+ outputs = {}
1618+
1619+ class MockZoneInfoTimezone (datetime .tzinfo ):
1620+ def __str__ (self ):
1621+ return 'America/New_York'
1622+
1623+ field = serializers .DateTimeField (default_timezone = MockZoneInfoTimezone ())
1624+
1625+
15991626class TestTimeField (FieldValues ):
16001627 """
16011628 Valid and invalid values for `TimeField`.
0 commit comments