diff --git a/cms/djangoapps/api/v1/tests/test_serializers/test_course_runs.py b/cms/djangoapps/api/v1/tests/test_serializers/test_course_runs.py index c648b2e5b875..09ca66da210a 100644 --- a/cms/djangoapps/api/v1/tests/test_serializers/test_course_runs.py +++ b/cms/djangoapps/api/v1/tests/test_serializers/test_course_runs.py @@ -3,7 +3,7 @@ import datetime import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import RequestFactory from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole @@ -22,7 +22,7 @@ class CourseRunSerializerTests(ModuleStoreTestCase): # lint-amnesty, pylint: di def setUp(self): super().setUp() - self.course_start = datetime.datetime.now(pytz.UTC) + self.course_start = datetime.datetime.now(get_utc_timezone()) self.course_end = self.course_start + datetime.timedelta(days=30) self.request = RequestFactory().get('') diff --git a/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py b/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py index 78261a421424..bc126667897c 100644 --- a/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py +++ b/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py @@ -5,7 +5,7 @@ from unittest.mock import patch # lint-amnesty, pylint: disable=unused-import import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.core.files.uploadedfile import SimpleUploadedFile from django.test import RequestFactory, override_settings from django.urls import reverse @@ -117,7 +117,7 @@ def test_update(self): assert CourseAccessRole.objects.filter(course_id=course_run.id).count() == 0 url = reverse('api:v1:course_run-detail', kwargs={'pk': str(course_run.id)}) - start = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + start = datetime.datetime.now(get_utc_timezone()).replace(microsecond=0) end = start + datetime.timedelta(days=30) title = 'A New Testing Strategy' user = UserFactory() @@ -165,7 +165,7 @@ def test_update_with_pacing_type(self): """ Test that update run updates the pacing type """ - start = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + start = datetime.datetime.now(get_utc_timezone()).replace(microsecond=0) course_run = CourseFactory(start=start, end=None, self_paced=False) data = { 'pacing_type': 'self_paced', @@ -183,7 +183,7 @@ def test_update_with_instructor_role(self): Test that update creates a new instructor role only if it does not exist """ instructor_role = 'instructor' - start = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + start = datetime.datetime.now(get_utc_timezone()).replace(microsecond=0) new_user = UserFactory() course_run = CourseFactory(start=start, end=None, self_paced=False) assert CourseAccessRole.objects.filter(course_id=course_run.id).count() == 0 @@ -214,7 +214,7 @@ def test_update_with_multiple_roles(self): """ staff_role = 'staff' instructor_role = 'instructor' - start = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + start = datetime.datetime.now(get_utc_timezone()).replace(microsecond=0) course_run = CourseFactory(start=start, end=None, self_paced=False) existing_user = UserFactory() @@ -255,7 +255,7 @@ def test_update_with_multiple_roles(self): def test_create(self, pacing_type, expected_self_paced_value): """Tests successful course run creation""" user = UserFactory() - start = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + start = datetime.datetime.now(get_utc_timezone()).replace(microsecond=0) end = start + datetime.timedelta(days=30) role = 'staff' data = self.get_course_run_data(user, start, end, pacing_type, role) @@ -280,7 +280,7 @@ def test_create_with_invalid_course_team(self): with expected validation message """ user = UserFactory() - start = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + start = datetime.datetime.now(get_utc_timezone()).replace(microsecond=0) end = start + datetime.timedelta(days=30) data = self.get_course_run_data(user, start, end, 'self-paced') data['team'] = [{'user': 'invalid-username'}] @@ -334,7 +334,7 @@ def test_rerun(self, pacing_type, expected_self_paced_value, number): 'short_name': original_course_run.id.org, # lint-amnesty, pylint: disable=no-member 'description': 'Testing Organization Description', }) - start = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + start = datetime.datetime.now(get_utc_timezone()).replace(microsecond=0) end = start + datetime.timedelta(days=30) user = UserFactory() role = 'instructor' diff --git a/cms/djangoapps/contentstore/api/views/course_validation.py b/cms/djangoapps/contentstore/api/views/course_validation.py index be5208430a18..b4e78d849415 100644 --- a/cms/djangoapps/contentstore/api/views/course_validation.py +++ b/cms/djangoapps/contentstore/api/views/course_validation.py @@ -2,7 +2,7 @@ import logging import dateutil -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework.generics import GenericAPIView from rest_framework.response import Response @@ -260,34 +260,34 @@ def _get_open_responses(self, course, graded_only): def _has_date_before_start(self, ora, start): # lint-amnesty, pylint: disable=missing-function-docstring if ora.submission_start: - if dateutil.parser.parse(ora.submission_start).replace(tzinfo=UTC) < start: + if dateutil.parser.parse(ora.submission_start).replace(tzinfo=get_utc_timezone()) < start: return True if ora.submission_due: - if dateutil.parser.parse(ora.submission_due).replace(tzinfo=UTC) < start: + if dateutil.parser.parse(ora.submission_due).replace(tzinfo=get_utc_timezone()) < start: return True for assessment in ora.rubric_assessments: if assessment['start']: - if dateutil.parser.parse(assessment['start']).replace(tzinfo=UTC) < start: + if dateutil.parser.parse(assessment['start']).replace(tzinfo=get_utc_timezone()) < start: return True if assessment['due']: - if dateutil.parser.parse(assessment['due']).replace(tzinfo=UTC) < start: + if dateutil.parser.parse(assessment['due']).replace(tzinfo=get_utc_timezone()) < start: return True return False def _has_date_after_end(self, ora, end): # lint-amnesty, pylint: disable=missing-function-docstring if ora.submission_start: - if dateutil.parser.parse(ora.submission_start).replace(tzinfo=UTC) > end: + if dateutil.parser.parse(ora.submission_start).replace(tzinfo=get_utc_timezone()) > end: return True if ora.submission_due: - if dateutil.parser.parse(ora.submission_due).replace(tzinfo=UTC) > end: + if dateutil.parser.parse(ora.submission_due).replace(tzinfo=get_utc_timezone()) > end: return True for assessment in ora.rubric_assessments: if assessment['start']: - if dateutil.parser.parse(assessment['start']).replace(tzinfo=UTC) > end: + if dateutil.parser.parse(assessment['start']).replace(tzinfo=get_utc_timezone()) > end: return True if assessment['due']: - if dateutil.parser.parse(assessment['due']).replace(tzinfo=UTC) > end: + if dateutil.parser.parse(assessment['due']).replace(tzinfo=get_utc_timezone()) > end: return True return False diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_clean_stale_certificate_availability_dates.py b/cms/djangoapps/contentstore/management/commands/tests/test_clean_stale_certificate_availability_dates.py index 42b3ca408aa2..b26eb8cd201a 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_clean_stale_certificate_availability_dates.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_clean_stale_certificate_availability_dates.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from django.core.management import CommandError, call_command -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from cms.djangoapps.contentstore.models import CleanStaleCertificateAvailabilityDatesConfig from openedx.core.lib.courses import get_course_by_id @@ -26,7 +26,7 @@ def setUp(self): self.instructor_paced_course1 = CourseFactory() # set the self-paced courses to self-paced, create and insert an invalid certificate available date for them - self.certificate_available_date = datetime.now(pytz.UTC) + timedelta(days=30) + self.certificate_available_date = datetime.now(get_utc_timezone()) + timedelta(days=30) self.self_paced_course1.self_paced = True self.self_paced_course1.certificate_available_date = self.certificate_available_date self.self_paced_course2.self_paced = True diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py index 3e88e0401060..3b042020e7c9 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py @@ -2,13 +2,13 @@ Unit tests for home page view. """ import ddt -import pytz from collections import OrderedDict from datetime import datetime, timedelta from django.conf import settings from django.test import override_settings from django.urls import reverse from rest_framework import status +from openedx.core.lib.time_zone_utils import get_utc_timezone from cms.djangoapps.contentstore.tests.utils import CourseTestCase from cms.djangoapps.contentstore.tests.test_libraries import LibraryTestCase @@ -190,7 +190,7 @@ def test_filter_and_ordering_courses( display_name="Course (Demo)", id=archived_course_key, org=archived_course_key.org, - end=(datetime.now() - timedelta(days=365)).replace(tzinfo=pytz.UTC), + end=(datetime.now() - timedelta(days=365)).replace(tzinfo=get_utc_timezone()), ) active_course_key = self.store.make_course_key("sample-org", "sample-number", "sample-run") CourseOverviewFactory.create( diff --git a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py index 6a51610ac9f2..fca5630a2edc 100644 --- a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py +++ b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py @@ -6,10 +6,10 @@ from datetime import datetime, timedelta import ddt -import pytz from django.conf import settings from django.urls import reverse from rest_framework import status +from openedx.core.lib.time_zone_utils import get_utc_timezone from cms.djangoapps.contentstore.tests.utils import CourseTestCase from cms.djangoapps.contentstore.utils import reverse_course_url @@ -36,7 +36,7 @@ def setUp(self): display_name="Demo Course (Sample)", id=archived_course_key, org=archived_course_key.org, - end=(datetime.now() - timedelta(days=365)).replace(tzinfo=pytz.UTC), + end=(datetime.now() - timedelta(days=365)).replace(tzinfo=get_utc_timezone()), ) self.non_staff_client, _ = self.create_non_staff_authed_user_client() @@ -256,7 +256,7 @@ def test_filter_and_ordering_courses( display_name="Course (Demo)", id=archived_course_key, org=archived_course_key.org, - end=(datetime.now() - timedelta(days=365)).replace(tzinfo=pytz.UTC), + end=(datetime.now() - timedelta(days=365)).replace(tzinfo=get_utc_timezone()), ) active_course_key = self.store.make_course_key("foo-org", "foo-number", "foo-run") CourseOverviewFactory.create( diff --git a/cms/djangoapps/contentstore/signals/handlers.py b/cms/djangoapps/contentstore/signals/handlers.py index 263bf632f585..58d53bdbd25d 100644 --- a/cms/djangoapps/contentstore/signals/handlers.py +++ b/cms/djangoapps/contentstore/signals/handlers.py @@ -29,7 +29,7 @@ XBLOCK_DELETED, XBLOCK_UPDATED, ) -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from cms.djangoapps.contentstore.courseware_index import ( CourseAboutSearchIndexer, @@ -171,8 +171,8 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable= # Kick off a courseware indexing action after the data is ready if CoursewareSearchIndexer.indexing_is_enabled() and CourseAboutSearchIndexer.indexing_is_enabled(): - transaction.on_commit(lambda: update_search_index.delay(course_key_str, datetime.now(UTC).isoformat())) - + transaction.on_commit(lambda: update_search_index.delay( + course_key_str, datetime.now(get_utc_timezone()).isoformat())) update_discussions_settings_from_course_task.apply_async( args=[course_key_str], countdown=settings.DISCUSSION_SETTINGS['COURSE_PUBLISH_TASK_DELAY'], @@ -201,7 +201,7 @@ def listen_for_library_update(sender, library_key, **kwargs): # pylint: disable # import here, because signal is registered at startup, but items in tasks are not yet able to be loaded from cms.djangoapps.contentstore.tasks import update_library_index - update_library_index.delay(str(library_key), datetime.now(UTC).isoformat()) + update_library_index.delay(str(library_key), datetime.now(get_utc_timezone()).isoformat()) @receiver(SignalHandler.item_deleted) diff --git a/cms/djangoapps/contentstore/tasks.py b/cms/djangoapps/contentstore/tasks.py index 239fb89840b0..1b72e1d1aaa2 100644 --- a/cms/djangoapps/contentstore/tasks.py +++ b/cms/djangoapps/contentstore/tasks.py @@ -39,7 +39,7 @@ from organizations.exceptions import InvalidOrganizationException from organizations.models import Organization from path import Path as path -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from user_tasks.models import UserTaskArtifact, UserTaskStatus from user_tasks.tasks import UserTask @@ -220,7 +220,7 @@ def _parse_time(time_isoformat): # remove the +00:00 from the end of the formats generated within the system time_isoformat.split('+')[0], "%Y-%m-%dT%H:%M:%S.%f" - ).replace(tzinfo=UTC) + ).replace(tzinfo=get_utc_timezone()) @shared_task diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index a72d14e931f7..74a9a6e0ff7e 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -26,7 +26,7 @@ from edx_toggles.toggles.testutils import override_waffle_flag from milestones.models import MilestoneRelationshipType from milestones.tests.utils import MilestonesTestCaseMixin -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from cms.djangoapps.contentstore import toggles from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_usage_url @@ -84,7 +84,7 @@ def test_pre_1900_date(self): doesn't work for these dates. """ details = CourseDetails.fetch(self.course.id) - pre_1900 = datetime.datetime(1564, 4, 23, 1, 1, 1, tzinfo=UTC) + pre_1900 = datetime.datetime(1564, 4, 23, 1, 1, 1, tzinfo=get_utc_timezone()) details.enrollment_start = pre_1900 dumped_jsondetails = json.dumps(details, cls=CourseSettingsEncoder) loaded_jsondetails = json.loads(dumped_jsondetails) @@ -97,7 +97,7 @@ def test_ooc_encoder(self): details = { 'number': 1, 'string': 'string', - 'datetime': datetime.datetime.now(UTC) + 'datetime': datetime.datetime.now(get_utc_timezone()) } jsondetails = json.dumps(details, cls=CourseSettingsEncoder) jsondetails = json.loads(jsondetails) @@ -126,7 +126,6 @@ def setUp(self): @override_settings(FEATURES={'DISABLE_MOBILE_COURSE_AVAILABLE': True}) @override_waffle_flag(toggles.LEGACY_STUDIO_ADVANCED_SETTINGS, True) def test_mobile_field_available(self): - """ Test to check `Mobile Course Available` field is not viewable in Studio when DISABLE_MOBILE_COURSE_AVAILABLE is true. @@ -256,12 +255,14 @@ def test_update_and_fetch(self): resp = self.client.get_json(url) self.compare_details_with_encoding(json.loads(resp.content.decode('utf-8')), details.__dict__, "virgin get") - self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 12, 1, 30, tzinfo=UTC)) - self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 1, 13, 30, tzinfo=UTC)) - self.alter_field(url, details, 'end_date', datetime.datetime(2013, 2, 12, 1, 30, tzinfo=UTC)) - self.alter_field(url, details, 'enrollment_start', datetime.datetime(2012, 10, 12, 1, 30, tzinfo=UTC)) + self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 12, 1, 30, tzinfo=get_utc_timezone())) + self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 1, 13, 30, tzinfo=get_utc_timezone())) + self.alter_field(url, details, 'end_date', datetime.datetime(2013, 2, 12, 1, 30, tzinfo=get_utc_timezone())) + self.alter_field(url, details, 'enrollment_start', datetime.datetime( + 2012, 10, 12, 1, 30, tzinfo=get_utc_timezone())) - self.alter_field(url, details, 'enrollment_end', datetime.datetime(2012, 11, 15, 1, 30, tzinfo=UTC)) + self.alter_field(url, details, 'enrollment_end', datetime.datetime( + 2012, 11, 15, 1, 30, tzinfo=get_utc_timezone())) self.alter_field(url, details, 'short_description', "Short Description") self.alter_field(url, details, 'about_sidebar_html', "About Sidebar HTML") self.alter_field(url, details, 'overview', "Overview") @@ -1497,7 +1498,7 @@ def test_validate_update_does_not_allow_proctoring_provider_changes_after_course Only admin users may update the provider if the course has started. """ field_name = "proctoring_provider" - course = CourseFactory.create(start=datetime.datetime.now(UTC) - datetime.timedelta(days=1)) + course = CourseFactory.create(start=datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1)) user = UserFactory.create(is_staff=staff_user) did_validate, errors, test_model = CourseMetadata.validate_and_update_from_json( diff --git a/cms/djangoapps/contentstore/tests/test_courseware_index.py b/cms/djangoapps/contentstore/tests/test_courseware_index.py index 3ab3fa373f81..9964a0258700 100644 --- a/cms/djangoapps/contentstore/tests/test_courseware_index.py +++ b/cms/djangoapps/contentstore/tests/test_courseware_index.py @@ -11,7 +11,7 @@ import pytest from django.conf import settings from lazy.lazy import lazy -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from search.search_engine_base import SearchEngine from cms.djangoapps.contentstore.courseware_index import ( @@ -56,7 +56,7 @@ def create_children(store, parent, category, load_factor): display_name=f"{category} {child_index} {time.clock()}", # lint-amnesty, pylint: disable=no-member modulestore=store, publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) created_count += 1 @@ -72,7 +72,7 @@ def create_large_course(store, load_factor): load_factor ^ 4 - e.g. load_factor of 10 => 10 chapters, 100 sequentials, 1000 verticals, 10000 html blocks """ - course = CourseFactory.create(modulestore=store, start=datetime(2015, 3, 1, tzinfo=UTC)) + course = CourseFactory.create(modulestore=store, start=datetime(2015, 3, 1, tzinfo=get_utc_timezone())) with store.bulk_operations(course.id): child_count = create_children(store, course, COURSE_CHILD_STRUCTURE["course"], load_factor) return course, child_count @@ -148,7 +148,7 @@ def setup_course_base(self, store): """ self.course = CourseFactory.create( modulestore=store, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), display_name="Search Index Test Course" ) @@ -158,7 +158,7 @@ def setup_course_base(self, store): display_name="Week 1", modulestore=store, publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.sequential = BlockFactory.create( parent_location=self.chapter.location, @@ -166,7 +166,7 @@ def setup_course_base(self, store): display_name="Lesson 1", modulestore=store, publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.vertical = BlockFactory.create( parent_location=self.sequential.location, @@ -174,7 +174,7 @@ def setup_course_base(self, store): display_name='Subsection 1', modulestore=store, publish_item=True, - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) # unspecified start - should inherit from container self.html_unit = BlockFactory.create( @@ -193,7 +193,7 @@ def reindex_course(self, store): def index_recent_changes(self, store, since_time): """ index course using recent changes """ - trigger_time = datetime.now(UTC) + trigger_time = datetime.now(get_utc_timezone()) return CoursewareSearchIndexer.index( store, self.course.id, @@ -322,7 +322,7 @@ def _test_time_based_index(self, store): display_name='Section 2', modulestore=store, publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) # add a new vertical @@ -341,7 +341,7 @@ def _test_time_based_index(self, store): modulestore=store, ) - before_time = datetime.now(UTC) + before_time = datetime.now(get_utc_timezone()) self.publish_item(store, vertical2.location) # index based on time, will include an index of the origin sequential # because it is in a common subtree but not of the original vertical @@ -432,7 +432,7 @@ def _test_course_location_null(self, store): display_name=None, modulestore=store, publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) # add a new vertical vertical2 = BlockFactory.create( @@ -505,7 +505,9 @@ def test_delete_course_from_search_index_after_course_deletion(self): self._test_delete_course_from_search_index_after_course_deletion(self.store) def test_empty_course(self): - empty_course = CourseFactory.create(modulestore=self.store, start=datetime(2015, 3, 1, tzinfo=UTC)) + empty_course = CourseFactory.create( + modulestore=self.store, start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()) + ) added_to_index = CoursewareSearchIndexer.do_course_reindex(self.store, empty_course.id) assert added_to_index == 0 @@ -605,28 +607,28 @@ def setUpClass(cls): super().setUpClass() SignalHandler.course_published.disconnect(listen_for_course_publish) SignalHandler.library_updated.disconnect(listen_for_library_update) - cls.course = CourseFactory.create(start=datetime(2015, 3, 1, tzinfo=UTC)) + cls.course = CourseFactory.create(start=datetime(2015, 3, 1, tzinfo=get_utc_timezone())) cls.chapter = BlockFactory.create( parent_location=cls.course.location, category='chapter', display_name="Week 1", publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) cls.sequential = BlockFactory.create( parent_location=cls.chapter.location, category='sequential', display_name="Lesson 1", publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) cls.vertical = BlockFactory.create( parent_location=cls.sequential.location, category='vertical', display_name='Subsection 1', publish_item=True, - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) # unspecified start - should inherit from container cls.html_unit = BlockFactory.create( @@ -881,7 +883,7 @@ def _setup_course_with_content(self): display_name="Week 1", modulestore=self.store, publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.sequential = BlockFactory.create( @@ -890,7 +892,7 @@ def _setup_course_with_content(self): display_name="Lesson 1", modulestore=self.store, publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.sequential2 = BlockFactory.create( @@ -899,7 +901,7 @@ def _setup_course_with_content(self): display_name="Lesson 2", modulestore=self.store, publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.vertical = BlockFactory.create( @@ -908,7 +910,7 @@ def _setup_course_with_content(self): display_name='Subsection 1', modulestore=self.store, publish_item=True, - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) self.vertical2 = BlockFactory.create( @@ -917,7 +919,7 @@ def _setup_course_with_content(self): display_name='Subsection 2', modulestore=self.store, publish_item=True, - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) self.vertical3 = BlockFactory.create( @@ -926,7 +928,7 @@ def _setup_course_with_content(self): display_name='Subsection 3', modulestore=self.store, publish_item=True, - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) # unspecified start - should inherit from container @@ -1091,7 +1093,7 @@ def _html_group_result(self, html_unit, content_groups): 'content_type': 'Text', 'org': self.course.org, 'content_groups': content_groups, - 'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=UTC) + 'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=get_utc_timezone()) } def _html_experiment_group_result(self, html_unit, content_groups): @@ -1111,7 +1113,7 @@ def _html_experiment_group_result(self, html_unit, content_groups): 'content_type': 'Text', 'org': self.course.org, 'content_groups': content_groups, - 'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=UTC) + 'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=get_utc_timezone()) } def _vertical_experiment_group_result(self, vertical, content_groups): @@ -1119,7 +1121,7 @@ def _vertical_experiment_group_result(self, vertical, content_groups): Return object with arguments and content group for split_test vertical. """ return { - 'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=UTC), + 'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=get_utc_timezone()), 'content': {'display_name': vertical.display_name}, 'course': str(self.course.id), 'location': [ @@ -1151,7 +1153,7 @@ def _html_nogroup_result(self, html_unit): 'content_type': 'Text', 'org': self.course.org, 'content_groups': None, - 'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=UTC) + 'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=get_utc_timezone()) } def _get_index_values_from_call_args(self, mock_index): diff --git a/cms/djangoapps/contentstore/tests/test_exams.py b/cms/djangoapps/contentstore/tests/test_exams.py index 798b5e51fd80..d1a323c73e30 100644 --- a/cms/djangoapps/contentstore/tests/test_exams.py +++ b/cms/djangoapps/contentstore/tests/test_exams.py @@ -9,7 +9,7 @@ from django.conf import settings from edx_toggles.toggles.testutils import override_waffle_flag from freezegun import freeze_time -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from cms.djangoapps.contentstore.signals.handlers import listen_for_course_publish from openedx.core.djangoapps.course_apps.toggles import EXAMS_IDA @@ -54,7 +54,7 @@ def setUp(self): display_name='Homework 1', graded=True, is_time_limited=False, - due=datetime.now(utc) + timedelta(minutes=60), + due=datetime.now(get_utc_timezone()) + timedelta(minutes=60), ) def _get_exams_url(self, course_id): @@ -93,7 +93,8 @@ def test_publishing_exam(self, is_proctored_exam, is_practice_exam, When a course is published it will register all exams sections with the exams service """ default_time_limit_minutes = 10 - due_date = datetime.now(utc) + timedelta(minutes=default_time_limit_minutes + 1) + due_date = datetime.now(get_utc_timezone()) + timedelta(minutes=default_time_limit_minutes + 1) + sequence = BlockFactory.create( parent=self.chapter, category='sequential', @@ -244,7 +245,7 @@ def test_subsection_due_date_prioritized(self, is_self_paced, proctoring_provide self.course.proctoring_provider = proctoring_provider self.course = self.update_course(self.course, 1) - sequential_due_date = datetime.now(utc) + timedelta(minutes=60) + sequential_due_date = datetime.now(get_utc_timezone()) + timedelta(minutes=60) sequence = BlockFactory.create( parent=self.chapter, category='sequential', diff --git a/cms/djangoapps/contentstore/tests/test_filters.py b/cms/djangoapps/contentstore/tests/test_filters.py index 4011ae728b34..1f15a4e5e1fb 100644 --- a/cms/djangoapps/contentstore/tests/test_filters.py +++ b/cms/djangoapps/contentstore/tests/test_filters.py @@ -4,7 +4,7 @@ from datetime import datetime from urllib.parse import urljoin -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import override_settings from cms.djangoapps.contentstore import asset_storage_handlers @@ -38,7 +38,7 @@ class LMSPageURLRequestedFiltersTest(ModuleStoreTestCase): def setUp(self): # pylint: disable=arguments-differ super().setUp() - self.upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC) + self.upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=get_utc_timezone()) self.content_type = 'image/jpg' self.course_key = CourseLocator('org', 'class', 'run') self.location = self.course_key.make_asset_key('asset', 'my_file_name.jpg') diff --git a/cms/djangoapps/contentstore/tests/test_proctoring.py b/cms/djangoapps/contentstore/tests/test_proctoring.py index d9b83a811ef0..02dd7aeff09f 100644 --- a/cms/djangoapps/contentstore/tests/test_proctoring.py +++ b/cms/djangoapps/contentstore/tests/test_proctoring.py @@ -9,7 +9,7 @@ import ddt from django.conf import settings from edx_proctoring.api import get_all_exams_for_course, get_review_policy_by_exam_id -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory @@ -97,7 +97,7 @@ def test_onboarding_exam_is_practice_exam(self, is_practice_exam, is_onboarding_ default_time_limit_minutes=default_time_limit_minutes, is_proctored_enabled=is_proctored_exam, is_practice_exam=is_practice_exam, - due=datetime.now(UTC) + timedelta(minutes=default_time_limit_minutes + 1), + due=datetime.now(get_utc_timezone()) + timedelta(minutes=default_time_limit_minutes + 1), exam_review_rules="allow_use_of_paper", hide_after_due=True, is_onboarding_exam=is_onboarding_exam, @@ -132,7 +132,7 @@ def test_publishing_exam(self, is_proctored_exam, default_time_limit_minutes=default_time_limit_minutes, is_proctored_enabled=is_proctored_exam, is_practice_exam=is_practice_exam, - due=datetime.now(UTC) + timedelta(minutes=default_time_limit_minutes + 1), + due=datetime.now(get_utc_timezone()) + timedelta(minutes=default_time_limit_minutes + 1), exam_review_rules="allow_use_of_paper", hide_after_due=hide_after_due, is_onboarding_exam=False, @@ -300,7 +300,7 @@ def test_self_paced_no_due_dates(self): default_time_limit_minutes=60, is_proctored_enabled=False, is_practice_exam=False, - due=datetime.now(UTC) + timedelta(minutes=60), + due=datetime.now(get_utc_timezone()) + timedelta(minutes=60), exam_review_rules="allow_use_of_paper", hide_after_due=True, is_onboarding_exam=False, diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index a46d9831d4e5..6e19a9f8ce36 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -14,7 +14,7 @@ from opaque_keys.edx.locator import CourseLocator, LibraryLocator from openedx_events.tests.utils import OpenEdxEventsTestMixin from path import Path as path -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status from user_tasks.models import UserTaskArtifact, UserTaskStatus @@ -120,8 +120,8 @@ def setUpClass(cls): super().setUpClass() cls.dummy_user = ModuleStoreEnum.UserID.test - cls.past = datetime(1970, 1, 1, tzinfo=UTC) - cls.future = datetime.now(UTC) + timedelta(days=1) + cls.past = datetime(1970, 1, 1, tzinfo=get_utc_timezone()) + cls.future = datetime.now(get_utc_timezone()) + timedelta(days=1) cls.course = CourseFactory.create() def test_private_unreleased_xblock(self): @@ -201,8 +201,8 @@ def setUp(self): self.sequential = self.store.get_item(self.sequential.location) self.vertical = self.store.get_item(self.vertical.location) - self.date_one = datetime(1980, 1, 1, tzinfo=UTC) - self.date_two = datetime(2020, 1, 1, tzinfo=UTC) + self.date_one = datetime(1980, 1, 1, tzinfo=get_utc_timezone()) + self.date_two = datetime(2020, 1, 1, tzinfo=get_utc_timezone()) def _update_release_dates(self, chapter_start, sequential_start, vertical_start): """Sets the release dates of the chapter, sequential, and vertical""" diff --git a/cms/djangoapps/contentstore/tests/test_video_utils.py b/cms/djangoapps/contentstore/tests/test_video_utils.py index e65e4f6638c6..5ea564a07f48 100644 --- a/cms/djangoapps/contentstore/tests/test_video_utils.py +++ b/cms/djangoapps/contentstore/tests/test_video_utils.py @@ -8,7 +8,7 @@ from unittest import mock import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone import requests from django.conf import settings from django.core.files.base import ContentFile @@ -63,7 +63,7 @@ def setUp(self): super().setUp() course_ids = [str(self.course.id)] profiles = ['youtube'] - created = datetime.now(pytz.utc) + created = datetime.now(get_utc_timezone()) previous_uploads = [ { 'edx_video_id': 'test1', diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index 40b0f8ad4cbf..2617895e664d 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -19,7 +19,7 @@ from django.test.utils import override_settings from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from cms.djangoapps.contentstore import toggles from cms.djangoapps.contentstore.tests.test_course_settings import CourseTestCase @@ -219,7 +219,7 @@ def set_blackout_dates(self, blackout_dates): ] def test_blackouts(self): - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(get_utc_timezone()) times1 = [ (now - datetime.timedelta(days=14), now - datetime.timedelta(days=11)), (now + datetime.timedelta(days=24), now + datetime.timedelta(days=30)) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index c4049a818fe1..9c4d0ad0a18f 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -31,7 +31,7 @@ from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED from openedx_events.learning.data import CourseNotificationData from openedx_events.learning.signals import COURSE_NOTIFICATION_REQUESTED -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xblock.fields import Scope from cms.djangoapps.contentstore.toggles import ( @@ -601,7 +601,7 @@ def is_currently_visible_to_students(xblock): # Check start date if 'detached' not in published._class_tags and published.start is not None: # lint-amnesty, pylint: disable=protected-access - return datetime.now(UTC) > published.start + return datetime.now(get_utc_timezone()) > published.start # No start date, so it's always visible return True diff --git a/cms/djangoapps/contentstore/video_storage_handlers.py b/cms/djangoapps/contentstore/video_storage_handlers.py index 87086c9951ac..4b9432123158 100644 --- a/cms/djangoapps/contentstore/video_storage_handlers.py +++ b/cms/djangoapps/contentstore/video_storage_handlers.py @@ -45,7 +45,7 @@ from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from path import Path as path -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status as rest_status from rest_framework.response import Response from tempfile import NamedTemporaryFile, mkdtemp @@ -597,7 +597,7 @@ def convert_video_status(video, is_video_encodes_ready=False): * `YouTube Duplicate` if status is `invalid_token` * user-friendly video status """ - now = datetime.now(video.get('created', datetime.now().replace(tzinfo=UTC)).tzinfo) + now = datetime.now(video.get('created', datetime.now().replace(tzinfo=get_utc_timezone())).tzinfo) if video['status'] == 'upload' and (now - video['created']) > timedelta(hours=MAX_UPLOAD_HOURS): new_status = 'upload_failed' diff --git a/cms/djangoapps/contentstore/views/tests/test_assets.py b/cms/djangoapps/contentstore/views/tests/test_assets.py index 2b13338c0dc7..339007767166 100644 --- a/cms/djangoapps/contentstore/views/tests/test_assets.py +++ b/cms/djangoapps/contentstore/views/tests/test_assets.py @@ -16,7 +16,7 @@ from opaque_keys.edx.keys import AssetKey from opaque_keys.edx.locator import CourseLocator from PIL import Image -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from cms.djangoapps.contentstore import toggles from cms.djangoapps.contentstore.tests.utils import CourseTestCase @@ -230,7 +230,7 @@ def test_mocked_filtered_response(self, mock_get_all_content_for_course): """ asset_key = self.course.id.make_asset_key( AssetMetadata.GENERAL_ASSET_TYPE, 'test.jpg') - upload_date = datetime(2015, 1, 12, 10, 30, tzinfo=UTC) + upload_date = datetime(2015, 1, 12, 10, 30, tzinfo=get_utc_timezone()) thumbnail_location = [ 'c4x', 'edX', 'toy', 'thumbnail', 'test_thumb.jpg', None] @@ -429,7 +429,7 @@ class AssetToJsonTestCase(AssetsTestCase): """ @override_settings(LMS_ROOT_URL="https://lms_root_url") def test_basic(self): - upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC) + upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=get_utc_timezone()) content_type = 'image/jpg' course_key = CourseLocator('org', 'class', 'run') location = course_key.make_asset_key('asset', 'my_file_name.jpg') @@ -475,7 +475,7 @@ def post_asset_update(lock, course): """ Helper method for posting asset update. """ content_type = 'application/txt' course_key = CourseLocator('org', 'class', 'run') - upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC) + upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=get_utc_timezone()) asset_location = course.id.make_asset_key('asset', 'sample_static.html') url = reverse_course_url( 'assets_handler', course.id, kwargs={'asset_key_string': str(asset_location)} diff --git a/cms/djangoapps/contentstore/views/tests/test_block.py b/cms/djangoapps/contentstore/views/tests/test_block.py index 5447e6e59678..68bc69f0bc21 100644 --- a/cms/djangoapps/contentstore/views/tests/test_block.py +++ b/cms/djangoapps/contentstore/views/tests/test_block.py @@ -23,7 +23,7 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator from pyquery import PyQuery -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from bs4 import BeautifulSoup from web_fragments.fragment import Fragment from webob import Response @@ -635,11 +635,11 @@ def test_create_block_negative(self): self.assertEqual(resp.status_code, 200) def test_create_with_future_date(self): - self.assertEqual(self.course.start, datetime(2030, 1, 1, tzinfo=UTC)) + self.assertEqual(self.course.start, datetime(2030, 1, 1, tzinfo=get_utc_timezone())) resp = self.create_xblock(category="chapter") usage_key = self.response_usage_key(resp) obj = self.get_item_from_modulestore(usage_key) - self.assertEqual(obj.start, datetime(2030, 1, 1, tzinfo=UTC)) + self.assertEqual(obj.start, datetime(2030, 1, 1, tzinfo=get_utc_timezone())) def test_static_tabs_initialization(self): """ @@ -1984,13 +1984,13 @@ def test_date_fields(self): self.seq_update_url, data={"metadata": {"due": "2010-11-22T04:00Z"}} ) sequential = self.get_item_from_modulestore(self.seq_usage_key) - self.assertEqual(sequential.due, datetime(2010, 11, 22, 4, 0, tzinfo=UTC)) + self.assertEqual(sequential.due, datetime(2010, 11, 22, 4, 0, tzinfo=get_utc_timezone())) self.client.ajax_post( self.seq_update_url, data={"metadata": {"start": "2010-09-12T14:00Z"}} ) sequential = self.get_item_from_modulestore(self.seq_usage_key) - self.assertEqual(sequential.due, datetime(2010, 11, 22, 4, 0, tzinfo=UTC)) - self.assertEqual(sequential.start, datetime(2010, 9, 12, 14, 0, tzinfo=UTC)) + self.assertEqual(sequential.due, datetime(2010, 11, 22, 4, 0, tzinfo=get_utc_timezone())) + self.assertEqual(sequential.start, datetime(2010, 9, 12, 14, 0, tzinfo=get_utc_timezone())) @ddt.data( "1000-01-01T00:00Z", @@ -2305,7 +2305,7 @@ def _make_draft_content_different_from_published(self): self.problem_update_url, data={"metadata": {"due": "2077-10-10T04:00Z"}} ) updated_draft = self.get_item_from_modulestore(self.problem_usage_key) - self.assertEqual(updated_draft.due, datetime(2077, 10, 10, 4, 0, tzinfo=UTC)) + self.assertEqual(updated_draft.due, datetime(2077, 10, 10, 4, 0, tzinfo=get_utc_timezone())) self.assertIsNone(published.due) # Fetch the published version again to make sure the due date is still unset. published = modulestore().get_item( @@ -2320,7 +2320,7 @@ def test_make_public_with_update(self): data={"metadata": {"due": "2077-10-10T04:00Z"}, "publish": "make_public"}, ) published = self.get_item_from_modulestore(self.problem_usage_key) - self.assertEqual(published.due, datetime(2077, 10, 10, 4, 0, tzinfo=UTC)) + self.assertEqual(published.due, datetime(2077, 10, 10, 4, 0, tzinfo=get_utc_timezone())) def test_published_and_draft_contents_with_update(self): """Create a draft and publish it then modify the draft and check that published content is not modified""" @@ -3439,7 +3439,7 @@ def test_validate_start_date(self): parent_location=course.location, category='chapter', display_name='Week 1' ) - chapter.start = datetime(year=1899, month=1, day=1, tzinfo=UTC) + chapter.start = datetime(year=1899, month=1, day=1, tzinfo=get_utc_timezone()) xblock_info = create_xblock_info( chapter, @@ -4119,7 +4119,7 @@ def test_published_unit(self): sequential = self._create_child(chapter, "sequential", "Test Sequential") self._create_child(sequential, "vertical", "Published Unit", publish_item=True) self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) - self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1)) + self._set_release_date(chapter.location, datetime.now(get_utc_timezone()) + timedelta(days=1)) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.ready) self._verify_visibility_state( @@ -4140,7 +4140,7 @@ def test_released_unit(self): sequential = self._create_child(chapter, "sequential", "Test Sequential") self._create_child(sequential, "vertical", "Published Unit", publish_item=True) self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) - self._set_release_date(chapter.location, datetime.now(UTC) - timedelta(days=1)) + self._set_release_date(chapter.location, datetime.now(get_utc_timezone()) - timedelta(days=1)) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.live) self._verify_visibility_state( @@ -4184,11 +4184,11 @@ def test_partially_released_section(self): released_sequential = self._create_child(chapter, 'sequential', "Released Sequential") self._create_child(released_sequential, 'vertical', "Released Unit", publish_item=True) self._create_child(released_sequential, 'vertical', "Staff Only Unit 1", staff_only=True) - self._set_release_date(chapter.location, datetime.now(UTC) - timedelta(days=1)) + self._set_release_date(chapter.location, datetime.now(get_utc_timezone()) - timedelta(days=1)) published_sequential = self._create_child(chapter, 'sequential', "Published Sequential") self._create_child(published_sequential, 'vertical', "Published Unit", publish_item=True) self._create_child(published_sequential, 'vertical', "Staff Only Unit 2", staff_only=True) - self._set_release_date(published_sequential.location, datetime.now(UTC) + timedelta(days=1)) + self._set_release_date(published_sequential.location, datetime.now(get_utc_timezone()) + timedelta(days=1)) xblock_info = self._get_xblock_info(chapter.location) # Verify the state of the released sequential @@ -4349,7 +4349,7 @@ def test_unscheduled_section_with_live_subsection(self): self._create_child(sequential, "vertical", "Published Unit", publish_item=True) self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) self._set_release_date( - sequential.location, datetime.now(UTC) - timedelta(days=1) + sequential.location, datetime.now(get_utc_timezone()) - timedelta(days=1) ) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.needs_attention) @@ -4368,9 +4368,9 @@ def test_unreleased_section_with_live_subsection(self): sequential = self._create_child(chapter, "sequential", "Test Sequential") self._create_child(sequential, "vertical", "Published Unit", publish_item=True) self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) - self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1)) + self._set_release_date(chapter.location, datetime.now(get_utc_timezone()) + timedelta(days=1)) self._set_release_date( - sequential.location, datetime.now(UTC) - timedelta(days=1) + sequential.location, datetime.now(get_utc_timezone()) - timedelta(days=1) ) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.needs_attention) @@ -4428,7 +4428,7 @@ def test_self_paced_item_visibility_state(self): # Create course, chapter and setup future release date to make chapter in scheduled state course = CourseFactory.create() chapter = self._create_child(course, "chapter", "Test Chapter") - self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1)) + self._set_release_date(chapter.location, datetime.now(get_utc_timezone()) + timedelta(days=1)) # Check that chapter has scheduled state xblock_info = self._get_xblock_info(chapter.location) @@ -4471,7 +4471,7 @@ def create_source_block(self, course): parent=course, category="course_info", display_name="Source Block", - metadata={"due": datetime(2010, 11, 22, 4, 0, tzinfo=UTC)}, + metadata={"due": datetime(2010, 11, 22, 4, 0, tzinfo=get_utc_timezone())}, ) def_id = self.runtime.id_generator.create_definition("html") @@ -4491,7 +4491,7 @@ def create_source_block(self, course): # quick sanity checks source_block = self.store.get_item(source_block.location) - self.assertEqual(source_block.due, datetime(2010, 11, 22, 4, 0, tzinfo=UTC)) + self.assertEqual(source_block.due, datetime(2010, 11, 22, 4, 0, tzinfo=get_utc_timezone())) self.assertEqual(source_block.display_name, "Source Block") self.assertEqual( source_block.runtime.get_asides(source_block)[0].field11, "html_new_value1" @@ -4550,7 +4550,7 @@ def test_update_clobbers(self): parent=course, category="course_info", display_name="Destination Chapter", - metadata={"due": datetime(2025, 10, 21, 6, 5, tzinfo=UTC)}, + metadata={"due": datetime(2025, 10, 21, 6, 5, tzinfo=get_utc_timezone())}, ) def_id = self.runtime.id_generator.create_definition("html") diff --git a/cms/djangoapps/contentstore/views/tests/test_container_page.py b/cms/djangoapps/contentstore/views/tests/test_container_page.py index e6b58257b69d..ffa911401a55 100644 --- a/cms/djangoapps/contentstore/views/tests/test_container_page.py +++ b/cms/djangoapps/contentstore/views/tests/test_container_page.py @@ -11,8 +11,8 @@ from django.test.client import RequestFactory from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag -from pytz import UTC from urllib.parse import quote +from openedx.core.lib.time_zone_utils import get_utc_timezone import cms.djangoapps.contentstore.views.component as views from cms.djangoapps.contentstore import toggles @@ -41,8 +41,8 @@ def setUp(self): self.video = self._create_block(self.child_vertical, "video", "My Video") self.store = modulestore() - past = datetime.datetime(1970, 1, 1, tzinfo=UTC) - future = datetime.datetime.now(UTC) + datetime.timedelta(days=1) + past = datetime.datetime(1970, 1, 1, tzinfo=get_utc_timezone()) + future = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) self.released_private_vertical = self._create_block( parent=self.sequential, category='vertical', display_name='Released Private Unit', start=past) diff --git a/cms/djangoapps/contentstore/views/tests/test_course_index.py b/cms/djangoapps/contentstore/views/tests/test_course_index.py index a22ce637fedd..de25ba825814 100644 --- a/cms/djangoapps/contentstore/views/tests/test_course_index.py +++ b/cms/djangoapps/contentstore/views/tests/test_course_index.py @@ -9,7 +9,7 @@ import ddt import lxml -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.core.exceptions import PermissionDenied from django.test.utils import override_settings @@ -348,7 +348,7 @@ class TestCourseIndexArchived(CourseTestCase): MODULESTORE = TEST_DATA_SPLIT_MODULESTORE - NOW = datetime.datetime.now(pytz.utc) + NOW = datetime.datetime.now(get_utc_timezone()) DAY = datetime.timedelta(days=1) YESTERDAY = NOW - DAY TOMORROW = NOW + DAY @@ -741,7 +741,7 @@ def setUp(self): super().setUp() - self.course.start = datetime.datetime(2014, 1, 1, tzinfo=pytz.utc) + self.course.start = datetime.datetime(2014, 1, 1, tzinfo=get_utc_timezone()) modulestore().update_item(self.course, self.user.id) self.chapter = BlockFactory.create( diff --git a/cms/djangoapps/contentstore/views/tests/test_videos.py b/cms/djangoapps/contentstore/views/tests/test_videos.py index 2d17229f59c1..3811ffa4ffef 100644 --- a/cms/djangoapps/contentstore/views/tests/test_videos.py +++ b/cms/djangoapps/contentstore/views/tests/test_videos.py @@ -14,7 +14,7 @@ import dateutil.parser from common.djangoapps.student.tests.factories import UserFactory import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import TestCase from django.conf import settings from django.test.utils import override_settings @@ -86,7 +86,7 @@ def setUp(self): # course ids for videos course_ids = [str(self.course.id), str(self.course2.id)] - created = datetime.now(pytz.utc) + created = datetime.now(get_utc_timezone()) self.profiles = ["profile1", "profile2"] self.previous_uploads = [ @@ -742,12 +742,12 @@ def test_convert_video_status(self): video = self.previous_uploads[0] # video status should be failed if it's in upload state for more than 24 hours - video['created'] = datetime(2016, 1, 1, 10, 10, 10, 0, pytz.UTC) + video['created'] = datetime(2016, 1, 1, 10, 10, 10, 0, get_utc_timezone()) status = convert_video_status(video) self.assertEqual(status, StatusDisplayStrings.get('upload_failed')) # `invalid_token` should be converted to `youtube_duplicate` - video['created'] = datetime.now(pytz.UTC) + video['created'] = datetime.now(get_utc_timezone()) video['status'] = 'invalid_token' status = convert_video_status(video) self.assertEqual(status, StatusDisplayStrings.get('youtube_duplicate')) diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py index a7ae0e518e84..a8c1cec50924 100644 --- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py +++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py @@ -30,7 +30,7 @@ from edx_proctoring.exceptions import ProctoredExamNotFoundException from help_tokens.core import HelpUrlExpert from opaque_keys.edx.locator import LibraryUsageLocator, LibraryUsageLocatorV2 -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xblock.core import XBlock from xblock.fields import Scope @@ -1181,7 +1181,7 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements "studio_url": xblock_studio_url(xblock, parent_xblock), "lms_url": xblock_lms_url(xblock), "embed_lms_url": xblock_embed_lms_url(xblock), - "released_to_students": datetime.now(UTC) > xblock.start, + "released_to_students": datetime.now(get_utc_timezone()) > xblock.start, "release_date": release_date, "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields[ @@ -1522,7 +1522,7 @@ def _compute_visibility_state( return VisibilityState.needs_attention is_unscheduled = xblock.start == DEFAULT_START_DATE - is_live = is_course_self_paced or datetime.now(UTC) > xblock.start + is_live = is_course_self_paced or datetime.now(get_utc_timezone()) > xblock.start if child_info and child_info.get("children", []): # pylint: disable=too-many-nested-blocks all_staff_only = True all_unscheduled = True diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 1cd87ed5a0a1..0cc6e94266b3 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -6,7 +6,7 @@ import logging from datetime import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ @@ -470,7 +470,7 @@ def validate_proctoring_settings(cls, block, settings_dict, user): cls._has_requested_proctoring_provider_changed( block.proctoring_provider, proctoring_provider_model.get('value') ) and - datetime.now(pytz.UTC) > block.start + datetime.now(get_utc_timezone()) > block.start ): message = ( 'The proctoring provider cannot be modified after a course has started.' diff --git a/cms/lib/xblock/tagging/test.py b/cms/lib/xblock/tagging/test.py index 173523452d54..811b8db33970 100644 --- a/cms/lib/xblock/tagging/test.py +++ b/cms/lib/xblock/tagging/test.py @@ -11,7 +11,7 @@ from django.test.client import RequestFactory from lxml import etree from opaque_keys.edx.asides import AsideUsageKeyV1, AsideUsageKeyV2 -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xblock.fields import ScopeIds from xblock.runtime import DictKeyValueStore, KvsFieldData from xblock.test.tools import TestRuntime @@ -56,21 +56,21 @@ def setUp(self): category='chapter', display_name="Week 1", publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.sequential = BlockFactory.create( parent_location=self.chapter.location, category='sequential', display_name="Lesson 1", publish_item=True, - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.vertical = BlockFactory.create( parent_location=self.sequential.location, category='vertical', display_name='Subsection 1', publish_item=True, - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) self.problem = BlockFactory.create( category="problem", diff --git a/cms/lib/xblock/test/test_upstream_sync.py b/cms/lib/xblock/test/test_upstream_sync.py index 8902d67c8cb1..3443a9331bde 100644 --- a/cms/lib/xblock/test/test_upstream_sync.py +++ b/cms/lib/xblock/test/test_upstream_sync.py @@ -2,11 +2,11 @@ Test CMS's upstream->downstream syncing system """ import datetime +from openedx.core.lib.time_zone_utils import get_utc_timezone import ddt from organizations.api import ensure_organization from organizations.models import Organization -from pytz import utc from cms.lib.xblock.upstream_sync import ( BadDownstream, @@ -291,7 +291,7 @@ def test_sync_updates_to_downstream_only_fields(self): # Modifing downstream-only fields are "safe" customizations downstream.display_name = "Downstream Title Override" downstream.attempts_before_showanswer_button = 2 - downstream.due = datetime.datetime(2025, 2, 2, tzinfo=utc) + downstream.due = datetime.datetime(2025, 2, 2, tzinfo=get_utc_timezone()) downstream.force_save_button = True downstream.graceperiod = '2d' downstream.grading_method = 'last_score' @@ -319,7 +319,7 @@ def test_sync_updates_to_downstream_only_fields(self): # but "safe" customizations survive assert downstream.display_name == "Downstream Title Override" assert downstream.attempts_before_showanswer_button == 2 - assert downstream.due == datetime.datetime(2025, 2, 2, tzinfo=utc) + assert downstream.due == datetime.datetime(2025, 2, 2, tzinfo=get_utc_timezone()) assert downstream.force_save_button assert downstream.graceperiod == '2d' assert downstream.grading_method == 'last_score' diff --git a/common/djangoapps/course_modes/admin.py b/common/djangoapps/course_modes/admin.py index c874a7892581..812f6791db1a 100644 --- a/common/djangoapps/course_modes/admin.py +++ b/common/djangoapps/course_modes/admin.py @@ -7,7 +7,8 @@ from django.http.request import QueryDict from django.utils.translation import gettext_lazy as _ from opaque_keys.edx.keys import CourseKey -from pytz import UTC, timezone +from openedx.core.lib.time_zone_utils import get_utc_timezone +from zoneinfo import ZoneInfo from common.djangoapps.course_modes.models import CourseMode, CourseModeExpirationConfig # Technically, we shouldn't be doing this, since verify_student is defined @@ -73,7 +74,7 @@ def __init__(self, *args, **kwargs): # However, the args copy above before the super() call handles this case. pass - default_tz = timezone(settings.TIME_ZONE) + default_tz = ZoneInfo(settings.TIME_ZONE) if self.instance._expiration_datetime: # django admin is using default timezone. To avoid time conversion from db to form @@ -81,7 +82,7 @@ def __init__(self, *args, **kwargs): _expiration_datetime = self.instance._expiration_datetime.replace( tzinfo=None ) - self.initial["_expiration_datetime"] = default_tz.localize(_expiration_datetime) + self.initial["_expiration_datetime"] = _expiration_datetime.replace(tzinfo=default_tz) # Load the verification deadline # Since this is stored on a model in verify student, we need to load it from there. # We need to munge the timezone a bit to get Django admin to display it without converting @@ -90,7 +91,7 @@ def __init__(self, *args, **kwargs): if self.instance.course_id and self.instance.mode_slug in CourseMode.VERIFIED_MODES: deadline = verification_models.VerificationDeadline.deadline_for_course(self.instance.course_id) self.initial["verification_deadline"] = ( - default_tz.localize(deadline.replace(tzinfo=None)) + deadline.replace(tzinfo=None).replace(tzinfo=default_tz) if deadline is not None else None ) @@ -107,14 +108,14 @@ def clean__expiration_datetime(self): # django admin saving the date with default timezone to avoid time conversion from form to db # changes its tzinfo to UTC if self.cleaned_data.get("_expiration_datetime"): - return self.cleaned_data.get("_expiration_datetime").replace(tzinfo=UTC) + return self.cleaned_data.get("_expiration_datetime").replace(tzinfo=get_utc_timezone()) def clean_verification_deadline(self): """ Ensure that the verification deadline we save uses the UTC timezone. """ if self.cleaned_data.get("verification_deadline"): - return self.cleaned_data.get("verification_deadline").replace(tzinfo=UTC) + return self.cleaned_data.get("verification_deadline").replace(tzinfo=get_utc_timezone()) def clean(self): """ diff --git a/common/djangoapps/course_modes/tests/test_admin.py b/common/djangoapps/course_modes/tests/test_admin.py index 25b6558d24ab..fc003f7ae06c 100644 --- a/common/djangoapps/course_modes/tests/test_admin.py +++ b/common/djangoapps/course_modes/tests/test_admin.py @@ -3,11 +3,12 @@ """ from datetime import datetime, timedelta +from zoneinfo import ZoneInfo import ddt from django.conf import settings from django.urls import reverse -from pytz import UTC, timezone +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.admin import CourseModeForm from common.djangoapps.course_modes.models import CourseMode @@ -41,7 +42,7 @@ def test_expiration_timezone(self): user = UserFactory.create(is_staff=True, is_superuser=True) user.save() course = CourseFactory.create() - expiration = datetime(2015, 10, 20, 1, 10, 23, tzinfo=timezone(settings.TIME_ZONE)) + expiration = datetime(2015, 10, 20, 1, 10, 23, tzinfo=ZoneInfo(settings.TIME_ZONE)) CourseOverview.load_from_module_store(course.id) data = { @@ -84,7 +85,7 @@ class AdminCourseModeFormTest(ModuleStoreTestCase): Test the course modes Django admin form validation and saving. """ - UPGRADE_DEADLINE = datetime.now(UTC) + UPGRADE_DEADLINE = datetime.now(get_utc_timezone()) VERIFICATION_DEADLINE = UPGRADE_DEADLINE + timedelta(days=5) def setUp(self): diff --git a/common/djangoapps/course_modes/tests/test_signals.py b/common/djangoapps/course_modes/tests/test_signals.py index 029d7c1e4b28..d804da19d458 100644 --- a/common/djangoapps/course_modes/tests/test_signals.py +++ b/common/djangoapps/course_modes/tests/test_signals.py @@ -8,7 +8,7 @@ import ddt from django.conf import settings -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.signals import _listen_for_course_publish @@ -26,7 +26,7 @@ class CourseModeSignalTest(ModuleStoreTestCase): def setUp(self): super().setUp() - self.end = datetime.now(tz=UTC).replace(microsecond=0) + timedelta(days=7) + self.end = datetime.now(tz=get_utc_timezone()).replace(microsecond=0) + timedelta(days=7) self.course = CourseFactory.create(end=self.end) CourseMode.objects.all().delete() diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index b4a777d2d677..642650ac1cda 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -10,7 +10,7 @@ import ddt import freezegun import httpretty -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.test import override_settings from django.urls import reverse @@ -50,7 +50,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest @patch.dict(settings.FEATURES, {'MODE_CREATION_FOR_TESTING': True}) def setUp(self): super().setUp() - now = datetime.now(pytz.utc) + now = datetime.now(get_utc_timezone()) day = timedelta(days=1) tomorrow = now + day yesterday = now - day diff --git a/common/djangoapps/edxmako/template.py b/common/djangoapps/edxmako/template.py index 855a2714b865..3fddeb4e27a8 100644 --- a/common/djangoapps/edxmako/template.py +++ b/common/djangoapps/edxmako/template.py @@ -17,6 +17,7 @@ from django.template import Context, engines, Origin from edx_django_utils.cache import RequestCache from mako.template import Template as MakoTemplate +from zoneinfo import ZoneInfo from . import Engines, LOOKUP from .request_context import get_template_request_context @@ -113,6 +114,7 @@ def _add_core_context(context_dictionary): context_dictionary['marketing_link'] = marketing_link context_dictionary['is_any_marketing_link_set'] = is_any_marketing_link_set context_dictionary['is_marketing_link_set'] = is_marketing_link_set + context_dictionary['ZoneInfo'] = ZoneInfo @staticmethod def _evaluate_lazy_csrf_tokens(context_dictionary): diff --git a/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py index 86d4ae6a87e1..d67ece981f49 100644 --- a/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py +++ b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py @@ -11,7 +11,7 @@ from django.urls import reverse from django.utils.timezone import now from opaque_keys.edx.locator import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory @@ -1023,7 +1023,7 @@ def test_already_enrolled_course_ended(self, mock_get_course_runs): mock_get_course_runs.return_value = self.return_values # Setup enrollment period to be in the past - utc_now = datetime.now(UTC) + utc_now = datetime.now(get_utc_timezone()) self.course.start = utc_now - timedelta(days=15) self.course.end = utc_now - timedelta(days=1) self.course = self.update_course(self.course, self.user.id) diff --git a/common/djangoapps/entitlements/tests/test_tasks.py b/common/djangoapps/entitlements/tests/test_tasks.py index fe450551513c..863ed2475e93 100644 --- a/common/djangoapps/entitlements/tests/test_tasks.py +++ b/common/djangoapps/entitlements/tests/test_tasks.py @@ -7,7 +7,7 @@ from unittest import mock import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import TestCase from common.djangoapps.entitlements import tasks @@ -19,7 +19,7 @@ def make_entitlement(expired=False): # lint-amnesty, pylint: disable=missing-function-docstring age = CourseEntitlementPolicy.DEFAULT_EXPIRATION_PERIOD_DAYS - past_datetime = datetime.now(tz=pytz.UTC) - timedelta(days=age) + past_datetime = datetime.now(tz=get_utc_timezone()) - timedelta(days=age) expired_at = past_datetime if expired else None entitlement = CourseEntitlementFactory.create(created=past_datetime, expired_at=expired_at) return entitlement diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py index b5d46949b506..49c7268f068b 100644 --- a/common/djangoapps/student/helpers.py +++ b/common/djangoapps/student/helpers.py @@ -9,6 +9,7 @@ import urllib.parse from collections import OrderedDict from datetime import datetime +from zoneinfo import ZoneInfo from completion.exceptions import UnavailableCompletionData from completion.utilities import get_key_to_last_completed_block @@ -20,7 +21,7 @@ from django.db import IntegrityError, ProgrammingError, transaction from django.urls import NoReverseMatch, reverse from django.utils.translation import gettext as _ -from pytz import UTC, timezone +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps import third_party_auth from common.djangoapps.course_modes.models import CourseMode @@ -191,7 +192,7 @@ def check_verify_status_by_course(user, course_enrollments): relevant_verification.status not in ["created", "ready"] ) if status is None and not submitted: - if deadline is None or deadline > datetime.now(UTC): + if deadline is None or deadline > datetime.now(get_utc_timezone()): if IDVerificationService.user_is_verified(user) and verification_expiring_soon: # The user has an active verification, but the verification # is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks). @@ -221,7 +222,7 @@ def check_verify_status_by_course(user, course_enrollments): if status is not None: days_until_deadline = None - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) if deadline is not None and deadline > now: days_until_deadline = (deadline - now).days @@ -866,7 +867,7 @@ def get_course_dates_for_email(user, course_id, request): would be after today. """ user_timezone_locale = user_timezone_locale_prefs(request) - user_timezone = timezone(user_timezone_locale['user_timezone'] or str(UTC)) + user_timezone = ZoneInfo(user_timezone_locale['user_timezone'] or str(get_utc_timezone())) course = get_course_with_access(user, 'load', course_id) date_blocks = get_course_date_blocks(course, user, request, include_access=True, include_past_dates=True) @@ -886,7 +887,7 @@ def get_course_dates_for_email(user, course_id, request): course_date_list = [{**course_date, }, {**course_date, 'date': today}, {**course_date}] for block in blocks: block_date = datetime.strptime(block.get('date')[:19], '%Y-%m-%dT%H:%M:%S') - block_date = block_date.replace(tzinfo=UTC) + block_date = block_date.replace(tzinfo=get_utc_timezone()) block_date = block_date.astimezone(user_timezone) if block_date < today: diff --git a/common/djangoapps/student/management/commands/assigngroups.py b/common/djangoapps/student/management/commands/assigngroups.py index acde7994a33e..974135256cad 100644 --- a/common/djangoapps/student/management/commands/assigngroups.py +++ b/common/djangoapps/student/management/commands/assigngroups.py @@ -8,7 +8,7 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.management.base import BaseCommand -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.models import UserTestGroup @@ -75,7 +75,7 @@ def handle(self, *args, **options): utg = UserTestGroup() utg.name = group utg.description = json.dumps({"description": options['description']}, # lint-amnesty, pylint: disable=too-many-function-args - {"time": datetime.datetime.now(UTC).isoformat()}) + {"time": datetime.datetime.now(get_utc_timezone()).isoformat()}) group_objects[group] = utg group_objects[group].save() diff --git a/common/djangoapps/student/models/course_enrollment.py b/common/djangoapps/student/models/course_enrollment.py index 7350d5b40701..f1c79783ec9f 100644 --- a/common/djangoapps/student/models/course_enrollment.py +++ b/common/djangoapps/student/models/course_enrollment.py @@ -5,6 +5,7 @@ from collections import defaultdict, namedtuple # lint-amnesty, pylint: disable=wrong-import-order from datetime import date, datetime, timedelta # lint-amnesty, pylint: disable=wrong-import-order from urllib.parse import urljoin +from zoneinfo import ZoneInfo from config_models.models import ConfigurationModel from django.conf import settings @@ -29,7 +30,7 @@ COURSE_UNENROLLMENT_COMPLETED, ) from openedx_filters.learning.filters import CourseEnrollmentStarted, CourseUnenrollmentStarted -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from requests.exceptions import HTTPError, RequestException from simple_history.models import HistoricalRecords @@ -152,7 +153,7 @@ def with_certificates(self, username): """ return self.filter(course_id__in=self.get_user_course_ids_with_certificates(username)) - def in_progress(self, username, time_zone=UTC): + def in_progress(self, username, time_zone=get_utc_timezone()): """ Returns a queryset of CourseEnrollment objects for courses that are currently in progress. """ @@ -170,7 +171,7 @@ def completed(self, username): """ return self.active().with_certificates(username) - def expired(self, username, time_zone=UTC): + def expired(self, username, time_zone=get_utc_timezone()): """ Returns a queryset of CourseEnrollment objects for courses that have expired. """ @@ -1092,7 +1093,7 @@ def refundable(self): if refund_cutoff_date is None: log.info("Refund cutoff date is null") return False - if datetime.now(UTC) > refund_cutoff_date: + if datetime.now(get_utc_timezone()) > refund_cutoff_date: log.info(f"Refund cutoff date: {refund_cutoff_date} has passed") return False @@ -1138,7 +1139,8 @@ def refund_cutoff_date(self): self.course_overview.start.replace(tzinfo=None) ) - return refund_window_start_date.replace(tzinfo=UTC) + EnrollmentRefundConfiguration.current().refund_window + return refund_window_start_date.replace(tzinfo=ZoneInfo( + "UTC")) + EnrollmentRefundConfiguration.current().refund_window def is_order_voucher_refundable(self): """ Checks if the coupon batch expiration date has passed to determine whether order voucher is refundable. """ @@ -1147,8 +1149,9 @@ def is_order_voucher_refundable(self): if not vouchers: return False voucher_end_datetime_str = vouchers[0]['end_datetime'] - voucher_expiration_date = datetime.strptime(voucher_end_datetime_str, ECOMMERCE_DATE_FORMAT).replace(tzinfo=UTC) - return datetime.now(UTC) < voucher_expiration_date + voucher_expiration_date = datetime.strptime( + voucher_end_datetime_str, ECOMMERCE_DATE_FORMAT).replace(tzinfo=get_utc_timezone()) + return datetime.now(get_utc_timezone()) < voucher_expiration_date def get_order_attribute_from_ecommerce(self, attribute_name): """ @@ -1270,7 +1273,7 @@ def upgrade_deadline(self): if self.dynamic_upgrade_deadline is not None: # When course modes expire they aren't found any more and None would be returned. # Replicate that behavior here by returning None if the personalized deadline is in the past. - if self.dynamic_upgrade_deadline <= datetime.now(UTC): + if self.dynamic_upgrade_deadline <= datetime.now(get_utc_timezone()): log.debug('Schedules: Returning None since dynamic upgrade deadline has already passed.') return None diff --git a/common/djangoapps/student/models/user.py b/common/djangoapps/student/models/user.py index 94cb99d0ce3f..c79ceb20f2a0 100644 --- a/common/djangoapps/student/models/user.py +++ b/common/djangoapps/student/models/user.py @@ -18,6 +18,7 @@ from datetime import datetime, timedelta from functools import total_ordering from importlib import import_module +from zoneinfo import ZoneInfo from urllib.parse import urlencode import crum @@ -43,7 +44,7 @@ from eventtracking import tracker from model_utils.models import TimeStampedModel from opaque_keys.edx.django.models import CourseKeyField, LearningContextKeyField -from pytz import UTC, timezone +from openedx.core.lib.time_zone_utils import get_utc_timezone from user_util import user_util import openedx.core.djangoapps.django_comment_common.comment_client as cc @@ -456,7 +457,7 @@ class Meta: location = models.CharField(blank=True, max_length=255, db_index=True) # Optional demographic data we started capturing from Fall 2012 - this_year = datetime.now(UTC).year + this_year = datetime.now(get_utc_timezone()).year VALID_YEARS = list(range(this_year, this_year - 120, -1)) year_of_birth = models.IntegerField(blank=True, null=True, db_index=True) GENDER_CHOICES = ( @@ -572,7 +573,7 @@ def has_profile_image(self): def age(self): """ Convenience method that returns the age given a year_of_birth. """ year_of_birth = self.year_of_birth - year = datetime.now(UTC).year + year = datetime.now(get_utc_timezone()).year if year_of_birth is not None: return self._calculate_age(year, year_of_birth) @@ -798,7 +799,7 @@ def user_post_save_callback(sender, **kwargs): 'username': user.username, 'name': profile.name, 'age': profile.age or -1, - 'yearOfBirth': profile.year_of_birth or datetime.now(UTC).year, + 'yearOfBirth': profile.year_of_birth or datetime.now(get_utc_timezone()).year, 'education': profile.level_of_education_display, 'address': profile.mailing_address, 'gender': profile.gender_display, @@ -982,7 +983,7 @@ def is_user_locked_out(cls, user): if not record.lockout_until: return False - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) until = record.lockout_until is_locked_out = until and now < until @@ -1003,7 +1004,7 @@ def increment_lockout_counter(cls, user): if record.failure_count >= max_failures_allowed: # yes, then store when this account is locked out until lockout_period_secs = settings.MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS - record.lockout_until = datetime.now(UTC) + timedelta(seconds=lockout_period_secs) + record.lockout_until = datetime.now(get_utc_timezone()) + timedelta(seconds=lockout_period_secs) record.save() @@ -1738,8 +1739,8 @@ def _get_now(cls, browser_timezone): # importing here to avoid a circular import from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs user_timezone_locale = user_timezone_locale_prefs(crum.get_current_request()) - user_timezone = timezone(user_timezone_locale['user_timezone'] or browser_timezone or str(UTC)) - return user_timezone.localize(datetime.now()) + user_timezone = ZoneInfo(user_timezone_locale['user_timezone'] or browser_timezone or "UTC") + return datetime.now(user_timezone) def _calculate_streak_updates(self, today): """ Calculate the updates that should be applied to the streak fields of the provided celebration diff --git a/common/djangoapps/student/models_api.py b/common/djangoapps/student/models_api.py index 997f5454db53..859c8d82a5ed 100644 --- a/common/djangoapps/student/models_api.py +++ b/common/djangoapps/student/models_api.py @@ -4,7 +4,7 @@ import datetime import logging -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.models import CourseAccessRole as _CourseAccessRole from common.djangoapps.student.models import CourseEnrollment as _CourseEnrollment @@ -158,7 +158,7 @@ def confirm_name_change(user, pending_name_change): if 'old_names' not in meta: meta['old_names'] = [] meta['old_names'].append( - [user_profile.name, pending_name_change.rationale, datetime.datetime.now(UTC).isoformat()] + [user_profile.name, pending_name_change.rationale, datetime.datetime.now(get_utc_timezone()).isoformat()] ) user_profile.set_meta(meta) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 27e245288dd6..128ec5d834e8 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -10,7 +10,7 @@ from django.test.client import RequestFactory from factory.django import DjangoModelFactory from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.models import ( AccountRecovery, @@ -91,8 +91,8 @@ class Meta: is_staff = False is_active = True is_superuser = False - last_login = datetime(2012, 1, 1, tzinfo=UTC) - date_joined = datetime(2011, 1, 1, tzinfo=UTC) + last_login = datetime(2012, 1, 1, tzinfo=get_utc_timezone()) + date_joined = datetime(2011, 1, 1, tzinfo=get_utc_timezone()) @factory.post_generation def profile(obj, create, extracted, **kwargs): # pylint: disable=unused-argument, missing-function-docstring diff --git a/common/djangoapps/student/tests/test_admin_views.py b/common/djangoapps/student/tests/test_admin_views.py index 968ba330a99a..3c7b4a90d51c 100644 --- a/common/djangoapps/student/tests/test_admin_views.py +++ b/common/djangoapps/student/tests/test_admin_views.py @@ -17,7 +17,7 @@ from django.urls import reverse from django.utils.timezone import now from edx_toggles.toggles.testutils import override_waffle_switch -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.admin import ( # lint-amnesty, pylint: disable=line-too-long COURSE_ENROLLMENT_ADMIN_SWITCH, @@ -335,7 +335,7 @@ def setUp(self): super().setUp() self.client.login(username=self.user.username, password=self.TEST_PASSWORD) self.user2 = UserFactory.create(username='Zażółć gęślÄ… jaźń') - self.user_lockout_until = datetime.datetime.now(UTC) + self.user_lockout_until = datetime.datetime.now(get_utc_timezone()) LoginFailures.objects.create(user=self.user, failure_count=10, lockout_until=self.user_lockout_until) LoginFailures.objects.create(user=self.user2, failure_count=2) diff --git a/common/djangoapps/student/tests/test_certificates.py b/common/djangoapps/student/tests/test_certificates.py index 3a69f64b2b6c..9260431e59bc 100644 --- a/common/djangoapps/student/tests/test_certificates.py +++ b/common/djangoapps/student/tests/test_certificates.py @@ -7,7 +7,7 @@ from django.conf import settings from django.test.utils import override_settings from django.urls import reverse -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -24,8 +24,8 @@ # pylint: disable=no-member -PAST_DATE = datetime.datetime.now(UTC) - datetime.timedelta(days=2) -FUTURE_DATE = datetime.datetime.now(UTC) + datetime.timedelta(days=2) +PAST_DATE = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=2) +FUTURE_DATE = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=2) class CertificateDisplayTestBase(SharedModuleStoreTestCase): @@ -94,7 +94,7 @@ def setUpClass(cls): def _check_message(self, visible_date): # lint-amnesty, pylint: disable=missing-function-docstring response = self.client.get(reverse('dashboard')) - is_past = visible_date < datetime.datetime.now(UTC) + is_past = visible_date < datetime.datetime.now(get_utc_timezone()) if is_past: test_message = 'Your grade and certificate will be ready after' diff --git a/common/djangoapps/student/tests/test_credit.py b/common/djangoapps/student/tests/test_credit.py index e4264e689c31..eaf0659fa7c4 100644 --- a/common/djangoapps/student/tests/test_credit.py +++ b/common/djangoapps/student/tests/test_credit.py @@ -6,7 +6,7 @@ from unittest.mock import patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.test.utils import override_settings from django.urls import reverse @@ -101,7 +101,7 @@ def test_eligible_for_credit(self): # Move the eligibility deadline so it's within 30 days eligibility = CreditEligibility.objects.get(username=self.USERNAME) - eligibility.deadline = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=29) + eligibility.deadline = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=29) eligibility.save() # The user should still have the option to purchase credit, diff --git a/common/djangoapps/student/tests/test_models.py b/common/djangoapps/student/tests/test_models.py index 02df1a6714c6..bb4cfe4c1daa 100644 --- a/common/djangoapps/student/tests/test_models.py +++ b/common/djangoapps/student/tests/test_models.py @@ -5,7 +5,6 @@ from unittest.mock import MagicMock import ddt -import pytz from crum import set_current_request from django.contrib.auth.models import AnonymousUser, User # lint-amnesty, pylint: disable=imported-auth-user from django.core.cache import cache @@ -15,7 +14,7 @@ from edx_toggles.toggles.testutils import override_waffle_flag from freezegun import freeze_time from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory @@ -150,7 +149,7 @@ def test_upgrade_deadline(self): course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. - expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1) + expiration_datetime=datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) ) enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT) Schedule.objects.all().delete() @@ -164,7 +163,7 @@ def test_upgrade_deadline_with_schedule(self): course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. - expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30), + expiration_datetime=datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=30), ) course_overview = CourseOverview.load_from_module_store(course.id) CourseEnrollmentFactory( @@ -172,7 +171,7 @@ def test_upgrade_deadline_with_schedule(self): mode=CourseMode.AUDIT, course=course_overview, ) - Schedule.objects.update(upgrade_deadline=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=5)) + Schedule.objects.update(upgrade_deadline=datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=5)) enrollment = CourseEnrollment.objects.first() # The schedule's upgrade deadline should be used if a schedule exists @@ -189,7 +188,7 @@ def test_upgrade_deadline_for_non_upgradeable_enrollment(self, mode): @skip_unless_lms def test_upgrade_deadline_instructor_paced(self): course = CourseFactory(self_paced=False) - course_upgrade_deadline = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1) + course_upgrade_deadline = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) CourseModeFactory( course_id=course.id, mode_slug=CourseMode.VERIFIED, @@ -226,7 +225,7 @@ def test_enrollments_not_deleted(self): course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. - expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30), + expiration_datetime=datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=30), ) # Create a CourseOverview with an outdated version @@ -280,7 +279,7 @@ def setUp(self): def test_first_check_streak_celebration(self): STREAK_LENGTH_TO_CELEBRATE = UserCelebration.perform_streak_updates(self.user, self.course_key) - today = datetime.datetime.now(UTC).date() + today = datetime.datetime.now(get_utc_timezone()).date() assert self.user.celebration.streak_length == 1 assert self.user.celebration.last_day_of_streak == today assert STREAK_LENGTH_TO_CELEBRATE is None @@ -300,7 +299,7 @@ def test_celebrate_only_once_in_continuous_streak(self): | 2/9/21 | 6 | 2/9/21 | None | Day 6 of Streak | +---------+---------------------+--------------------+-------------------------+------------------+------------------+ """ - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(get_utc_timezone()) for i in range(1, (self.STREAK_LENGTH_TO_CELEBRATE * 2) + 1): with freeze_time(now + datetime.timedelta(days=i)): STREAK_LENGTH_TO_CELEBRATE = UserCelebration.perform_streak_updates(self.user, self.course_key) @@ -321,7 +320,7 @@ def test_longest_streak_updates_correctly(self): | 2/9/21 | 6 | 2/9/21 | None | longest_streak_ever is 6 | +---------+---------------------+--------------------+-------------------------+------------------+---------------------+ """ - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(get_utc_timezone()) for i in range(1, (self.STREAK_LENGTH_TO_CELEBRATE * 2) + 1): with freeze_time(now + datetime.timedelta(days=i)): UserCelebration.perform_streak_updates(self.user, self.course_key) @@ -342,7 +341,7 @@ def test_celebrate_only_once_with_multiple_calls_on_the_same_day(self): | 2/6/21 | 3 | 2/6/21 | None | Already celebrated this streak. | +---------+---------------------+--------------------+-------------------------+------------------+----------------------------+ """ - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(get_utc_timezone()) for i in range(1, self.STREAK_LENGTH_TO_CELEBRATE + 1): with freeze_time(now + datetime.timedelta(days=i)): streak_length_to_celebrate = UserCelebration.perform_streak_updates(self.user, self.course_key) @@ -382,7 +381,7 @@ def test_celebrate_twice_with_broken_streak_in_between(self): | 2/10/21 | 3 | 2/10/21 | 3 | Completed 3 Day Streak so we should celebrate | +---------+---------------------+--------------------+-------------------------+------------------+-----------------------------------------------+ """ - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(get_utc_timezone()) for i in range(1, self.STREAK_LENGTH_TO_CELEBRATE + self.STREAK_BREAK_LENGTH + self.STREAK_LENGTH_TO_CELEBRATE + 1): with freeze_time(now + datetime.timedelta(days=i)): if self.STREAK_LENGTH_TO_CELEBRATE < i <= self.STREAK_LENGTH_TO_CELEBRATE + self.STREAK_BREAK_LENGTH: @@ -413,7 +412,7 @@ def test_streak_resets_if_day_is_missed(self): | 2/12/21 | 1 | 2/12/21 | None | Day 2 of streak was missed, so streak resets | +---------+---------------------+--------------------+-------------------------+------------------+-----------------------------------------------+ """ - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(get_utc_timezone()) for i in range(1, self.STREAK_LENGTH_TO_CELEBRATE * 3 + 1, 2): with freeze_time(now + datetime.timedelta(days=i)): streak_length_to_celebrate = UserCelebration.perform_streak_updates(self.user, self.course_key) @@ -440,7 +439,7 @@ def test_streak_does_not_reset_if_day_is_missed_with_longer_break(self): +---------+---------------------+--------------------+-------------------------+------------------+ """ UserCelebration.STREAK_BREAK_LENGTH = 2 - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(get_utc_timezone()) for i in range(1, self.STREAK_LENGTH_TO_CELEBRATE * 3 + 1, 2): with freeze_time(now + datetime.timedelta(days=i)): streak_length_to_celebrate = UserCelebration.perform_streak_updates(self.user, self.course_key) @@ -831,7 +830,7 @@ def register_and_enroll_student(): return CourseEnrollment.get_enrollment(student, self.course.id) # Set enrollment end date to a past date so that enrollment is ended - enrollment_end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=2) + enrollment_end = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=2) course_overview = CourseOverviewFactory.create(id=self.course.id, enrollment_end=enrollment_end) course_overview.save() diff --git a/common/djangoapps/student/tests/test_recent_enrollments.py b/common/djangoapps/student/tests/test_recent_enrollments.py index d8a060eb5fdf..f5cc0da53e31 100644 --- a/common/djangoapps/student/tests/test_recent_enrollments.py +++ b/common/djangoapps/student/tests/test_recent_enrollments.py @@ -8,7 +8,7 @@ from django.urls import reverse from django.utils.timezone import now from opaque_keys.edx import locator -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.test.utils import XssTestMixin from common.djangoapps.student.models import CourseEnrollment, DashboardConfiguration @@ -40,7 +40,7 @@ def setUp(self): # Old Course old_course_location = locator.CourseLocator('Org0', 'Course0', 'Run0') __, enrollment = self._create_course_and_enrollment(old_course_location) - enrollment.created = datetime.datetime(1900, 12, 31, 0, 0, 0, 0, tzinfo=UTC) + enrollment.created = datetime.datetime(1900, 12, 31, 0, 0, 0, 0, tzinfo=get_utc_timezone()) enrollment.save() # New Course diff --git a/common/djangoapps/student/tests/test_refunds.py b/common/djangoapps/student/tests/test_refunds.py index 4544a54fef81..561331745877 100644 --- a/common/djangoapps/student/tests/test_refunds.py +++ b/common/djangoapps/student/tests/test_refunds.py @@ -9,7 +9,7 @@ import ddt import httpretty -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone # Explicitly import the cache from ConfigurationModel so we can reset it after each test from config_models.models import cache from django.test.client import Client @@ -56,7 +56,7 @@ def setUp(self): course_id=self.course.id, mode_slug='verified', mode_display_name='Verified', - expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1) + expiration_datetime=datetime.now(get_utc_timezone()) + timedelta(days=1) ) self.enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode='verified') @@ -67,14 +67,14 @@ def setUp(self): @patch('common.djangoapps.student.models.course_enrollment.CourseEnrollment.refund_cutoff_date') def test_refundable(self, cutoff_date): """ Assert base case is refundable""" - cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(days=1) + cutoff_date.return_value = datetime.now(get_utc_timezone()) + timedelta(days=1) assert self.enrollment.refundable() @patch('common.djangoapps.student.models.course_enrollment.CourseEnrollment.refund_cutoff_date') def test_refundable_expired_verification(self, cutoff_date): """ Assert that enrollment is refundable if course mode has expired.""" - cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(days=1) - self.verified_mode.expiration_datetime = datetime.now(pytz.UTC) - timedelta(days=1) + cutoff_date.return_value = datetime.now(get_utc_timezone()) + timedelta(days=1) + self.verified_mode.expiration_datetime = datetime.now(get_utc_timezone()) - timedelta(days=1) self.verified_mode.save() assert self.enrollment.refundable() @@ -82,7 +82,7 @@ def test_refundable_expired_verification(self, cutoff_date): def test_refundable_when_certificate_exists(self, cutoff_date): """ Assert that enrollment is not refundable once a certificat has been generated.""" - cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(days=1) + cutoff_date.return_value = datetime.now(get_utc_timezone()) + timedelta(days=1) assert self.enrollment.refundable() @@ -110,13 +110,13 @@ def test_refundable_when_certificate_exists(self, cutoff_date): @patch('common.djangoapps.student.models.course_enrollment.CourseEnrollment.refund_cutoff_date') def test_refundable_with_cutoff_date(self, cutoff_date): """ Assert enrollment is refundable before cutoff and not refundable after.""" - cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(days=1) + cutoff_date.return_value = datetime.now(get_utc_timezone()) + timedelta(days=1) assert self.enrollment.refundable() - cutoff_date.return_value = datetime.now(pytz.UTC) - timedelta(minutes=5) + cutoff_date.return_value = datetime.now(get_utc_timezone()) - timedelta(minutes=5) assert not self.enrollment.refundable() - cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(minutes=5) + cutoff_date.return_value = datetime.now(get_utc_timezone()) + timedelta(minutes=5) assert self.enrollment.refundable() @ddt.data( @@ -132,7 +132,7 @@ def test_refund_cutoff_date(self, order_date_delta, course_start_delta, expected """ Assert that the later date is used with the configurable refund period in calculating the returned cutoff date. """ - now = datetime.now(pytz.UTC).replace(microsecond=0) + now = datetime.now(get_utc_timezone()).replace(microsecond=0) order_date = now + order_date_delta course_start = now + course_start_delta expected_date = now + expected_date_delta @@ -170,9 +170,9 @@ def test_refund_cutoff_date(self, order_date_delta, course_start_delta, expected assert expected_date_placed_attr in CourseEnrollmentAttribute.get_enrollment_attributes(self.enrollment) @ddt.data( - (datetime.now(pytz.UTC) + timedelta(days=1), True), - (datetime.now(pytz.UTC) - timedelta(days=1), False), - (datetime.now(pytz.UTC) - timedelta(minutes=5), False), + (datetime.now(get_utc_timezone()) + timedelta(days=1), True), + (datetime.now(get_utc_timezone()) - timedelta(days=1), False), + (datetime.now(get_utc_timezone()) - timedelta(minutes=5), False), ) @ddt.unpack @httpretty.activate @@ -260,7 +260,7 @@ def test_refund_cutoff_date_with_date_placed_attr(self, mock_ecommerce_api_clien Assert that the refund_cutoff_date returns order placement date if order:date_placed attribute exist without calling ecommerce. """ - now = datetime.now(pytz.UTC).replace(microsecond=0) + now = datetime.now(get_utc_timezone()).replace(microsecond=0) order_date = now + timedelta(days=2) course_start = now + timedelta(days=1) @@ -280,7 +280,7 @@ def test_refund_cutoff_date_with_date_placed_attr(self, mock_ecommerce_api_clien @override_settings(ECOMMERCE_API_URL=TEST_API_URL) def test_multiple_refunds_dashbaord_page_error(self): """ Order with mutiple refunds will not throw 500 error when dashboard page will access.""" - now = datetime.now(pytz.UTC).replace(microsecond=0) + now = datetime.now(get_utc_timezone()).replace(microsecond=0) order_date = now + timedelta(days=1) expected_content = f'{{"date_placed": "{order_date.strftime(ECOMMERCE_DATE_FORMAT)}"}}' diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index b63c522bbd0f..ab46a75dbe18 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -10,7 +10,7 @@ from unittest.mock import patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing from django.conf import settings from django.test.utils import override_settings @@ -948,7 +948,7 @@ def test_course_upgrade_notification( course_id=course.id, mode_slug='verified', mode_display_name='Verified', - expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1), + expiration_datetime=datetime.now(get_utc_timezone()) + timedelta(days=1), sku=sku ) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index c2e6d3b2aa9c..af13251784e5 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -8,7 +8,7 @@ from urllib.parse import quote import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from config_models.models import cache from django.conf import settings from django.contrib.auth.models import AnonymousUser, User # lint-amnesty, pylint: disable=imported-auth-user @@ -81,7 +81,7 @@ def test_cert_info(self): course = CourseOverviewFactory.create( end_of_course_survey_url=survey_url, certificates_display_behavior=CertificatesDisplayBehaviors.END, - end=datetime.now(pytz.UTC) - timedelta(days=2) + end=datetime.now(get_utc_timezone()) - timedelta(days=2) ) cert = GeneratedCertificateFactory.create( user=user, @@ -233,7 +233,7 @@ def test_cert_grade(self, persisted_grade, cert_grade): course = CourseOverviewFactory.create( end_of_course_survey_url=survey_url, certificates_display_behavior=CertificatesDisplayBehaviors.END, - end=datetime.now(pytz.UTC) - timedelta(days=2), + end=datetime.now(get_utc_timezone()) - timedelta(days=2), ) enrollment = CourseEnrollmentFactory(user=user, course_id=course.id, mode=CourseMode.VERIFIED) @@ -260,7 +260,7 @@ def test_cert_grade_no_grades(self): course = CourseOverviewFactory.create( end_of_course_survey_url=survey_url, certificates_display_behavior=CertificatesDisplayBehaviors.END, - end=datetime.now(pytz.UTC) - timedelta(days=2), + end=datetime.now(get_utc_timezone()) - timedelta(days=2), ) cert_status = {'status': 'generating', 'mode': 'honor', 'uuid': None} enrollment = CourseEnrollmentFactory(user=user, course_id=course.id, mode=CourseMode.VERIFIED) @@ -358,14 +358,14 @@ def test_course_mode_info(self): course_id=self.course.id, mode_slug='verified', mode_display_name='Verified', - expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1) + expiration_datetime=datetime.now(get_utc_timezone()) + timedelta(days=1) ) enrollment = CourseEnrollment.enroll(self.user, self.course.id) course_mode_info = complete_course_mode_info(self.course.id, enrollment) assert course_mode_info['show_upsell'] assert course_mode_info['days_for_upsell'] == 1 - verified_mode.expiration_datetime = datetime.now(pytz.UTC) + timedelta(days=-1) + verified_mode.expiration_datetime = datetime.now(get_utc_timezone()) + timedelta(days=-1) verified_mode.save() course_mode_info = complete_course_mode_info(self.course.id, enrollment) assert not course_mode_info['show_upsell'] @@ -380,13 +380,13 @@ def test_linked_in_add_to_profile_btn_not_appearing_without_config(self): course_id=self.course.id, mode_slug='verified', mode_display_name='verified', - expiration_datetime=datetime.now(pytz.UTC) - timedelta(days=1) + expiration_datetime=datetime.now(get_utc_timezone()) - timedelta(days=1) ) CourseEnrollment.enroll(self.user, self.course.id, mode='honor') - self.course.start = datetime.now(pytz.UTC) - timedelta(days=2) - self.course.end = datetime.now(pytz.UTC) - timedelta(days=1) + self.course.start = datetime.now(get_utc_timezone()) - timedelta(days=2) + self.course.end = datetime.now(get_utc_timezone()) - timedelta(days=1) self.course.display_name = "Omega" self.course = self.update_course(self.course, self.user.id) @@ -419,12 +419,12 @@ def test_linked_in_add_to_profile_btn_with_certificate(self): course_id=self.course.id, mode_slug='verified', mode_display_name='verified', - expiration_datetime=datetime.now(pytz.UTC) - timedelta(days=1) + expiration_datetime=datetime.now(get_utc_timezone()) - timedelta(days=1) ) CourseEnrollment.enroll(self.user, self.course.id, mode='honor') - self.course.certificate_available_date = datetime.now(pytz.UTC) - timedelta(days=1) - self.course.start = datetime.now(pytz.UTC) - timedelta(days=2) - self.course.end = datetime.now(pytz.UTC) - timedelta(days=1) + self.course.certificate_available_date = datetime.now(get_utc_timezone()) - timedelta(days=1) + self.course.start = datetime.now(get_utc_timezone()) - timedelta(days=2) + self.course.end = datetime.now(get_utc_timezone()) - timedelta(days=1) self.course.display_name = 'Omega' self.course = self.update_course(self.course, self.user.id) @@ -520,7 +520,7 @@ def _enrollment_with_complete_course(self, enrollment_mode): course_id=self.course.id, mode_slug='verified', mode_display_name='Verified', - expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1) + expiration_datetime=datetime.now(get_utc_timezone()) + timedelta(days=1) ) enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode=enrollment_mode) return complete_course_mode_info(self.course.id, enrollment) diff --git a/common/djangoapps/student/views/dashboard.py b/common/djangoapps/student/views/dashboard.py index e5078269208b..4b20dcf93d1c 100644 --- a/common/djangoapps/student/views/dashboard.py +++ b/common/djangoapps/student/views/dashboard.py @@ -20,7 +20,7 @@ from edx_toggles.toggles import WaffleFlag from opaque_keys.edx.keys import CourseKey from openedx_filters.learning.filters import DashboardRenderStarted -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from edx_django_utils.plugins import pluggable_override from lms.djangoapps.bulk_email.api import is_bulk_email_feature_enabled @@ -108,7 +108,7 @@ def _get_recently_enrolled_courses(course_enrollments): list[CourseEnrollment]: A list of recent course enrollments. """ seconds = DashboardConfiguration.current().recent_enrollment_time_delta - time_delta = (datetime.datetime.now(UTC) - datetime.timedelta(seconds=seconds)) + time_delta = (datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(seconds=seconds)) return [ enrollment for enrollment in course_enrollments # If the enrollment has no created date, we are explicitly excluding the course @@ -270,7 +270,7 @@ def complete_course_mode_info(course_id, enrollment, modes=None): mode_info['verified_bulk_sku'] = modes['verified'].bulk_sku # if there is an expiration date, find out how long from now it is if modes['verified'].expiration_datetime: - today = datetime.datetime.now(UTC).date() + today = datetime.datetime.now(get_utc_timezone()).date() mode_info['days_for_upsell'] = (modes['verified'].expiration_datetime.date() - today).days mode_info['expiration_datetime'] = modes['verified'].expiration_datetime.date() diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 4cf8fad8aea5..14f0e4cda552 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -35,7 +35,7 @@ # Note that this lives in LMS, so this dependency should be refactored. from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework.decorators import api_view, authentication_classes, permission_classes from rest_framework.permissions import IsAuthenticated @@ -539,7 +539,7 @@ def disable_account_ajax(request): context['message'] = _("Unexpected account status") return JsonResponse(context, status=400) user_account.changed_by = request.user - user_account.standing_last_changed_at = datetime.datetime.now(UTC) + user_account.standing_last_changed_at = datetime.datetime.now(get_utc_timezone()) user_account.save() return JsonResponse(context) @@ -921,7 +921,7 @@ def confirm_email_change(request, key): meta = u_prof.get_meta() if 'old_emails' not in meta: meta['old_emails'] = [] - meta['old_emails'].append([user.email, datetime.datetime.now(UTC).isoformat()]) + meta['old_emails'].append([user.email, datetime.datetime.now(get_utc_timezone()).isoformat()]) u_prof.set_meta(meta) u_prof.save() # Send it to the old email... diff --git a/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py b/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py index 5dbf7a803ef3..bdc5bcd521a1 100644 --- a/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py +++ b/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py @@ -4,7 +4,7 @@ from unittest import mock from uuid import uuid4 # lint-amnesty, pylint: disable=wrong-import-order -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.contrib.sites.models import Site from django.urls import reverse from django.utils.http import urlencode @@ -33,7 +33,7 @@ 'entity_id': 'http://entity-id-1', 'sso_url': 'http://test.url', 'public_key': 'a-key0Aid98', - 'fetched_at': datetime.now(pytz.UTC).replace(microsecond=0) + 'fetched_at': datetime.now(get_utc_timezone()).replace(microsecond=0) } SINGLE_PROVIDER_DATA_2 = copy.copy(SINGLE_PROVIDER_DATA) diff --git a/common/djangoapps/third_party_auth/tests/test_pipeline_integration.py b/common/djangoapps/third_party_auth/tests/test_pipeline_integration.py index c19b0b8d96aa..889adc762b7b 100644 --- a/common/djangoapps/third_party_auth/tests/test_pipeline_integration.py +++ b/common/djangoapps/third_party_auth/tests/test_pipeline_integration.py @@ -6,7 +6,7 @@ import ddt import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django import test from django.contrib.auth import models, REDIRECT_FIELD_NAME from django.core import mail @@ -562,7 +562,7 @@ def test_set_id_verification_status_expired(self): ) with mock.patch('common.djangoapps.third_party_auth.pipeline.earliest_allowed_verification_date') as earliest_date: # lint-amnesty, pylint: disable=line-too-long - earliest_date.return_value = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1) + earliest_date.return_value = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) # Begin the pipeline. pipeline.set_id_verification_status( auth_entry=pipeline.AUTH_ENTRY_LOGIN, diff --git a/common/djangoapps/third_party_auth/utils.py b/common/djangoapps/third_party_auth/utils.py index 720c52ea5beb..bc71c2f64cb3 100644 --- a/common/djangoapps/third_party_auth/utils.py +++ b/common/djangoapps/third_party_auth/utils.py @@ -7,7 +7,7 @@ from uuid import UUID import dateutil.parser -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone import requests from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.utils.timezone import now @@ -93,7 +93,7 @@ def parse_metadata_xml(xml, entity_id): expires_at = dateutil.parser.parse(xml.attrib["validUntil"]) if "cacheDuration" in xml.attrib: cache_expires = OneLogin_Saml2_Utils.parse_duration(xml.attrib["cacheDuration"]) - cache_expires = datetime.datetime.fromtimestamp(cache_expires, tz=pytz.utc) + cache_expires = datetime.datetime.fromtimestamp(cache_expires, tz=get_utc_timezone()) if expires_at is None or cache_expires < expires_at: expires_at = cache_expires diff --git a/common/djangoapps/track/backends/tests/test_logger.py b/common/djangoapps/track/backends/tests/test_logger.py index d932a4515a21..7a181bc0326e 100644 --- a/common/djangoapps/track/backends/tests/test_logger.py +++ b/common/djangoapps/track/backends/tests/test_logger.py @@ -32,6 +32,5 @@ def test_logger_backend(caplog): 'time': '2012-05-01T07:27:01.000200+00:00', 'date': '2012-05-07' } - assert saved_events[0] == unpacked_event assert saved_events[1] == unpacked_event diff --git a/common/djangoapps/track/tests/__init__.py b/common/djangoapps/track/tests/__init__.py index 0fd070c5a298..1a68c59ea019 100644 --- a/common/djangoapps/track/tests/__init__.py +++ b/common/djangoapps/track/tests/__init__.py @@ -8,9 +8,9 @@ from eventtracking import tracker from eventtracking.django import DjangoTracker from freezegun import freeze_time -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone -FROZEN_TIME = datetime(2013, 10, 3, 8, 24, 55, tzinfo=UTC) +FROZEN_TIME = datetime(2013, 10, 3, 8, 24, 55, tzinfo=get_utc_timezone()) IN_MEMORY_BACKEND_CONFIG = { 'mem': { 'ENGINE': 'common.djangoapps.track.tests.InMemoryBackend' diff --git a/common/djangoapps/track/tests/test_util.py b/common/djangoapps/track/tests/test_util.py index 351349b341c4..0c39f60f885e 100644 --- a/common/djangoapps/track/tests/test_util.py +++ b/common/djangoapps/track/tests/test_util.py @@ -4,7 +4,7 @@ from datetime import datetime from django.test import TestCase -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.track.utils import DateTimeJSONEncoder @@ -12,7 +12,7 @@ class TestDateTimeJSONEncoder(TestCase): # lint-amnesty, pylint: disable=missing-class-docstring def test_datetime_encoding(self): a_naive_datetime = datetime(2012, 5, 1, 7, 27, 10, 20000) - a_tz_datetime = datetime(2012, 5, 1, 7, 27, 10, 20000, tzinfo=UTC) + a_tz_datetime = datetime(2012, 5, 1, 7, 27, 10, 20000, tzinfo=get_utc_timezone()) a_date = a_naive_datetime.date() an_iso_datetime = '2012-05-01T07:27:10.020000+00:00' an_iso_date = '2012-05-01' diff --git a/common/djangoapps/track/utils.py b/common/djangoapps/track/utils.py index 5efdde4c5ad0..52a4f9abc805 100644 --- a/common/djangoapps/track/utils.py +++ b/common/djangoapps/track/utils.py @@ -3,8 +3,7 @@ import json from datetime import date, datetime - -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone class DateTimeJSONEncoder(json.JSONEncoder): @@ -20,10 +19,10 @@ def default(self, obj): # lint-amnesty, pylint: disable=arguments-differ, metho if isinstance(obj, datetime): if obj.tzinfo is None: # Localize to UTC naive datetime objects - obj = UTC.localize(obj) # lint-amnesty, pylint: disable=no-value-for-parameter + obj = obj.replace(tzinfo=get_utc_timezone()) # lint-amnesty, pylint: disable=no-value-for-parameter else: # Convert to UTC datetime objects from other timezones - obj = obj.astimezone(UTC) + obj = obj.astimezone(get_utc_timezone()) return obj.isoformat() elif isinstance(obj, date): return obj.isoformat() diff --git a/common/djangoapps/util/date_utils.py b/common/djangoapps/util/date_utils.py index 968fb55d7cda..29bb0f00388f 100644 --- a/common/djangoapps/util/date_utils.py +++ b/common/djangoapps/util/date_utils.py @@ -5,10 +5,12 @@ import re from datetime import datetime, timedelta +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError import crum from django.utils.translation import get_language, gettext, pgettext -from pytz import UnknownTimeZoneError, timezone, utc +from openedx.core.lib.time_zone_utils import get_utc_timezone + from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs from openedx.core.djangolib.markup import HTML @@ -48,17 +50,17 @@ def get_time_display(dtime, format_string=None, coerce_tz=None): If the format_string is None, or if format_string is improperly formatted, this method will return the value from `get_default_time_display`. - Coerces aware datetime to tz=coerce_tz if set. coerce_tz should be a pytz timezone string - like "US/Pacific", or None + Coerces aware datetime to tz=coerce_tz if set. coerce_tz should be a time zone string + like "US/Pacific" (compatible with ZoneInfo), or None. format_string should be a unicode string that is a valid argument for datetime's strftime method. """ if dtime is not None and dtime.tzinfo is not None and coerce_tz: try: - to_tz = timezone(coerce_tz) - except UnknownTimeZoneError: - to_tz = utc - dtime = to_tz.normalize(dtime.astimezone(to_tz)) + to_tz = ZoneInfo(coerce_tz) + except ZoneInfoNotFoundError: + to_tz = get_utc_timezone() + dtime = dtime.astimezone(to_tz) if dtime is None or format_string is None: return get_default_time_display(dtime) try: @@ -83,7 +85,7 @@ def to_timestamp(datetime_value): Convert a datetime into a timestamp, represented as the number of seconds since January 1, 1970 UTC. """ - return int((datetime_value - datetime(1970, 1, 1, tzinfo=utc)).total_seconds()) + return int((datetime_value - datetime(1970, 1, 1, tzinfo=get_utc_timezone())).total_seconds()) def from_timestamp(timestamp): @@ -94,7 +96,7 @@ def from_timestamp(timestamp): If the timestamp cannot be converted, returns None instead. """ try: - return datetime.utcfromtimestamp(int(timestamp)).replace(tzinfo=utc) + return datetime.fromtimestamp(int(timestamp), tz=get_utc_timezone()) except (ValueError, TypeError): return None diff --git a/common/djangoapps/util/file.py b/common/djangoapps/util/file.py index b2892e6f42c9..317beac05125 100644 --- a/common/djangoapps/util/file.py +++ b/common/djangoapps/util/file.py @@ -12,7 +12,7 @@ from django.utils.text import get_valid_filename from django.utils.translation import gettext as _ from django.utils.translation import ngettext -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from storages.backends.s3boto3 import S3Boto3Storage @@ -145,7 +145,7 @@ def course_and_time_based_filename_generator(course_id, base_name): return "{course_prefix}_{base_name}_{timestamp_str}".format( course_prefix=course_filename_prefix_generator(course_id), base_name=get_valid_filename(base_name), - timestamp_str=datetime.now(UTC).strftime("%Y-%m-%d-%H%M%S") + timestamp_str=datetime.now(get_utc_timezone()).strftime("%Y-%m-%d-%H%M%S") ) diff --git a/common/djangoapps/util/tests/test_date_utils.py b/common/djangoapps/util/tests/test_date_utils.py index 5843d88ea81b..5d859770b19a 100644 --- a/common/djangoapps/util/tests/test_date_utils.py +++ b/common/djangoapps/util/tests/test_date_utils.py @@ -10,7 +10,7 @@ import ddt import pytest from markupsafe import Markup -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test.client import RequestFactory @@ -21,7 +21,7 @@ def test_get_default_time_display(): assert get_default_time_display(None) == "" - test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=utc) + test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=get_utc_timezone()) assert get_default_time_display(test_time) == "Mar 12, 1992 at 15:03 UTC" @@ -32,12 +32,12 @@ def test_get_dflt_time_disp_notz(): def test_get_time_disp_ret_empty(): assert get_time_display(None) == "" - test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=utc) + test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=get_utc_timezone()) assert get_time_display(test_time, "") == "" def test_get_time_display(): - test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=utc) + test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=get_utc_timezone()) assert get_time_display(test_time, 'dummy text') == "dummy text" assert get_time_display(test_time, '%b %d %Y') == "Mar 12 1992" assert get_time_display(test_time, '%b %d %Y %Z') == "Mar 12 1992 UTC" @@ -45,15 +45,15 @@ def test_get_time_display(): def test_get_time_pass_through(): - test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=utc) + test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=get_utc_timezone()) assert get_time_display(test_time) == "Mar 12, 1992 at 15:03 UTC" assert get_time_display(test_time, None) == "Mar 12, 1992 at 15:03 UTC" assert get_time_display(test_time, "%") == "Mar 12, 1992 at 15:03 UTC" def test_get_time_display_coerce(): - test_time_standard = datetime(1992, 1, 12, 15, 3, 30, tzinfo=utc) - test_time_daylight = datetime(1992, 7, 12, 15, 3, 30, tzinfo=utc) + test_time_standard = datetime(1992, 1, 12, 15, 3, 30, tzinfo=get_utc_timezone()) + test_time_daylight = datetime(1992, 7, 12, 15, 3, 30, tzinfo=get_utc_timezone()) assert get_time_display(test_time_standard, None, coerce_tz="US/Pacific") == "Jan 12, 1992 at 07:03 PST" assert get_time_display(test_time_standard, None, coerce_tz="NONEXISTENTTZ") == "Jan 12, 1992 at 15:03 UTC" assert get_time_display(test_time_standard, '%b %d %H:%M', coerce_tz="US/Pacific") == "Jan 12 07:03" diff --git a/common/djangoapps/util/tests/test_file.py b/common/djangoapps/util/tests/test_file.py index cc16cd36129d..1040dd4825df 100644 --- a/common/djangoapps/util/tests/test_file.py +++ b/common/djangoapps/util/tests/test_file.py @@ -17,7 +17,7 @@ from django.test.utils import override_settings from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locations import CourseLocator -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from ccx_keys.locator import CCXLocator @@ -99,7 +99,7 @@ class FilenameGeneratorTestCase(TestCase): """ Tests for course_and_time_based_filename_generator """ - NOW = datetime.strptime('1974-06-22T01:02:03', '%Y-%m-%dT%H:%M:%S').replace(tzinfo=UTC) + NOW = datetime.strptime('1974-06-22T01:02:03', '%Y-%m-%dT%H:%M:%S').replace(tzinfo=get_utc_timezone()) def setUp(self): super().setUp() diff --git a/lms/djangoapps/branding/tests/test_page.py b/lms/djangoapps/branding/tests/test_page.py index 46f10bd6923a..a313d11298e9 100644 --- a/lms/djangoapps/branding/tests/test_page.py +++ b/lms/djangoapps/branding/tests/test_page.py @@ -13,7 +13,7 @@ from django.test.utils import override_settings from django.urls import reverse from milestones.tests.utils import MilestonesTestCaseMixin -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.edxmako.shortcuts import render_to_response from common.djangoapps.util.milestones_helpers import set_prerequisite_courses @@ -48,7 +48,7 @@ def setUp(self): self.factory = RequestFactory() self.course = CourseFactory.create( days_early_for_beta=5, - enrollment_start=datetime.datetime.now(UTC) + datetime.timedelta(days=3), + enrollment_start=datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=3), user_id=self.user.id, ) @@ -181,8 +181,8 @@ def setUp(self): number='1000', display_name='Starting later, Announced later', metadata={ - 'start': datetime.datetime.now(UTC) + datetime.timedelta(days=4), - 'announcement': datetime.datetime.now(UTC) + datetime.timedelta(days=3), + 'start': datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=4), + 'announcement': datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=3), }, emit_signals=True, ) @@ -191,8 +191,8 @@ def setUp(self): number='1001', display_name='Starting earlier, Announced earlier', metadata={ - 'start': datetime.datetime.now(UTC) + datetime.timedelta(days=2), - 'announcement': datetime.datetime.now(UTC) + datetime.timedelta(days=1), + 'start': datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=2), + 'announcement': datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1), }, emit_signals=True, ) diff --git a/lms/djangoapps/bulk_email/tests/test_models.py b/lms/djangoapps/bulk_email/tests/test_models.py index 43062492e842..6c5e0bc807d6 100644 --- a/lms/djangoapps/bulk_email/tests/test_models.py +++ b/lms/djangoapps/bulk_email/tests/test_models.py @@ -12,7 +12,7 @@ from django.test import TestCase from django.test.utils import override_settings from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory, StaffFactory @@ -84,8 +84,8 @@ def test_bad_to_option(self): CourseEmail.create(course_id, sender, to_option, subject, html_message) @ddt.data( - datetime.datetime(1999, 1, 1, tzinfo=UTC), - datetime.datetime(datetime.MAXYEAR, 1, 1, tzinfo=UTC), + datetime.datetime(1999, 1, 1, tzinfo=get_utc_timezone()), + datetime.datetime(datetime.MAXYEAR, 1, 1, tzinfo=get_utc_timezone()), ) def test_track_target(self, expiration_datetime): """ diff --git a/lms/djangoapps/ccx/api/v0/views.py b/lms/djangoapps/ccx/api/v0/views.py index 8ca15e065006..845932fcb2ce 100644 --- a/lms/djangoapps/ccx/api/v0/views.py +++ b/lms/djangoapps/ccx/api/v0/views.py @@ -5,7 +5,7 @@ import json import logging -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from ccx_keys.locator import CCXLocator from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.db import transaction @@ -465,7 +465,7 @@ def post(self, request): ccx_course_object.save() # Make sure start/due are overridden for entire course - start = TODAY().replace(tzinfo=pytz.UTC) + start = TODAY().replace(tzinfo=get_utc_timezone()) override_field_for_ccx(ccx_course_object, master_course_object, 'start', start) override_field_for_ccx(ccx_course_object, master_course_object, 'due', None) diff --git a/lms/djangoapps/ccx/models.py b/lms/djangoapps/ccx/models.py index 21f0136f0709..5c1087cfd431 100644 --- a/lms/djangoapps/ccx/models.py +++ b/lms/djangoapps/ccx/models.py @@ -12,7 +12,7 @@ from django.db import models from lazy import lazy from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.error_block import ErrorBlock from xmodule.modulestore.django import modulestore @@ -76,14 +76,14 @@ def max_student_enrollments_allowed(self): def has_started(self): """Return True if the CCX start date is in the past""" - return datetime.now(utc) > self.start + return datetime.now(get_utc_timezone()) > self.start def has_ended(self): """Return True if the CCX due date is set and is in the past""" if self.due is None: return False - return datetime.now(utc) > self.due + return datetime.now(get_utc_timezone()) > self.due @property def structure(self): diff --git a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py index f5e98a942cde..a90fbf32d403 100644 --- a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py +++ b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py @@ -7,7 +7,7 @@ from collections import deque from itertools import chain -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from ccx_keys.locator import CCXLocator from six.moves import zip_longest from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -24,8 +24,8 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): def setUpClass(cls): super().setUpClass() cls.course = CourseFactory.create() - start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC) - due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC) + start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=get_utc_timezone()) + due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=get_utc_timezone()) # Create a course outline cls.chapters = chapters = [ BlockFactory.create(start=start, parent=cls.course) for _ in range(2) diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py index a7128495eb59..10359ab0fd90 100644 --- a/lms/djangoapps/ccx/tests/test_field_override_performance.py +++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py @@ -18,7 +18,7 @@ from django.test.utils import override_settings from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xblock.core import XBlock from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls, check_sum_of_calls @@ -117,7 +117,7 @@ def setup_course(self, size, enable_ccx, view_as_ccx): self.course = CourseFactory.create( graded=True, - start=datetime.now(UTC), + start=datetime.now(get_utc_timezone()), grading_policy=grading_policy, enable_ccx=enable_ccx, ) diff --git a/lms/djangoapps/ccx/tests/test_models.py b/lms/djangoapps/ccx/tests/test_models.py index 11bd7e43d312..6a553da852ce 100644 --- a/lms/djangoapps/ccx/tests/test_models.py +++ b/lms/djangoapps/ccx/tests/test_models.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta import ddt -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls @@ -59,7 +59,7 @@ def test_ccx_start_is_correct(self): For this reason we test the difference between and make sure it is less than one second. """ - expected = datetime.now(utc) + expected = datetime.now(get_utc_timezone()) self.set_ccx_override('start', expected) actual = self.ccx.start diff = expected - actual @@ -67,7 +67,7 @@ def test_ccx_start_is_correct(self): def test_ccx_start_caching(self): """verify that caching the start property works to limit queries""" - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) self.set_ccx_override('start', now) with check_mongo_calls(2): # these statements are used entirely to demonstrate the @@ -84,7 +84,7 @@ def test_ccx_due_without_override(self): def test_ccx_due_is_correct(self): """verify that the due datetime for a ccx is correctly retrieved""" - expected = datetime.now(utc) + expected = datetime.now(get_utc_timezone()) self.set_ccx_override('due', expected) actual = self.ccx.due diff = expected - actual @@ -92,7 +92,7 @@ def test_ccx_due_is_correct(self): def test_ccx_due_caching(self): """verify that caching the due property works to limit queries""" - expected = datetime.now(utc) + expected = datetime.now(get_utc_timezone()) self.set_ccx_override('due', expected) with check_mongo_calls(2): # these statements are used entirely to demonstrate the @@ -104,7 +104,7 @@ def test_ccx_due_caching(self): def test_ccx_has_started(self): """verify that a ccx marked as starting yesterday has started""" - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) delta = timedelta(1) then = now - delta self.set_ccx_override('start', then) @@ -112,7 +112,7 @@ def test_ccx_has_started(self): def test_ccx_has_not_started(self): """verify that a ccx marked as starting tomorrow has not started""" - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) delta = timedelta(1) then = now + delta self.set_ccx_override('start', then) @@ -120,7 +120,7 @@ def test_ccx_has_not_started(self): def test_ccx_has_ended(self): """verify that a ccx that has a due date in the past has ended""" - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) delta = timedelta(1) then = now - delta self.set_ccx_override('due', then) @@ -129,7 +129,7 @@ def test_ccx_has_ended(self): def test_ccx_has_not_ended(self): """verify that a ccx that has a due date in the future has not eneded """ - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) delta = timedelta(1) then = now + delta self.set_ccx_override('due', then) diff --git a/lms/djangoapps/ccx/tests/test_overrides.py b/lms/djangoapps/ccx/tests/test_overrides.py index c6822f9ab76f..486eb6621e54 100644 --- a/lms/djangoapps/ccx/tests/test_overrides.py +++ b/lms/djangoapps/ccx/tests/test_overrides.py @@ -6,7 +6,7 @@ import datetime from unittest import mock -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from ccx_keys.locator import CCXLocator from django.test.utils import override_settings from edx_django_utils.cache import RequestCache @@ -41,8 +41,8 @@ def setUpClass(cls): cls.course.enable_ccx = True # Create a course outline - start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC) - due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC) + start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=get_utc_timezone()) + due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=get_utc_timezone()) chapters = [BlockFactory.create(start=start, parent=cls.course) for _ in range(2)] sequentials = flatten([ @@ -91,7 +91,7 @@ def test_override_start(self): """ Test that overriding start date on a chapter works. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=get_utc_timezone()) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) assert chapter.start == ccx_start @@ -100,7 +100,7 @@ def test_override_num_queries_new_field(self): """ Test that for creating new field executed only create query """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=get_utc_timezone()) chapter = self.ccx_course.get_children()[0] # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the # transaction.atomic decorator wrapping override_field_for_ccx. @@ -114,8 +114,8 @@ def test_override_num_queries_update_existing_field(self): """ Test that overriding existing field executed create, fetch and update queries. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) - new_ccx_start = datetime.datetime(2015, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=get_utc_timezone()) + new_ccx_start = datetime.datetime(2015, 12, 25, 00, 00, tzinfo=get_utc_timezone()) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) with self.assertNumQueries(3): @@ -125,7 +125,7 @@ def test_override_num_queries_field_value_not_changed(self): """ Test that if value of field does not changed no query execute. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=get_utc_timezone()) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) with self.assertNumQueries(2): # 2 savepoints @@ -135,7 +135,7 @@ def test_overriden_field_access_produces_no_extra_queries(self): """ Test no extra queries when accessing an overriden field more than once. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=get_utc_timezone()) chapter = self.ccx_course.get_children()[0] # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the # transaction.atomic decorator wrapping override_field_for_ccx. @@ -149,7 +149,7 @@ def test_override_is_inherited(self): """ Test that sequentials inherit overridden start date from chapter. """ - ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) + ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=get_utc_timezone()) chapter = self.ccx_course.get_children()[0] override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) assert chapter.get_children()[0].start == ccx_start @@ -161,7 +161,7 @@ def test_override_is_inherited_even_if_set_in_mooc(self): (verticals) even if a due date is set explicitly on grandchildren in the mooc. """ - ccx_due = datetime.datetime(2015, 1, 1, 00, 00, tzinfo=pytz.UTC) + ccx_due = datetime.datetime(2015, 1, 1, 00, 00, tzinfo=get_utc_timezone()) chapter = self.ccx_course.get_children()[0] chapter.display_name = 'itsme!' override_field_for_ccx(self.ccx, chapter, 'due', ccx_due) diff --git a/lms/djangoapps/ccx/tests/test_views.py b/lms/djangoapps/ccx/tests/test_views.py index e993d5dc0b7b..a221aa907edc 100644 --- a/lms/djangoapps/ccx/tests/test_views.py +++ b/lms/djangoapps/ccx/tests/test_views.py @@ -18,7 +18,7 @@ from django.utils.translation import gettext as _ from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase @@ -176,8 +176,8 @@ def setUpClass(cls): Set up tests """ super().setUpClass() - start = datetime.datetime(2016, 7, 1, 0, 0, tzinfo=UTC) - due = datetime.datetime(2016, 7, 8, 0, 0, tzinfo=UTC) + start = datetime.datetime(2016, 7, 1, 0, 0, tzinfo=get_utc_timezone()) + due = datetime.datetime(2016, 7, 8, 0, 0, tzinfo=get_utc_timezone()) cls.course = course = CourseFactory.create(enable_ccx=True, start=start) chapter = BlockFactory.create(start=start, parent=course, category='chapter') @@ -466,7 +466,7 @@ def test_edit_schedule(self, today): """ Get CCX schedule, modify it, save it. """ - today.return_value = datetime.datetime(2014, 11, 25, tzinfo=UTC) + today.return_value = datetime.datetime(2014, 11, 25, tzinfo=get_utc_timezone()) self.make_coach() ccx = self.make_ccx() url = reverse( @@ -805,10 +805,10 @@ def setUp(self): # Create a course outline self.mooc_start = start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=UTC + 2010, 5, 12, 2, 42, tzinfo=get_utc_timezone() ) self.mooc_due = due = datetime.datetime( - 2010, 7, 7, 0, 0, tzinfo=UTC + 2010, 7, 7, 0, 0, tzinfo=get_utc_timezone() ) self.chapters = [ @@ -890,7 +890,7 @@ def test_get_ccx_schedule(self, today): Hides nodes at a different depth and checks that these nodes are not in the schedule. """ - today.return_value = datetime.datetime(2014, 11, 25, tzinfo=UTC) + today.return_value = datetime.datetime(2014, 11, 25, tzinfo=get_utc_timezone()) self.make_coach() ccx = self.make_ccx() url = reverse( @@ -950,7 +950,7 @@ def setUpClass(cls): # Create a course outline cls.mooc_start = start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=UTC + 2010, 5, 12, 2, 42, tzinfo=get_utc_timezone() ) chapter = BlockFactory.create( start=start, parent=course, category='sequential' @@ -1206,7 +1206,7 @@ def setUp(self): # Create a CCX course and enroll the user in it. self.ccx = CcxFactory(course_id=self.split_course.id, coach=self.coach) - last_week = datetime.datetime.now(UTC) - datetime.timedelta(days=7) + last_week = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=7) override_field_for_ccx(self.ccx, self.split_course, 'start', last_week) # Required by self.ccx.has_started(). self.ccx_course_key = CCXLocator.from_course_locator(self.split_course.id, self.ccx.id) CourseEnrollment.enroll(self.student, self.ccx_course_key) diff --git a/lms/djangoapps/ccx/tests/utils.py b/lms/djangoapps/ccx/tests/utils.py index 4f8c34e14c2d..74056e662302 100644 --- a/lms/djangoapps/ccx/tests/utils.py +++ b/lms/djangoapps/ccx/tests/utils.py @@ -5,7 +5,7 @@ import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -31,10 +31,10 @@ def setUpClass(cls): # Create a course outline cls.mooc_start = start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=pytz.UTC + 2010, 5, 12, 2, 42, tzinfo=get_utc_timezone() ) cls.mooc_due = due = datetime.datetime( - 2010, 7, 7, 0, 0, tzinfo=pytz.UTC + 2010, 7, 7, 0, 0, tzinfo=get_utc_timezone() ) cls.chapters = [ diff --git a/lms/djangoapps/ccx/utils.py b/lms/djangoapps/ccx/utils.py index 28ecbba34947..6e8869fd59ba 100644 --- a/lms/djangoapps/ccx/utils.py +++ b/lms/djangoapps/ccx/utils.py @@ -10,7 +10,7 @@ from contextlib import contextmanager from smtplib import SMTPException -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.exceptions import ValidationError from django.core.validators import validate_email @@ -148,7 +148,7 @@ def parse_date(datestring): hour, minute = list(map(int, time.split(':'))) if validate_date(year, month, day, hour, minute): return datetime.datetime( - year, month, day, hour, minute, tzinfo=pytz.UTC) + year, month, day, hour, minute, tzinfo=get_utc_timezone()) return None diff --git a/lms/djangoapps/ccx/views.py b/lms/djangoapps/ccx/views.py index 7c6a75aaf6d4..d6e7e9a004ea 100644 --- a/lms/djangoapps/ccx/views.py +++ b/lms/djangoapps/ccx/views.py @@ -8,7 +8,7 @@ import logging from copy import deepcopy -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from ccx_keys.locator import CCXLocator from django.conf import settings from django.contrib import messages @@ -190,7 +190,7 @@ def create_ccx(request, course, ccx=None): ccx.save() # Make sure start/due are overridden for entire course - start = TODAY().replace(tzinfo=pytz.UTC) + start = TODAY().replace(tzinfo=get_utc_timezone()) override_field_for_ccx(ccx, course, 'start', start) override_field_for_ccx(ccx, course, 'due', None) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 02b254ffad6c..8adc9eaa5a1a 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -17,7 +17,7 @@ from opaque_keys.edx.django.models import CourseKeyField from opaque_keys.edx.keys import CourseKey from organizations.api import get_course_organization_id -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.api import is_user_enrolled_in_course @@ -887,7 +887,7 @@ def display_date_for_certificate(course, certificate): except ObjectDoesNotExist: pass - if _course_uses_available_date(course) and course.certificate_available_date < datetime.now(UTC): + if _course_uses_available_date(course) and course.certificate_available_date < datetime.now(get_utc_timezone()): return course.certificate_available_date # It is possible for a self-paced course run to end up configured with a display behavior of "END" even though it # shouldn't be a valid option. We must check if the course is instructor-paced here to ensure that we are selecting diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index ca12c989660d..222f1a824adc 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -7,7 +7,7 @@ from unittest.mock import patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from config_models.models import cache from django.conf import settings from django.test import RequestFactory, TestCase @@ -120,9 +120,9 @@ def setUp(self): org="edx", number="verified", display_name="Verified Course", - end=datetime.now(pytz.UTC), + end=datetime.now(get_utc_timezone()), self_paced=False, - certificate_available_date=datetime.now(pytz.UTC) - timedelta(days=2), + certificate_available_date=datetime.now(get_utc_timezone()) - timedelta(days=2), ) GeneratedCertificateFactory.create( @@ -233,7 +233,7 @@ def test_cert_api_return( """ Test 'downloadable status' """ - cert_avail_date = datetime.now(pytz.UTC) + cert_avail_delta + cert_avail_date = datetime.now(get_utc_timezone()) + cert_avail_delta self.course.self_paced = self_paced self.course.certificate_available_date = cert_avail_date self.course.certificates_display_behavior = certificates_display_behavior @@ -1046,8 +1046,8 @@ def __init__(self, user=None, course_id=None, mode=None, status=None): self.course_id = course_id self.mode = mode self.status = status - self.created_date = datetime.now(pytz.UTC) - self.modified_date = datetime.now(pytz.UTC) + self.created_date = datetime.now(get_utc_timezone()) + self.modified_date = datetime.now(get_utc_timezone()) self.date_override = None def is_valid(self): @@ -1059,7 +1059,7 @@ def is_valid(self): class MockCertificateDateOverride: def __init__(self, date=None): - self.date = date or datetime.now(pytz.UTC) + self.date = date or datetime.now(get_utc_timezone()) @contextmanager @@ -1080,8 +1080,8 @@ class CertificatesApiTestCase(TestCase): def setUp(self): super().setUp() self.course = CourseOverviewFactory.create( - start=datetime(2017, 1, 1, tzinfo=pytz.UTC), - end=datetime(2017, 1, 31, tzinfo=pytz.UTC), + start=datetime(2017, 1, 1, tzinfo=get_utc_timezone()), + end=datetime(2017, 1, 31, tzinfo=get_utc_timezone()), certificate_available_date=None, ) self.user = UserFactory.create() @@ -1126,14 +1126,14 @@ def test_available_vs_display_date(self, feature_enabled, is_self_paced, uses_av assert self.certificate.modified_date == display_date_for_certificate(self.course, self.certificate) # With an available date set in the past, both return the available date (if configured) - self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=pytz.UTC) + self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=get_utc_timezone()) self.course.certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date assert maybe_avail == available_date_for_certificate(self.course, self.certificate) assert maybe_avail == display_date_for_certificate(self.course, self.certificate) # With a future available date, they each return a different date - self.course.certificate_available_date = datetime.max.replace(tzinfo=pytz.UTC) + self.course.certificate_available_date = datetime.max.replace(tzinfo=get_utc_timezone()) maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date assert maybe_avail == available_date_for_certificate(self.course, self.certificate) assert self.certificate.modified_date == display_date_for_certificate(self.course, self.certificate) @@ -1162,7 +1162,7 @@ def test_display_date_for_certificate_cdb_end_with_date(self): with configure_waffle_namespace(True): self.course.self_paced = False self.course.certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE - self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=pytz.UTC) + self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=get_utc_timezone()) assert display_date_for_certificate(self.course, self.certificate) == self.course.certificate_available_date def test_display_date_for_certificate_cdb_end(self): @@ -1181,7 +1181,7 @@ def test_display_date_for_certificate_date_override(self): if-and-only-if date override associated with the certificate instance. """ with configure_waffle_namespace(True): - self.certificate.date_override = datetime(2016, 1, 1, tzinfo=pytz.UTC) + self.certificate.date_override = datetime(2016, 1, 1, tzinfo=get_utc_timezone()) assert display_date_for_certificate(self.course, self.certificate) == self.certificate.date_override.date def test_display_date_for_self_paced_course_run(self): diff --git a/lms/djangoapps/certificates/tests/test_utils.py b/lms/djangoapps/certificates/tests/test_utils.py index 298e624fdd8e..5793fae6d4dd 100644 --- a/lms/djangoapps/certificates/tests/test_utils.py +++ b/lms/djangoapps/certificates/tests/test_utils.py @@ -6,13 +6,13 @@ import ddt from django.test import TestCase -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.certificates.utils import has_html_certificates_enabled, should_certificate_be_visible from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order -_TODAY = datetime.now(utc) +_TODAY = datetime.now(get_utc_timezone()) _LAST_MONTH = _TODAY - timedelta(days=30) _LAST_WEEK = _TODAY - timedelta(days=7) _NEXT_WEEK = _TODAY + timedelta(days=7) diff --git a/lms/djangoapps/certificates/tests/tests.py b/lms/djangoapps/certificates/tests/tests.py index a6b6362792b8..a55c9a171392 100644 --- a/lms/djangoapps/certificates/tests/tests.py +++ b/lms/djangoapps/certificates/tests/tests.py @@ -9,7 +9,7 @@ from ddt import data, ddt, unpack from django.conf import settings from milestones.tests.utils import MilestonesTestCaseMixin -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory @@ -33,7 +33,7 @@ class CertificatesModelTest(ModuleStoreTestCase, MilestonesTestCaseMixin): def setUp(self): super().setUp() - today = datetime.now(UTC) + today = datetime.now(get_utc_timezone()) self.instructor_paced_course = CourseFactory.create( org='edx', number='instructor', display_name='Instructor Paced Course', start=today - timedelta(days=30), diff --git a/lms/djangoapps/certificates/utils.py b/lms/djangoapps/certificates/utils.py index 7ff2a4c97b27..ed2a6f127b73 100644 --- a/lms/djangoapps/certificates/utils.py +++ b/lms/djangoapps/certificates/utils.py @@ -8,7 +8,7 @@ from django.urls import reverse from eventtracking import tracker from opaque_keys.edx.keys import CourseKey -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student import models_api as student_api from lms.djangoapps.certificates.data import CertificateStatuses @@ -160,7 +160,7 @@ def should_certificate_be_visible( past_available_date = ( certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE and certificate_available_date - and certificate_available_date < datetime.now(utc) + and certificate_available_date < datetime.now(get_utc_timezone()) ) ended_without_available_date = ( certificates_display_behavior == CertificatesDisplayBehaviors.END diff --git a/lms/djangoapps/certificates/views/webview.py b/lms/djangoapps/certificates/views/webview.py index 3b6cc75e4890..8f3f59971d79 100644 --- a/lms/djangoapps/certificates/views/webview.py +++ b/lms/djangoapps/certificates/views/webview.py @@ -7,8 +7,8 @@ import urllib from datetime import datetime from uuid import uuid4 +from zoneinfo import ZoneInfo -import pytz from django.conf import settings from django.contrib.auth.decorators import login_required from django.http import Http404, HttpResponse, HttpResponseRedirect @@ -175,7 +175,7 @@ def _update_context_with_basic_info(context, course_id, platform_name, configura # Translators: 'All rights reserved' is a legal term used in copyrighting to protect published content reserved = _("All rights reserved") context['copyright_text'] = '© {year} {platform_name}. {reserved}.'.format( - year=datetime.now(pytz.timezone(settings.TIME_ZONE)).year, + year=datetime.now(ZoneInfo(settings.TIME_ZONE)).year, platform_name=platform_name, reserved=reserved ) diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py index ef9327fe6f43..e37dec891378 100644 --- a/lms/djangoapps/commerce/api/v0/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py @@ -8,7 +8,7 @@ from uuid import uuid4 import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.test import TestCase from django.test.utils import override_settings @@ -272,7 +272,7 @@ def test_closed_course(self): """ Verifies that the view returns HTTP 406 when a course is closed. """ - self.course.enrollment_end = datetime.now(pytz.UTC) - timedelta(days=1) + self.course.enrollment_end = datetime.now(get_utc_timezone()) - timedelta(days=1) modulestore().update_item(self.course, self.user.id) assert self._post_to_view().status_code == 406 diff --git a/lms/djangoapps/commerce/api/v1/serializers.py b/lms/djangoapps/commerce/api/v1/serializers.py index 8d7fb25cb463..0d7fd40c3e77 100644 --- a/lms/djangoapps/commerce/api/v1/serializers.py +++ b/lms/djangoapps/commerce/api/v1/serializers.py @@ -3,7 +3,7 @@ from datetime import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.utils.translation import gettext as _ from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey @@ -95,7 +95,7 @@ def validate(self, attrs): if expires: # If we don't already have an upgrade_deadline value, use datetime.max so that we can actually # complete the comparison. - upgrade_deadline = min(expires, upgrade_deadline or datetime.max.replace(tzinfo=pytz.utc)) + upgrade_deadline = min(expires, upgrade_deadline or datetime.max.replace(tzinfo=get_utc_timezone())) # In cases where upgrade_deadline is None (e.g. the verified professional mode), allow a verification # deadline to be set anyway. diff --git a/lms/djangoapps/commerce/api/v1/tests/test_views.py b/lms/djangoapps/commerce/api/v1/tests/test_views.py index 71b9a4810f19..1687143db053 100644 --- a/lms/djangoapps/commerce/api/v1/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v1/tests/test_views.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.contrib.auth.models import Permission from django.test import TestCase @@ -176,7 +176,7 @@ def test_update(self): assert VerificationDeadline.deadline_for_course(self.course.id) is None # Generate the expected data - now = datetime.now(pytz.utc) + now = datetime.now(get_utc_timezone()) verification_deadline = now + timedelta(days=1) expiration_datetime = now response, expected = self._get_update_response_and_expected_data(expiration_datetime, verification_deadline) @@ -195,8 +195,8 @@ def test_update_invalid_dates(self): """ Verify the API does not allow the verification deadline to be set before the course mode upgrade deadlines. """ - expiration_datetime = datetime.now(pytz.utc) - verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc) + expiration_datetime = datetime.now(get_utc_timezone()) + verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=get_utc_timezone()) response, __ = self._get_update_response_and_expected_data(expiration_datetime, verification_deadline) assert response.status_code == 400 @@ -212,7 +212,7 @@ def test_update_verification_deadline_without_expiring_modes(self): This accounts for the verified professional mode, which requires verification but should never expire. """ - verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc) + verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=get_utc_timezone()) response, __ = self._get_update_response_and_expected_data(None, verification_deadline) assert response.status_code == 200 @@ -222,7 +222,7 @@ def test_update_remove_verification_deadline(self): """ Verify that verification deadlines can be removed through the API. """ - verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc) + verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=get_utc_timezone()) response, __ = self._get_update_response_and_expected_data(None, verification_deadline) assert VerificationDeadline.deadline_for_course(self.course.id) == verification_deadline @@ -247,7 +247,7 @@ def test_update_verification_deadline_left_alone(self): When the course's verification deadline is set and an update request doesn't include it, we should take no action on it. """ - verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc) + verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=get_utc_timezone()) response, __ = self._get_update_response_and_expected_data(None, verification_deadline) assert VerificationDeadline.deadline_for_course(self.course.id) == verification_deadline @@ -273,7 +273,7 @@ def test_remove_upgrade_deadline(self): Verify that course mode upgrade deadlines can be removed through the API. """ # First create a deadline - upgrade_deadline = datetime.now(pytz.utc) + timedelta(days=1) + upgrade_deadline = datetime.now(get_utc_timezone()) + timedelta(days=1) response, __ = self._get_update_response_and_expected_data(upgrade_deadline, None) assert response.status_code == 200 verified_mode = CourseMode.verified_mode_for_course(self.course.id) diff --git a/lms/djangoapps/course_blocks/transformers/hidden_content.py b/lms/djangoapps/course_blocks/transformers/hidden_content.py index e82d8d3441d6..edc703fe25ea 100644 --- a/lms/djangoapps/course_blocks/transformers/hidden_content.py +++ b/lms/djangoapps/course_blocks/transformers/hidden_content.py @@ -5,14 +5,14 @@ from datetime import datetime -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.content.block_structure.transformer import BlockStructureTransformer from xmodule.seq_block import SequenceBlock # lint-amnesty, pylint: disable=wrong-import-order from .utils import collect_merged_boolean_field, collect_merged_date_field -MAXIMUM_DATE = utc.localize(datetime.max) +MAXIMUM_DATE = datetime.max.replace(tzinfo=get_utc_timezone()) class HiddenContentTransformer(BlockStructureTransformer): diff --git a/lms/djangoapps/course_blocks/transformers/start_date.py b/lms/djangoapps/course_blocks/transformers/start_date.py index 13ba3b7470c2..32359f27bdc1 100644 --- a/lms/djangoapps/course_blocks/transformers/start_date.py +++ b/lms/djangoapps/course_blocks/transformers/start_date.py @@ -4,7 +4,7 @@ from datetime import datetime -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.courseware.access_utils import check_start_date from openedx.core.djangoapps.content.block_structure.transformer import ( @@ -94,7 +94,7 @@ def transform_block_filters(self, usage_info, block_structure): if usage_info.has_staff_access or usage_info.allow_start_dates_in_future: return [block_structure.create_universal_filter()] - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) def _removal_condition(block_key): return not check_start_date( diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_load_override_data.py b/lms/djangoapps/course_blocks/transformers/tests/test_load_override_data.py index d0fef5065b43..e7399970c95d 100644 --- a/lms/djangoapps/course_blocks/transformers/tests/test_load_override_data.py +++ b/lms/djangoapps/course_blocks/transformers/tests/test_load_override_data.py @@ -6,7 +6,7 @@ import datetime import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase from xmodule.modulestore.tests.factories import ToyCourseFactory @@ -18,11 +18,11 @@ expected_overrides = { 'start': datetime.datetime( - 2017, 1, 20, 2, 42, tzinfo=pytz.UTC + 2017, 1, 20, 2, 42, tzinfo=get_utc_timezone() ), 'display_name': "Section", 'due': datetime.datetime( - 2017, 2, 20, 2, 42, tzinfo=pytz.UTC + 2017, 2, 20, 2, 42, tzinfo=get_utc_timezone() ) } diff --git a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py index 46d46832a6d9..2e646e8830a0 100644 --- a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py @@ -5,7 +5,7 @@ from botocore.exceptions import NoCredentialsError from django.contrib.sites.models import Site from edx_ace import Recipient, Message -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from unittest import mock # lint-amnesty, pylint: disable=wrong-import-order import ddt @@ -55,8 +55,8 @@ def make_valid_goal(self, **kwargs): """Creates a goal that will cause an email to be sent as the goal is valid but has been missed""" kwargs.setdefault('days_per_week', 6) kwargs.setdefault('subscribed_to_reminders', True) - kwargs.setdefault('overview__start', datetime(2021, 1, 1, tzinfo=UTC)) - kwargs.setdefault('overview__end', datetime(2021, 4, 1, tzinfo=UTC)) # Have it end in the future + kwargs.setdefault('overview__start', datetime(2021, 1, 1, tzinfo=get_utc_timezone())) + kwargs.setdefault('overview__end', datetime(2021, 4, 1, tzinfo=get_utc_timezone())) # Have it end in the future goal = CourseGoalFactory(**kwargs) with freeze_time('2021-02-01 10:00:00'): # Create enrollment before March @@ -114,7 +114,8 @@ def test_will_send_on_right_day(self, days_per_week, days_of_activity, current_d """Verify that via the normal conditions, we either send or not based on the days of activity""" goal = self.make_valid_goal(days_per_week=days_per_week) for day in range(days_of_activity): - UserActivityFactory(user=goal.user, course_key=goal.course_key, date=datetime(2021, 3, day + 1, tzinfo=UTC)) + UserActivityFactory(user=goal.user, course_key=goal.course_key, + date=datetime(2021, 3, day + 1, tzinfo=get_utc_timezone())) self.call_command(day=current_day, expect_sent=expect_sent) @@ -150,12 +151,12 @@ def test_inactive_enrollment(self): def test_recent_enrollment(self): self.make_valid_goal() - CourseEnrollment.objects.update(created=datetime(2021, 3, 1, tzinfo=UTC)) + CourseEnrollment.objects.update(created=datetime(2021, 3, 1, tzinfo=get_utc_timezone())) self.call_command(expect_sent=False) @ddt.data( - (datetime(2021, 3, 8, tzinfo=UTC), True), - (datetime(2021, 3, 7, tzinfo=UTC), False), + (datetime(2021, 3, 8, tzinfo=get_utc_timezone()), True), + (datetime(2021, 3, 7, tzinfo=get_utc_timezone()), False), ) @ddt.unpack @mock.patch('lms.djangoapps.course_goals.management.commands.goal_reminder_email.get_user_course_expiration_date') @@ -183,8 +184,8 @@ def test_no_days_per_week(self): self.call_command(expect_sent=False) @ddt.data( - datetime(2021, 2, 1, tzinfo=UTC), # very over and done with - datetime(2021, 3, 7, tzinfo=UTC), # ending this Sunday + datetime(2021, 2, 1, tzinfo=get_utc_timezone()), # very over and done with + datetime(2021, 3, 7, tzinfo=get_utc_timezone()), # ending this Sunday ) def test_old_course(self, end): self.make_valid_goal(overview__end=end) diff --git a/lms/djangoapps/course_home_api/progress/serializers.py b/lms/djangoapps/course_home_api/progress/serializers.py index 6bdc204434af..0b2e25518879 100644 --- a/lms/djangoapps/course_home_api/progress/serializers.py +++ b/lms/djangoapps/course_home_api/progress/serializers.py @@ -5,7 +5,7 @@ from rest_framework import serializers from rest_framework.reverse import reverse -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.course_home_api.serializers import ReadOnlySerializer, VerifiedModeSerializer @@ -68,7 +68,7 @@ def get_url(self, subsection): """ hide_url_date = subsection.end if subsection.self_paced else subsection.due if (not self.context['staff_access'] and subsection.hide_after_due and hide_url_date - and datetime.now(UTC) > hide_url_date): + and datetime.now(get_utc_timezone()) > hide_url_date): return None relative_path = reverse('jump_to', args=[self.context['course_key'], subsection.location]) diff --git a/lms/djangoapps/course_home_api/progress/tests/test_views.py b/lms/djangoapps/course_home_api/progress/tests/test_views.py index d13ebec29c21..1d4587c1546d 100644 --- a/lms/djangoapps/course_home_api/progress/tests/test_views.py +++ b/lms/djangoapps/course_home_api/progress/tests/test_views.py @@ -10,7 +10,7 @@ from django.urls import reverse from django.utils.timezone import now from edx_toggles.toggles.testutils import override_waffle_flag -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore.tests.factories import BlockFactory from common.djangoapps.course_modes.models import CourseMode @@ -133,7 +133,7 @@ def test_end(self): self.update_course(self.course, self.user.id) response = self.client.get(self.url) assert response.status_code == 200 - end = dateutil.parser.parse(response.json()['end']).replace(tzinfo=UTC) + end = dateutil.parser.parse(response.json()['end']).replace(tzinfo=get_utc_timezone()) assert end.date() == future.date() def test_user_has_passing_grade(self): diff --git a/lms/djangoapps/courseware/access_utils.py b/lms/djangoapps/courseware/access_utils.py index 23ec992f8189..33548671f054 100644 --- a/lms/djangoapps/courseware/access_utils.py +++ b/lms/djangoapps/courseware/access_utils.py @@ -9,7 +9,7 @@ from crum import get_current_request from django.conf import settings from enterprise.models import EnterpriseCourseEnrollment, EnterpriseCustomerUser -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.roles import CourseBetaTesterRole @@ -141,7 +141,7 @@ def check_start_date(user, days_early_for_beta, start, course_key, display_error return ACCESS_GRANTED if now is None: - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) effective_start = adjust_start_date(user, days_early_for_beta, start, course_key) should_grant_access = now > effective_start diff --git a/lms/djangoapps/courseware/context_processor.py b/lms/djangoapps/courseware/context_processor.py index 2832c577ca46..28dcfb2c82af 100644 --- a/lms/djangoapps/courseware/context_processor.py +++ b/lms/djangoapps/courseware/context_processor.py @@ -6,10 +6,11 @@ """ import string +from zoneinfo import ZoneInfo from django.utils.translation import get_language -from pytz import timezone -from pytz.exceptions import UnknownTimeZoneError +from openedx.core.lib.time_zone_utils import get_utc_timezone + from edx_django_utils.cache import TieredCache from lms.djangoapps.courseware.models import LastSeenCoursewareTimezone @@ -97,6 +98,6 @@ def get_user_timezone_or_last_seen_timezone_or_utc(user): user_timezone = filter(lambda l: l in string.printable, user_timezone) user_timezone = ''.join(user_timezone) try: - return timezone(user_timezone) - except UnknownTimeZoneError as err: - return timezone('UTC') + return ZoneInfo(user_timezone) + except KeyError: + return get_utc_timezone() diff --git a/lms/djangoapps/courseware/course_tools.py b/lms/djangoapps/courseware/course_tools.py index c0e37c76e068..ece15909a0c6 100644 --- a/lms/djangoapps/courseware/course_tools.py +++ b/lms/djangoapps/courseware/course_tools.py @@ -5,7 +5,7 @@ import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.urls import reverse from django.utils.translation import gettext as _ @@ -33,7 +33,7 @@ def is_enabled(cls, request, course_key): """ Show this link for active courses where financial assistance is available, unless upgrade deadline has passed """ - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(get_utc_timezone()) feature_flags = None try: course_overview = CourseOverview.objects.get(id=course_key) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index a8333dd52ed2..38691d4b0ede 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -9,7 +9,7 @@ from datetime import datetime import six -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from crum import get_current_request from dateutil.parser import parse as parse_date from django.conf import settings @@ -319,7 +319,7 @@ def course_open_for_self_enrollment(course_key): # Check the enrollment start and end dates. course_details = get_course_enrollment_details(str(course_key)) - now = datetime.now().replace(tzinfo=pytz.UTC) + now = datetime.now().replace(tzinfo=get_utc_timezone()) start = course_details['enrollment_start'] end = course_details['enrollment_end'] @@ -533,7 +533,7 @@ def date_block_key_fn(block): If the block's date is None, return the maximum datetime in order to force it to the end of the list of displayed blocks. """ - return block.date or datetime.max.replace(tzinfo=pytz.UTC) + return block.date or datetime.max.replace(tzinfo=get_utc_timezone()) def _get_absolute_url(request, url_path): @@ -634,7 +634,7 @@ def get_course_assignments(course_key, user, include_access=False, include_witho course_usage_key = store.make_course_usage_key(course_key) block_data = get_course_blocks(user, course_usage_key, allow_start_dates_in_future=True, include_completion=True) - now = datetime.now(pytz.UTC) + now = datetime.now(get_utc_timezone()) assignments = [] for section_key in block_data.get_children(course_usage_key): # lint-amnesty, pylint: disable=too-many-nested-blocks for subsection_key in block_data.get_children(section_key): @@ -755,9 +755,9 @@ def _ora_assessment_to_assignment( else: assessment_start, assessment_due = None, None if assessment.get('start'): - assessment_start = parse_date(assessment.get('start')).replace(tzinfo=pytz.UTC) + assessment_start = parse_date(assessment.get('start')).replace(tzinfo=get_utc_timezone()) if assessment.get('due'): - assessment_due = parse_date(assessment.get('due')).replace(tzinfo=pytz.UTC) + assessment_due = parse_date(assessment.get('due')).replace(tzinfo=get_utc_timezone()) extra_info = _( "This Open Response Assessment's due dates are set by your instructor and can't be shifted." ) @@ -781,7 +781,7 @@ def _ora_assessment_to_assignment( assessment_type = assessment_name title = f"{block_title} ({assessment_type})" url = '' - now = datetime.now(pytz.UTC) + now = datetime.now(get_utc_timezone()) assignment_released = not assessment_start or assessment_start < now if assignment_released: url = reverse('jump_to', args=[block_key.course_key, ora_block]) diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index e6bd5ef70597..b72abdf2dfdc 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -15,7 +15,7 @@ from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy from lazy import lazy -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.certificates.api import get_active_web_certificate, can_show_certificate_available_date_field @@ -42,7 +42,7 @@ def current_time(self): Returns a consistent current time. """ if self._current_time is None: - self._current_time = datetime.datetime.now(utc) + self._current_time = datetime.datetime.now(get_utc_timezone()) return self._current_time @property diff --git a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py index 97a20deac993..ea9f1e2d4b37 100644 --- a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py +++ b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py @@ -7,7 +7,7 @@ from io import StringIO import factory -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.core.management import call_command @@ -22,8 +22,8 @@ DATA_DIR = settings.COMMON_TEST_DATA_ROOT XML_COURSE_DIRS = ['simple'] -TEST_COURSE_START = datetime.datetime(2012, 7, 1, tzinfo=pytz.UTC) -TEST_COURSE_END = datetime.datetime(2012, 12, 31, tzinfo=pytz.UTC) +TEST_COURSE_START = datetime.datetime(2012, 7, 1, tzinfo=get_utc_timezone()) +TEST_COURSE_END = datetime.datetime(2012, 12, 31, tzinfo=get_utc_timezone()) class CommandsTestBase(SharedModuleStoreTestCase): diff --git a/lms/djangoapps/courseware/masquerade.py b/lms/djangoapps/courseware/masquerade.py index 5ce266c463d6..b323689d8e28 100644 --- a/lms/djangoapps/courseware/masquerade.py +++ b/lms/djangoapps/courseware/masquerade.py @@ -15,7 +15,7 @@ from django.utils.translation import gettext as _ from django.views import View from opaque_keys.edx.keys import CourseKey -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from web_fragments.fragment import Fragment from xblock.runtime import KeyValueStore @@ -387,7 +387,7 @@ def check_content_start_date_for_masquerade_user(course_key, user, request, cour Add a warning message if the masquerade user would not have access to this content due to the content start date being in the future. """ - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) most_future_date = course_start if chapter_start and section_start: most_future_date = max(course_start, chapter_start, section_start) diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index bd0c1854ab76..3870eb144488 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -8,7 +8,7 @@ from unittest.mock import patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.test.utils import override_settings from django.urls import reverse @@ -358,7 +358,7 @@ def setUp(self): self.course = CourseFactory.create(metadata={"invitation_only": False}) # Setup enrollment period to be in future - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(get_utc_timezone()) tomorrow = now + datetime.timedelta(days=1) nextday = tomorrow + datetime.timedelta(days=1) diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index 5b910565e4ae..4c279e26f720 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -9,7 +9,7 @@ from unittest.mock import Mock, patch import pytest import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from ccx_keys.locator import CCXLocator from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -175,8 +175,8 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes TOMORROW = 'tomorrow' YESTERDAY = 'yesterday' DATES = { - TOMORROW: datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1), - YESTERDAY: datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1), + TOMORROW: datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1), + YESTERDAY: datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1), None: None, } @@ -466,8 +466,8 @@ def test__has_access_to_block_with_start_date(self, start, expected_error_type): self.verify_access(mock_unit, expected_access, expected_error_type) def test__has_access_course_can_enroll(self): - yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) - tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) + yesterday = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1) + tomorrow = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) # Non-staff can enroll if authenticated and specifically allowed for that course # even outside the open enrollment period @@ -507,8 +507,8 @@ def test__has_access_course_can_enroll(self): @override_settings(FEATURES={**settings.FEATURES, 'DISABLE_ALLOWED_ENROLLMENT_IF_ENROLLMENT_CLOSED': True}) def test__has_access_course_with_disable_allowed_enrollment_flag(self): - yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) - tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) + yesterday = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1) + tomorrow = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) # Non-staff user invited to course, cannot enroll outside the open enrollment period # if DISABLE_ALLOWED_ENROLLMENT_IF_ENROLLMENT_CLOSED is True @@ -586,8 +586,8 @@ def test_old_mongo_is_invite_only(self, old_mongo): self.assertEqual(access._has_access_course(user, 'enroll', course).has_access, not old_mongo) def _mock_course_with_invitation(self, invitation, deprecated=False): - yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) - tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) + yesterday = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1) + tomorrow = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) return Mock( enrollment_start=yesterday, enrollment_end=tomorrow, id=CourseLocator('edX', 'test', '2012_Fall', deprecated=deprecated), enrollment_domain='', @@ -733,7 +733,7 @@ class CourseOverviewAccessTestCase(ModuleStoreTestCase): def setUp(self): super().setUp() - today = datetime.datetime.now(pytz.UTC) + today = datetime.datetime.now(get_utc_timezone()) last_week = today - datetime.timedelta(days=7) next_week = today + datetime.timedelta(days=7) diff --git a/lms/djangoapps/courseware/tests/test_block_render.py b/lms/djangoapps/courseware/tests/test_block_render.py index 182f8d0c03ab..95332f643abc 100644 --- a/lms/djangoapps/courseware/tests/test_block_render.py +++ b/lms/djangoapps/courseware/tests/test_block_render.py @@ -10,7 +10,7 @@ import pytest import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from bson import ObjectId from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH # lint-amnesty, pylint: disable=wrong-import-order from completion.models import BlockCompletion # lint-amnesty, pylint: disable=wrong-import-order @@ -2161,7 +2161,7 @@ def test_xblock_runtime_publish_delete(self): @patch('lms.djangoapps.grades.signals.handlers.PROBLEM_RAW_SCORE_CHANGED.send') def test_score_change_signal(self, send_mock): """Test that a Django signal is generated when a score changes""" - with freeze_time(datetime.now().replace(tzinfo=pytz.UTC)): + with freeze_time(datetime.now().replace(tzinfo=get_utc_timezone())): self.set_block_grade_using_publish(self.grade_dict) expected_signal_kwargs = { 'sender': None, @@ -2172,7 +2172,7 @@ def test_score_change_signal(self, send_mock): 'course_id': str(self.course.id), 'usage_id': str(self.problem.location), 'only_if_higher': None, - 'modified': datetime.now().replace(tzinfo=pytz.UTC), + 'modified': datetime.now().replace(tzinfo=get_utc_timezone()), 'score_db_table': 'csm', 'score_deleted': None, 'grader_response': None diff --git a/lms/djangoapps/courseware/tests/test_context_processor.py b/lms/djangoapps/courseware/tests/test_context_processor.py index e1a65d822fe4..bd8c6fc6c29f 100644 --- a/lms/djangoapps/courseware/tests/test_context_processor.py +++ b/lms/djangoapps/courseware/tests/test_context_processor.py @@ -2,8 +2,10 @@ Unit tests for courseware context_processor """ -from pytz import timezone +from openedx.core.lib.time_zone_utils import get_utc_timezone from unittest.mock import Mock, patch # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo + from django.conf import settings from django.contrib.auth.models import AnonymousUser @@ -60,20 +62,25 @@ def test_get_user_timezone_or_last_seen_timezone_or_utc(self): # We default to UTC course = CourseFactory() time_zone = get_user_timezone_or_last_seen_timezone_or_utc(self.user) - assert time_zone == timezone('UTC') + # Check that we get some form of UTC timezone - both should represent the same timezone + expected_utc = get_utc_timezone() + # Compare timezone names/representations since the objects might be different types + assert str(time_zone).upper() in ['UTC', '+00:00'] or time_zone == expected_utc # We record the timezone when a user hits the courseware api. Also sanitize input test self.client.login(username=self.user.username, password='foo') self.client.get(f'/api/courseware/course/{course.id}?browser_timezone=America/New_York\x00') time_zone = get_user_timezone_or_last_seen_timezone_or_utc(self.user) - assert time_zone == timezone('America/New_York') + assert time_zone == ZoneInfo('America/New_York') # If a user has their timezone set, then we use that setting set_user_preference(self.user, 'time_zone', 'Asia/Tokyo') time_zone = get_user_timezone_or_last_seen_timezone_or_utc(self.user) - assert time_zone == timezone('Asia/Tokyo') + assert time_zone == ZoneInfo('Asia/Tokyo') # If we do not recognize the user's timezone, we default to UTC with patch('lms.djangoapps.courseware.context_processor.get_user_preference', return_value='Unknown/Timezone'): time_zone = get_user_timezone_or_last_seen_timezone_or_utc(self.user) - assert time_zone == timezone('UTC') + # Check that we get some form of UTC timezone - both should represent the same timezone + expected_utc = get_utc_timezone() + assert str(time_zone).upper() in ['UTC', '+00:00'] or time_zone == expected_utc diff --git a/lms/djangoapps/courseware/tests/test_course_tools.py b/lms/djangoapps/courseware/tests/test_course_tools.py index e1d3391e2991..089738259d46 100644 --- a/lms/djangoapps/courseware/tests/test_course_tools.py +++ b/lms/djangoapps/courseware/tests/test_course_tools.py @@ -7,7 +7,7 @@ from unittest.mock import patch import crum -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import RequestFactory from common.djangoapps.course_modes.models import CourseMode @@ -27,7 +27,7 @@ class FinancialAssistanceToolTest(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.now = datetime.datetime.now(pytz.UTC) + cls.now = datetime.datetime.now(get_utc_timezone()) cls.course = CourseFactory.create( org='edX', diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py index 804f9037791f..046614e38483 100644 --- a/lms/djangoapps/courseware/tests/test_courses.py +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -9,7 +9,7 @@ from unittest import mock import pytest import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from completion.models import BlockCompletion from completion.test_utils import CompletionWaffleTestMixin from crum import set_current_request @@ -313,7 +313,7 @@ def test_get_course_about_section_render(self): class CourseEnrollmentOpenTests(ModuleStoreTestCase): # lint-amnesty, pylint: disable=missing-class-docstring def setUp(self): super().setUp() - self.now = datetime.datetime.now().replace(tzinfo=pytz.UTC) + self.now = datetime.datetime.now().replace(tzinfo=get_utc_timezone()) def test_course_enrollment_open(self): start = self.now - datetime.timedelta(days=1) @@ -486,7 +486,7 @@ def test_completion_does_not_treat_unreleased_as_complete(self): @ddt.ddt class TestGetCourseAssignmentsORA(CompletionWaffleTestMixin, ModuleStoreTestCase): """ Tests for ora-related behavior in get_course_assignments """ - TODAY = datetime.datetime(2023, 8, 2, 12, 23, 45, tzinfo=pytz.UTC) + TODAY = datetime.datetime(2023, 8, 2, 12, 23, 45, tzinfo=get_utc_timezone()) def setUp(self): super().setUp() diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index 1762d37bdee0..cdff73a079de 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -11,7 +11,7 @@ from django.test import RequestFactory from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from freezegun import freeze_time -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory @@ -129,7 +129,7 @@ def test_enabled_block_types_with_assignments(self): # pylint: disable=too-many user = create_user() request = self.make_request(user) CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) assignment_title_html = [''] with self.store.bulk_operations(course.id): section = BlockFactory.create(category='chapter', parent_location=course.location) @@ -318,7 +318,7 @@ def test_dates_with_openassessments(self, rubric_assessments, date_block_count): user = create_user() request = self.make_request(user) CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) chapter = BlockFactory.create( parent=course, @@ -350,7 +350,8 @@ def test_enabled_block_types_with_expired_course(self): self.make_request(user) # These two lines are to trigger the course expired block to be rendered CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.AUDIT) - CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=utc)) + CourseDurationLimitConfig.objects.create( + enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) expected_blocks = ( TodaysDate, CourseEndDate, CourseExpiredDate, VerifiedUpgradeDeadlineDate @@ -390,7 +391,7 @@ def test_todays_date_block(self): block = TodaysDate(course, user) assert block.is_enabled assert block.is_allowed - assert block.date == datetime.now(utc) + assert block.date == datetime.now(get_utc_timezone()) assert block.title == 'current_datetime' ## Tests Course Start Date @@ -402,19 +403,21 @@ def test_course_start_date(self): @ddt.data( # Instructor-paced course: Use course start date - (False, datetime(2025, 1, 10, tzinfo=utc), datetime(2025, 1, 12, tzinfo=utc), - datetime(2025, 1, 10, tzinfo=utc), 'Course starts'), + (False, datetime(2025, 1, 10, tzinfo=get_utc_timezone()), + datetime(2025, 1, 12, tzinfo=get_utc_timezone()), + datetime(2025, 1, 10, tzinfo=get_utc_timezone()), 'Course starts'), # Self-paced course: Enrollment created later than course start - (True, datetime(2025, 1, 10, tzinfo=utc), datetime(2025, 1, 12), datetime(2025, 1, 12, tzinfo=utc), - 'Enrollment Date'), + (True, datetime(2025, 1, 10, tzinfo=get_utc_timezone()), datetime(2025, 1, 12), + datetime(2025, 1, 12, tzinfo=get_utc_timezone()), 'Enrollment Date'), # Self-paced course: Enrollment created earlier than course start - (True, datetime(2025, 1, 10, tzinfo=utc), datetime(2025, 1, 8), datetime(2025, 1, 10, tzinfo=utc), - 'Course starts'), + (True, datetime(2025, 1, 10, tzinfo=get_utc_timezone()), datetime(2025, 1, 8), + datetime(2025, 1, 10, tzinfo=get_utc_timezone()), 'Course starts'), # Self-paced course: No enrollment - (True, datetime(2025, 1, 10, tzinfo=utc), None, datetime(2025, 1, 10, tzinfo=utc), 'Course starts'), + (True, datetime(2025, 1, 10, tzinfo=get_utc_timezone()), None, + datetime(2025, 1, 10, tzinfo=get_utc_timezone()), 'Course starts'), ) @ddt.unpack def test_course_start_date_label(self, self_paced, course_start, enrollment_created, expected_date, expected_title): @@ -465,7 +468,7 @@ def test_course_end_date_self_paced(self, days_till_end): In self-paced courses, the end date will only show up if the learner views the course within 365 days of the course end date. """ - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) course = CourseFactory.create( start=now + timedelta(days=-7), end=now + timedelta(days=days_till_end), self_paced=True) user = create_user() @@ -507,7 +510,7 @@ def test_no_certificate_available_date_for_self_paced(self): course = create_self_paced_course_run() verified_user = create_user() CourseEnrollmentFactory(course_id=course.id, user=verified_user, mode=CourseMode.VERIFIED) - course.certificate_available_date = datetime.now(utc) + timedelta(days=7) + course.certificate_available_date = datetime.now(get_utc_timezone()) + timedelta(days=7) course.save() block = CertificateAvailableDate(course, verified_user) assert block.date is not None @@ -528,7 +531,7 @@ def test_no_certificate_available_date_for_audit_course(self): assert len(all_course_modes) == 1 assert all_course_modes[0].slug == CourseMode.AUDIT - course.certificate_available_date = datetime.now(utc) + timedelta(days=7) + course.certificate_available_date = datetime.now(get_utc_timezone()) + timedelta(days=7) course.save() # Verify Certificate Available Date is not enabled for learner. @@ -543,7 +546,7 @@ def test_certificate_available_date_defined(self): CourseEnrollmentFactory(course_id=course.id, user=audit_user, mode=CourseMode.AUDIT) verified_user = create_user() CourseEnrollmentFactory(course_id=course.id, user=verified_user, mode=CourseMode.VERIFIED) - course.certificate_available_date = datetime.now(utc) + timedelta(days=7) + course.certificate_available_date = datetime.now(get_utc_timezone()) + timedelta(days=7) enable_course_certificates(course) expected_blocks = [ CourseEndDate, CourseStartDate, TodaysDate, VerificationDeadlineDate, CertificateAvailableDate @@ -587,7 +590,7 @@ def test_verification_deadline_date_upcoming(self): block = VerificationDeadlineDate(course, user) assert block.css_class == 'verification-deadline-upcoming' assert block.title == 'Verification Deadline' - assert block.date == (datetime.now(utc) + timedelta(days=14)) + assert block.date == (datetime.now(get_utc_timezone()) + timedelta(days=14)) assert block.description ==\ 'You must successfully complete verification before this date to qualify for a Verified Certificate.' assert block.link_text == 'Verify My Identity' @@ -602,7 +605,7 @@ def test_verification_deadline_date_retry(self): block = VerificationDeadlineDate(course, user) assert block.css_class == 'verification-deadline-retry' assert block.title == 'Verification Deadline' - assert block.date == (datetime.now(utc) + timedelta(days=14)) + assert block.date == (datetime.now(get_utc_timezone()) + timedelta(days=14)) assert block.description ==\ 'You must successfully complete verification before this date to qualify for a Verified Certificate.' assert block.link_text == 'Retry Verification' @@ -617,7 +620,7 @@ def test_verification_deadline_date_denied(self): block = VerificationDeadlineDate(course, user) assert block.css_class == 'verification-deadline-passed' assert block.title == 'Missed Verification Deadline' - assert block.date == (datetime.now(utc) + timedelta(days=- 1)) + assert block.date == (datetime.now(get_utc_timezone()) + timedelta(days=(- 1))) assert block.description == "Unfortunately you missed this course's deadline for a successful verification." assert block.link_text == 'Learn More' assert block.link == '' @@ -820,7 +823,7 @@ def create_course_run( days_till_verification_deadline (int): Number of days until the course run's verification deadline. If this value is set to `None` no deadline will be verification deadline will be created. """ - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) course = CourseFactory.create(start=now + timedelta(days=days_till_start)) course.end = None @@ -854,7 +857,7 @@ def create_self_paced_course_run(days_till_start=1, org_id=None): days_till_start (int): Number of days until the course starts. org_id (string): String org id to assign the course to (default: None; use CourseFactory default) """ - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) course = CourseFactory.create(start=now + timedelta(days=days_till_start), self_paced=True, org=org_id if org_id else 'TestedX') diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 0f0eca4dc2c7..da686c0c3fd8 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -13,7 +13,7 @@ from django.conf import settings from django.test import TestCase, RequestFactory from django.urls import reverse -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xblock.runtime import DictKeyValueStore from xmodule.capa.tests.response_xml_factory import OptionResponseXMLFactory @@ -48,7 +48,9 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase, Mas @classmethod def setUpClass(cls): super().setUpClass() - cls.course = CourseFactory.create(number='masquerade-test', metadata={'start': datetime.now(UTC)}) + cls.course = CourseFactory.create( + number='masquerade-test', metadata={'start': datetime.now(get_utc_timezone())} + ) cls.info_page = BlockFactory.create( category="course_info", parent_location=cls.course.location, data="OOGIE BLOOGIE", display_name="updates" @@ -510,7 +512,8 @@ class SetupMasqueradeTests(SharedModuleStoreTestCase, ): def setUp(self): super().setUp() - self.course = CourseFactory.create(number='setup-masquerade-test', metadata={'start': datetime.now(UTC)}) + self.course = CourseFactory.create( + number='setup-masquerade-test', metadata={'start': datetime.now(get_utc_timezone())}) self.request = RequestFactory().request() self.staff = StaffFactory(course_key=self.course.id) self.student = UserFactory() diff --git a/lms/djangoapps/courseware/tests/test_self_paced_overrides.py b/lms/djangoapps/courseware/tests/test_self_paced_overrides.py index fef054a4c966..ef03cd134147 100644 --- a/lms/djangoapps/courseware/tests/test_self_paced_overrides.py +++ b/lms/djangoapps/courseware/tests/test_self_paced_overrides.py @@ -3,7 +3,7 @@ import datetime from unittest.mock import patch -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test.utils import override_settings from common.djangoapps.student.tests.factories import BetaTesterFactory @@ -31,7 +31,7 @@ def setUp(self): super().setUp() self.non_staff_user, __ = self.create_non_staff_user() - self.now = datetime.datetime.now(pytz.UTC).replace(microsecond=0) + self.now = datetime.datetime.now(get_utc_timezone()).replace(microsecond=0) self.future = self.now + datetime.timedelta(days=30) def tearDown(self): @@ -88,7 +88,7 @@ def test_course_access_to_beta_users(self): """ Test that beta testers can access `self_paced` course prior to start date. """ - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(get_utc_timezone()) one_month_from_now = now + datetime.timedelta(days=30) course_options = { 'days_early_for_beta': 100, diff --git a/lms/djangoapps/courseware/tests/test_submitting_problems.py b/lms/djangoapps/courseware/tests/test_submitting_problems.py index 4d04204078de..ddfe0f8bccbf 100644 --- a/lms/djangoapps/courseware/tests/test_submitting_problems.py +++ b/lms/djangoapps/courseware/tests/test_submitting_problems.py @@ -12,7 +12,7 @@ from unittest.mock import patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.db import connections @@ -214,7 +214,7 @@ def add_graded_section_to_course(self, name, section_format='Homework', late=Fal metadata={ 'graded': True, 'format': section_format, - 'due': datetime(2013, 5, 20, 23, 30, tzinfo=pytz.utc), + 'due': datetime(2013, 5, 20, 23, 30, tzinfo=get_utc_timezone()), }, ) elif reset: diff --git a/lms/djangoapps/courseware/tests/test_user_state_client.py b/lms/djangoapps/courseware/tests/test_user_state_client.py index 690a64b8b8c5..3a774281ff16 100644 --- a/lms/djangoapps/courseware/tests/test_user_state_client.py +++ b/lms/djangoapps/courseware/tests/test_user_state_client.py @@ -3,7 +3,7 @@ defined in edx_user_state_client. """ -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator from xblock.fields import Scope from datetime import datetime @@ -339,9 +339,9 @@ def test_delete_many_last_field(self): self.assertCountEqual(self.get_many(user=0, blocks=[0, 1]), []) def test_get_mod_date(self): - start_time = datetime.now(pytz.utc) + start_time = datetime.now(get_utc_timezone()) self.set_many(user=0, block_to_state={0: {'a': 'b'}, 1: {'b': 'c'}}) - end_time = datetime.now(pytz.utc) + end_time = datetime.now(get_utc_timezone()) mod_dates = self.get(user=0, block=0) @@ -350,15 +350,15 @@ def test_get_mod_date(self): self.assertLess(mod_dates.updated, end_time) def test_get_many_mod_date(self): - start_time = datetime.now(pytz.utc) + start_time = datetime.now(get_utc_timezone()) self.set_many( user=0, block_to_state={0: {'a': 'b'}, 1: {'a': 'd'}}) - mid_time = datetime.now(pytz.utc) + mid_time = datetime.now(get_utc_timezone()) self.set_many( user=0, block_to_state={1: {'a': 'c'}}) - end_time = datetime.now(pytz.utc) + end_time = datetime.now(get_utc_timezone()) mod_dates = list(self.get_many( user=0, @@ -607,7 +607,7 @@ def _add_state(self, username, block_key, scope, state): Add the specified state to the state history of this block. """ history_list = self._history.setdefault((username, block_key, scope), []) - history_list.insert(0, XBlockUserState(username, block_key, state, datetime.now(pytz.utc), scope)) + history_list.insert(0, XBlockUserState(username, block_key, state, datetime.now(get_utc_timezone()), scope)) def get_many(self, username, block_keys, scope=Scope.user_state, fields=None): for key in block_keys: diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index f9668853b244..faba24a8f447 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -6,7 +6,7 @@ import datetime from unittest.mock import patch -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.urls import reverse from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -253,7 +253,7 @@ def test_dark_launch_enrolled_student(self): """ # Make courses start in the future - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(get_utc_timezone()) tomorrow = now + datetime.timedelta(days=1) self.course.start = tomorrow self.test_course.start = tomorrow @@ -278,7 +278,7 @@ def test_dark_launch_instructor(self): Make sure that before course start instructors can access the page for their course. """ - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(get_utc_timezone()) tomorrow = now + datetime.timedelta(days=1) self.course.start = tomorrow self.test_course.start = tomorrow @@ -301,7 +301,7 @@ def test_dark_launch_global_staff(self): Make sure that before course start staff can access course pages. """ - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(get_utc_timezone()) tomorrow = now + datetime.timedelta(days=1) self.course.start = tomorrow @@ -323,7 +323,7 @@ def test_enrollment_period(self): Check that enrollment periods work. """ # Make courses start in the future - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(get_utc_timezone()) tomorrow = now + datetime.timedelta(days=1) nextday = tomorrow + datetime.timedelta(days=1) yesterday = now - datetime.timedelta(days=1) @@ -361,7 +361,7 @@ class TestBetatesterAccess(ModuleStoreTestCase, CourseAccessTestMixin): def setUp(self): super().setUp() - now = datetime.datetime.now(pytz.UTC) + now = datetime.datetime.now(get_utc_timezone()) tomorrow = now + datetime.timedelta(days=1) self.course = CourseFactory(days_early_for_beta=2, start=tomorrow) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 4b59bfac24da..e685a314e961 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -27,7 +27,7 @@ from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from freezegun import freeze_time from opaque_keys.edx.keys import CourseKey, UsageKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel from rest_framework import status from rest_framework.test import APIClient @@ -293,7 +293,7 @@ def setUp(self): self.course_key = self.course.id # Set profile country to Ã…land Islands to check Unicode characters does not raise error self.user = UserFactory(username='dummy', profile__country='AX') - self.date = datetime(2013, 1, 22, tzinfo=UTC) + self.date = datetime(2013, 1, 22, tzinfo=get_utc_timezone()) self.enrollment = CourseEnrollment.enroll(self.user, self.course_key) self.enrollment.created = self.date self.enrollment.save() @@ -375,7 +375,7 @@ class ViewsTestCase(BaseViewsTestCase): """ YESTERDAY = 'yesterday' DATES = { - YESTERDAY: datetime.now(UTC) - timedelta(days=1), + YESTERDAY: datetime.now(get_utc_timezone()) - timedelta(days=1), None: None, } @@ -1014,7 +1014,7 @@ def create_course(self, **options): self.course = CourseFactory.create( # pylint: disable=attribute-defined-outside-init start=datetime(2013, 9, 16, 7, 17, 28), end=datetime.now(), - certificate_available_date=datetime.now(UTC), + certificate_available_date=datetime.now(get_utc_timezone()), certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE, **options, ) @@ -1614,7 +1614,7 @@ class ProgressPageShowCorrectnessTests(ProgressPageBaseTests): Tests that verify that the progress page works correctly when displaying subsections where correctness is hidden. """ # Constants used in the test data - NOW = datetime.now(UTC) + NOW = datetime.now(get_utc_timezone()) DAY_DELTA = timedelta(days=1) YESTERDAY = 'yesterday' TODAY = 'today' @@ -2678,7 +2678,7 @@ def test_is_course_open_for_learner( * A mock request session to pre-cache the enterprise customer data. """ staff_user = AdminFactory() - start_date = datetime.now(UTC) + timedelta(days=start_date_modifier) + start_date = datetime.now(get_utc_timezone()) + timedelta(days=start_date_modifier) course = CourseFactory.create(start=start_date) request = RequestFactory().get('/') request.user = staff_user @@ -2800,7 +2800,7 @@ def setUp(self): self.course_key = self.course.id self.user = UserFactory(username='staff_user', profile__country='AX', is_staff=True) - self.date = datetime(2013, 1, 22, tzinfo=UTC) + self.date = datetime(2013, 1, 22, tzinfo=get_utc_timezone()) self.enrollment = CourseEnrollment.enroll(self.user, self.course_key) self.enrollment.created = self.date self.enrollment.save() diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py index 5409c89f636b..ef7294504874 100644 --- a/lms/djangoapps/courseware/utils.py +++ b/lms/djangoapps/courseware/utils.py @@ -9,7 +9,7 @@ from django.http import HttpResponse, HttpResponseBadRequest from edx_rest_api_client.client import OAuthAPIClient from oauth2_provider.models import Application -from pytz import utc # lint-amnesty, pylint: disable=wrong-import-order +from openedx.core.lib.time_zone_utils import get_utc_timezone # lint-amnesty, pylint: disable=wrong-import-order from rest_framework import status from xmodule.partitions.partitions import \ ENROLLMENT_TRACK_PARTITION_ID # lint-amnesty, pylint: disable=wrong-import-order @@ -107,7 +107,7 @@ def can_show_verified_upgrade(user, enrollment, course=None): if upgrade_deadline is None: return False - if datetime.datetime.now(utc).date() > upgrade_deadline.date(): + if datetime.datetime.now(get_utc_timezone()).date() > upgrade_deadline.date(): return False # Show the summary if user enrollment is in which allow user to upsell diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index af157e76a079..4ee4849d241b 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -41,7 +41,7 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from openedx_filters.learning.filters import CourseAboutRenderStarted, RenderXBlockStarted from requests.exceptions import ConnectionError, Timeout # pylint: disable=redefined-builtin -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status from rest_framework.decorators import api_view, throttle_classes from rest_framework.response import Response @@ -2304,7 +2304,7 @@ def get_financial_aid_courses(user, course_id=None): enrollment.course_overview and \ enrollment.course_overview.eligible_for_financial_aid and \ CourseMode.objects.filter( - Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(UTC)), + Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(get_utc_timezone())), course_id=enrollment.course_id, mode_slug=CourseMode.VERIFIED).exists(): # This is a workaround to set course_id before disabling the field in case of new financial assistance flow. diff --git a/lms/djangoapps/discussion/django_comment_client/tests/test_utils.py b/lms/djangoapps/discussion/django_comment_client/tests/test_utils.py index 8800504c86d5..c1327b6826ef 100644 --- a/lms/djangoapps/discussion/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/discussion/django_comment_client/tests/test_utils.py @@ -13,7 +13,7 @@ from django.urls import reverse from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone import lms.djangoapps.discussion.django_comment_client.utils as utils from common.djangoapps.course_modes.models import CourseMode @@ -369,14 +369,14 @@ def setUp(self): # This test needs to use a course that has already started -- # discussion topics only show up if the course has already started, # and the default start date for courses is Jan 1, 2030. - start=datetime.datetime(2012, 2, 3, tzinfo=UTC) + start=datetime.datetime(2012, 2, 3, tzinfo=get_utc_timezone()) ) # Courses get a default discussion topic on creation, so remove it self.course.discussion_topics = {} self.discussion_num = 0 self.instructor = InstructorFactory(course_key=self.course.id) self.maxDiff = None # pylint: disable=invalid-name - self.later = datetime.datetime(2050, 1, 1, tzinfo=UTC) + self.later = datetime.datetime(2050, 1, 1, tzinfo=get_utc_timezone()) def create_discussion(self, discussion_category, discussion_target, **kwargs): self.discussion_num += 1 diff --git a/lms/djangoapps/discussion/django_comment_client/utils.py b/lms/djangoapps/discussion/django_comment_client/utils.py index e26b748270e3..00d54f286229 100644 --- a/lms/djangoapps/discussion/django_comment_client/utils.py +++ b/lms/djangoapps/discussion/django_comment_client/utils.py @@ -16,7 +16,7 @@ from django.urls import reverse from django.utils.deprecation import MiddlewareMixin from opaque_keys.edx.keys import CourseKey, UsageKey, i4xEncoder -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.models import get_user_by_username_or_email from common.djangoapps.student.roles import GlobalStaff @@ -281,7 +281,7 @@ def _filter_unstarted_categories(category_map, course): Returns a subset of categories from the provided map which have not yet met the start date Includes information about category children, subcategories (different), and entries """ - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) result_map = {} @@ -397,7 +397,7 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex sort_key = xblock.sort_key category = " / ".join([x.strip() for x in xblock.discussion_category.split("/")]) # Handle case where xblock.start is None - entry_start_date = xblock.start if xblock.start else datetime.max.replace(tzinfo=UTC) + entry_start_date = xblock.start if xblock.start else datetime.max.replace(tzinfo=get_utc_timezone()) unexpanded_category_map[category].append({"title": title, "id": discussion_id, "sort_key": sort_key, @@ -464,7 +464,7 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex category_map['entries'][topic] = { "id": entry["id"], "sort_key": entry.get("sort_key", topic), - "start_date": datetime.now(UTC), + "start_date": datetime.now(get_utc_timezone()), "is_divided": ( discussion_division_enabled and entry["id"] in divided_discussion_ids ) diff --git a/lms/djangoapps/discussion/rest_api/api.py b/lms/djangoapps/discussion/rest_api/api.py index d3eaab4118e9..901053382a9a 100644 --- a/lms/djangoapps/discussion/rest_api/api.py +++ b/lms/djangoapps/discussion/rest_api/api.py @@ -11,7 +11,7 @@ from enum import Enum from typing import Dict, Iterable, List, Literal, Optional, Set, Tuple from urllib.parse import urlencode, urlunparse -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.contrib.auth import get_user_model @@ -419,7 +419,7 @@ def get_courseware_topics( courseware_topics = [] existing_topic_ids = set() - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) discussion_xblocks = get_accessible_discussion_xblocks(course, request.user) xblocks_by_category = defaultdict(list) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_api.py b/lms/djangoapps/discussion/rest_api/tests/test_api.py index 4542df116e82..91f2c5786527 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_api.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_api.py @@ -18,7 +18,7 @@ from django.test.client import RequestFactory from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import CourseLocator -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework.exceptions import PermissionDenied from xmodule.modulestore.django import modulestore @@ -145,8 +145,8 @@ def _set_course_discussion_blackout(course, user_id): user_id: User id of user enrolled in the course """ course.discussion_blackouts = [ - datetime.now(UTC) - timedelta(days=3), - datetime.now(UTC) + timedelta(days=3) + datetime.now(get_utc_timezone()) - timedelta(days=3), + datetime.now(get_utc_timezone()) + timedelta(days=3) ] configuration = DiscussionsConfiguration.get(course.id) configuration.posting_restrictions = PostingRestriction.SCHEDULED @@ -259,7 +259,7 @@ def test_blackout(self): # A variety of formats is accepted self.course.discussion_blackouts = [ ["2015-06-09T00:00:00Z", "6-10-15"], - [1433980800000, datetime(2015, 6, 12, tzinfo=UTC)], + [1433980800000, datetime(2015, 6, 12, tzinfo=get_utc_timezone())], ] self.update_course(self.course, self.user.id) result = get_course(self.request, self.course.id) @@ -302,7 +302,7 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), + start=datetime.now(get_utc_timezone()), discussion_topics={"Test Topic": {"id": "non-courseware-topic-id"}}, user_partitions=[self.partition], cohort_config={"cohorted": True}, @@ -549,7 +549,7 @@ def test_access_control(self): "courseware-5", "Second", "Future Start Date", - start=datetime.now(UTC) + timedelta(days=1) + start=datetime.now(get_utc_timezone()) + timedelta(days=1) ) self.make_discussion_xblock("courseware-4", "Second", "Staff Only", visible_to_staff_only=True) @@ -627,13 +627,13 @@ def test_un_released_discussion_topic(self): "courseware-2", "First", "Released", - start=datetime.now(UTC) - timedelta(days=1) + start=datetime.now(get_utc_timezone()) - timedelta(days=1) ) self.make_discussion_xblock( "courseware-3", "First", "Future release", - start=datetime.now(UTC) + timedelta(days=1) + start=datetime.now(get_utc_timezone()) + timedelta(days=1) ) self.request.user = staff @@ -3187,20 +3187,20 @@ def setUp(self) -> None: parent_location=self.course.location, category='chapter', display_name="Week 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.sequential = BlockFactory.create( parent_location=self.chapter.location, category='sequential', display_name="Lesson 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.verticals = [ BlockFactory.create( parent_location=self.sequential.location, category='vertical', display_name=f'vertical-{idx}', - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) for idx in range(10) ] diff --git a/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py b/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py index 62284b904c78..f38161fc836b 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_api_v2.py @@ -21,8 +21,8 @@ from django.test.client import RequestFactory from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import CourseLocator -from pytz import UTC from rest_framework.exceptions import PermissionDenied +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore @@ -168,8 +168,8 @@ def _set_course_discussion_blackout(course, user_id): user_id: User id of user enrolled in the course """ course.discussion_blackouts = [ - datetime.now(UTC) - timedelta(days=3), - datetime.now(UTC) + timedelta(days=3), + datetime.now(get_utc_timezone()) - timedelta(days=3), + datetime.now(get_utc_timezone()) + timedelta(days=3), ] configuration = DiscussionsConfiguration.get(course.id) configuration.posting_restrictions = PostingRestriction.SCHEDULED diff --git a/lms/djangoapps/discussion/rest_api/tests/test_utils.py b/lms/djangoapps/discussion/rest_api/tests/test_utils.py index 49c1e3889daa..626e4d7a13d3 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_utils.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_utils.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta import ddt -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory @@ -39,8 +39,8 @@ def setUp(self): super().setUp() # lint-amnesty, pylint: disable=super-with-arguments self.course = CourseFactory.create() - self.course.discussion_blackouts = [datetime.now(UTC) - timedelta(days=3), - datetime.now(UTC) + timedelta(days=3)] + self.course.discussion_blackouts = [datetime.now(get_utc_timezone()) - timedelta(days=3), + datetime.now(get_utc_timezone()) + timedelta(days=3)] configuration = DiscussionsConfiguration.get(self.course.id) configuration.posting_restrictions = PostingRestriction.SCHEDULED configuration.save() @@ -197,7 +197,7 @@ def _get_date_ranges(self): Returns: list: List of date range tuples. """ - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) date_ranges = [ (now - timedelta(days=14), now + timedelta(days=23)), ] @@ -248,7 +248,7 @@ def test_posting_scheduled_future(self): Assertion: Posting should be allowed. """ - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) date_ranges = [ (now + timedelta(days=6), now + timedelta(days=23)), ] diff --git a/lms/djangoapps/discussion/rest_api/tests/test_views.py b/lms/djangoapps/discussion/rest_api/tests/test_views.py index 4bb508813672..315b11cd37e9 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_views.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_views.py @@ -16,7 +16,7 @@ from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status from rest_framework.test import APIClient, APITestCase @@ -92,7 +92,7 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), + start=datetime.now(get_utc_timezone()), discussion_topics={"Test Topic": {"id": "test_topic"}} ) self.password = "Password1234" @@ -177,7 +177,7 @@ def setUp(self): ), } self.user = UserFactory.create(password=self.TEST_PASSWORD) - self.course = CourseFactory.create(org='a', course='b', run='c', start=datetime.now(UTC)) + self.course = CourseFactory.create(org='a', course='b', run='c', start=datetime.now(get_utc_timezone())) self.url = reverse("upload_file", kwargs={"course_id": str(self.course.id)}) patcher = mock.patch( 'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', @@ -347,7 +347,7 @@ def setUp(self): self.other_user = UserFactory.create(password=self.TEST_PASSWORD) self.register_get_user_response(self.other_user) - self.course = CourseFactory.create(org="a", course="b", run="c", start=datetime.now(UTC)) + self.course = CourseFactory.create(org="a", course="b", run="c", start=datetime.now(get_utc_timezone())) CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) self.url = self.build_url(self.user.username, self.course.id) @@ -796,7 +796,7 @@ def create_course(self, blocks_count, module_store, topics): org="a", course="b", run="c", - start=datetime.now(UTC), + start=datetime.now(get_utc_timezone()), default_store=module_store, discussion_topics=topics ) @@ -944,19 +944,19 @@ def test_new_course_structure_response(self): parent_location=self.course.location, category='chapter', display_name="Week 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) sequential = BlockFactory.create( parent_location=chapter.location, category='sequential', display_name="Lesson 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) BlockFactory.create( parent_location=sequential.location, category='vertical', display_name='vertical', - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) DiscussionsConfiguration.objects.create( context_key=self.course.id, @@ -1005,20 +1005,20 @@ def setUp(self) -> None: parent_location=self.course.location, category='chapter', display_name="Week 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.sequential = BlockFactory.create( parent_location=self.chapter.location, category='sequential', display_name="Lesson 1", - start=datetime(2015, 3, 1, tzinfo=UTC), + start=datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) self.verticals = [ BlockFactory.create( parent_location=self.sequential.location, category='vertical', display_name='vertical', - start=datetime(2015, 4, 1, tzinfo=UTC), + start=datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) ] course_key = self.course.id @@ -1317,7 +1317,7 @@ def test_rate_limit_on_learners(self): """ Test rate limit is applied on learners when creating posts """ - self.user.date_joined = datetime.now(UTC) + self.user.date_joined = datetime.now(get_utc_timezone()) self.user.save() self.mock_is_captcha_enabled.side_effect = lambda course_key: False self.register_get_user_response(self.user) @@ -1356,7 +1356,7 @@ def test_rate_limit_not_applied_to_privileged_user(self, role): """ Test rate limit is not applied on privileged roles when creating posts """ - self.user.date_joined = datetime.now(UTC) - timedelta(days=4) + self.user.date_joined = datetime.now(get_utc_timezone()) - timedelta(days=4) self.user.save() self.mock_is_captcha_enabled.side_effect = lambda course_key: False @@ -1397,7 +1397,7 @@ def test_rate_limit_not_applied_to_aged_account(self): """ Test rate limit is not applied on aged accounts when creating posts """ - self.user.date_joined = datetime.now(UTC) - timedelta(days=2) + self.user.date_joined = datetime.now(get_utc_timezone()) - timedelta(days=2) self.user.save() self.mock_is_captcha_enabled.side_effect = lambda course_key: False @@ -2595,7 +2595,7 @@ def test_rate_limit_on_learners(self): """ Tests rate limit is applied on learners when creating comments """ - self.user.date_joined = datetime.now(UTC) + self.user.date_joined = datetime.now(get_utc_timezone()) self.user.save() self.mock_is_captcha_enabled.side_effect = lambda course_key: False @@ -2629,7 +2629,7 @@ def test_rate_limit_not_applied_to_privileged_user(self, role): """ Test rate limit is not applied on privileged roles when creating comments """ - self.user.date_joined = datetime.now(UTC) - timedelta(days=4) + self.user.date_joined = datetime.now(get_utc_timezone()) - timedelta(days=4) self.user.save() self.mock_is_captcha_enabled.side_effect = lambda course_key: False @@ -2663,7 +2663,7 @@ def test_rate_limit_not_applied_to_aged_account(self): """ Test rate limit on applied on aged accounts when creating comments """ - self.user.date_joined = datetime.now(UTC) - timedelta(days=2) + self.user.date_joined = datetime.now(get_utc_timezone()) - timedelta(days=2) self.user.save() self.mock_is_captcha_enabled.side_effect = lambda course_key: False @@ -2916,7 +2916,7 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), + start=datetime.now(get_utc_timezone()), discussion_topics={"Test Topic": {"id": "test_topic"}} ) self.path = reverse('discussion_course_settings', kwargs={'course_id': str(self.course.id)}) @@ -3227,7 +3227,7 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), + start=datetime.now(get_utc_timezone()), ) self.password = self.TEST_PASSWORD self.user = UserFactory(username='staff', password=self.password, is_staff=True) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py b/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py index 2351d92ee669..4a359fbc08bc 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py @@ -18,13 +18,29 @@ from forum.backends.mongodb.threads import CommentThread import httpretty from django.urls import reverse -from pytz import UTC +from edx_toggles.toggles.testutils import override_waffle_flag +from lms.djangoapps.discussion.django_comment_client.tests.mixins import MockForumApiMixin +from opaque_keys.edx.keys import CourseKey from rest_framework import status from rest_framework.parsers import JSONParser from rest_framework.test import APIClient from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory +from rest_framework.test import APIClient, APITestCase +from openedx.core.lib.time_zone_utils import get_utc_timezone + +from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE +from lms.djangoapps.discussion.rest_api.utils import get_usernames_from_search_string +from xmodule.modulestore import ModuleStoreEnum +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, check_mongo_calls + +from common.djangoapps.course_modes.models import CourseMode +from common.djangoapps.course_modes.tests.factories import CourseModeFactory +from common.djangoapps.student.models import get_retired_username_by_username, CourseEnrollment +from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole, GlobalStaff from common.djangoapps.student.tests.factories import ( CourseEnrollmentFactory, UserFactory, @@ -69,8 +85,8 @@ def setUp(self): org="x", course="y", run="z", - start=datetime.now(UTC), - discussion_topics={"Test Topic": {"id": "test_topic"}}, + start=datetime.now(get_utc_timezone()), + discussion_topics={"Test Topic": {"id": "test_topic"}} ) self.password = "Password1234" self.user = UserFactory.create(password=self.password) diff --git a/lms/djangoapps/discussion/rest_api/tests/utils.py b/lms/djangoapps/discussion/rest_api/tests/utils.py index 2cd6628bf861..ce2b98238635 100644 --- a/lms/djangoapps/discussion/rest_api/tests/utils.py +++ b/lms/djangoapps/discussion/rest_api/tests/utils.py @@ -12,7 +12,7 @@ import httpretty from PIL import Image -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.discussion.django_comment_client.tests.mixins import MockForumApiMixin from openedx.core.djangoapps.profile_images.images import create_profile_images @@ -901,7 +901,7 @@ class ProfileImageTestMixin: Mixin with utility methods for user profile image """ - TEST_PROFILE_IMAGE_UPLOADED_AT = datetime(2002, 1, 9, 15, 43, 1, tzinfo=UTC) + TEST_PROFILE_IMAGE_UPLOADED_AT = datetime(2002, 1, 9, 15, 43, 1, tzinfo=get_utc_timezone()) def create_profile_image(self, user, storage): """ diff --git a/lms/djangoapps/discussion/rest_api/utils.py b/lms/djangoapps/discussion/rest_api/utils.py index 0f02a0dcdcf2..22fb0ecb4615 100644 --- a/lms/djangoapps/discussion/rest_api/utils.py +++ b/lms/djangoapps/discussion/rest_api/utils.py @@ -11,7 +11,7 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.paginator import Paginator from django.db.models.functions import Length -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole from common.djangoapps.student.models import CourseAccessRole @@ -384,7 +384,7 @@ def is_posting_allowed(posting_restrictions: str, blackout_schedules: List): Returns: bool: True if posting is allowed, False otherwise. """ - now = datetime.now(UTC) + now = datetime.now(get_utc_timezone()) if posting_restrictions == PostingRestriction.DISABLED: return True elif posting_restrictions == PostingRestriction.SCHEDULED: diff --git a/lms/djangoapps/experiments/flags.py b/lms/djangoapps/experiments/flags.py index 3b45bcee5b5c..81e04ae5bf64 100644 --- a/lms/djangoapps/experiments/flags.py +++ b/lms/djangoapps/experiments/flags.py @@ -6,7 +6,7 @@ import logging import dateutil -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from crum import get_current_request from edx_django_utils.cache import RequestCache @@ -121,7 +121,7 @@ def _is_enrollment_inside_date_bounds(self, experiment_values, user, course_key) if not enrollment_start and not enrollment_end: return True # early exit just to avoid any further lookups - now = datetime.datetime.now(pytz.utc) + now = datetime.datetime.now(get_utc_timezone()) enrollment = CourseEnrollment.get_enrollment(user, course_key) # If the user isn't enrolled, act like they would enroll right now (this keeps the pre-enroll and post-enroll @@ -131,7 +131,7 @@ def _is_enrollment_inside_date_bounds(self, experiment_values, user, course_key) # Enrollment must be after any enrollment_start date, if specified if enrollment_start: try: - start_date = dateutil.parser.parse(enrollment_start).replace(tzinfo=pytz.UTC) + start_date = dateutil.parser.parse(enrollment_start).replace(tzinfo=get_utc_timezone()) except ValueError: log.exception('Could not parse enrollment start date for experiment %d', self.experiment_id) return False @@ -141,7 +141,7 @@ def _is_enrollment_inside_date_bounds(self, experiment_values, user, course_key) # Enrollment must be before any enrollment_end date, if specified if enrollment_end: try: - end_date = dateutil.parser.parse(enrollment_end).replace(tzinfo=pytz.UTC) + end_date = dateutil.parser.parse(enrollment_end).replace(tzinfo=get_utc_timezone()) except ValueError: log.exception('Could not parse enrollment end date for experiment %d', self.experiment_id) return False diff --git a/lms/djangoapps/experiments/tests/test_flags.py b/lms/djangoapps/experiments/tests/test_flags.py index 006efa17770d..858f213b7961 100644 --- a/lms/djangoapps/experiments/tests/test_flags.py +++ b/lms/djangoapps/experiments/tests/test_flags.py @@ -5,7 +5,7 @@ from unittest.mock import patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from crum import set_current_request from dateutil import parser from django.test.client import RequestFactory @@ -75,7 +75,7 @@ def test_not_enabled(self): def test_enrollment_start(self, experiment_start, enrollment_created, expected_bucket): if enrollment_created: enrollment = CourseEnrollmentFactory(user=self.user, course_id='a/b/c') - enrollment.created = parser.parse(enrollment_created).replace(tzinfo=pytz.UTC) + enrollment.created = parser.parse(enrollment_created).replace(tzinfo=get_utc_timezone()) enrollment.save() if experiment_start: ExperimentKeyValueFactory(experiment_id=0, key='enrollment_start', value=experiment_start) @@ -93,7 +93,7 @@ def test_enrollment_start(self, experiment_start, enrollment_created, expected_b def test_enrollment_end(self, experiment_end, enrollment_created, expected_bucket): if enrollment_created: enrollment = CourseEnrollmentFactory(user=self.user, course_id='a/b/c') - enrollment.created = parser.parse(enrollment_created).replace(tzinfo=pytz.UTC) + enrollment.created = parser.parse(enrollment_created).replace(tzinfo=get_utc_timezone()) enrollment.save() if experiment_end: ExperimentKeyValueFactory(experiment_id=0, key='enrollment_end', value=experiment_end) diff --git a/lms/djangoapps/grades/api.py b/lms/djangoapps/grades/api.py index bb1b43fa7ff8..a748d50bac28 100644 --- a/lms/djangoapps/grades/api.py +++ b/lms/djangoapps/grades/api.py @@ -6,7 +6,7 @@ from datetime import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.core.exceptions import ObjectDoesNotExist from opaque_keys.edx.keys import CourseKey, UsageKey @@ -126,7 +126,7 @@ def undo_override_subsection_grade(user_id, course_key_or_id, usage_key_or_id, f course_id=str(course_key), usage_id=str(usage_key), only_if_higher=False, - modified=datetime.now().replace(tzinfo=pytz.UTC), # Not used when score_deleted=True + modified=datetime.now().replace(tzinfo=get_utc_timezone()), # Not used when score_deleted=True score_deleted=True, score_db_table=constants.ScoreDatabaseTableEnum.overrides ) diff --git a/lms/djangoapps/grades/management/commands/recalculate_subsection_grades.py b/lms/djangoapps/grades/management/commands/recalculate_subsection_grades.py index 4161e6680652..48abfbcfc2e4 100644 --- a/lms/djangoapps/grades/management/commands/recalculate_subsection_grades.py +++ b/lms/djangoapps/grades/management/commands/recalculate_subsection_grades.py @@ -8,7 +8,7 @@ from datetime import datetime from django.core.management.base import BaseCommand, CommandError -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from submissions.models import Submission from common.djangoapps.student.models import user_by_anonymous_id @@ -55,8 +55,8 @@ def handle(self, *args, **options): if 'modified_end' not in options: raise CommandError('modified_end must be provided.') - modified_start = utc.localize(datetime.strptime(options['modified_start'], DATE_FORMAT)) - modified_end = utc.localize(datetime.strptime(options['modified_end'], DATE_FORMAT)) + modified_start = datetime.strptime(options['modified_start'], DATE_FORMAT).replace(tzinfo=get_utc_timezone()) + modified_end = datetime.strptime(options['modified_end'], DATE_FORMAT).replace(tzinfo=get_utc_timezone()) event_transaction_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) kwargs = {'modified__range': (modified_start, modified_end), 'module_type': 'problem'} diff --git a/lms/djangoapps/grades/management/commands/tests/test_recalculate_subsection_grades.py b/lms/djangoapps/grades/management/commands/tests/test_recalculate_subsection_grades.py index 8f679cd1c6c8..90a77b8c2d2c 100644 --- a/lms/djangoapps/grades/management/commands/tests/test_recalculate_subsection_grades.py +++ b/lms/djangoapps/grades/management/commands/tests/test_recalculate_subsection_grades.py @@ -8,7 +8,7 @@ import ddt from opaque_keys.edx.keys import CourseKey -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.track.event_transaction_utils import get_event_transaction_id from common.djangoapps.util.date_utils import to_timestamp @@ -40,7 +40,7 @@ def test_submissions(self, task_mock, id_mock, subs_mock): course_id=CourseKey.from_string('course-v1:x+y+z'), item_id='abc', ) - submission.created_at = utc.localize(datetime.strptime('2016-08-23 16:43', DATE_FORMAT)) + submission.created_at = datetime.strptime('2016-08-23 16:43', DATE_FORMAT).replace(tzinfo=get_utc_timezone()) subs_mock.objects.filter.return_value = [submission] id_mock.return_value = MagicMock() id_mock.return_value.id = "ID" @@ -54,7 +54,7 @@ def test_csm(self, task_mock, id_mock, csm_mock): csm_record.student_id = "ID" csm_record.course_id = CourseKey.from_string('course-v1:x+y+z') csm_record.module_state_key = "abc" - csm_record.modified = utc.localize(datetime.strptime('2016-08-23 16:43', DATE_FORMAT)) + csm_record.modified = datetime.strptime('2016-08-23 16:43', DATE_FORMAT).replace(tzinfo=get_utc_timezone()) csm_mock.objects.filter.return_value = [csm_record] id_mock.return_value = MagicMock() id_mock.return_value.id = "ID" @@ -67,7 +67,11 @@ def _run_command_and_check_output(self, task_mock, score_db_table, include_anony "course_id": 'course-v1:x+y+z', "usage_id": 'abc', "only_if_higher": False, - "expected_modified_time": to_timestamp(utc.localize(datetime.strptime('2016-08-23 16:43', DATE_FORMAT))), + "expected_modified_time": to_timestamp( + datetime.strptime('2016-08-23 16:43', DATE_FORMAT).replace( + tzinfo=get_utc_timezone() + ) + ), "score_deleted": False, "event_transaction_id": str(get_event_transaction_id()), "event_transaction_type": 'edx.grades.problem.submitted', diff --git a/lms/djangoapps/grades/rest_api/v1/tests/mixins.py b/lms/djangoapps/grades/rest_api/v1/tests/mixins.py index 1d3a6a0b4551..14b581877e7a 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/mixins.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/mixins.py @@ -5,7 +5,7 @@ from datetime import datetime -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory @@ -57,7 +57,7 @@ class GradeViewTestMixin(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.date = datetime(2013, 1, 22, tzinfo=UTC) + cls.date = datetime(2013, 1, 22, tzinfo=get_utc_timezone()) cls.course = cls._create_test_course_with_default_grading_policy( display_name='test course', run="Testing_course" ) diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py index 42dd9139ee21..0a6208f2fe05 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py @@ -16,7 +16,7 @@ from edx_toggles.toggles.testutils import override_waffle_flag from freezegun import freeze_time from opaque_keys.edx.locator import BlockUsageLocator -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status from rest_framework.test import APITestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -1025,7 +1025,7 @@ def test_filter_enrollment_mode(self, login_method, num_enrollments, num_filtere _ = CourseEnrollmentFactory( course_id=self.course.id, user=verified_student, - created=datetime(2013, 1, 1, tzinfo=UTC), + created=datetime(2013, 1, 1, tzinfo=get_utc_timezone()), mode=CourseMode.VERIFIED, ) with override_waffle_flag(self.waffle_flag, active=True): @@ -1890,7 +1890,7 @@ def setUpClass(cls): "earned_graded": 6.0, "possible_graded": 8.0, "visible_blocks": cls.block_records, - "first_attempted": datetime(2000, 1, 1, 12, 30, 45, tzinfo=UTC), + "first_attempted": datetime(2000, 1, 1, 12, 30, 45, tzinfo=get_utc_timezone()), } cls.grade = PersistentSubsectionGrade.update_or_create_grade(**cls.params) @@ -2225,7 +2225,7 @@ def test_get_override_for_unreleased_block(self): parent_location=self.chapter_1.location, category='sequential', graded=True, - start=datetime(2999, 1, 1, tzinfo=UTC), # arbitrary future date + start=datetime(2999, 1, 1, tzinfo=get_utc_timezone()), # arbitrary future date display_name='Unreleased Section', ) diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py b/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py index aa5f526332e5..099be56038f9 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py @@ -7,7 +7,7 @@ import ddt from django.urls import reverse -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory @@ -149,8 +149,8 @@ def test_course_keys(self, modulestore_type): The view should be addressable by course-keys from both module stores. """ course = CourseFactory.create( - start=datetime(2014, 6, 16, 14, 30, tzinfo=UTC), - end=datetime(2015, 1, 16, tzinfo=UTC), + start=datetime(2014, 6, 16, 14, 30, tzinfo=get_utc_timezone()), + end=datetime(2015, 1, 16, tzinfo=get_utc_timezone()), org="MTD", default_store=modulestore_type, ) diff --git a/lms/djangoapps/grades/tests/integration/test_problems.py b/lms/djangoapps/grades/tests/integration/test_problems.py index b4a2d06c9f45..9d26eed5fdd0 100644 --- a/lms/djangoapps/grades/tests/integration/test_problems.py +++ b/lms/djangoapps/grades/tests/integration/test_problems.py @@ -3,7 +3,7 @@ import itertools import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from crum import set_current_request from xmodule.graders import ProblemScore from xmodule.modulestore import ModuleStoreEnum @@ -109,7 +109,7 @@ class TestVariedMetadata(ProblemSubmissionTestMixin, ModuleStoreTestCase): default_problem_metadata = { 'graded': True, 'weight': 2.5, - 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=get_utc_timezone()), } def setUp(self): diff --git a/lms/djangoapps/grades/tests/test_models.py b/lms/djangoapps/grades/tests/test_models.py index acdb20c5f1a7..04502f62e947 100644 --- a/lms/djangoapps/grades/tests/test_models.py +++ b/lms/djangoapps/grades/tests/test_models.py @@ -12,7 +12,7 @@ import ddt import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.db.utils import IntegrityError from django.test import TestCase from django.utils.timezone import now @@ -220,7 +220,7 @@ def setUp(self): "earned_graded": 6.0, "possible_graded": 8.0, "visible_blocks": self.block_records, - "first_attempted": datetime(2000, 1, 1, 12, 30, 45, tzinfo=pytz.UTC), + "first_attempted": datetime(2000, 1, 1, 12, 30, 45, tzinfo=get_utc_timezone()), } self.user = UserFactory(id=self.params['user_id']) @@ -389,7 +389,7 @@ def setUp(self): minute=53, second=24, microsecond=354741, - tzinfo=pytz.UTC, + tzinfo=get_utc_timezone(), ), "percent_grade": 77.7, "letter_grade": "Great job", diff --git a/lms/djangoapps/grades/tests/test_services.py b/lms/djangoapps/grades/tests/test_services.py index 07a77ff13f13..99c8ade6bb1f 100644 --- a/lms/djangoapps/grades/tests/test_services.py +++ b/lms/djangoapps/grades/tests/test_services.py @@ -7,7 +7,7 @@ from unittest.mock import call, patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from freezegun import freeze_time from common.djangoapps.student.tests.factories import UserFactory @@ -256,7 +256,7 @@ def test_undo_override_subsection_grade(self): course_id=str(self.course.id), usage_id=str(self.subsection.location), only_if_higher=False, - modified=datetime.now().replace(tzinfo=pytz.UTC), + modified=datetime.now().replace(tzinfo=get_utc_timezone()), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides ) diff --git a/lms/djangoapps/grades/tests/test_signals.py b/lms/djangoapps/grades/tests/test_signals.py index 72a20d406482..2adc35147db8 100644 --- a/lms/djangoapps/grades/tests/test_signals.py +++ b/lms/djangoapps/grades/tests/test_signals.py @@ -9,7 +9,7 @@ import ddt import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import TestCase from django.test.utils import override_settings from opaque_keys.edx.locator import CourseLocator @@ -36,7 +36,7 @@ UUID_REGEX = re.compile('{hex}{{8}}-{hex}{{4}}-{hex}{{4}}-{hex}{{4}}-{hex}{{12}}'.format(hex='[0-9a-f]')) -FROZEN_NOW_DATETIME = datetime.now().replace(tzinfo=pytz.UTC) +FROZEN_NOW_DATETIME = datetime.now().replace(tzinfo=get_utc_timezone()) FROZEN_NOW_TIMESTAMP = to_timestamp(FROZEN_NOW_DATETIME) SUBMISSIONS_SCORE_SET_HANDLER = 'submissions_score_set_handler' @@ -412,7 +412,7 @@ def test_segment_event_on_course_grade_passed_first_time(self, signal_mock, segm minute=53, second=24, microsecond=354741, - tzinfo=pytz.UTC, + tzinfo=get_utc_timezone(), ), "percent_grade": 77.7, "letter_grade": "Great job", diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py index 347879105aad..124fde20ff41 100644 --- a/lms/djangoapps/grades/tests/test_tasks.py +++ b/lms/djangoapps/grades/tests/test_tasks.py @@ -10,7 +10,7 @@ from unittest.mock import MagicMock, patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.db.utils import IntegrityError from django.utils import timezone from edx_toggles.toggles.testutils import override_waffle_flag @@ -66,7 +66,7 @@ def set_up_course(self, create_multiple_subsections=False, course_end=None): seq2 = BlockFactory.create(parent=self.chapter, category='sequential') BlockFactory.create(parent=seq2, category='problem') - self.frozen_now_datetime = datetime.now().replace(tzinfo=pytz.UTC) + self.frozen_now_datetime = datetime.now().replace(tzinfo=get_utc_timezone()) self.frozen_now_timestamp = to_timestamp(self.frozen_now_datetime) self.problem_weighted_score_changed_kwargs = OrderedDict([ @@ -296,7 +296,7 @@ def test_retry_on_integrity_error(self, mock_update, mock_retry): def test_retry_when_db_not_updated(self, score_db_table, mock_log, mock_retry): self.set_up_course() self.recalculate_subsection_grade_kwargs['score_db_table'] = score_db_table - modified_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - timedelta(days=1) + modified_datetime = datetime.utcnow().replace(tzinfo=get_utc_timezone()) - timedelta(days=1) if score_db_table == ScoreDatabaseTableEnum.submissions: with patch('lms.djangoapps.grades.tasks.sub_api.get_score') as mock_sub_score: mock_sub_score.return_value = { @@ -383,7 +383,7 @@ def test_no_log_known_error(self, mock_update, mock_retry, mock_log): def _apply_recalculate_subsection_grade( self, mock_score=MagicMock( - modified=datetime.utcnow().replace(tzinfo=pytz.UTC) + timedelta(days=1), + modified=datetime.utcnow().replace(tzinfo=get_utc_timezone()) + timedelta(days=1), grade=1.0, max_grade=2.0, ) @@ -637,7 +637,7 @@ def test_recalculate_subsection_grade_v3(self, freeze_flag_value, end_date_adjus CourseEnrollment.enroll(user, self.course.id) with override_waffle_flag(self.freeze_grade_flag, active=freeze_flag_value): - modified_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - timedelta(days=1) # lint-amnesty, pylint: disable=unused-variable + modified_datetime = datetime.utcnow().replace(tzinfo=get_utc_timezone()) - timedelta(days=1) # lint-amnesty, pylint: disable=unused-variable with patch('lms.djangoapps.grades.tasks._has_db_updated_with_new_score') as mock_has_db_updated: result = recalculate_subsection_grade_v3.apply_async(kwargs=self.recalculate_subsection_grade_kwargs) self._assert_for_freeze_grade_flag( diff --git a/lms/djangoapps/grades/tests/test_transformer.py b/lms/djangoapps/grades/tests/test_transformer.py index 7ef13e5b5b7c..81ed2c212fd2 100644 --- a/lms/djangoapps/grades/tests/test_transformer.py +++ b/lms/djangoapps/grades/tests/test_transformer.py @@ -8,7 +8,7 @@ from copy import deepcopy import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import check_mongo_calls_range @@ -35,7 +35,7 @@ class GradesTransformerTestCase(CourseStructureTestCase): problem_metadata = { 'graded': True, 'weight': 1, - 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=get_utc_timezone()), } def setUp(self): @@ -280,7 +280,7 @@ def test_collecting_staff_only_problem(self): problem_metadata = { 'graded': True, 'weight': 1, - 'due': datetime.datetime(2016, 10, 16, 0, 4, 0, tzinfo=pytz.utc), + 'due': datetime.datetime(2016, 10, 16, 0, 4, 0, tzinfo=get_utc_timezone()), 'visible_to_staff_only': True, } @@ -449,7 +449,7 @@ def test_modulestore_performance(self, store_type, max_mongo_calls, min_mongo_ca 'metadata': { 'graded': True, 'weight': 1, - 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=get_utc_timezone()), }, '#type': 'problem', '#ref': f'problem_{problem_number}', diff --git a/lms/djangoapps/grades/tests/utils.py b/lms/djangoapps/grades/tests/utils.py index 9111820379b4..0c74ff96f889 100644 --- a/lms/djangoapps/grades/tests/utils.py +++ b/lms/djangoapps/grades/tests/utils.py @@ -7,7 +7,7 @@ from datetime import datetime from unittest.mock import MagicMock, patch -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.block_render import get_block @@ -34,7 +34,7 @@ def mock_passing_grade(letter_grade='Pass', percent=0.75, last_updated=None): @contextmanager -def mock_get_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.UTC)): +def mock_get_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=get_utc_timezone())): """ Mocks the get_score function to return a valid grade. """ @@ -52,7 +52,11 @@ def mock_get_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, @contextmanager -def mock_get_submissions_score(earned=0, possible=1, first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.UTC)): +def mock_get_submissions_score( + earned=0, + possible=1, + first_attempted=datetime(2000, 1, 1, 0, 0, 0, tzinfo=get_utc_timezone()), +): """ Mocks the _get_submissions_score function to return the specified values """ diff --git a/lms/djangoapps/instructor/enrollment.py b/lms/djangoapps/instructor/enrollment.py index 23d0ce3d3fb8..8657f4788d6c 100644 --- a/lms/djangoapps/instructor/enrollment.py +++ b/lms/djangoapps/instructor/enrollment.py @@ -9,7 +9,7 @@ from contextlib import ExitStack, contextmanager from datetime import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.template.loader import render_to_string @@ -456,7 +456,7 @@ def _fire_score_changed_for_block( usage_id=str(module_state_key), score_deleted=True, only_if_higher=False, - modified=datetime.now().replace(tzinfo=pytz.UTC), + modified=datetime.now().replace(tzinfo=get_utc_timezone()), score_db_table=grades_constants.ScoreDatabaseTableEnum.courseware_student_module, ) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index af8408ba8fde..aed6f94e332b 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -13,7 +13,7 @@ import dateutil import ddt import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from botocore.exceptions import ClientError from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -29,7 +29,6 @@ from edx_when.api import get_dates_for_course, get_overrides_for_user, set_date_for_block from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import UsageKey -from pytz import UTC from testfixtures import LogCapture from common.djangoapps.course_modes.models import CourseMode @@ -376,14 +375,14 @@ def setUpClass(cls): category='chapter', display_name="Chapter", publish_item=True, - start=datetime.datetime(2018, 3, 10, tzinfo=UTC), + start=datetime.datetime(2018, 3, 10, tzinfo=get_utc_timezone()), ) cls.sequential = BlockFactory.create( parent=cls.chapter, category='sequential', display_name="Lesson", publish_item=True, - start=datetime.datetime(2018, 3, 10, tzinfo=UTC), + start=datetime.datetime(2018, 3, 10, tzinfo=get_utc_timezone()), metadata={'graded': True, 'format': 'Homework'}, ) cls.vertical = BlockFactory.create( @@ -391,7 +390,7 @@ def setUpClass(cls): category='vertical', display_name='Subsection', publish_item=True, - start=datetime.datetime(2018, 3, 10, tzinfo=UTC), + start=datetime.datetime(2018, 3, 10, tzinfo=get_utc_timezone()), ) cls.problem = BlockFactory.create( category="problem", @@ -3813,7 +3812,7 @@ def test_send_email_with_schedule(self, mock_task_api): """ schedule = "2099-05-02T14:00:00.000Z" self.full_test_message['schedule'] = schedule - expected_schedule = dateutil.parser.parse(schedule).replace(tzinfo=pytz.utc) + expected_schedule = dateutil.parser.parse(schedule).replace(tzinfo=get_utc_timezone()) url = reverse('send_email', kwargs={'course_id': str(self.course.id)}) response = self.client.post(url, self.full_test_message) @@ -4270,7 +4269,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): def setUpClass(cls): super().setUpClass() cls.course = CourseFactory.create() - cls.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + cls.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=get_utc_timezone()) with cls.store.bulk_operations(cls.course.id, emit_signals=False): cls.week1 = BlockFactory.create(due=cls.due) @@ -4349,7 +4348,7 @@ def setUp(self): def test_change_due_date(self): url = reverse('change_due_date', kwargs={'course_id': str(self.course.id)}) - due_date = datetime.datetime(2013, 12, 30, tzinfo=UTC) + due_date = datetime.datetime(2013, 12, 30, tzinfo=get_utc_timezone()) response = self.client.post(url, { 'student': self.user1.username, 'url': str(self.week1.location), @@ -4362,7 +4361,7 @@ def test_change_due_date(self): def test_change_due_date_with_reason(self): url = reverse('change_due_date', kwargs={'course_id': str(self.course.id)}) - due_date = datetime.datetime(2013, 12, 30, tzinfo=UTC) + due_date = datetime.datetime(2013, 12, 30, tzinfo=get_utc_timezone()) response = self.client.post(url, { 'student': self.user1.username, 'url': str(self.week1.location), @@ -4450,12 +4449,12 @@ def test_reset_date(self): def test_reset_date_only_in_edx_when(self): # Start with a unit that only has a date in edx-when assert get_date_for_block(self.course, self.week3, self.user1) is None - original_due = datetime.datetime(2010, 4, 1, tzinfo=UTC) + original_due = datetime.datetime(2010, 4, 1, tzinfo=get_utc_timezone()) set_date_for_block(self.course.id, self.week3.location, 'due', original_due) assert get_date_for_block(self.course, self.week3, self.user1) == original_due # set override, confirm it took - override = datetime.datetime(2010, 7, 1, tzinfo=UTC) + override = datetime.datetime(2010, 7, 1, tzinfo=get_utc_timezone()) set_date_for_block(self.course.id, self.week3.location, 'due', override, user=self.user1) assert get_date_for_block(self.course, self.week3, self.user1) == override @@ -4507,7 +4506,7 @@ def setUp(self): super().setUp() self.course = CourseFactory.create() - self.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + self.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=get_utc_timezone()) with self.store.bulk_operations(self.course.id, emit_signals=False): self.week1 = BlockFactory.create(due=self.due) @@ -4592,7 +4591,7 @@ def test_reset_extension_to_deleted_date(self): 'due_datetime': '12/30/2013 00:00' }) assert response.status_code == 200, response.content - assert datetime.datetime(2013, 12, 30, 0, 0, tzinfo=UTC) ==\ + assert datetime.datetime(2013, 12, 30, 0, 0, tzinfo=get_utc_timezone()) ==\ get_extended_due(self.course, self.week1, self.user1) self.week1.due = None diff --git a/lms/djangoapps/instructor/tests/test_tools.py b/lms/djangoapps/instructor/tests/test_tools.py index b9f754dbfff1..14b4077551b3 100644 --- a/lms/djangoapps/instructor/tests/test_tools.py +++ b/lms/djangoapps/instructor/tests/test_tools.py @@ -16,7 +16,7 @@ from edx_when.api import get_dates_for_course, set_dates_for_course from edx_when.field_data import DateLookupFieldData from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.fields import Date from xmodule.modulestore.tests.django_utils import ( TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase, SharedModuleStoreTestCase, @@ -91,7 +91,9 @@ class TestParseDatetime(unittest.TestCase): Test date parsing. """ def test_parse_no_error(self): - assert tools.parse_datetime('5/12/2010 2:42') == datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + assert tools.parse_datetime('5/12/2010 2:42') == datetime.datetime( + 2010, 5, 12, 2, 42, tzinfo=get_utc_timezone() + ) def test_parse_error(self): with pytest.raises(tools.DashboardError): @@ -149,7 +151,7 @@ def setUp(self): week2 = BlockFactory.create(parent=course) child = BlockFactory.create(parent=week1) - due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=get_utc_timezone()) set_dates_for_course(course.id, [ (week1.location, {'due': due}), (week2.location, {'due': due}), @@ -212,7 +214,7 @@ def setUp(self): """ super().setUp() - self.due = due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + self.due = due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=get_utc_timezone()) course = CourseFactory.create() week1 = BlockFactory.create(due=due, parent=course) week2 = BlockFactory.create(due=due, parent=course) @@ -249,7 +251,7 @@ def _clear_field_data_cache(self): def test_set_due_date_extension(self): # First, extend the leaf assignment date - extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=UTC) + extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=get_utc_timezone()) tools.set_due_date_extension(self.course, self.assignment, self.user, extended_hw) self._clear_field_data_cache() assert self.week1.due == self.due @@ -257,7 +259,7 @@ def test_set_due_date_extension(self): assert self.assignment.due == extended_hw # Now, extend the whole section that the assignment was in. Both it and all under it should change - extended_week = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended_week = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=get_utc_timezone()) tools.set_due_date_extension(self.course, self.week1, self.user, extended_week) self._clear_field_data_cache() assert self.week1.due == extended_week @@ -265,17 +267,17 @@ def test_set_due_date_extension(self): assert self.assignment.due == extended_week def test_set_due_date_extension_invalid_date(self): - extended = datetime.datetime(2009, 1, 1, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2009, 1, 1, 0, 0, tzinfo=get_utc_timezone()) with pytest.raises(tools.DashboardError), self.allow_transaction_exception(): tools.set_due_date_extension(self.course, self.week1, self.user, extended) def test_set_due_date_extension_no_date(self): - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=get_utc_timezone()) with pytest.raises(tools.DashboardError), self.allow_transaction_exception(): tools.set_due_date_extension(self.course, self.week3, self.user, extended) def test_reset_due_date_extension(self): - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=get_utc_timezone()) tools.set_due_date_extension(self.course, self.week1, self.user, extended) tools.set_due_date_extension(self.course, self.week1, self.user, None) assert self.week1.due == self.due @@ -286,7 +288,7 @@ def test_reset_due_date_extension_with_no_enrollment(self): for a block given the user is not enrolled in the course. """ user = UserFactory.create() - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=get_utc_timezone()) with pytest.raises(tools.DashboardError): tools.set_due_date_extension(self.course, self.week3, user, extended) @@ -295,7 +297,7 @@ def test_set_due_date_extension_cache_invalidation(self, mock_method: MagicMock) """ Tests that the course dates are reloaded once they are overridden. """ - extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=UTC) + extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=get_utc_timezone()) tools.set_due_date_extension(self.course, self.assignment, self.user, extended_hw) assert mock_method.call_count == 3 @@ -308,7 +310,7 @@ def test_set_due_date_extension_cache_invalidation_with_version(self, mock_metho a unified experience across the platform. """ self.course.course_version = 'test_version' - extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=UTC) + extended_hw = datetime.datetime(2013, 10, 25, 0, 0, tzinfo=get_utc_timezone()) tools.set_due_date_extension(self.course, self.assignment, self.user, extended_hw) assert mock_method.call_count == 3 @@ -328,7 +330,7 @@ def setUp(self): """ super().setUp() - due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=UTC) + due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=get_utc_timezone()) course = CourseFactory.create() week1 = BlockFactory.create(due=due, parent=course) week2 = BlockFactory.create(due=due, parent=course) @@ -352,7 +354,7 @@ def setUp(self): handlers.extract_dates(None, course.id) def test_dump_module_extensions(self): - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=get_utc_timezone()) tools.set_due_date_extension(self.course, self.week1, self.user1, extended) tools.set_due_date_extension(self.course, self.week1, self.user2, @@ -379,7 +381,7 @@ def test_dump_module_extensions(self): ) def test_dump_student_extensions(self): - extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=UTC) + extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=get_utc_timezone()) tools.set_due_date_extension(self.course, self.week1, self.user1, extended) tools.set_due_date_extension(self.course, self.week2, self.user1, diff --git a/lms/djangoapps/instructor/tests/utils.py b/lms/djangoapps/instructor/tests/utils.py index 64911f87d897..cfb075e636c6 100644 --- a/lms/djangoapps/instructor/tests/utils.py +++ b/lms/djangoapps/instructor/tests/utils.py @@ -7,7 +7,7 @@ import json import random -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone class FakeInfo: @@ -65,7 +65,7 @@ def __init__(self, email_id): day = random.randint(1, 28) hour = random.randint(0, 23) minute = random.randint(0, 59) - self.created = datetime.datetime(year, month, day, hour, minute, tzinfo=UTC) + self.created = datetime.datetime(year, month, day, hour, minute, tzinfo=get_utc_timezone()) self.targets = FakeTargetGroup() diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index e48098b9ef80..13f5a362eefb 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -13,7 +13,7 @@ from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag from pyquery import PyQuery as pq -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, check_mongo_calls @@ -652,14 +652,14 @@ def test_spoc_gradebook_mongo_calls(self): category='chapter', display_name="Chapter", publish_item=True, - start=datetime.datetime(2015, 3, 1, tzinfo=UTC), + start=datetime.datetime(2015, 3, 1, tzinfo=get_utc_timezone()), ) sequential = BlockFactory.create( parent=chapter, category='sequential', display_name="Lesson", publish_item=True, - start=datetime.datetime(2015, 3, 1, tzinfo=UTC), + start=datetime.datetime(2015, 3, 1, tzinfo=get_utc_timezone()), metadata={'graded': True, 'format': 'Homework'}, ) vertical = BlockFactory.create( @@ -667,7 +667,7 @@ def test_spoc_gradebook_mongo_calls(self): category='vertical', display_name='Subsection', publish_item=True, - start=datetime.datetime(2015, 4, 1, tzinfo=UTC), + start=datetime.datetime(2015, 4, 1, tzinfo=get_utc_timezone()), ) for i in range(10): problem = BlockFactory.create( diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index f8b6dc06f4d6..2ea66503bf11 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -15,7 +15,7 @@ import re import dateutil -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone import edx_api_doc_tools as apidocs from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -3044,8 +3044,8 @@ def post(self, request, course_id): # convert the schedule from a string to a datetime, then check if its a # valid future date and time, dateutil # will throw a ValueError if the schedule is no good. - schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=pytz.utc) - if schedule_dt < datetime.datetime.now(pytz.utc): + schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=get_utc_timezone()) + if schedule_dt < datetime.datetime.now(get_utc_timezone()): raise ValueError("the requested schedule is in the past") except ValueError as value_error: error_message = ( diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index be2ae51dbdab..6451f129d5ba 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -6,7 +6,7 @@ from functools import reduce import markupsafe -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.contrib.auth.decorators import login_required from django.http import Http404, HttpResponseRedirect, HttpResponseServerError @@ -431,7 +431,7 @@ def set_course_mode_price(request, course_id): CourseModesArchive.objects.create( course_id=course_id, mode_slug='honor', mode_display_name='Honor Code Certificate', min_price=course_honor_mode[0].min_price, currency=course_honor_mode[0].currency, - expiration_datetime=datetime.datetime.now(pytz.utc), expiration_date=datetime.date.today() + expiration_datetime=datetime.datetime.now(get_utc_timezone()), expiration_date=datetime.date.today() ) course_honor_mode.update( min_price=course_price, diff --git a/lms/djangoapps/instructor/views/tools.py b/lms/djangoapps/instructor/views/tools.py index 5a62f705f530..1a598f6eb0c8 100644 --- a/lms/djangoapps/instructor/views/tools.py +++ b/lms/djangoapps/instructor/views/tools.py @@ -11,7 +11,7 @@ from django.http import HttpResponseBadRequest from django.utils.translation import gettext as _ from edx_when import api -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.models import CourseEnrollment, get_user_by_username_or_email from openedx.core.djangoapps.schedules.models import Schedule @@ -88,7 +88,7 @@ def parse_datetime(datestr): UTC. """ try: - return dateutil.parser.parse(datestr).replace(tzinfo=UTC) + return dateutil.parser.parse(datestr).replace(tzinfo=get_utc_timezone()) except ValueError: raise DashboardError(_("Unable to parse date: ") + datestr) # lint-amnesty, pylint: disable=raise-missing-from diff --git a/lms/djangoapps/instructor_task/api.py b/lms/djangoapps/instructor_task/api.py index 6474efc1d374..0f92cf8b186d 100644 --- a/lms/djangoapps/instructor_task/api.py +++ b/lms/djangoapps/instructor_task/api.py @@ -11,7 +11,7 @@ import logging from collections import Counter -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from celery.states import READY_STATES from common.djangoapps.util import milestones_helpers @@ -594,7 +594,7 @@ def process_scheduled_instructor_tasks(): Utility function that retrieves tasks whose schedules have elapsed and should be processed. Only retrieves instructor tasks that are in the `SCHEDULED` state. Then submits these tasks for processing by Celery. """ - now = datetime.datetime.now(pytz.utc) + now = datetime.datetime.now(get_utc_timezone()) due_schedules = InstructorTaskSchedule.objects.filter(task__task_state=SCHEDULED).filter(task_due__lte=now) log.info(f"Retrieved {due_schedules.count()} scheduled instructor tasks due for execution") for schedule in due_schedules: diff --git a/lms/djangoapps/instructor_task/management/commands/fail_old_tasks.py b/lms/djangoapps/instructor_task/management/commands/fail_old_tasks.py index a1b5f65ab1a5..e0a588d5416a 100644 --- a/lms/djangoapps/instructor_task/management/commands/fail_old_tasks.py +++ b/lms/djangoapps/instructor_task/management/commands/fail_old_tasks.py @@ -8,7 +8,7 @@ from celery.states import FAILURE from django.core.management.base import BaseCommand, CommandError -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.instructor_task.models import PROGRESS, QUEUING, InstructorTask @@ -72,7 +72,7 @@ def parse_date(date_string): Converts an isoformat string into a python datetime object. Localizes that datetime object to UTC. """ - return utc.localize(datetime.strptime(date_string, "%Y-%m-%d")) + return datetime.strptime(date_string, "%Y-%m-%d").replace(tzinfo=get_utc_timezone()) def handle(self, *args, **options): diff --git a/lms/djangoapps/instructor_task/management/commands/tests/test_fail_old_tasks.py b/lms/djangoapps/instructor_task/management/commands/tests/test_fail_old_tasks.py index 28e611eae64f..1623f6a8cdc7 100644 --- a/lms/djangoapps/instructor_task/management/commands/tests/test_fail_old_tasks.py +++ b/lms/djangoapps/instructor_task/management/commands/tests/test_fail_old_tasks.py @@ -6,7 +6,7 @@ from datetime import datetime import pytest import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from celery.states import FAILURE from django.core.management import call_command from django.core.management.base import CommandError @@ -51,7 +51,7 @@ def update_task_created(self, created_date): Override each task's "created" date """ for task in self.tasks: - task.created = datetime.strptime(created_date, "%Y-%m-%d").replace(tzinfo=pytz.UTC) + task.created = datetime.strptime(created_date, "%Y-%m-%d").replace(tzinfo=get_utc_timezone()) task.save() def get_tasks(self): diff --git a/lms/djangoapps/instructor_task/rest_api/v1/tests/test_views.py b/lms/djangoapps/instructor_task/rest_api/v1/tests/test_views.py index bb338f0bf480..9c48e4b71c8f 100644 --- a/lms/djangoapps/instructor_task/rest_api/v1/tests/test_views.py +++ b/lms/djangoapps/instructor_task/rest_api/v1/tests/test_views.py @@ -4,7 +4,7 @@ import datetime import json from uuid import uuid4 -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from celery.states import REVOKED import ddt @@ -227,7 +227,7 @@ def test_update_schedule_new_date(self): self._create_scheduled_course_emails_for_course(self.course1.id, self.instructor_course1, SCHEDULED, 1) task = InstructorTask.objects.get(course_id=self.course1.id) task_schedule = InstructorTaskSchedule.objects.get(task=task) - schedule_datetime = datetime.datetime(3000, 6, 1, 17, 15, 0, tzinfo=pytz.utc) + schedule_datetime = datetime.datetime(3000, 6, 1, 17, 15, 0, tzinfo=get_utc_timezone()) data = { "schedule": schedule_datetime.strftime('%Y-%m-%dT%H:%M:%SZ'), "browser_timezone": "UTC" diff --git a/lms/djangoapps/instructor_task/rest_api/v1/views.py b/lms/djangoapps/instructor_task/rest_api/v1/views.py index 812b88e11da3..85249d511d44 100644 --- a/lms/djangoapps/instructor_task/rest_api/v1/views.py +++ b/lms/djangoapps/instructor_task/rest_api/v1/views.py @@ -4,7 +4,7 @@ import datetime import json import logging -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone import dateutil from celery.states import REVOKED @@ -117,7 +117,7 @@ def patch(self, request, *args, **kwargs): with transaction.atomic(): if schedule: - schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=pytz.utc) + schedule_dt = dateutil.parser.parse(schedule).replace(tzinfo=get_utc_timezone()) self._verify_valid_schedule(schedule_id, schedule_dt) task_schedule.task_due = schedule_dt task_schedule.save() @@ -149,7 +149,7 @@ def _verify_valid_schedule(self, schedule_id, schedule): Verifies that the updated schedule data for the task is valid. We check to make sure that the date or time requested is not in the past. """ - now = datetime.datetime.now(pytz.utc) + now = datetime.datetime.now(get_utc_timezone()) if schedule < now: raise TaskUpdateException( f"Cannot update instructor task schedule '{schedule_id}', the updated schedule occurs in the past" diff --git a/lms/djangoapps/instructor_task/tasks_helper/enrollments.py b/lms/djangoapps/instructor_task/tasks_helper/enrollments.py index 468786323b79..cc592d42291a 100644 --- a/lms/djangoapps/instructor_task/tasks_helper/enrollments.py +++ b/lms/djangoapps/instructor_task/tasks_helper/enrollments.py @@ -6,7 +6,7 @@ import logging from datetime import datetime from time import time -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.instructor_analytics.basic import ( enrolled_students_features, list_inactive_enrolled_students, @@ -29,7 +29,7 @@ def upload_may_enroll_csv(_xblock_instance_args, _entry_id, course_id, task_inpu yet, and store using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) current_step = {'step': 'Calculating info about students who may enroll'} @@ -61,7 +61,7 @@ def upload_inactive_enrolled_students_info_csv(_xblock_instance_args, _entry_id, activated their account yet, and store using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) current_step = {'step': 'Calculating info about students who are enrolled and their account is inactive'} @@ -93,7 +93,7 @@ def upload_students_csv(_xblock_instance_args, _entry_id, course_id, task_input, `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) enrolled_students = CourseEnrollment.objects.users_enrolled_in(course_id) task_progress = TaskProgress(action_name, enrolled_students.count(), start_time) diff --git a/lms/djangoapps/instructor_task/tasks_helper/grades.py b/lms/djangoapps/instructor_task/tasks_helper/grades.py index 5358af370897..a8a63d428884 100644 --- a/lms/djangoapps/instructor_task/tasks_helper/grades.py +++ b/lms/djangoapps/instructor_task/tasks_helper/grades.py @@ -16,7 +16,7 @@ from django.contrib.auth import get_user_model from lazy import lazy from opaque_keys.edx.keys import UsageKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from six.moves import zip_longest from common.djangoapps.course_modes.models import CourseMode @@ -301,7 +301,7 @@ def _upload(self, success_headers, success_rows, error_headers, error_rows): """ Creates and uploads a CSV for the given headers and rows. """ - date = datetime.now(UTC) + date = datetime.now(get_utc_timezone()) upload_csv_to_report_store( [success_headers] + success_rows, self.context.upload_filename, @@ -389,7 +389,7 @@ def upload_temp_files(self, success_file, error_file, has_errors): """ Uploads success and error csv files to report store """ - date = datetime.now(UTC) + date = datetime.now(get_utc_timezone()) success_file.seek(0) upload_csv_file_to_report_store( @@ -966,7 +966,7 @@ def generate(cls, _xblock_instance_args, _entry_id, course_id, task_input, actio all student answers to a given problem, and store using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) current_step = {'step': 'Calculating students answers to problem'} diff --git a/lms/djangoapps/instructor_task/tasks_helper/misc.py b/lms/djangoapps/instructor_task/tasks_helper/misc.py index 85a5d4e1361e..e057f299909d 100644 --- a/lms/djangoapps/instructor_task/tasks_helper/misc.py +++ b/lms/djangoapps/instructor_task/tasks_helper/misc.py @@ -17,7 +17,7 @@ from django.core.exceptions import ValidationError from django.core.files.storage import DefaultStorage from openassessment.data import OraAggregateData, OraDownloadData -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.models import unique_id_for_user, anonymous_id_for_user from lms.djangoapps.instructor_analytics.basic import get_proctored_exam_results @@ -44,7 +44,7 @@ def upload_course_survey_report(_xblock_instance_args, _entry_id, course_id, _ta For a given `course_id`, generate a html report containing the survey results for a course. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) @@ -103,7 +103,7 @@ def upload_proctored_exam_results_report(_xblock_instance_args, _entry_id, cours information about proctored exam results, and store using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) num_reports = 1 task_progress = TaskProgress(action_name, num_reports, start_time) current_step = {'step': 'Calculating info about proctored exam results in a course'} @@ -171,7 +171,7 @@ def cohort_students_and_upload(_xblock_instance_args, _entry_id, course_id, task using a `ReportStore`. """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) # Iterate through rows to get total assignments for task progress with DefaultStorage().open(task_input['file_name']) as f: @@ -305,7 +305,7 @@ def _upload_ora2_data_common( """ Common code for uploading data or summary csv report. """ - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) start_time = time() num_attempted = 1 @@ -411,7 +411,7 @@ def upload_ora2_submission_files( """ start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) num_attempted = 1 num_total = 1 @@ -511,7 +511,7 @@ def _log_and_update_progress(step): TASK_LOG.info('%s, Task type: %s, Starting task execution', task_info_string, action_name) start_time = time() - start_date = datetime.now(UTC) + start_date = datetime.now(get_utc_timezone()) students = User.objects.filter( courseenrollment__course_id=course_id, diff --git a/lms/djangoapps/instructor_task/tests/test_api.py b/lms/djangoapps/instructor_task/tests/test_api.py index 4b84fbff7f70..d73db036732b 100644 --- a/lms/djangoapps/instructor_task/tests/test_api.py +++ b/lms/djangoapps/instructor_task/tests/test_api.py @@ -8,7 +8,7 @@ from uuid import uuid4 import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone import ddt from testfixtures import LogCapture from celery.states import FAILURE, SUCCESS @@ -465,7 +465,7 @@ def test_submit_bulk_course_email_with_schedule(self, mock_submit_task, mock_sch A test to determine if the right helper function is being called when a scheduled task is being processed. """ email_id = self._define_course_email() - schedule = datetime.datetime(2030, 8, 15, 8, 15, 12, 0, pytz.utc) + schedule = datetime.datetime(2030, 8, 15, 8, 15, 12, 0, get_utc_timezone()) submit_bulk_course_email( self.create_task_request(self.instructor), self.course.id, diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py index 0a8c5e788bfc..e97f6ab7e5e3 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py @@ -22,7 +22,7 @@ from django.test.utils import override_settings from edx_django_utils.cache import RequestCache from freezegun import freeze_time -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone import openedx.core.djangoapps.user_api.course_tag.api as course_tag_api import openedx.core.djangoapps.content.block_structure.api as bs_api @@ -1750,8 +1750,8 @@ def create_course(self): """ Creates a course with various subsections for testing """ - in_the_past = datetime.now(UTC) - timedelta(days=5) - in_the_future = datetime.now(UTC) + timedelta(days=5) + in_the_past = datetime.now(get_utc_timezone()) - timedelta(days=5) + in_the_future = datetime.now(get_utc_timezone()) + timedelta(days=5) self.course = CourseFactory.create( grading_policy={ "GRADER": [ @@ -1937,7 +1937,7 @@ class TestGradeReportEnrollmentAndCertificateInfo(TestReportMixin, InstructorTas def setUp(self): super().setUp() - today = datetime.now(UTC) + today = datetime.now(get_utc_timezone()) course_factory_kwargs = { 'start': today - timedelta(days=30), 'end': today - timedelta(days=2), @@ -2594,7 +2594,7 @@ def test_report_stores_results(self): return_val = upload_ora2_data(None, None, self.course.id, None, 'generated') - timestamp_str = datetime.now(UTC).strftime('%Y-%m-%d-%H%M') + timestamp_str = datetime.now(get_utc_timezone()).strftime('%Y-%m-%d-%H%M') key = self.course.id filename = f'{key.org}_{key.course}_{key.run}_ORA_data_{timestamp_str}.csv' @@ -2647,7 +2647,7 @@ def test_summary_report_stores_results(self): ) as mock_store_rows: return_val = upload_ora2_summary(None, None, self.course.id, None, 'generated') - timestamp_str = datetime.now(UTC).strftime('%Y-%m-%d-%H%M') + timestamp_str = datetime.now(get_utc_timezone()).strftime('%Y-%m-%d-%H%M') key = self.course.id filename = f'{key.org}_{key.course}_{key.run}_ORA_summary_{timestamp_str}.csv' diff --git a/lms/djangoapps/mobile_api/middleware.py b/lms/djangoapps/mobile_api/middleware.py index 5fbfb23f4c80..7190f382fb04 100644 --- a/lms/djangoapps/mobile_api/middleware.py +++ b/lms/djangoapps/mobile_api/middleware.py @@ -9,7 +9,7 @@ from django.core.cache import cache from django.http import HttpResponse from django.utils.deprecation import MiddlewareMixin -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.mobile_api.mobile_platform import MobilePlatform from lms.djangoapps.mobile_api.models import AppVersionConfig @@ -42,7 +42,7 @@ def process_request(self, request): if version_data: last_supported_date = version_data[self.LAST_SUPPORTED_DATE_HEADER] if last_supported_date != self.NO_LAST_SUPPORTED_DATE: - if datetime.now().replace(tzinfo=UTC) > last_supported_date: + if datetime.now().replace(tzinfo=get_utc_timezone()) > last_supported_date: return HttpResponse(status=426) # Http status 426; Update Required def process_response(self, __, response): diff --git a/lms/djangoapps/mobile_api/tests/test_course_info_views.py b/lms/djangoapps/mobile_api/tests/test_course_info_views.py index 56c020ec8fa3..f56ff3486a5b 100644 --- a/lms/djangoapps/mobile_api/tests/test_course_info_views.py +++ b/lms/djangoapps/mobile_api/tests/test_course_info_views.py @@ -12,7 +12,7 @@ from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag from milestones.tests.utils import MilestonesTestCaseMixin -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status from common.djangoapps.student.tests.factories import UserFactory # pylint: disable=unused-import @@ -534,7 +534,7 @@ def test_course_not_started(self, mock_certificate_downloadable_status): 'is_downloadable': True, 'download_url': certificate_url, } - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) course_not_started = CourseFactory.create( mobile_available=True, static_asset_path="needed_for_split", @@ -561,7 +561,7 @@ def test_course_closed(self, mock_certificate_downloadable_status): 'is_downloadable': True, 'download_url': certificate_url, } - now = datetime.now(utc) + now = datetime.now(get_utc_timezone()) course_closed = CourseFactory.create( mobile_available=True, static_asset_path="needed_for_split", diff --git a/lms/djangoapps/mobile_api/tests/test_middleware.py b/lms/djangoapps/mobile_api/tests/test_middleware.py index 925138c6e4dd..98bfd028651c 100644 --- a/lms/djangoapps/mobile_api/tests/test_middleware.py +++ b/lms/djangoapps/mobile_api/tests/test_middleware.py @@ -9,7 +9,7 @@ import ddt from django.core.cache import caches from django.http import HttpRequest, HttpResponse -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.mobile_api.middleware import AppVersionUpgrade from lms.djangoapps.mobile_api.models import AppVersionConfig @@ -35,13 +35,13 @@ def set_app_version_config(self): AppVersionConfig( platform="iOS", version="2.2.2", - expire_at=datetime(2014, 1, 1, tzinfo=UTC), + expire_at=datetime(2014, 1, 1, tzinfo=get_utc_timezone()), enabled=True ).save() AppVersionConfig( platform="iOS", version="4.4.4", - expire_at=datetime(9000, 1, 1, tzinfo=UTC), + expire_at=datetime(9000, 1, 1, tzinfo=get_utc_timezone()), enabled=True ).save() AppVersionConfig(platform="iOS", version="6.6.6", expire_at=None, enabled=True).save() @@ -50,13 +50,13 @@ def set_app_version_config(self): AppVersionConfig( platform="Android", version="2.2.2", - expire_at=datetime(2014, 1, 1, tzinfo=UTC), + expire_at=datetime(2014, 1, 1, tzinfo=get_utc_timezone()), enabled=True ).save() AppVersionConfig( platform="Android", version="4.4.4", - expire_at=datetime(5000, 1, 1, tzinfo=UTC), + expire_at=datetime(5000, 1, 1, tzinfo=get_utc_timezone()), enabled=True ).save() AppVersionConfig(platform="Android", version="8.8.8", expire_at=None, enabled=True).save() diff --git a/lms/djangoapps/mobile_api/tests/test_model.py b/lms/djangoapps/mobile_api/tests/test_model.py index 6c2b324817d0..6bb6e785f6b6 100644 --- a/lms/djangoapps/mobile_api/tests/test_model.py +++ b/lms/djangoapps/mobile_api/tests/test_model.py @@ -7,7 +7,7 @@ import ddt from django.test import TestCase -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from lms.djangoapps.mobile_api.models import AppVersionConfig, MobileApiConfig, MobileConfig @@ -24,19 +24,19 @@ def set_app_version_config(self): AppVersionConfig( platform="ios", version="2.2.2", - expire_at=datetime(2014, 1, 1, tzinfo=UTC), + expire_at=datetime(2014, 1, 1, tzinfo=get_utc_timezone()), enabled=True ).save() AppVersionConfig( platform="ios", version="4.1.1", - expire_at=datetime(5000, 1, 1, tzinfo=UTC), + expire_at=datetime(5000, 1, 1, tzinfo=get_utc_timezone()), enabled=False ).save() AppVersionConfig( platform="ios", version="4.4.4", - expire_at=datetime(9000, 1, 1, tzinfo=UTC), + expire_at=datetime(9000, 1, 1, tzinfo=get_utc_timezone()), enabled=True ).save() AppVersionConfig(platform="ios", version="6.6.6", expire_at=None, enabled=True).save() @@ -46,13 +46,13 @@ def set_app_version_config(self): AppVersionConfig( platform="android", version="2.2.2", - expire_at=datetime(2014, 1, 1, tzinfo=UTC), + expire_at=datetime(2014, 1, 1, tzinfo=get_utc_timezone()), enabled=True ).save() AppVersionConfig( platform="android", version="4.4.4", - expire_at=datetime(9000, 1, 1, tzinfo=UTC), + expire_at=datetime(9000, 1, 1, tzinfo=get_utc_timezone()), enabled=True ).save() AppVersionConfig(platform="android", version="8.8.8", expire_at=None, enabled=True).save() @@ -75,10 +75,10 @@ def test_latest_version(self, platform, latest_version): assert latest_version == AppVersionConfig.latest_version(platform) @ddt.data( - ('ios', '3.3.3', datetime(9000, 1, 1, tzinfo=UTC)), - ('ios', '4.4.4', datetime(9000, 1, 1, tzinfo=UTC)), + ('ios', '3.3.3', datetime(9000, 1, 1, tzinfo=get_utc_timezone())), + ('ios', '4.4.4', datetime(9000, 1, 1, tzinfo=get_utc_timezone())), ('ios', '6.6.6', None), - ("android", '4.4.4', datetime(9000, 1, 1, tzinfo=UTC)), + ("android", '4.4.4', datetime(9000, 1, 1, tzinfo=get_utc_timezone())), ('android', '8.8.8', None) ) @ddt.unpack diff --git a/lms/djangoapps/mobile_api/testutils.py b/lms/djangoapps/mobile_api/testutils.py index f74d4b45a5fd..316cf3053383 100644 --- a/lms/djangoapps/mobile_api/testutils.py +++ b/lms/djangoapps/mobile_api/testutils.py @@ -16,7 +16,7 @@ from unittest.mock import patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.urls import reverse from django.utils import timezone @@ -46,8 +46,8 @@ def setUp(self): self.course = CourseFactory.create( mobile_available=True, static_asset_path="needed_for_split", - end=datetime.datetime.now(pytz.UTC), - certificate_available_date=datetime.datetime.now(pytz.UTC) + end=datetime.datetime.now(get_utc_timezone()), + certificate_available_date=datetime.datetime.now(get_utc_timezone()) ) self.user = UserFactory.create() self.password = self.TEST_PASSWORD diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index 7c4b3e437de5..5fb2a6581d11 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -8,7 +8,6 @@ from urllib.parse import parse_qs import ddt -import pytz from completion.models import BlockCompletion from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing from django.conf import settings @@ -20,6 +19,7 @@ from milestones.tests.utils import MilestonesTestCaseMixin from opaque_keys.edx.keys import CourseKey from rest_framework import status +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollment @@ -108,8 +108,8 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest ALLOW_ACCESS_TO_UNRELEASED_COURSE = True ALLOW_ACCESS_TO_MILESTONE_COURSE = True ALLOW_ACCESS_TO_NON_VISIBLE_COURSE = True - NEXT_WEEK = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=7) - LAST_WEEK = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7) + NEXT_WEEK = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=7) + LAST_WEEK = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=7) THREE_YEARS_AGO = now() - datetime.timedelta(days=(365 * 3)) ADVERTISED_START = "Spring 2016" ENABLED_SIGNALS = ['course_published'] @@ -1467,7 +1467,7 @@ def test_user_have_active_and_inactive_enrollments_and_no_completions(self) -> N old_course = CourseFactory.create(org="edx", mobile_available=True) self.enroll(old_course.id) old_enrollment = CourseEnrollment.objects.filter(user=self.user, course=old_course.course_id).first() - old_enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=31) + old_enrollment.created = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=31) old_enrollment.save() response = self.api_response(api_version=API_V1) @@ -1495,7 +1495,7 @@ def test_different_enrollment_dates(self, enrolled_days_ago: int, recently_activ course = CourseFactory.create(org="edx", mobile_available=True, run='1001') self.enroll(course.id) enrollment = CourseEnrollment.objects.filter(user=self.user, course=course.course_id).first() - enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=enrolled_days_ago) + enrollment.created = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=enrolled_days_ago) enrollment.save() response = self.api_response(api_version=API_V1) @@ -1529,7 +1529,7 @@ def test_different_completion_dates(self, completed_days_ago: int, recently_acti self.enroll(course.id) enrollment = CourseEnrollment.objects.filter(user=self.user, course=course.course_id).first() # make enrollment older 30 days ago - enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=50) + enrollment.created = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=50) enrollment.save() completion = BlockCompletion.objects.create( user=self.user, @@ -1538,7 +1538,7 @@ def test_different_completion_dates(self, completed_days_ago: int, recently_acti block_key=section.location, completion=0.5, ) - completion.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=completed_days_ago) + completion.created = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=completed_days_ago) completion.save() response = self.api_response(api_version=API_V1) diff --git a/lms/djangoapps/mobile_api/users/views.py b/lms/djangoapps/mobile_api/users/views.py index 324db83a374c..e29784cb321a 100644 --- a/lms/djangoapps/mobile_api/users/views.py +++ b/lms/djangoapps/mobile_api/users/views.py @@ -8,7 +8,6 @@ from functools import cached_property from typing import Dict, List, Optional, Set -import pytz from completion.exceptions import UnavailableCompletionData from completion.models import BlockCompletion from completion.utilities import get_key_to_last_completed_block @@ -25,6 +24,7 @@ from rest_framework.decorators import api_view from rest_framework.permissions import SAFE_METHODS from rest_framework.response import Response +from openedx.core.lib.time_zone_utils import get_utc_timezone from xblock.fields import Scope from xblock.runtime import KeyValueStore from edx_rest_framework_extensions.paginators import DefaultPagination @@ -603,7 +603,7 @@ def get(self, request, *args, **kwargs) -> Response: """ Gets user's enrollments status. """ - active_status_date = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=30) + active_status_date = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=30) username = kwargs.get('username') course_ids_where_user_has_completions = self._get_course_ids_where_user_has_completions( username, diff --git a/lms/djangoapps/program_enrollments/rest_api/v1/utils.py b/lms/djangoapps/program_enrollments/rest_api/v1/utils.py index e72cb76ce87b..16766ee29e26 100644 --- a/lms/djangoapps/program_enrollments/rest_api/v1/utils.py +++ b/lms/djangoapps/program_enrollments/rest_api/v1/utils.py @@ -7,7 +7,7 @@ from django.core.exceptions import PermissionDenied from django.utils.functional import cached_property from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status from rest_framework.pagination import CursorPagination @@ -362,7 +362,7 @@ def get_course_run_status(course_overview, certificate_info): else: return CourseRunProgressStatuses.UPCOMING elif course_overview.pacing == 'self': - thirty_days_ago = datetime.now(UTC) - timedelta(30) + thirty_days_ago = datetime.now(get_utc_timezone()) - timedelta(30) certificate_completed = is_certificate_passing and ( certificate_creation_date <= thirty_days_ago ) diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py index 4abc4508ea35..44b1197f6083 100644 --- a/lms/djangoapps/support/tests/test_views.py +++ b/lms/djangoapps/support/tests/test_views.py @@ -27,7 +27,7 @@ from oauth2_provider.models import AccessToken, RefreshToken from opaque_keys.edx.locator import BlockUsageLocator from organizations.tests.factories import OrganizationFactory -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status from social_django.models import UserSocialAuth from xmodule.modulestore.tests.django_utils import ( @@ -316,7 +316,7 @@ def setUp(self): self.verification_deadline = VerificationDeadline( course_key=self.course.id, - deadline=datetime.now(UTC) + timedelta(days=365) + deadline=datetime.now(get_utc_timezone()) + timedelta(days=365) ) self.verification_deadline.save() @@ -738,15 +738,15 @@ def assert_update_enrollment(self, search_string_type, new_mode): def set_course_end_date_and_expiry(self): """ Set the course-end date and expire its verified mode.""" - self.course.start = datetime(year=1970, month=1, day=1, tzinfo=UTC) - self.course.end = datetime(year=1970, month=1, day=10, tzinfo=UTC) + self.course.start = datetime(year=1970, month=1, day=1, tzinfo=get_utc_timezone()) + self.course.end = datetime(year=1970, month=1, day=10, tzinfo=get_utc_timezone()) # change verified mode expiry. verified_mode = CourseMode.objects.get( course_id=self.course.id, mode_slug=CourseMode.VERIFIED ) - verified_mode.expiration_datetime = datetime(year=1970, month=1, day=9, tzinfo=UTC) + verified_mode.expiration_datetime = datetime(year=1970, month=1, day=9, tzinfo=get_utc_timezone()) verified_mode.save() @@ -2103,7 +2103,7 @@ def setUp(self): """ super().setUp() SupportStaffRole().add_users(self.user) - self.now = datetime.now().replace(tzinfo=UTC) + self.now = datetime.now().replace(tzinfo=get_utc_timezone()) self.course = CourseFactory.create( start=self.now - timedelta(days=90), end=self.now + timedelta(days=90), diff --git a/lms/djangoapps/teams/models.py b/lms/djangoapps/teams/models.py index 56de6f965a72..631c058f6c2c 100644 --- a/lms/djangoapps/teams/models.py +++ b/lms/djangoapps/teams/models.py @@ -6,7 +6,7 @@ from datetime import datetime from uuid import uuid4 -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.exceptions import ObjectDoesNotExist from django.db import models @@ -101,7 +101,7 @@ def handle_activity(user, post, original_author_id=None): def utc_now(): - return datetime.utcnow().replace(tzinfo=pytz.utc) + return datetime.utcnow().replace(tzinfo=get_utc_timezone()) class CourseTeam(models.Model): @@ -286,7 +286,7 @@ def save(self, *args, **kwargs): # lint-amnesty, pylint: disable=arguments-diff if self.pk is None: should_reset_team_size = True if not self.last_activity_at: - self.last_activity_at = datetime.utcnow().replace(tzinfo=pytz.utc) + self.last_activity_at = datetime.utcnow().replace(tzinfo=get_utc_timezone()) super().save(*args, **kwargs) if should_reset_team_size: self.team.reset_team_size() @@ -345,7 +345,7 @@ def update_last_activity(cls, user, discussion_topic_id): # information. except ObjectDoesNotExist: return - now = datetime.utcnow().replace(tzinfo=pytz.utc) + now = datetime.utcnow().replace(tzinfo=get_utc_timezone()) membership.last_activity_at = now membership.team.last_activity_at = now membership.team.save() diff --git a/lms/djangoapps/teams/tests/factories.py b/lms/djangoapps/teams/tests/factories.py index 178d926183ee..1e475a133e5d 100644 --- a/lms/djangoapps/teams/tests/factories.py +++ b/lms/djangoapps/teams/tests/factories.py @@ -7,12 +7,12 @@ from uuid import uuid4 import factory -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from factory.django import DjangoModelFactory from lms.djangoapps.teams.models import CourseTeam, CourseTeamMembership -LAST_ACTIVITY_AT = datetime(2015, 8, 15, 0, 0, 0, tzinfo=pytz.utc) +LAST_ACTIVITY_AT = datetime(2015, 8, 15, 0, 0, 0, tzinfo=get_utc_timezone()) class CourseTeamFactory(DjangoModelFactory): diff --git a/lms/djangoapps/teams/tests/test_models.py b/lms/djangoapps/teams/tests/test_models.py index 60190076004c..5e05dc1bf7c4 100644 --- a/lms/djangoapps/teams/tests/test_models.py +++ b/lms/djangoapps/teams/tests/test_models.py @@ -10,7 +10,7 @@ import ddt import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from opaque_keys.edx.keys import CourseKey from common.djangoapps.course_modes.models import CourseMode @@ -292,7 +292,7 @@ def assert_last_activity_updated(self, should_update): if should_update: assert team.last_activity_at > team_last_activity assert team_membership.last_activity_at > team_membership_last_activity - now = datetime.utcnow().replace(tzinfo=pytz.utc) + now = datetime.utcnow().replace(tzinfo=get_utc_timezone()) assert now > team.last_activity_at assert now > team_membership.last_activity_at self.assert_event_emitted( diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index 6eaa1bdb680e..a7566127eb6f 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -10,7 +10,7 @@ from uuid import UUID import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from dateutil import parser from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -862,7 +862,7 @@ def test_order_by(self, field, status, names): dispatch_uid='teams.signals.course_team_post_save_callback' ): solar_team = self.test_team_name_id_map['Sólar team'] - solar_team.last_activity_at = datetime.utcnow().replace(tzinfo=pytz.utc) + solar_team.last_activity_at = datetime.utcnow().replace(tzinfo=get_utc_timezone()) solar_team.save() data = {'order_by': field} if field else {} @@ -1292,7 +1292,8 @@ def test_full_student_creator(self): del team['membership'] # verify that it's been set to a time today. - assert parser.parse(team['last_activity_at']).date() == datetime.utcnow().replace(tzinfo=pytz.utc).date() + expected_date = datetime.utcnow().replace(tzinfo=get_utc_timezone()).date() + assert parser.parse(team['last_activity_at']).date() == expected_date del team['last_activity_at'] # Verify that the creating user gets added to the team. diff --git a/lms/djangoapps/verify_student/tests/test_services.py b/lms/djangoapps/verify_student/tests/test_services.py index 4bfa8c27d5b3..13e66606a3e5 100644 --- a/lms/djangoapps/verify_student/tests/test_services.py +++ b/lms/djangoapps/verify_student/tests/test_services.py @@ -13,7 +13,7 @@ from django.utils.translation import gettext as _ from freezegun import freeze_time from openedx_filters import PipelineStep -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.verify_student.models import ( @@ -257,7 +257,7 @@ def test_approved_software_secure_verification(self): SoftwareSecurePhotoVerification.objects.create(user=self.user, status='approved') status = IDVerificationService.user_status(self.user) expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '', - 'status_date': datetime.now(utc)} + 'status_date': datetime.now(get_utc_timezone())} self.assertDictEqual(status, expected_status) def test_denied_software_secure_verification(self): @@ -280,7 +280,7 @@ def test_approved_verification_attempt_verification(self): VerificationAttempt.objects.create(user=self.user, status='approved') status = IDVerificationService.user_status(self.user) expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '', - 'status_date': datetime.now(utc)} + 'status_date': datetime.now(get_utc_timezone())} self.assertDictEqual(status, expected_status) def test_denied_verification_attempt_verification(self): @@ -303,7 +303,7 @@ def test_approved_sso_verification(self): SSOVerification.objects.create(user=self.user, status='approved') status = IDVerificationService.user_status(self.user) expected_status = {'status': 'approved', 'error': '', 'should_display': False, 'verification_expiry': '', - 'status_date': datetime.now(utc)} + 'status_date': datetime.now(get_utc_timezone())} self.assertDictEqual(status, expected_status) def test_denied_sso_verification(self): @@ -324,7 +324,7 @@ def test_manual_verification(self): ManualVerification.objects.create(user=self.user, status='approved') status = IDVerificationService.user_status(self.user) expected_status = {'status': 'approved', 'error': '', 'should_display': False, 'verification_expiry': '', - 'status_date': datetime.now(utc)} + 'status_date': datetime.now(get_utc_timezone())} self.assertDictEqual(status, expected_status) @ddt.idata(itertools.product( @@ -336,13 +336,13 @@ def test_expiring_software_secure_verification(self, value): with freeze_time('2015-07-11') as frozen_datetime: # create approved photo verification for the user verification_model.objects.create(user=self.user, status='approved') - expiring_datetime = datetime.now(utc) + expiring_datetime = datetime.now(get_utc_timezone()) frozen_datetime.move_to('2015-07-14') # create another according to status passed in. verification_model.objects.create(user=self.user, status=new_status) status_date = expiring_datetime if new_status == 'approved': - status_date = datetime.now(utc) + status_date = datetime.now(get_utc_timezone()) expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '', 'status_date': status_date} status = IDVerificationService.user_status(self.user) diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html index 711fad895427..e543fd4df67d 100644 --- a/lms/templates/courseware/progress.html +++ b/lms/templates/courseware/progress.html @@ -9,7 +9,7 @@ from django.urls import reverse from urllib.parse import quote_plus from django.utils.translation import gettext as _ -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.certificates.data import CertificateStatuses @@ -170,7 +170,7 @@

${ chapter['display_name']}

<% hide_url_date = (section.self_paced and section.end) or section.due - hide_url = not staff_access and section.hide_after_due and hide_url_date and datetime.now(UTC) > hide_url_date + hide_url = not staff_access and section.hide_after_due and hide_url_date and datetime.now(get_utc_timezone()) > hide_url_date earned = section.graded_total.earned total = section.graded_total.possible diff --git a/lms/templates/courseware/submission_history.html b/lms/templates/courseware/submission_history.html index b6b8f65676a6..0233d18a1b5c 100644 --- a/lms/templates/courseware/submission_history.html +++ b/lms/templates/courseware/submission_history.html @@ -1,11 +1,12 @@ <%page expression_filter="h"/> -<% import json, pytz %> +<% import json +from openedx.core.lib.time_zone_utils import get_utc_timezone %>

${username} > ${course_id} > ${location}

% for i, (entry, score) in enumerate(zip(history_entries, scores)):
- <% timedate = entry.updated.astimezone(pytz.timezone(settings.TIME_ZONE))%> + <% timedate = entry.updated.astimezone(ZoneInfo(settings.TIME_ZONE))%> <% timedate_str = timedate.strftime('%Y-%m-%d %H:%M:%S %Z') %> #${len(history_entries) - i}: ${timedate_str}
Score: ${score.grade} / ${score.max_grade} diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index d44cdefc6389..20944719df94 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -3,7 +3,7 @@ <%def name="online_help_token()"><% return "learnerdashboard" %> <%namespace name='static' file='static_content.html'/> <%! -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from datetime import datetime, timedelta from django.urls import reverse from django.utils.translation import gettext as _ @@ -166,7 +166,7 @@ entitlement = enrollment if isinstance(enrollment, CourseEntitlement) else None entitlement_session = entitlement.enrollment_course_run if entitlement else None entitlement_days_until_expiration = entitlement.get_days_until_expiration() if entitlement else None - entitlement_expiration = datetime.now(tz=pytz.UTC) + timedelta(days=entitlement_days_until_expiration) if (entitlement and entitlement_days_until_expiration < settings.ENTITLEMENT_EXPIRED_ALERT_PERIOD) else None + entitlement_expiration = datetime.now(tz=get_utc_timezone()) + timedelta(days=entitlement_days_until_expiration) if (entitlement and entitlement_days_until_expiration < settings.ENTITLEMENT_EXPIRED_ALERT_PERIOD) else None entitlement_expiration_date = strftime_localized(entitlement_expiration, 'SHORT_DATE') if entitlement and entitlement_expiration else None entitlement_expired_at = strftime_localized(entitlement.expired_at_datetime, 'SHORT_DATE') if entitlement and entitlement.expired_at_datetime else None diff --git a/lms/templates/instructor/instructor_dashboard_2/special_exams.html b/lms/templates/instructor/instructor_dashboard_2/special_exams.html index 2658af0bc70e..a5f730ba5bda 100644 --- a/lms/templates/instructor/instructor_dashboard_2/special_exams.html +++ b/lms/templates/instructor/instructor_dashboard_2/special_exams.html @@ -2,7 +2,7 @@ <%! from django.utils.translation import gettext as _ from datetime import datetime, timedelta -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone %>
% if section_data.get('mfe_view_url'): diff --git a/openedx/core/djangoapps/bookmarks/tests/test_models.py b/openedx/core/djangoapps/bookmarks/tests/test_models.py index 2c6877218acc..b0167036844c 100644 --- a/openedx/core/djangoapps/bookmarks/tests/test_models.py +++ b/openedx/core/djangoapps/bookmarks/tests/test_models.py @@ -8,7 +8,7 @@ from unittest import mock import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from freezegun import freeze_time from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator @@ -352,7 +352,7 @@ def test_path(self, seconds_delta, paths, get_path_call_count, mock_get_path): bookmark, __ = Bookmark.create(bookmark_data) assert bookmark.xblock_cache is not None - modification_datetime = datetime.datetime.now(pytz.utc) + datetime.timedelta(seconds=seconds_delta) + modification_datetime = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(seconds=seconds_delta) with freeze_time(modification_datetime): bookmark.xblock_cache.paths = paths bookmark.xblock_cache.save() diff --git a/openedx/core/djangoapps/catalog/utils.py b/openedx/core/djangoapps/catalog/utils.py index 3a241a5c5137..8a81f2a5dfe2 100644 --- a/openedx/core/djangoapps/catalog/utils.py +++ b/openedx/core/djangoapps/catalog/utils.py @@ -13,7 +13,7 @@ from edx_rest_api_client.auth import SuppliedJwtAuth from edx_rest_api_client.client import USER_AGENT from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.entitlements.utils import is_course_run_entitlement_fulfillable from common.djangoapps.student.models import CourseEnrollment @@ -593,7 +593,7 @@ def get_fulfillable_course_runs_for_entitlement(entitlement, course_runs): enrollable_sessions = [] # Only retrieve list of published course runs that can still be enrolled and upgraded - search_time = datetime.datetime.now(UTC) + search_time = datetime.datetime.now(get_utc_timezone()) for course_run in course_runs: course_id = CourseKey.from_string(course_run.get("key")) (user_enrollment_mode, is_active) = CourseEnrollment.enrollment_mode_for_user( diff --git a/openedx/core/djangoapps/ccxcon/tests/test_api.py b/openedx/core/djangoapps/ccxcon/tests/test_api.py index 762dba70e8e9..ebb933f2ff02 100644 --- a/openedx/core/djangoapps/ccxcon/tests/test_api.py +++ b/openedx/core/djangoapps/ccxcon/tests/test_api.py @@ -6,7 +6,7 @@ from urllib import parse import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from opaque_keys.edx.keys import CourseKey from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -44,10 +44,10 @@ def setUpClass(cls): # Create a course outline start = datetime.datetime( - 2010, 5, 12, 2, 42, tzinfo=pytz.UTC + 2010, 5, 12, 2, 42, tzinfo=get_utc_timezone() ) due = datetime.datetime( - 2010, 7, 7, 0, 0, tzinfo=pytz.UTC + 2010, 7, 7, 0, 0, tzinfo=get_utc_timezone() ) cls.chapters = [ diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py index 10a56f0868fb..f3d32a800d1d 100644 --- a/openedx/core/djangoapps/content/course_overviews/models.py +++ b/openedx/core/djangoapps/content/course_overviews/models.py @@ -8,7 +8,7 @@ from datetime import datetime from urllib.parse import urlparse, urlunparse -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from ccx_keys.locator import CCXLocator from config_models.models import ConfigurationModel from django.conf import settings @@ -695,7 +695,7 @@ def get_all_courses(cls, orgs=None, filter_=None, active_only=False, course_keys course_overviews = course_overviews.filter(**filter_) if active_only: course_overviews = course_overviews.filter( - Q(end__isnull=True) | Q(end__gte=datetime.now().replace(tzinfo=pytz.UTC)) + Q(end__isnull=True) | Q(end__gte=datetime.now().replace(tzinfo=get_utc_timezone())) ) return course_overviews @@ -727,11 +727,11 @@ def get_courses_by_status(cls, active_only, archived_only, course_overviews): """ if active_only: return course_overviews.filter( - Q(end__isnull=True) | Q(end__gte=datetime.now().replace(tzinfo=pytz.UTC)) + Q(end__isnull=True) | Q(end__gte=datetime.now().replace(tzinfo=get_utc_timezone())) ) if archived_only: return course_overviews.filter( - end__lt=datetime.now().replace(tzinfo=pytz.UTC) + end__lt=datetime.now().replace(tzinfo=get_utc_timezone()) ) return course_overviews diff --git a/openedx/core/djangoapps/content/course_overviews/tests/test_course_overviews.py b/openedx/core/djangoapps/content/course_overviews/tests/test_course_overviews.py index 8e95c44825c5..cd29ac33bbb4 100644 --- a/openedx/core/djangoapps/content/course_overviews/tests/test_course_overviews.py +++ b/openedx/core/djangoapps/content/course_overviews/tests/test_course_overviews.py @@ -11,7 +11,7 @@ import itertools # lint-amnesty, pylint: disable=wrong-import-order import math # lint-amnesty, pylint: disable=wrong-import-order import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.db.utils import IntegrityError from django.test.utils import override_settings @@ -93,7 +93,7 @@ def get_seconds_since_epoch(date_time): """ if date_time is None: return None - epoch = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) + epoch = datetime.datetime.fromtimestamp(0, tz=get_utc_timezone()) return math.floor((date_time - epoch).total_seconds()) # Load the CourseOverview from the cache twice. The first load will be a cache miss (because the cache diff --git a/openedx/core/djangoapps/content/course_overviews/tests/test_signals.py b/openedx/core/djangoapps/content/course_overviews/tests/test_signals.py index 49adf5540003..a49ed655703d 100644 --- a/openedx/core/djangoapps/content/course_overviews/tests/test_signals.py +++ b/openedx/core/djangoapps/content/course_overviews/tests/test_signals.py @@ -9,7 +9,7 @@ import pytest import ddt -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.data import CertificatesDisplayBehaviors from xmodule.modulestore import ModuleStoreEnum @@ -29,7 +29,7 @@ class CourseOverviewSignalsTestCase(ModuleStoreTestCase): """ MODULESTORE = TEST_DATA_ONLY_SPLIT_MODULESTORE_DRAFT_PREFERRED ENABLED_SIGNALS = ['course_deleted', 'course_published'] - TODAY = datetime.datetime.utcnow().replace(tzinfo=UTC) + TODAY = datetime.datetime.utcnow().replace(tzinfo=get_utc_timezone()) NEXT_WEEK = TODAY + datetime.timedelta(days=7) def assert_changed_signal_sent(self, changes, mock_signal): diff --git a/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py b/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py index 7d28ca5197c1..80879d3fa1be 100644 --- a/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py +++ b/openedx/core/djangoapps/credentials/management/commands/notify_credentials.py @@ -17,7 +17,7 @@ from django.core.management.base import BaseCommand, CommandError from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.credentials.models import NotifyCredentialsConfig from openedx.core.djangoapps.credentials.tasks.v1.tasks import handle_notify_credentials @@ -32,7 +32,7 @@ def parsetime(timestr): dt = dateutil.parser.parse(timestr) if dt.tzinfo is None: - dt = dt.replace(tzinfo=UTC) + dt = dt.replace(tzinfo=get_utc_timezone()) return dt diff --git a/openedx/core/djangoapps/credit/api/provider.py b/openedx/core/djangoapps/credit/api/provider.py index 9875a7d0f515..bcbd767ae1ae 100644 --- a/openedx/core/djangoapps/credit/api/provider.py +++ b/openedx/core/djangoapps/credit/api/provider.py @@ -7,7 +7,7 @@ import logging import uuid -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.db import transaction from edx_proctoring.api import get_last_exam_completion_date @@ -296,7 +296,7 @@ def create_credit_request(course_key, provider_id, username): parameters = { "request_uuid": credit_request.uuid, - "timestamp": to_timestamp(datetime.datetime.now(pytz.UTC)), + "timestamp": to_timestamp(datetime.datetime.now(get_utc_timezone())), "course_org": course_key.org, "course_num": course_key.course, "course_run": course_key.run, diff --git a/openedx/core/djangoapps/credit/models.py b/openedx/core/djangoapps/credit/models.py index 9c14a15104b9..b7635ff7c57c 100644 --- a/openedx/core/djangoapps/credit/models.py +++ b/openedx/core/djangoapps/credit/models.py @@ -10,7 +10,7 @@ import logging from collections import defaultdict -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from config_models.models import ConfigurationModel from django.conf import settings from django.core.cache import cache @@ -536,7 +536,7 @@ def default_deadline_for_credit_eligibility(): """ The default deadline to use when creating a new CreditEligibility model. """ - return datetime.datetime.now(pytz.UTC) + datetime.timedelta( + return datetime.datetime.now(get_utc_timezone()) + datetime.timedelta( days=getattr(settings, "CREDIT_ELIGIBILITY_EXPIRATION_DAYS", 365) ) @@ -617,7 +617,7 @@ def get_user_eligibilities(cls, username): return cls.objects.filter( username=username, course__enabled=True, - deadline__gt=datetime.datetime.now(pytz.UTC) + deadline__gt=datetime.datetime.now(get_utc_timezone()) ).select_related('course') @classmethod @@ -636,7 +636,7 @@ def is_user_eligible_for_credit(cls, course_key, username): course__course_key=course_key, course__enabled=True, username=username, - deadline__gt=datetime.datetime.now(pytz.UTC), + deadline__gt=datetime.datetime.now(get_utc_timezone()), ).exists() def __str__(self): diff --git a/openedx/core/djangoapps/credit/serializers.py b/openedx/core/djangoapps/credit/serializers.py index 85e8fed44e57..f7b4dbdb13b0 100644 --- a/openedx/core/djangoapps/credit/serializers.py +++ b/openedx/core/djangoapps/credit/serializers.py @@ -4,7 +4,7 @@ import datetime import logging -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from rest_framework import serializers from rest_framework.exceptions import PermissionDenied @@ -78,7 +78,7 @@ def validate_timestamp(self, value): log.warning(msg) raise serializers.ValidationError(msg) - elapsed = (datetime.datetime.now(pytz.UTC) - date_time).total_seconds() + elapsed = (datetime.datetime.now(get_utc_timezone()) - date_time).total_seconds() if elapsed > settings.CREDIT_PROVIDER_TIMESTAMP_EXPIRATION: msg = f'[{value}] is too far in the past (over [{elapsed}] seconds).' log.warning(msg) diff --git a/openedx/core/djangoapps/credit/tests/factories.py b/openedx/core/djangoapps/credit/tests/factories.py index cd777bdfe93b..4ee2e94be889 100644 --- a/openedx/core/djangoapps/credit/tests/factories.py +++ b/openedx/core/djangoapps/credit/tests/factories.py @@ -7,7 +7,7 @@ import factory from factory.fuzzy import FuzzyText -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from openedx.core.djangoapps.credit.models import ( @@ -80,7 +80,7 @@ def post(obj, create, extracted, **kwargs): obj.parameters = json.dumps({ "request_uuid": obj.uuid, - "timestamp": to_timestamp(datetime.datetime.now(pytz.UTC)), + "timestamp": to_timestamp(datetime.datetime.now(get_utc_timezone())), "course_org": course_key.org, "course_num": course_key.course, "course_run": course_key.run, diff --git a/openedx/core/djangoapps/credit/tests/test_api.py b/openedx/core/djangoapps/credit/tests/test_api.py index 7dc644dc097a..508d826c2423 100644 --- a/openedx/core/djangoapps/credit/tests/test_api.py +++ b/openedx/core/djangoapps/credit/tests/test_api.py @@ -9,7 +9,7 @@ import pytest import ddt import httpretty -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core import mail from django.db import connection @@ -400,7 +400,7 @@ def test_eligibility_expired(self): CreditEligibility.objects.create( course=credit_course, username="staff", - deadline=datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=1) + deadline=datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1) ) # The user should NOT be eligible for credit @@ -960,7 +960,7 @@ def test_credit_request(self): # Validate the timestamp assert 'timestamp' in parameters parsed_date = from_timestamp(parameters['timestamp']) - assert parsed_date < datetime.datetime.now(pytz.UTC) + assert parsed_date < datetime.datetime.now(get_utc_timezone()) # Validate course information assert parameters['course_org'] == self.course_key.org diff --git a/openedx/core/djangoapps/credit/tests/test_signals.py b/openedx/core/djangoapps/credit/tests/test_signals.py index c3331c0ecfc6..692beefab918 100644 --- a/openedx/core/djangoapps/credit/tests/test_signals.py +++ b/openedx/core/djangoapps/credit/tests/test_signals.py @@ -7,7 +7,7 @@ from uuid import uuid4 import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test.client import RequestFactory from opaque_keys.edx.keys import UsageKey from openedx_events.data import EventsMetadata @@ -47,8 +47,8 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase): satisfied. But if student grade is less than and deadline is passed then user will be marked as failed. """ - VALID_DUE_DATE = datetime.now(pytz.UTC) + timedelta(days=20) - EXPIRED_DUE_DATE = datetime.now(pytz.UTC) - timedelta(days=20) + VALID_DUE_DATE = datetime.now(get_utc_timezone()) + timedelta(days=20) + EXPIRED_DUE_DATE = datetime.now(get_utc_timezone()) - timedelta(days=20) DATES = { 'valid': VALID_DUE_DATE, diff --git a/openedx/core/djangoapps/credit/tests/test_views.py b/openedx/core/djangoapps/credit/tests/test_views.py index deb3c8726aeb..e880a6311de4 100644 --- a/openedx/core/djangoapps/credit/tests/test_views.py +++ b/openedx/core/djangoapps/credit/tests/test_views.py @@ -7,7 +7,7 @@ import json import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.test import Client, TestCase from django.test.utils import override_settings @@ -523,7 +523,7 @@ def _credit_provider_callback(self, request_uuid, status, **kwargs): """ provider_id = kwargs.get('provider_id', self.provider.provider_id) secret_key = kwargs.get('secret_key', '931433d583c84ca7ba41784bad3232e6') - timestamp = kwargs.get('timestamp', to_timestamp(datetime.datetime.now(pytz.UTC))) + timestamp = kwargs.get('timestamp', to_timestamp(datetime.datetime.now(get_utc_timezone()))) keys = kwargs.get('keys', {self.provider.provider_id: secret_key}) url = reverse('credit:provider_callback', args=[provider_id]) @@ -577,7 +577,7 @@ def test_post_with_invalid_timestamp(self, timedelta): if timedelta == 'invalid': timestamp = timedelta else: - timestamp = to_timestamp(datetime.datetime.now(pytz.UTC) + timedelta) + timestamp = to_timestamp(datetime.datetime.now(get_utc_timezone()) + timedelta) request_uuid = self._create_credit_request_and_get_uuid() response = self._credit_provider_callback(request_uuid, 'approved', timestamp=timestamp) assert response.status_code == 400 @@ -585,7 +585,7 @@ def test_post_with_invalid_timestamp(self, timedelta): def test_post_with_string_timestamp(self): """ Verify the endpoint supports timestamps transmitted as strings instead of integers. """ request_uuid = self._create_credit_request_and_get_uuid() - timestamp = str(to_timestamp(datetime.datetime.now(pytz.UTC))) + timestamp = str(to_timestamp(datetime.datetime.now(get_utc_timezone()))) response = self._credit_provider_callback(request_uuid, 'approved', timestamp=timestamp) assert response.status_code == 200 diff --git a/openedx/core/djangoapps/credit/views.py b/openedx/core/djangoapps/credit/views.py index 2a06f85a321a..4d4ad01ee79a 100644 --- a/openedx/core/djangoapps/credit/views.py +++ b/openedx/core/djangoapps/credit/views.py @@ -6,7 +6,7 @@ import datetime import logging -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt @@ -166,7 +166,7 @@ def filter_queryset(self, queryset): return queryset.filter( username=username, course__course_key=course_key, - deadline__gt=datetime.datetime.now(pytz.UTC) + deadline__gt=datetime.datetime.now(get_utc_timezone()) ) diff --git a/openedx/core/djangoapps/enrollments/tests/test_data.py b/openedx/core/djangoapps/enrollments/tests/test_data.py index 93b299d75a3c..2e2b3dd6f3f7 100644 --- a/openedx/core/djangoapps/enrollments/tests/test_data.py +++ b/openedx/core/djangoapps/enrollments/tests/test_data.py @@ -8,7 +8,7 @@ import ddt import pytest -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory @@ -369,7 +369,7 @@ def test_get_course_without_expired_mode_included(self): def _update_verified_mode_as_expired(self, course_id): """Dry method to change verified mode expiration.""" mode = CourseMode.objects.get(course_id=course_id, mode_slug=CourseMode.VERIFIED) - mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=UTC) + mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=get_utc_timezone()) mode.save() def assert_enrollment_modes(self, expected_modes, include_expired): diff --git a/openedx/core/djangoapps/enrollments/tests/test_views.py b/openedx/core/djangoapps/enrollments/tests/test_views.py index a6b34cbfc60b..b89a7a2b3c16 100644 --- a/openedx/core/djangoapps/enrollments/tests/test_views.py +++ b/openedx/core/djangoapps/enrollments/tests/test_views.py @@ -12,7 +12,7 @@ import ddt import httpretty import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.core.cache import cache from django.core.exceptions import ImproperlyConfigured @@ -356,8 +356,8 @@ def test_enroll_without_user(self): @ddt.unpack def test_force_enrollment(self, course_modes, enrollment_mode, force_enrollment): # Create the course modes (if any) required for this test case - start_date = datetime.datetime(2021, 12, 1, 5, 0, 0, tzinfo=pytz.UTC) - end_date = datetime.datetime(2022, 12, 1, 5, 0, 0, tzinfo=pytz.UTC) + start_date = datetime.datetime(2021, 12, 1, 5, 0, 0, tzinfo=get_utc_timezone()) + end_date = datetime.datetime(2022, 12, 1, 5, 0, 0, tzinfo=get_utc_timezone()) self.course = CourseFactory.create( emit_signals=True, start=start_date, @@ -658,11 +658,11 @@ def test_get_course_details_with_credit_course(self): # enforced at the data layer, so we need to handle the case # in which no dates are specified. (None, None, None, None), - (datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=pytz.UTC), None, "2015-01-02T03:04:05Z", None), - (None, datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=pytz.UTC), None, "2015-01-02T03:04:05Z"), + (datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=get_utc_timezone()), None, "2015-01-02T03:04:05Z", None), + (None, datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=get_utc_timezone()), None, "2015-01-02T03:04:05Z"), ( - datetime.datetime(2014, 6, 7, 8, 9, 10, tzinfo=pytz.UTC), - datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=pytz.UTC), + datetime.datetime(2014, 6, 7, 8, 9, 10, tzinfo=get_utc_timezone()), + datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=get_utc_timezone()), "2014-06-07T08:09:10Z", "2015-01-02T03:04:05Z", ), @@ -1078,7 +1078,7 @@ def test_deactivate_enrollment_expired_mode(self): # Change verified mode expiration. mode = CourseMode.objects.get(course_id=self.course.id, mode_slug=CourseMode.VERIFIED) - mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=pytz.utc) + mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=get_utc_timezone()) mode.save() # Deactivate enrollment. @@ -1198,7 +1198,7 @@ def test_update_enrollment_with_expired_mode(self, using_api_key, updated_mode): # Change verified mode expiration. mode = CourseMode.objects.get(course_id=self.course.id, mode_slug=CourseMode.VERIFIED) - mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=pytz.utc) + mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=get_utc_timezone()) mode.save() self.assert_enrollment_status( as_server=using_api_key, @@ -1784,7 +1784,7 @@ class CourseEnrollmentsApiListTest(APITestCase, ModuleStoreTestCase): """ Test the course enrollments list API. """ - CREATED_DATA = datetime.datetime(2018, 1, 1, 0, 0, 1, tzinfo=pytz.UTC) + CREATED_DATA = datetime.datetime(2018, 1, 1, 0, 0, 1, tzinfo=get_utc_timezone()) def setUp(self): super().setUp() diff --git a/openedx/core/djangoapps/models/tests/test_course_details.py b/openedx/core/djangoapps/models/tests/test_course_details.py index 41e739ecb4c8..df0e4ebd0dbf 100644 --- a/openedx/core/djangoapps/models/tests/test_course_details.py +++ b/openedx/core/djangoapps/models/tests/test_course_details.py @@ -7,7 +7,7 @@ from django.test import override_settings import pytest import ddt -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from xmodule.modulestore import ModuleStoreEnum @@ -86,13 +86,13 @@ def test_update_and_fetch(self): jsondetails.self_paced = True assert CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).self_paced ==\ jsondetails.self_paced - jsondetails.start_date = datetime.datetime(2010, 10, 1, 0, tzinfo=UTC) + jsondetails.start_date = datetime.datetime(2010, 10, 1, 0, tzinfo=get_utc_timezone()) assert CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).start_date ==\ jsondetails.start_date - jsondetails.end_date = datetime.datetime(2011, 10, 1, 0, tzinfo=UTC) + jsondetails.end_date = datetime.datetime(2011, 10, 1, 0, tzinfo=get_utc_timezone()) assert CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).end_date ==\ jsondetails.end_date - jsondetails.certificate_available_date = datetime.datetime(2010, 10, 1, 0, tzinfo=UTC) + jsondetails.certificate_available_date = datetime.datetime(2010, 10, 1, 0, tzinfo=get_utc_timezone()) assert CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user)\ .certificate_available_date == jsondetails.certificate_available_date jsondetails.course_image_name = "an_image.jpg" @@ -126,7 +126,7 @@ def test_update_and_fetch(self): jsondetails.instructor_info def test_toggle_pacing_during_course_run(self): - self.course.start = datetime.datetime.now(UTC) + self.course.start = datetime.datetime.now(get_utc_timezone()) self.store.update_item(self.course, self.user.id) details = CourseDetails.fetch(self.course.id) diff --git a/openedx/core/djangoapps/notifications/email/tests/test_utils.py b/openedx/core/djangoapps/notifications/email/tests/test_utils.py index e7b5db54e800..503522ea5b47 100644 --- a/openedx/core/djangoapps/notifications/email/tests/test_utils.py +++ b/openedx/core/djangoapps/notifications/email/tests/test_utils.py @@ -7,7 +7,7 @@ from django.http.response import Http404 from itertools import product -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from waffle import get_waffle_flag_model # pylint: disable=invalid-django-waffle-import from common.djangoapps.student.tests.factories import UserFactory @@ -90,7 +90,7 @@ def test_get_time_ago(self): """ Tests time_ago string """ - current_datetime = utc.localize(datetime.datetime.now()) + current_datetime = datetime.datetime.now(get_utc_timezone()) assert "Today" == get_time_ago(current_datetime) assert "1d" == get_time_ago(current_datetime - datetime.timedelta(days=1)) assert "1w" == get_time_ago(current_datetime - datetime.timedelta(days=7)) diff --git a/openedx/core/djangoapps/notifications/email/utils.py b/openedx/core/djangoapps/notifications/email/utils.py index 8ff985d993a9..0bcdf716b0b8 100644 --- a/openedx/core/djangoapps/notifications/email/utils.py +++ b/openedx/core/djangoapps/notifications/email/utils.py @@ -9,7 +9,7 @@ from django.contrib.auth import get_user_model from django.shortcuts import get_object_or_404 from django.utils.translation import gettext as _ -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from waffle import get_waffle_flag_model # pylint: disable=invalid-django-waffle-import from common.djangoapps.student.models import CourseEnrollment @@ -196,7 +196,7 @@ def get_start_end_date(cadence_type): start_date = end_date - datetime.timedelta(days=1, minutes=15) if cadence_type == EmailCadence.WEEKLY: start_date = start_date - datetime.timedelta(days=6) - return utc.localize(start_date), utc.localize(end_date) + return start_date.replace(tzinfo=get_utc_timezone()), end_date.replace(tzinfo=get_utc_timezone()) def get_course_info(course_key): @@ -212,7 +212,7 @@ def get_time_ago(datetime_obj): """ Returns time_ago for datetime instance """ - current_date = utc.localize(datetime.datetime.today()) + current_date = datetime.datetime.now(get_utc_timezone()) days_diff = (current_date - datetime_obj).days if days_diff == 0: return _("Today") diff --git a/openedx/core/djangoapps/notifications/grouping_notifications.py b/openedx/core/djangoapps/notifications/grouping_notifications.py index c855ca3d234e..dd9584755641 100644 --- a/openedx/core/djangoapps/notifications/grouping_notifications.py +++ b/openedx/core/djangoapps/notifications/grouping_notifications.py @@ -1,12 +1,10 @@ """ Notification grouping utilities for notifications """ -import datetime from abc import ABC, abstractmethod +from datetime import datetime, timezone from typing import Dict, Type, Union -from pytz import utc - from openedx.core.djangoapps.notifications.base_notification import COURSE_NOTIFICATION_TYPES from openedx.core.djangoapps.notifications.models import Notification @@ -120,7 +118,7 @@ def group_user_notifications(new_notification: Notification, old_notification: N old_notification.content_url = new_notification.content_url old_notification.last_read = None old_notification.last_seen = None - old_notification.created = utc.localize(datetime.datetime.now()) + old_notification.created = datetime.now(timezone.utc) old_notification.save() diff --git a/openedx/core/djangoapps/notifications/tasks.py b/openedx/core/djangoapps/notifications/tasks.py index fb9f95990d3a..18d67b4b41c6 100644 --- a/openedx/core/djangoapps/notifications/tasks.py +++ b/openedx/core/djangoapps/notifications/tasks.py @@ -10,7 +10,7 @@ from django.core.exceptions import ValidationError from edx_django_utils.monitoring import set_code_owner_attribute from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.notifications.audience_filters import NotificationFilter from openedx.core.djangoapps.notifications.base_notification import ( @@ -75,7 +75,7 @@ def delete_expired_notifications(): This task deletes all expired notifications """ batch_size = settings.EXPIRED_NOTIFICATIONS_DELETE_BATCH_SIZE - expiry_date = datetime.now(UTC) - timedelta(days=settings.NOTIFICATIONS_EXPIRY) + expiry_date = datetime.now(get_utc_timezone()) - timedelta(days=settings.NOTIFICATIONS_EXPIRY) start_time = datetime.now() total_deleted = 0 delete_count = None diff --git a/openedx/core/djangoapps/notifications/tests/test_notification_grouping.py b/openedx/core/djangoapps/notifications/tests/test_notification_grouping.py index debd72d9011f..d12696eaa65a 100644 --- a/openedx/core/djangoapps/notifications/tests/test_notification_grouping.py +++ b/openedx/core/djangoapps/notifications/tests/test_notification_grouping.py @@ -6,7 +6,7 @@ import unittest from unittest.mock import MagicMock, patch from datetime import datetime -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student.tests.factories import UserFactory from openedx.core.djangoapps.notifications.grouping_notifications import ( @@ -128,7 +128,7 @@ def test_group_user_notifications_no_grouper(self): self.assertFalse(old_notification.save.called) - @ddt.data(datetime(2023, 1, 1, tzinfo=utc), None) + @ddt.data(datetime(2023, 1, 1, tzinfo=get_utc_timezone()), None) def test_not_grouped_when_notification_is_seen(self, last_seen): """ Notification is not grouped if the notification is marked as seen @@ -172,11 +172,11 @@ def test_get_user_existing_notifications(self, mock_filter): # Mock the notification objects returned by the filter mock_notification1 = MagicMock(spec=Notification) mock_notification1.user_id = 1 - mock_notification1.created = datetime(2023, 9, 1, tzinfo=utc) + mock_notification1.created = datetime(2023, 9, 1, tzinfo=get_utc_timezone()) mock_notification2 = MagicMock(spec=Notification) mock_notification2.user_id = 1 - mock_notification2.created = datetime(2023, 9, 2, tzinfo=utc) + mock_notification2.created = datetime(2023, 9, 2, tzinfo=get_utc_timezone()) mock_filter.return_value = [mock_notification1, mock_notification2] diff --git a/openedx/core/djangoapps/notifications/tests/test_views.py b/openedx/core/djangoapps/notifications/tests/test_views.py index b07beb9506c8..debb36077f02 100644 --- a/openedx/core/djangoapps/notifications/tests/test_views.py +++ b/openedx/core/djangoapps/notifications/tests/test_views.py @@ -10,7 +10,7 @@ from django.test.utils import override_settings from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import status from rest_framework.test import APIClient, APITestCase @@ -190,7 +190,7 @@ def test_list_notifications_with_expiry_date(self): """ Test that the view can filter notifications by expiry date. """ - today = datetime.now(UTC) + today = datetime.now(get_utc_timezone()) # Create two notifications for the user, one with current date and other with expiry date. Notification.objects.create( diff --git a/openedx/core/djangoapps/notifications/views.py b/openedx/core/djangoapps/notifications/views.py index d257439106f4..b495a609a09d 100644 --- a/openedx/core/djangoapps/notifications/views.py +++ b/openedx/core/djangoapps/notifications/views.py @@ -7,7 +7,7 @@ from django.db.models import Count from django.shortcuts import get_object_or_404 from django.utils.translation import gettext as _ -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import generics, status from rest_framework.decorators import api_view from rest_framework.generics import UpdateAPIView @@ -78,7 +78,7 @@ def get_queryset(self): """ Override the get_queryset method to filter the queryset by app name, request.user and created """ - expiry_date = datetime.now(UTC) - timedelta(days=settings.NOTIFICATIONS_EXPIRY) + expiry_date = datetime.now(get_utc_timezone()) - timedelta(days=settings.NOTIFICATIONS_EXPIRY) app_name = self.request.query_params.get('app_name') if self.request.query_params.get('tray_opened'): @@ -210,7 +210,7 @@ def patch(self, request, *args, **kwargs): - 404: Not Found status code if the notification was not found. """ notification_id = request.data.get('notification_id', None) - read_at = datetime.now(UTC) + read_at = datetime.now(get_utc_timezone()) if notification_id: notification = get_object_or_404(Notification, pk=notification_id, user=request.user) diff --git a/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py b/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py index f8cf0538140d..fe48c1d8a701 100644 --- a/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py +++ b/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py @@ -11,7 +11,7 @@ from oauth2_provider.models import AccessToken from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.scopes import get_scopes_backend -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from ..models import RestrictedApplication # pylint: disable=W0223 @@ -23,7 +23,7 @@ def on_access_token_presave(sender, instance, *args, **kwargs): # pylint: disab Mark AccessTokens as expired for 'restricted applications' if required. """ if RestrictedApplication.should_expire_access_token(instance.application): - instance.expires = datetime(1970, 1, 1, tzinfo=utc) + instance.expires = datetime(1970, 1, 1, tzinfo=get_utc_timezone()) class EdxOAuth2Validator(OAuth2Validator): @@ -152,4 +152,4 @@ def _get_utc_now(): """ Return current time in UTC. """ - return datetime.utcnow().replace(tzinfo=utc) + return datetime.utcnow().replace(tzinfo=get_utc_timezone()) diff --git a/openedx/core/djangoapps/oauth_dispatch/models.py b/openedx/core/djangoapps/oauth_dispatch/models.py index 2e635167e4c7..35e232282921 100644 --- a/openedx/core/djangoapps/oauth_dispatch/models.py +++ b/openedx/core/djangoapps/oauth_dispatch/models.py @@ -11,7 +11,7 @@ from django_mysql.models import ListCharField from oauth2_provider.settings import oauth2_settings from organizations.models import Organization -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangolib.markup import HTML from openedx.core.lib.request_utils import get_request_or_stub @@ -53,7 +53,7 @@ def verify_access_token_as_expired(cls, access_token): For access_tokens for RestrictedApplications, make sure that the expiry date is set at the beginning of the epoch which is Jan. 1, 1970 """ - return access_token.expires == datetime(1970, 1, 1, tzinfo=utc) + return access_token.expires == datetime(1970, 1, 1, tzinfo=get_utc_timezone()) class ApplicationAccess(models.Model): diff --git a/openedx/core/djangoapps/oauth_dispatch/tests/factories.py b/openedx/core/djangoapps/oauth_dispatch/tests/factories.py index 473bcd4ced9d..10cf1268385e 100644 --- a/openedx/core/djangoapps/oauth_dispatch/tests/factories.py +++ b/openedx/core/djangoapps/oauth_dispatch/tests/factories.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta import factory -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from factory.django import DjangoModelFactory from factory.fuzzy import FuzzyText from oauth2_provider.models import AccessToken, Application, RefreshToken @@ -39,7 +39,7 @@ class Meta: django_get_or_create = ('user', 'application') token = FuzzyText(length=32) - expires = datetime.now(pytz.UTC) + timedelta(days=1) + expires = datetime.now(get_utc_timezone()) + timedelta(days=1) class RefreshTokenFactory(DjangoModelFactory): diff --git a/openedx/core/djangoapps/password_policy/compliance.py b/openedx/core/djangoapps/password_policy/compliance.py index fdd103d2437d..e9a31695facd 100644 --- a/openedx/core/djangoapps/password_policy/compliance.py +++ b/openedx/core/djangoapps/password_policy/compliance.py @@ -4,7 +4,7 @@ from datetime import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.utils.translation import gettext as _ @@ -69,7 +69,7 @@ def enforce_compliance_on_login(user, password): if deadline is None: return - now = datetime.now(pytz.UTC) + now = datetime.now(get_utc_timezone()) if now >= deadline: # lint-amnesty, pylint: disable=no-else-raise raise NonCompliantPasswordException( HTML(_( diff --git a/openedx/core/djangoapps/password_policy/tests/test_compliance.py b/openedx/core/djangoapps/password_policy/tests/test_compliance.py index cb803bed99a9..dd226e67c17a 100644 --- a/openedx/core/djangoapps/password_policy/tests/test_compliance.py +++ b/openedx/core/djangoapps/password_policy/tests/test_compliance.py @@ -6,7 +6,7 @@ from unittest.mock import patch import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from dateutil.parser import parse as parse_date from django.test import TestCase, override_settings @@ -75,7 +75,7 @@ def test_enforce_compliance_on_login(self): mock_check_user_compliance.return_value = False with patch('openedx.core.djangoapps.password_policy.compliance._get_compliance_deadline_for_user') as \ mock_get_compliance_deadline_for_user: - mock_get_compliance_deadline_for_user.return_value = datetime.now(pytz.UTC) - timedelta(1) + mock_get_compliance_deadline_for_user.return_value = datetime.now(get_utc_timezone()) - timedelta(1) pytest.raises(NonCompliantPasswordException, enforce_compliance_on_login, user, password) # Test deadline is in the future @@ -84,7 +84,7 @@ def test_enforce_compliance_on_login(self): mock_check_user_compliance.return_value = False with patch('openedx.core.djangoapps.password_policy.compliance._get_compliance_deadline_for_user') as \ mock_get_compliance_deadline_for_user: - mock_get_compliance_deadline_for_user.return_value = datetime.now(pytz.UTC) + timedelta(1) + mock_get_compliance_deadline_for_user.return_value = datetime.now(get_utc_timezone()) + timedelta(1) assert pytest.raises(NonCompliantPasswordWarning, enforce_compliance_on_login, user, password) def test_check_user_compliance(self): diff --git a/openedx/core/djangoapps/profile_images/tests/test_views.py b/openedx/core/djangoapps/profile_images/tests/test_views.py index 0a276377589b..66163e02b067 100644 --- a/openedx/core/djangoapps/profile_images/tests/test_views.py +++ b/openedx/core/djangoapps/profile_images/tests/test_views.py @@ -7,7 +7,7 @@ import pytest import datetime # lint-amnesty, pylint: disable=wrong-import-order -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.urls import reverse from django.http import HttpResponse @@ -30,8 +30,8 @@ from .helpers import make_image_file TEST_PASSWORD = "test" -TEST_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 1, tzinfo=UTC) -TEST_UPLOAD_DT2 = datetime.datetime(2003, 1, 9, 15, 43, 1, tzinfo=UTC) +TEST_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 1, tzinfo=get_utc_timezone()) +TEST_UPLOAD_DT2 = datetime.datetime(2003, 1, 9, 15, 43, 1, tzinfo=get_utc_timezone()) class ProfileImageEndpointMixin(UserSettingsEventTestMixin): diff --git a/openedx/core/djangoapps/profile_images/views.py b/openedx/core/djangoapps/profile_images/views.py index b88b3ad32bdb..8cd78c87162f 100644 --- a/openedx/core/djangoapps/profile_images/views.py +++ b/openedx/core/djangoapps/profile_images/views.py @@ -11,7 +11,7 @@ from django.utils.translation import gettext as _ from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from rest_framework import permissions, status from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.response import Response @@ -38,7 +38,7 @@ def _make_upload_dt(): Generate a server-side timestamp for the upload. This is in a separate function so its behavior can be overridden in tests. """ - return datetime.datetime.utcnow().replace(tzinfo=UTC) + return datetime.datetime.utcnow().replace(tzinfo=get_utc_timezone()) class ProfileImageView(DeveloperErrorViewMixin, APIView): diff --git a/openedx/core/djangoapps/programs/tests/test_tasks.py b/openedx/core/djangoapps/programs/tests/test_tasks.py index e2b1c554c840..fd5a756022af 100644 --- a/openedx/core/djangoapps/programs/tests/test_tasks.py +++ b/openedx/core/djangoapps/programs/tests/test_tasks.py @@ -10,7 +10,7 @@ import ddt import httpretty import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone import requests from celery.exceptions import MaxRetriesExceededError from django.conf import settings @@ -520,7 +520,7 @@ class AwardCourseCertificatesTestCase(CredentialsApiConfigMixin, TestCase): def setUp(self): super().setUp() - self.available_date = datetime.now(pytz.UTC) + timedelta(days=1) + self.available_date = datetime.now(get_utc_timezone()) + timedelta(days=1) self.course = CourseOverviewFactory.create( self_paced=True, # Any option to allow the certificate to be viewable for the course certificate_available_date=self.available_date, @@ -1023,7 +1023,7 @@ class UpdateCertificateAvailableDateOnCourseUpdateTestCase(CredentialsApiConfigM def setUp(self): super().setUp() - self.end_date = datetime.now(pytz.UTC) + timedelta(days=90) + self.end_date = datetime.now(get_utc_timezone()) + timedelta(days=90) self.credentials_api_config = self.create_credentials_config(enabled=False) def tearDown(self): @@ -1135,7 +1135,7 @@ def test_update_certificate_available_date_instructor_paced_cdb_end_with_date(se explicitly set as part of the course overview. """ self._update_credentials_api_config(True) - certificate_available_date = datetime.now(pytz.UTC) + timedelta(days=120) + certificate_available_date = datetime.now(get_utc_timezone()) + timedelta(days=120) course_overview = self._create_course_overview( False, @@ -1168,7 +1168,7 @@ def test_update_certificate_available_date_self_paced(self, mock_update): invalid data is set in a course overview, we don't pass it to Credentials. """ self._update_credentials_api_config(True) - certificate_available_date = datetime.now(pytz.UTC) + timedelta(days=120) + certificate_available_date = datetime.now(get_utc_timezone()) + timedelta(days=120) course_overview = self._create_course_overview( True, diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py index 264f1a6aeebd..56471eabb75a 100644 --- a/openedx/core/djangoapps/programs/tests/test_utils.py +++ b/openedx/core/djangoapps/programs/tests/test_utils.py @@ -15,7 +15,7 @@ from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_switch from opaque_keys.edx.keys import CourseKey # lint-amnesty, pylint: disable=wrong-import-order -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from testfixtures import LogCapture from common.djangoapps.course_modes.models import CourseMode @@ -209,7 +209,7 @@ def test_single_program_multiple_entitlements(self, mock_get_programs): CourseEntitlementFactory.create( user=self.user, course_uuid=course_uuid, - expired_at=datetime.datetime.now(utc), + expired_at=datetime.datetime.now(get_utc_timezone()), mode=CourseMode.VERIFIED, enrollment_course_run=enrollment @@ -308,7 +308,7 @@ def test_in_progress_course_upgrade_deadline_check(self, offset, mock_get_progra the right type for which the upgrade deadline has not passed. """ course_run_key = generate_course_run_key() - now = datetime.datetime.now(utc) + now = datetime.datetime.now(get_utc_timezone()) upgrade_deadline = None if not offset else str(now + datetime.timedelta(days=offset)) required_seat = SeatFactory(type=CourseMode.VERIFIED, upgrade_deadline=upgrade_deadline) enrolled_seat = SeatFactory(type=CourseMode.AUDIT) @@ -488,7 +488,7 @@ def test_shared_entitlement_engagement(self, mock_get_programs): def test_simulate_progress(self, mock_get_programs): # lint-amnesty, pylint: disable=too-many-statements """Simulate the entirety of a user's progress through a program.""" - today = datetime.datetime.now(utc) + today = datetime.datetime.now(get_utc_timezone()) two_days_ago = today - datetime.timedelta(days=2) three_days_ago = today - datetime.timedelta(days=3) yesterday = today - datetime.timedelta(days=1) @@ -862,8 +862,8 @@ def _create_course(self, course_price, course_run_count=1, make_entitlement=Fals course_runs = [] for x in range(course_run_count): course = ModuleStoreCourseFactory.create(run='Run_' + str(x)) - course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) - course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) + course.start = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1) + course.end = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) course.instructor_info = self.instructors course = self.update_course(course, self.user.id) @@ -899,8 +899,8 @@ def setUp(self): super().setUp() self.course = ModuleStoreCourseFactory() - self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) - self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) + self.course.start = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1) + self.course.end = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) self.course_run = CourseRunFactory(key=str(self.course.id)) @@ -941,7 +941,7 @@ def test_is_enrollment_open(self, days_offset): Verify that changes to the course run end date do not affect our assessment of the course run being open for enrollment. """ - self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=days_offset) + self.course.end = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=days_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() @@ -1022,8 +1022,8 @@ def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollm """ Verify that course run enrollment status is reflected correctly. """ - self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset) - self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset) + self.course.enrollment_start = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=start_offset) + self.course.enrollment_end = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=end_offset) self.course = self.update_course(self.course, self.user.id) @@ -1040,7 +1040,7 @@ def test_no_enrollment_start_date(self): Verify that a closed course run with no explicit enrollment start date doesn't cause an error. Regression test for ECOM-4973. """ - self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=1) + self.course.enrollment_end = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index 76263c4b405c..e7c072e813be 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -14,7 +14,7 @@ from django.urls import reverse from django.utils.functional import cached_property from opaque_keys.edx.keys import CourseKey -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from requests.exceptions import RequestException from common.djangoapps.course_modes.api import get_paid_modes_for_course @@ -43,7 +43,7 @@ from xmodule.modulestore.django import modulestore # The datetime module's strftime() methods require a year >= 1900. -DEFAULT_ENROLLMENT_START_DATE = datetime.datetime(1900, 1, 1, tzinfo=utc) +DEFAULT_ENROLLMENT_START_DATE = datetime.datetime(1900, 1, 1, tzinfo=get_utc_timezone()) log = logging.getLogger(__name__) @@ -286,7 +286,7 @@ def progress(self, programs: list[dict | None] | None = None, count_only: bool = list of dict, each containing information about a user's progress towards completing a program. """ - now = datetime.datetime.now(utc) + now = datetime.datetime.now(get_utc_timezone()) progress = [] programs = programs or self.engaged_programs @@ -598,15 +598,16 @@ def _attach_course_run_enrollment_open_date(self, run_mode): run_mode["enrollment_open_date"] = strftime_localized(self.enrollment_start, "SHORT_DATE") def _attach_course_run_is_course_ended(self, run_mode): - end_date = self.course_overview.end or datetime.datetime.max.replace(tzinfo=utc) - run_mode["is_course_ended"] = end_date < datetime.datetime.now(utc) + end_date = self.course_overview.end or datetime.datetime.max.replace(tzinfo=get_utc_timezone()) + run_mode["is_course_ended"] = end_date < datetime.datetime.now(get_utc_timezone()) def _attach_course_run_is_enrolled(self, run_mode): run_mode["is_enrolled"] = CourseEnrollment.is_enrolled(self.user, self.course_run_key) def _attach_course_run_is_enrollment_open(self, run_mode): - enrollment_end = self.course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=utc) - run_mode["is_enrollment_open"] = self.enrollment_start <= datetime.datetime.now(utc) < enrollment_end + enrollment_end = self.course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=get_utc_timezone()) + run_mode["is_enrollment_open"] = self.enrollment_start <= datetime.datetime.now( + get_utc_timezone()) < enrollment_end def _attach_course_run_advertised_start(self, run_mode): """ diff --git a/openedx/core/djangoapps/schedules/management/commands/__init__.py b/openedx/core/djangoapps/schedules/management/commands/__init__.py index bd0082f5331e..e6b75aaaec1e 100644 --- a/openedx/core/djangoapps/schedules/management/commands/__init__.py +++ b/openedx/core/djangoapps/schedules/management/commands/__init__.py @@ -5,7 +5,7 @@ import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.contrib.sites.models import Site from django.core.management.base import BaseCommand @@ -46,7 +46,7 @@ def handle(self, *args, **options): current_date = datetime.datetime( *[int(x) for x in options['date'].split('-')], - tzinfo=pytz.UTC + tzinfo=get_utc_timezone() ) self.log_debug('Current date = %s', current_date.isoformat()) diff --git a/openedx/core/djangoapps/schedules/management/commands/send_course_next_section_update.py b/openedx/core/djangoapps/schedules/management/commands/send_course_next_section_update.py index 53e2100649a1..f227aef29f8f 100644 --- a/openedx/core/djangoapps/schedules/management/commands/send_course_next_section_update.py +++ b/openedx/core/djangoapps/schedules/management/commands/send_course_next_section_update.py @@ -3,7 +3,7 @@ """ import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from textwrap import dedent # lint-amnesty, pylint: disable=wrong-import-order from django.contrib.sites.models import Site @@ -23,7 +23,7 @@ class Command(SendEmailBaseCommand): def handle(self, *args, ** options): current_date = datetime.datetime( *[int(x) for x in options['date'].split('-')], - tzinfo=pytz.UTC + tzinfo=get_utc_timezone() ) site = Site.objects.get(domain__iexact=options['site_domain_name']) diff --git a/openedx/core/djangoapps/schedules/management/commands/setup_models_to_send_test_emails.py b/openedx/core/djangoapps/schedules/management/commands/setup_models_to_send_test_emails.py index 976f1aa16fd9..5cb2c944fc74 100644 --- a/openedx/core/djangoapps/schedules/management/commands/setup_models_to_send_test_emails.py +++ b/openedx/core/djangoapps/schedules/management/commands/setup_models_to_send_test_emails.py @@ -7,7 +7,7 @@ from textwrap import dedent import factory -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.contrib.sites.models import Site from django.core.management.base import BaseCommand @@ -26,29 +26,29 @@ class ThreeDayNudgeSchedule(ScheduleFactory): """ A ScheduleFactory that creates a Schedule set up for a 3-day nudge email. """ - start_date = factory.Faker('date_time_between', start_date='-3d', end_date='-3d', tzinfo=pytz.UTC) + start_date = factory.Faker('date_time_between', start_date='-3d', end_date='-3d', tzinfo=get_utc_timezone()) class TenDayNudgeSchedule(ScheduleFactory): """ A ScheduleFactory that creates a Schedule set up for a 10-day nudge email. """ - start_date = factory.Faker('date_time_between', start_date='-10d', end_date='-10d', tzinfo=pytz.UTC) + start_date = factory.Faker('date_time_between', start_date='-10d', end_date='-10d', tzinfo=get_utc_timezone()) class UpgradeReminderSchedule(ScheduleFactory): """ A ScheduleFactory that creates a Schedule set up for a 2-days-remaining upgrade reminder. """ - start_date = factory.Faker('past_datetime', tzinfo=pytz.UTC) - upgrade_deadline = factory.Faker('date_time_between', start_date='+2d', end_date='+2d', tzinfo=pytz.UTC) + start_date = factory.Faker('past_datetime', tzinfo=get_utc_timezone()) + upgrade_deadline = factory.Faker('date_time_between', start_date='+2d', end_date='+2d', tzinfo=get_utc_timezone()) class ContentHighlightSchedule(ScheduleFactory): """ A ScheduleFactory that creates a Schedule set up for a course highlights email. """ - start_date = factory.Faker('date_time_between', start_date='-7d', end_date='-7d', tzinfo=pytz.UTC) + start_date = factory.Faker('date_time_between', start_date='-7d', end_date='-7d', tzinfo=get_utc_timezone()) experience = factory.RelatedFactory(ScheduleExperienceFactory, 'schedule', experience_type=ScheduleExperience.EXPERIENCES.course_updates) # lint-amnesty, pylint: disable=line-too-long diff --git a/openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py b/openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py index 774f1f418124..81e3a1d59c04 100644 --- a/openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py +++ b/openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py @@ -11,7 +11,7 @@ import attr import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.db.models import Max @@ -119,7 +119,7 @@ def _next_user_id(self): return max_user_id + num_bins - (max_user_id % num_bins) def _get_dates(self, offset=None): # lint-amnesty, pylint: disable=missing-function-docstring - current_day = _get_datetime_beginning_of_day(datetime.datetime.now(pytz.UTC)) + current_day = _get_datetime_beginning_of_day(datetime.datetime.now(get_utc_timezone())) offset = offset or self.expected_offsets[0] target_day = current_day + datetime.timedelta(days=offset) if self.resolver.schedule_date_field == 'upgrade_deadline': @@ -148,7 +148,7 @@ def _schedule_factory(self, offset=None, **factory_kwargs): # lint-amnesty, pyl CourseModeFactory( course_id=course_id, mode_slug=CourseMode.VERIFIED, - expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30), + expiration_datetime=datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=30), ) self._courses_with_verified_modes.add(course_id) return schedule @@ -158,7 +158,7 @@ def _update_schedule_config(self, schedule_config_kwargs): Updates the schedule config model by making sure the new entry has a later timestamp. """ - later_time = datetime.datetime.now(pytz.UTC) + datetime.timedelta(minutes=1) + later_time = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(minutes=1) with freeze_time(later_time): ScheduleConfigFactory.create(**schedule_config_kwargs) @@ -167,7 +167,7 @@ def test_command_task_binding(self): def test_handle(self): with patch.object(self.command, 'async_send_task') as mock_send: - test_day = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) + test_day = datetime.datetime(2017, 8, 1, tzinfo=get_utc_timezone()) self.command().handle(date='2017-08-01', site_domain_name=self.site_config.site.domain) for offset in self.expected_offsets: @@ -287,7 +287,7 @@ def test_enqueue_config(self, is_enabled): } self._update_schedule_config(schedule_config_kwargs) - current_datetime = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) + current_datetime = datetime.datetime(2017, 8, 1, tzinfo=get_utc_timezone()) with patch.object(self.task, 'apply_async') as mock_apply_async: self.task.enqueue(self.site_config.site, current_datetime, 3) diff --git a/openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py b/openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py index 47ba67cc0999..5ca072c308d2 100644 --- a/openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py +++ b/openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py @@ -8,7 +8,7 @@ from unittest.mock import DEFAULT, Mock, patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from openedx.core.djangoapps.schedules.management.commands import SendEmailBaseCommand @@ -32,7 +32,7 @@ def test_handle(self): self.command.handle(site_domain_name=self.site.domain, date='2017-09-29') send_emails.assert_called_once_with( self.site, - datetime.datetime(2017, 9, 29, tzinfo=pytz.UTC), + datetime.datetime(2017, 9, 29, tzinfo=get_utc_timezone()), None ) diff --git a/openedx/core/djangoapps/schedules/tests/factories.py b/openedx/core/djangoapps/schedules/tests/factories.py index 882b62fb8b78..215f94a405c2 100644 --- a/openedx/core/djangoapps/schedules/tests/factories.py +++ b/openedx/core/djangoapps/schedules/tests/factories.py @@ -4,7 +4,7 @@ import factory -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.schedules import models from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory @@ -22,8 +22,8 @@ class ScheduleFactory(factory.django.DjangoModelFactory): # lint-amnesty, pylin class Meta: model = models.Schedule - start_date = factory.Faker('future_datetime', tzinfo=pytz.UTC) - upgrade_deadline = factory.Faker('future_datetime', tzinfo=pytz.UTC) + start_date = factory.Faker('future_datetime', tzinfo=get_utc_timezone()) + upgrade_deadline = factory.Faker('future_datetime', tzinfo=get_utc_timezone()) enrollment = factory.SubFactory(CourseEnrollmentFactory) experience = factory.RelatedFactory(ScheduleExperienceFactory, 'schedule') diff --git a/openedx/core/djangoapps/schedules/tests/test_resolvers.py b/openedx/core/djangoapps/schedules/tests/test_resolvers.py index 2c37608e5cff..0cde8ef2bb03 100644 --- a/openedx/core/djangoapps/schedules/tests/test_resolvers.py +++ b/openedx/core/djangoapps/schedules/tests/test_resolvers.py @@ -8,7 +8,7 @@ import crum import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import TestCase from django.test.client import RequestFactory from django.test.utils import override_settings @@ -123,7 +123,7 @@ def test_external_course_updates(self, bucket): # experiment. Note that the experiment waffle is currently inactive, but they should still be excluded because # they were bucketed at enrollment time. bin_num = BinnedSchedulesBaseResolver.bin_num_for_user_id(user.id) - resolver = BinnedSchedulesBaseResolver(None, self.site, datetime.datetime.now(pytz.UTC), 0, bin_num) + resolver = BinnedSchedulesBaseResolver(None, self.site, datetime.datetime.now(get_utc_timezone()), 0, bin_num) resolver.schedule_date_field = 'created' schedules = resolver.get_schedules_with_target_date_by_bin_and_orgs() diff --git a/openedx/core/djangoapps/schedules/tests/test_signals.py b/openedx/core/djangoapps/schedules/tests/test_signals.py index 023fbbdbafcc..63e173ae3761 100644 --- a/openedx/core/djangoapps/schedules/tests/test_signals.py +++ b/openedx/core/djangoapps/schedules/tests/test_signals.py @@ -8,7 +8,7 @@ import ddt import pytest -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory @@ -188,7 +188,7 @@ def _create_course_run(self_paced=True, start_day_offset=-1): Both audit and verified `CourseMode` objects will be created for the course run. """ - now = datetime.datetime.now(utc) + now = datetime.datetime.now(get_utc_timezone()) start = now + datetime.timedelta(days=start_day_offset) course = CourseFactory.create(start=start, self_paced=self_paced) diff --git a/openedx/core/djangoapps/schedules/tests/test_utils.py b/openedx/core/djangoapps/schedules/tests/test_utils.py index f1d0cd9fcba0..ea7d208ba3b9 100644 --- a/openedx/core/djangoapps/schedules/tests/test_utils.py +++ b/openedx/core/djangoapps/schedules/tests/test_utils.py @@ -5,7 +5,7 @@ import datetime import ddt -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -26,7 +26,7 @@ def create_schedule(self, enrollment_offset=0, course_start_offset=-100): # pylint: disable=attribute-defined-outside-init self.config = ScheduleConfigFactory() - start = datetime.datetime.now(utc) + datetime.timedelta(days=course_start_offset) + start = datetime.datetime.now(get_utc_timezone()) + datetime.timedelta(days=course_start_offset) self.course = CourseFactory.create(start=start, self_paced=True) self.enrollment = CourseEnrollmentFactory( diff --git a/openedx/core/djangoapps/schedules/utils.py b/openedx/core/djangoapps/schedules/utils.py index c2565a1c87a3..1b84546831d8 100644 --- a/openedx/core/djangoapps/schedules/utils.py +++ b/openedx/core/djangoapps/schedules/utils.py @@ -3,7 +3,7 @@ import datetime import logging -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.db import transaction from openedx.core.djangoapps.schedules.models import Schedule @@ -59,7 +59,7 @@ def reset_self_paced_schedule(user, course_key, use_enrollment_date=False): if use_enrollment_date: new_start_date = schedule.enrollment.created else: - new_start_date = datetime.datetime.now(pytz.utc) + new_start_date = datetime.datetime.now(get_utc_timezone()) # Make sure we don't start the clock on the learner's schedule before the course even starts new_start_date = max(new_start_date, schedule.enrollment.course.start) diff --git a/openedx/core/djangoapps/user_api/accounts/api.py b/openedx/core/djangoapps/user_api/accounts/api.py index 6970ea6f852f..253e9582e24a 100644 --- a/openedx/core/djangoapps/user_api/accounts/api.py +++ b/openedx/core/djangoapps/user_api/accounts/api.py @@ -12,7 +12,7 @@ from django.utils.translation import gettext as _ from django.utils.translation import override as override_language from eventtracking import tracker -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.student import views as student_views from common.djangoapps.student.models import ( @@ -375,7 +375,7 @@ def _store_old_name_if_needed(old_name, user_profile, requesting_user): meta['old_names'].append([ old_name, f"Name change requested through account API by {requesting_user.username}", - datetime.datetime.now(UTC).isoformat() + datetime.datetime.now(get_utc_timezone()).isoformat() ]) user_profile.set_meta(meta) user_profile.save() diff --git a/openedx/core/djangoapps/user_api/accounts/tests/retirement_helpers.py b/openedx/core/djangoapps/user_api/accounts/tests/retirement_helpers.py index a3c40002f61c..021405c92b5b 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/retirement_helpers.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/retirement_helpers.py @@ -6,7 +6,7 @@ import datetime import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import TestCase from social_django.models import UserSocialAuth @@ -67,7 +67,7 @@ def create_retirement_status(user, state=None, create_datetime=None): Assumes that retirement states have been setup before calling. """ if create_datetime is None: - create_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=8) + create_datetime = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=8) retirement = UserRetirementStatus.create_retirement(user) if state: diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py index f9071c06a5c2..85f91eb0a565 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py @@ -17,7 +17,7 @@ from django.test import TestCase from django.test.client import RequestFactory from django.urls import reverse -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from social_django.models import UserSocialAuth from common.djangoapps.student.models import ( @@ -381,7 +381,7 @@ def test_validate_name_change_same_name(self): meta['old_names'] = [] for num in range(3): meta['old_names'].append( - [f'old_name_{num}', 'test', datetime.datetime.now(UTC).isoformat()] + [f'old_name_{num}', 'test', datetime.datetime.now(get_utc_timezone()).isoformat()] ) user_profile.set_meta(meta) user_profile.save() diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py b/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py index 3608073a5217..73d410dafa63 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py @@ -8,7 +8,7 @@ from unittest.mock import patch from django.test import TestCase -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangolib.testing.utils import skip_unless_lms from common.djangoapps.student.tests.factories import UserFactory @@ -16,7 +16,7 @@ from ..image_helpers import get_profile_image_urls_for_user TEST_SIZES = {'full': 50, 'small': 10} -TEST_PROFILE_IMAGE_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 1, tzinfo=UTC) +TEST_PROFILE_IMAGE_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 1, tzinfo=get_utc_timezone()) @patch.dict('django.conf.settings.PROFILE_IMAGE_SIZES_MAP', TEST_SIZES, clear=True) diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py index 9d4efb2fa77c..674e93836e44 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py @@ -7,7 +7,7 @@ from unittest import mock import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from consent.models import DataSharingConsent from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.contrib.sites.models import Site @@ -516,7 +516,7 @@ def setUp(self): self.headers = build_jwt_headers(self.test_superuser) self.url = reverse('accounts_retirement_partner_report') self.maxDiff = None - self.test_created_datetime = datetime.datetime(2018, 1, 1, tzinfo=pytz.UTC) + self.test_created_datetime = datetime.datetime(2018, 1, 1, tzinfo=get_utc_timezone()) ExternalIdType.objects.get_or_create(name=ExternalIdType.CALIPER) def get_user_dict(self, user, enrollments): @@ -769,7 +769,7 @@ def test_date_filter(self): # retirements = [2018-04-10..., 2018-04-09..., 2018-04-08...] pending_state = RetirementState.objects.get(state_name='PENDING') for days_back in range(1, days_back_to_test, -1): - create_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=days_back) + create_datetime = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=days_back) retirements.append(create_retirement_status( UserFactory(), state=pending_state, @@ -927,12 +927,12 @@ def test_date_filter(self): # Create retirements for the last 10 days for days_back in range(0, 10): # lint-amnesty, pylint: disable=simplifiable-range - create_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=days_back) + create_datetime = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=days_back) ret = create_retirement_status(UserFactory(), state=complete_state, create_datetime=create_datetime) retirements.append(self._retirement_to_dict(ret)) # Go back in time adding days to the query, assert the correct retirements are present - end_date = datetime.datetime.now(pytz.UTC) + end_date = datetime.datetime.now(get_utc_timezone()) for days_back in range(1, 11): retirement_dicts = retirements[:days_back] start_date = end_date - datetime.timedelta(days=days_back - 1) diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py index 466e1e278abd..441085ec1140 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py @@ -10,7 +10,7 @@ from urllib.parse import quote import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.core.files.storage import FileSystemStorage from django.test.testcases import TransactionTestCase @@ -44,7 +44,7 @@ from .. import ALL_USERS_VISIBILITY, CUSTOM_VISIBILITY, PRIVATE_VISIBILITY -TEST_PROFILE_IMAGE_UPLOADED_AT = datetime.datetime(2002, 1, 9, 15, 43, 1, tzinfo=pytz.UTC) +TEST_PROFILE_IMAGE_UPLOADED_AT = datetime.datetime(2002, 1, 9, 15, 43, 1, tzinfo=get_utc_timezone()) # this is used in one test to check the behavior of profile image url # generation with a relative url in the config. @@ -304,7 +304,7 @@ def test_cancel_retirement_not_pending(self): current_state=retirement_state, last_state=retirement_state, original_email=self.user.email, - created=datetime.datetime.now(pytz.UTC) + created=datetime.datetime.now(get_utc_timezone()) ) url = reverse("cancel_account_retirement") response = client.post(url, data={'retirement_id': user_retirement_status.id}) @@ -329,7 +329,7 @@ def test_cancel_retirement_successful(self): current_state=retirement_state, last_state=retirement_state, original_email=self.user.email, - created=datetime.datetime.now(pytz.UTC) + created=datetime.datetime.now(get_utc_timezone()) ) user_retirement_status.user.set_unusable_password() assert UserRetirementStatus.objects.count() == 1 @@ -585,8 +585,8 @@ def test_get_account_by_user_id_non_integer(self, non_integer_id): @mock.patch('openedx.core.djangoapps.user_api.accounts.views.is_email_retired') @ddt.data( - (datetime.datetime.now(pytz.UTC), True), - (datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=15), False) + (datetime.datetime.now(get_utc_timezone()), True), + (datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=15), False) ) @ddt.unpack def test_search_emails_retired_before_cooloff_period(self, created_date, can_cancel, mock_is_email_retired): diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index 55b90aa67f78..ca1467355fde 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -9,7 +9,7 @@ import logging from functools import wraps -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from consent.models import DataSharingConsent from django.apps import apps from django.conf import settings @@ -197,11 +197,12 @@ def list(self, request): if is_email_retired(user_email): can_cancel_retirement = True retirement_id = None - earliest_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=settings.COOL_OFF_DAYS) + earliest_datetime = datetime.datetime.now( + get_utc_timezone()) - datetime.timedelta(days=settings.COOL_OFF_DAYS) try: retirement_status = UserRetirementStatus.objects.get( created__gt=earliest_datetime, - created__lt=datetime.datetime.now(pytz.UTC), + created__lt=datetime.datetime.now(get_utc_timezone()), original_email=user_email, ) retirement_id = retirement_status.id @@ -879,7 +880,7 @@ def retirement_queue(self, request): status=status.HTTP_400_BAD_REQUEST, ) - earliest_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=cool_off_days) + earliest_datetime = datetime.datetime.now(get_utc_timezone()) - datetime.timedelta(days=cool_off_days) retirements = ( UserRetirementStatus.objects.select_related("user", "current_state", "last_state") @@ -909,11 +910,15 @@ def retirements_by_status_and_date(self, request): so to get one day you would set both dates to that day. """ try: - start_date = datetime.datetime.strptime(request.GET["start_date"], "%Y-%m-%d").replace(tzinfo=pytz.UTC) - end_date = datetime.datetime.strptime(request.GET["end_date"], "%Y-%m-%d").replace(tzinfo=pytz.UTC) - now = datetime.datetime.now(pytz.UTC) + start_date = datetime.datetime.strptime( + request.GET["start_date"], "%Y-%m-%d").replace(tzinfo=get_utc_timezone()) + end_date = datetime.datetime.strptime( + request.GET["end_date"], "%Y-%m-%d").replace(tzinfo=get_utc_timezone()) + now = datetime.datetime.now(get_utc_timezone()) if start_date > now or end_date > now or start_date > end_date: - raise RetirementStateError("Dates must be today or earlier, and start must be earlier than end.") + raise RetirementStateError( + "Dates must be today or earlier, and start must be earlier than end." + ) # Add a day to make sure we get all the way to 23:59:59.999, this is compared "lt" in the query # not "lte". diff --git a/openedx/core/djangoapps/user_api/management/commands/create_user_gdpr_testing.py b/openedx/core/djangoapps/user_api/management/commands/create_user_gdpr_testing.py index 2008ce8652d5..e3ce3c0c05b3 100644 --- a/openedx/core/djangoapps/user_api/management/commands/create_user_gdpr_testing.py +++ b/openedx/core/djangoapps/user_api/management/commands/create_user_gdpr_testing.py @@ -20,7 +20,7 @@ ) from integrated_channels.sap_success_factors.models import SapSuccessFactorsLearnerDataTransmissionAudit from opaque_keys.edx.keys import CourseKey -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from common.djangoapps.entitlements.models import CourseEntitlement, CourseEntitlementSupportDetail from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification @@ -82,7 +82,7 @@ def handle(self, *args, **options): user.save() # UserProfile - profile_image_uploaded_date = datetime(2018, 5, 3, tzinfo=UTC) + profile_image_uploaded_date = datetime(2018, 5, 3, tzinfo=get_utc_timezone()) user_profile, __ = UserProfile.objects.get_or_create( user=user ) diff --git a/openedx/core/djangoapps/user_api/preferences/tests/test_api.py b/openedx/core/djangoapps/user_api/preferences/tests/test_api.py index 34295801801b..fa6b4c5803fa 100644 --- a/openedx/core/djangoapps/user_api/preferences/tests/test_api.py +++ b/openedx/core/djangoapps/user_api/preferences/tests/test_api.py @@ -11,7 +11,8 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.test.utils import override_settings from django.urls import reverse -from pytz import common_timezones, utc +from pytz import common_timezones +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms from openedx.core.lib.time_zone_utils import get_display_time_zone @@ -372,7 +373,7 @@ def test_change_email_optin(self, age, option, second_option, expected_result): # Set year of birth user = User.objects.get(username=self.USERNAME) profile = UserProfile.objects.get(user=user) - year_of_birth = datetime.datetime.now(utc).year - age + year_of_birth = datetime.datetime.now(get_utc_timezone()).year - age profile.year_of_birth = year_of_birth profile.save() @@ -403,23 +404,26 @@ class CountryTimeZoneTest(CacheIsolationTestCase): """ @ddt.data(('ES', ['Africa/Ceuta', 'Atlantic/Canary', 'Europe/Madrid']), - (None, common_timezones[:10]), - ('AA', common_timezones[:10])) + (None, common_timezones), + ('AA', common_timezones)) @ddt.unpack def test_get_country_time_zones(self, country_code, expected_time_zones): """ Verify that list of common country time zones dictionaries is returned An unrecognized country code (e.g. AA) will return the list of common timezones """ - expected_dict = [ - { - 'time_zone': time_zone, - 'description': get_display_time_zone(time_zone) - } - for time_zone in expected_time_zones - ] + expected_dict = sorted( + [ + { + 'time_zone': time_zone_name, + 'description': get_display_time_zone(time_zone_name), + } + for time_zone_name in expected_time_zones + ], + key=lambda tz_dict: tz_dict['description'] + ) country_time_zones_dicts = get_country_time_zones(country_code)[:10] - assert country_time_zones_dicts == expected_dict + assert country_time_zones_dicts == expected_dict[:10] def get_expected_validation_developer_message(preference_key, preference_value): diff --git a/openedx/core/djangoapps/user_authn/tests/utils.py b/openedx/core/djangoapps/user_authn/tests/utils.py index 09ca85145f35..608c62deb724 100644 --- a/openedx/core/djangoapps/user_authn/tests/utils.py +++ b/openedx/core/djangoapps/user_authn/tests/utils.py @@ -6,7 +6,7 @@ from unittest.mock import patch import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from oauth2_provider import models as dot_models from rest_framework import status @@ -42,7 +42,7 @@ def utcnow(): """ Helper function to return the current UTC time localized to the UTC timezone. """ - return datetime.now(pytz.UTC) + return datetime.now(get_utc_timezone()) @ddt.ddt diff --git a/openedx/core/djangoapps/user_authn/views/register.py b/openedx/core/djangoapps/user_authn/views/register.py index e95942057786..08e8b956a289 100644 --- a/openedx/core/djangoapps/user_authn/views/register.py +++ b/openedx/core/djangoapps/user_authn/views/register.py @@ -26,7 +26,7 @@ from openedx_events.learning.data import UserData, UserPersonalData from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED from openedx_filters.learning.filters import StudentRegistrationRequested -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from django_ratelimit.decorators import ratelimit from requests import HTTPError from rest_framework.response import Response @@ -371,7 +371,7 @@ def _track_user_registration(user, profile, params, third_party_provider, regist 'name': profile.name, # Mailchimp requires the age & yearOfBirth to be integers, we send a sane integer default if falsey. 'age': profile.age or -1, - 'yearOfBirth': profile.year_of_birth or datetime.datetime.now(UTC).year, + 'yearOfBirth': profile.year_of_birth or datetime.datetime.now(get_utc_timezone()).year, 'education': profile.level_of_education_display, 'address': profile.mailing_address, 'gender': profile.gender_display, @@ -530,7 +530,8 @@ def _record_utm_registration_attribution(request, user): # We divide by 1000 here because the javascript timestamp generated is in milliseconds not seconds. # PYTHON: time.time() => 1475590280.823698 # JS: new Date().getTime() => 1475590280823 - created_at_datetime = datetime.datetime.fromtimestamp(int(created_at_unixtime) / float(1000), tz=UTC) + created_at_datetime = datetime.datetime.fromtimestamp( + int(created_at_unixtime) / float(1000), tz=get_utc_timezone()) UserAttribute.set_user_attribute( user, REGISTRATION_UTM_CREATED_AT, diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_password.py b/openedx/core/djangoapps/user_authn/views/tests/test_password.py index a403298a6e78..7de6466a11e6 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_password.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_password.py @@ -19,7 +19,7 @@ from freezegun import freeze_time from oauth2_provider.models import AccessToken as dot_access_token from oauth2_provider.models import RefreshToken as dot_refresh_token -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from testfixtures import LogCapture from openedx.core.djangoapps.oauth_dispatch.tests import factories as dot_factories @@ -319,7 +319,7 @@ def test_password_change_rate_limited(self): # now reset the time to 1 min from now in future and change the email and # verify that it will allow another request from same IP - reset_time = datetime.now(UTC) + timedelta(seconds=61) + reset_time = datetime.now(get_utc_timezone()) + timedelta(seconds=61) with freeze_time(reset_time): response = self._change_password(email=self.OLD_EMAIL) assert response.status_code == 200 diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_register.py b/openedx/core/djangoapps/user_authn/views/tests/test_register.py index 54d42efa55c0..1ace37081926 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_register.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_register.py @@ -16,7 +16,7 @@ from django.test.utils import override_settings from django.urls import reverse from openedx_events.tests.utils import OpenEdxEventsTestMixin -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from social_django.models import Partial, UserSocialAuth from testfixtures import LogCapture @@ -949,7 +949,7 @@ def test_register_form_gender_translations(self, fake_gettext): ) def test_register_form_year_of_birth(self): - this_year = datetime.now(UTC).year + this_year = datetime.now(get_utc_timezone()).year year_options = ( [ { diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py b/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py index b89b458ed1ae..19f16a7477c3 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py @@ -24,7 +24,7 @@ from django.utils.http import int_to_base36 from freezegun import freeze_time from oauth2_provider import models as dot_models -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.oauth_dispatch.tests import factories as dot_factories from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers @@ -267,7 +267,7 @@ def test_ratelimited_from_different_ips_with_same_email(self): self.request_password_reset(200) # now reset the time to 1 min from now in future and change the email and # verify that it will allow another request from same IP - reset_time = datetime.now(UTC) + timedelta(seconds=61) + reset_time = datetime.now(get_utc_timezone()) + timedelta(seconds=61) with freeze_time(reset_time): for status in [200, 403]: self.request_password_reset(status) diff --git a/openedx/core/djangoapps/util/testing.py b/openedx/core/djangoapps/util/testing.py index 040a2b5af180..75e82f290427 100644 --- a/openedx/core/djangoapps/util/testing.py +++ b/openedx/core/djangoapps/util/testing.py @@ -3,7 +3,7 @@ from datetime import datetime -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.course_groups.models import CourseUserGroupPartitionGroup from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory @@ -32,7 +32,7 @@ def setUp(self): # This test needs to use a course that has already started -- # discussion topics only show up if the course has already started, # and the default start date for courses is Jan 1, 2030. - start=datetime(2012, 2, 3, tzinfo=UTC), + start=datetime(2012, 2, 3, tzinfo=get_utc_timezone()), user_partitions=[ UserPartition( 0, diff --git a/openedx/core/djangoapps/verified_track_content/tests/test_partition_scheme.py b/openedx/core/djangoapps/verified_track_content/tests/test_partition_scheme.py index a2667bfa1c0b..6d538a03e3ac 100644 --- a/openedx/core/djangoapps/verified_track_content/tests/test_partition_scheme.py +++ b/openedx/core/djangoapps/verified_track_content/tests/test_partition_scheme.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone import pytest from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollment @@ -38,7 +38,7 @@ def test_multiple_groups(self): # Note that the verified mode is expired-- this is intentional. create_mode( self.course, CourseMode.VERIFIED, "Verified Enrollment Track", min_price=1, - expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=-1) + expiration_datetime=datetime.now(get_utc_timezone()) + timedelta(days=-1) ) # Note that the credit mode is not selectable-- this is intentional so we # can test that it is filtered out. @@ -128,7 +128,7 @@ def test_enrolled_in_verified(self): def test_enrolled_in_expired(self): create_mode( self.course, CourseMode.VERIFIED, "Verified Enrollment Track", - min_price=1, expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=-1) + min_price=1, expiration_datetime=datetime.now(get_utc_timezone()) + timedelta(days=-1) ) CourseEnrollment.enroll(self.student, self.course.id, mode=CourseMode.VERIFIED) assert 'Verified Enrollment Track' == self._get_user_group().name @@ -153,7 +153,7 @@ def test_credit_after_upgrade_deadline(self): # the upgrade deadline has passed (see EDUCATOR-1511 for why this matters). create_mode( self.course, CourseMode.VERIFIED, "Verified Enrollment Track", min_price=1, - expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=-1) + expiration_datetime=datetime.now(get_utc_timezone()) + timedelta(days=-1) ) assert 'Verified Enrollment Track' == self._get_user_group().name diff --git a/openedx/core/djangoapps/xblock/tests/test_utils.py b/openedx/core/djangoapps/xblock/tests/test_utils.py index 229406e1bca1..0c5bbd026af9 100644 --- a/openedx/core/djangoapps/xblock/tests/test_utils.py +++ b/openedx/core/djangoapps/xblock/tests/test_utils.py @@ -71,7 +71,7 @@ }, True, ), - # Setting reference_time to 20 seconds before end of a 2 day time period(UTC) + # Setting reference_time to 20 seconds before end of a 2 day time periodget_utc_timezone() # Demonstrating minimum possible validity period is just above 2 days # This fails because validation time is just above the cutoff point ( diff --git a/openedx/core/lib/__init__.py b/openedx/core/lib/__init__.py index 61565e758131..64492942a8d0 100644 --- a/openedx/core/lib/__init__.py +++ b/openedx/core/lib/__init__.py @@ -9,6 +9,19 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from edx_toggles.toggles import WaffleSwitch + + +# .. toggle_name: open_edx_util.enable_zoneinfo_tz +# .. toggle_implementation: WaffleSwitch +# .. toggle_default: False +# .. toggle_description: Replaces pytz.UTC with get_utc_timezone(), when active. +# .. toggle_use_cases: opt_in +# .. toggle_creation_date: 2025-06-03 +# .. toggle_tickets: N/A +ENABLE_ZONEINFO_TZ = WaffleSwitch( + 'open_edx_util.enable_zoneinfo_tz', __name__ +) _LMS_URLCONF = 'lms.urls' _CMS_URLCONF = 'cms.urls' diff --git a/openedx/core/lib/tests/test_time_zone_utils.py b/openedx/core/lib/tests/test_time_zone_utils.py index c876f68be2b0..0f6e6f083754 100644 --- a/openedx/core/lib/tests/test_time_zone_utils.py +++ b/openedx/core/lib/tests/test_time_zone_utils.py @@ -3,8 +3,7 @@ from django.test import TestCase from freezegun import freeze_time -from pytz import timezone - +from zoneinfo import ZoneInfo from openedx.core.lib.time_zone_utils import get_display_time_zone, get_time_zone_abbr, get_time_zone_offset from common.djangoapps.student.tests.factories import UserFactory @@ -27,7 +26,7 @@ def _display_time_zone_helper(self, time_zone_string): Helper function to return all info from get_display_time_zone() """ tz_str = get_display_time_zone(time_zone_string) - time_zone = timezone(time_zone_string) + time_zone = ZoneInfo(time_zone_string) tz_abbr = get_time_zone_abbr(time_zone) tz_offset = get_time_zone_offset(time_zone) @@ -75,6 +74,6 @@ def test_display_time_zone_ambiguous_after(self): Test to ensure get_display_time_zone() returns correct abbreviations and offsets during ambiguous time periods (e.g. when DST is about to start/end) after the change """ - with freeze_time("2015-11-01 09:00:00"): + with freeze_time("2024-11-04 09:00:00"): tz_info = self._display_time_zone_helper('America/Los_Angeles') self._assert_time_zone_info_equal(tz_info, 'America/Los Angeles', 'PST', '-0800') diff --git a/openedx/core/lib/time_zone_utils.py b/openedx/core/lib/time_zone_utils.py index eabd471d1812..79ea8afa2ffd 100644 --- a/openedx/core/lib/time_zone_utils.py +++ b/openedx/core/lib/time_zone_utils.py @@ -4,7 +4,31 @@ from datetime import datetime -from pytz import common_timezones, timezone, utc +from pytz import common_timezones, UTC + +from zoneinfo import ZoneInfo + +from . import ENABLE_ZONEINFO_TZ + + +def get_utc_timezone(): + """ + Returns a UTC timezone object. + + Uses ZoneInfo if the ENABLE_ZONEINFO_TZ toggle is enabled, otherwise falls back to pytz UTC. + If there's an issue checking the toggle (e.g., during app startup), defaults to pytz UTC. + + Returns: + A timezone object representing UTC (either ZoneInfo or pytz UTC) + """ + try: + if ENABLE_ZONEINFO_TZ.is_enabled(): + return ZoneInfo('UTC') + else: + return UTC + except Exception: # pylint: disable=broad-except + # Fallback to UTC if toggle check fails (e.g., during app startup) + return UTC def _format_time_zone_string(time_zone, date_time, format_string): @@ -23,7 +47,7 @@ def get_time_zone_abbr(time_zone, date_time=None): """ Returns the time zone abbreviation (e.g. EST) of the time zone for given datetime """ - date_time = datetime.now(utc) if date_time is None else date_time + date_time = datetime.now(get_utc_timezone()) if date_time is None else date_time return _format_time_zone_string(time_zone, date_time, '%Z') @@ -31,7 +55,7 @@ def get_time_zone_offset(time_zone, date_time=None): """ Returns the time zone offset (e.g. -0800) of the time zone for given datetime """ - date_time = datetime.now(utc) if date_time is None else date_time + date_time = datetime.now(get_utc_timezone()) if date_time is None else date_time return _format_time_zone_string(time_zone, date_time, '%z') @@ -41,7 +65,7 @@ def get_display_time_zone(time_zone_name): :param time_zone_name (str): Name of Pytz time zone """ - time_zone = timezone(time_zone_name) + time_zone = ZoneInfo(time_zone_name) tz_abbr = get_time_zone_abbr(time_zone) tz_offset = get_time_zone_offset(time_zone) diff --git a/openedx/core/lib/xblock_utils/__init__.py b/openedx/core/lib/xblock_utils/__init__.py index a8b76541b6e5..72caffc65cb0 100644 --- a/openedx/core/lib/xblock_utils/__init__.py +++ b/openedx/core/lib/xblock_utils/__init__.py @@ -19,7 +19,7 @@ from edx_django_utils.plugins import pluggable_override from lxml import etree, html from opaque_keys.edx.asides import AsideUsageKeyV1, AsideUsageKeyV2 -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from web_fragments.fragment import Fragment from xblock.core import XBlock from xblock.exceptions import InvalidScopeError @@ -310,7 +310,7 @@ def add_staff_markup(user, disable_staff_debug_info, block, view, frag, context) # Useful to indicate to staff if problem has been released or not. # TODO (ichuang): use _has_access_block.can_load in lms.courseware.access, # instead of now>mstart comparison here. - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(get_utc_timezone()) is_released = "unknown" mstart = block.start diff --git a/openedx/features/calendar_sync/ics.py b/openedx/features/calendar_sync/ics.py index fc465443e714..7eb5ce181ae8 100644 --- a/openedx/features/calendar_sync/ics.py +++ b/openedx/features/calendar_sync/ics.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.utils.translation import gettext as _ from icalendar import Calendar, Event, vCalAddress, vText @@ -59,7 +59,7 @@ def generate_ics_files_for_user_course(course, user, user_calendar_sync_config_i assignments = get_course_assignments(course.id, user) platform_name = get_value('platform_name', settings.PLATFORM_NAME) platform_email = get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) - now = datetime.now(pytz.utc) + now = datetime.now(get_utc_timezone()) site_config = SiteConfiguration.get_configuration_for_org(course.org) ics_files = {} diff --git a/openedx/features/calendar_sync/tests/test_ics.py b/openedx/features/calendar_sync/tests/test_ics.py index 02301079285d..a9e2bcc0299c 100644 --- a/openedx/features/calendar_sync/tests/test_ics.py +++ b/openedx/features/calendar_sync/tests/test_ics.py @@ -3,7 +3,7 @@ from datetime import datetime, timedelta from unittest.mock import patch -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.test import RequestFactory, TestCase from freezegun import freeze_time @@ -21,7 +21,7 @@ class TestIcsGeneration(TestCase): def setUp(self): super().setUp() - freezer = freeze_time(datetime(2013, 10, 3, 8, 24, 55, tzinfo=pytz.utc)) + freezer = freeze_time(datetime(2013, 10, 3, 8, 24, 55, tzinfo=get_utc_timezone())) self.addCleanup(freezer.stop) freezer.start() @@ -103,7 +103,7 @@ def assert_ics(self, *assignments): def test_generate_ics_for_user_course(self): """ Tests that a simple sample set of course assignments is generated correctly """ - now = datetime.now(pytz.utc) + now = datetime.now(get_utc_timezone()) day1 = now + timedelta(1) day2 = now + timedelta(1) diff --git a/openedx/features/content_type_gating/partitions.py b/openedx/features/content_type_gating/partitions.py index 61851ec43bbd..e6dd304fe808 100644 --- a/openedx/features/content_type_gating/partitions.py +++ b/openedx/features/content_type_gating/partitions.py @@ -10,7 +10,7 @@ import logging import crum -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.template.loader import render_to_string from django.utils.translation import gettext_lazy as _ from web_fragments.fragment import Fragment @@ -88,7 +88,7 @@ def access_denied_fragment(self, block, user, user_group, allowed_groups): return None expiration_datetime = verified_mode.expiration_datetime - if expiration_datetime and expiration_datetime < datetime.datetime.now(pytz.UTC): + if expiration_datetime and expiration_datetime < datetime.datetime.now(get_utc_timezone()): ecommerce_checkout_link = None else: ecommerce_checkout_link = self._get_checkout_link(user, verified_mode.sku, str(course_key)) diff --git a/openedx/features/content_type_gating/tests/test_models.py b/openedx/features/content_type_gating/tests/test_models.py index 673ce805a750..9d6a1a27fd28 100644 --- a/openedx/features/content_type_gating/tests/test_models.py +++ b/openedx/features/content_type_gating/tests/test_models.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta # lint-amnesty, pylint: disable=wrong-import-order import ddt -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.utils import timezone from edx_django_utils.cache import RequestCache from unittest.mock import Mock # lint-amnesty, pylint: disable=wrong-import-order @@ -217,17 +217,17 @@ def test_all_current_course_configs(self): # Point-test some of the final configurations assert all_configs[CourseLocator('7-True', 'test_course', 'run-None')] == { 'enabled': (True, Provenance.org), - 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run), + 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=get_utc_timezone()), Provenance.run), 'studio_override_enabled': (None, Provenance.default) } assert all_configs[CourseLocator('7-True', 'test_course', 'run-False')] == { 'enabled': (False, Provenance.run), - 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run), + 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=get_utc_timezone()), Provenance.run), 'studio_override_enabled': (None, Provenance.default) } assert all_configs[CourseLocator('7-None', 'test_course', 'run-None')] == { 'enabled': (True, Provenance.site), - 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run), + 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=get_utc_timezone()), Provenance.run), 'studio_override_enabled': (None, Provenance.default) } diff --git a/openedx/features/course_duration_limits/tests/test_access.py b/openedx/features/course_duration_limits/tests/test_access.py index 558afec22ab3..10f48fd14ed8 100644 --- a/openedx/features/course_duration_limits/tests/test_access.py +++ b/openedx/features/course_duration_limits/tests/test_access.py @@ -8,7 +8,7 @@ from crum import set_current_request from django.test import RequestFactory from django.utils import timezone -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from common.djangoapps.course_modes.models import CourseMode @@ -34,9 +34,13 @@ class TestAccess(ModuleStoreTestCase): def setUp(self): super().setUp() # lint-amnesty, pylint: disable=super-with-arguments - CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=UTC)) + CourseDurationLimitConfig.objects.create( + enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone()) + ) DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) - self.course = CourseOverviewFactory.create(start=datetime(2018, 1, 1, tzinfo=UTC), self_paced=True) + self.course = CourseOverviewFactory.create( + start=datetime(2018, 1, 1, tzinfo=get_utc_timezone()), self_paced=True + ) def assertDateInMessage(self, date, message): # lint-amnesty, pylint: disable=missing-function-docstring # First, check that the formatted version is in there @@ -148,7 +152,7 @@ def test_schedule_start_date_in_past(self): course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) - Schedule.objects.update(start_date=datetime(2017, 1, 1, tzinfo=UTC)) + Schedule.objects.update(start_date=datetime(2017, 1, 1, tzinfo=get_utc_timezone())) content_availability_date = max(enrollment.created, enrollment.course.start) access_duration = get_user_course_duration(enrollment.user, enrollment.course) diff --git a/openedx/features/course_duration_limits/tests/test_models.py b/openedx/features/course_duration_limits/tests/test_models.py index 0473faefd330..fd46c082673e 100644 --- a/openedx/features/course_duration_limits/tests/test_models.py +++ b/openedx/features/course_duration_limits/tests/test_models.py @@ -8,7 +8,7 @@ import ddt import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.utils import timezone from edx_django_utils.cache import RequestCache from opaque_keys.edx.locator import CourseLocator @@ -178,13 +178,14 @@ def test_config_overrides(self, global_setting, site_setting, org_setting, cours def test_all_current_course_configs(self): # Set up test objects for global_setting in (True, False, None): - CourseDurationLimitConfig.objects.create(enabled=global_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long + CourseDurationLimitConfig.objects.create(enabled=global_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) # lint-amnesty, pylint: disable=line-too-long for site_setting in (True, False, None): test_site_cfg = SiteConfigurationFactory.create( site_values={'course_org_filter': []} ) CourseDurationLimitConfig.objects.create( - site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC) + site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime( + 2018, 1, 1, tzinfo=get_utc_timezone()) ) for org_setting in (True, False, None): @@ -193,7 +194,7 @@ def test_all_current_course_configs(self): test_site_cfg.save() CourseDurationLimitConfig.objects.create( - org=test_org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC) + org=test_org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone()) ) for course_setting in (True, False, None): @@ -202,7 +203,7 @@ def test_all_current_course_configs(self): id=CourseLocator(test_org, 'test_course', f'run-{course_setting}') ) CourseDurationLimitConfig.objects.create( - course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC) # lint-amnesty, pylint: disable=line-too-long + course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone()) # lint-amnesty, pylint: disable=line-too-long ) with self.assertNumQueries(4): @@ -216,22 +217,23 @@ def test_all_current_course_configs(self): # Point-test some of the final configurations assert all_configs[CourseLocator('7-True', 'test_course', 'run-None')] == { 'enabled': (True, Provenance.org), - 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), + 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=get_utc_timezone()), Provenance.run) } assert all_configs[CourseLocator('7-True', 'test_course', 'run-False')] == { 'enabled': (False, Provenance.run), - 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), + 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=get_utc_timezone()), Provenance.run) } assert all_configs[CourseLocator('7-None', 'test_course', 'run-None')] == { 'enabled': (True, Provenance.site), - 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), + 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=get_utc_timezone()), Provenance.run) } def test_caching_global(self): - global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) + global_config = CourseDurationLimitConfig( + enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) global_config.save() RequestCache.clear_all_namespaces() @@ -257,7 +259,7 @@ def test_caching_global(self): def test_caching_site(self): site_cfg = SiteConfigurationFactory() - site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long + site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) # lint-amnesty, pylint: disable=line-too-long site_config.save() RequestCache.clear_all_namespaces() @@ -281,7 +283,8 @@ def test_caching_site(self): with self.assertNumQueries(1): assert not CourseDurationLimitConfig.current(site=site_cfg.site).enabled - global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) + global_config = CourseDurationLimitConfig( + enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) global_config.save() RequestCache.clear_all_namespaces() @@ -295,7 +298,7 @@ def test_caching_org(self): site_cfg = SiteConfigurationFactory.create( site_values={'course_org_filter': course.org} ) - org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long + org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) # lint-amnesty, pylint: disable=line-too-long org_config.save() RequestCache.clear_all_namespaces() @@ -319,7 +322,8 @@ def test_caching_org(self): with self.assertNumQueries(2): assert not CourseDurationLimitConfig.current(org=course.org).enabled - global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) + global_config = CourseDurationLimitConfig( + enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) global_config.save() RequestCache.clear_all_namespaces() @@ -328,7 +332,7 @@ def test_caching_org(self): with self.assertNumQueries(0): assert not CourseDurationLimitConfig.current(org=course.org).enabled - site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long + site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) # lint-amnesty, pylint: disable=line-too-long site_config.save() RequestCache.clear_all_namespaces() @@ -342,7 +346,7 @@ def test_caching_course(self): site_cfg = SiteConfigurationFactory.create( site_values={'course_org_filter': course.org} ) - course_config = CourseDurationLimitConfig(course=course, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long + course_config = CourseDurationLimitConfig(course=course, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) # lint-amnesty, pylint: disable=line-too-long course_config.save() RequestCache.clear_all_namespaces() @@ -366,7 +370,8 @@ def test_caching_course(self): with self.assertNumQueries(2): assert not CourseDurationLimitConfig.current(course_key=course.id).enabled - global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) + global_config = CourseDurationLimitConfig( + enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) global_config.save() RequestCache.clear_all_namespaces() @@ -375,7 +380,7 @@ def test_caching_course(self): with self.assertNumQueries(0): assert not CourseDurationLimitConfig.current(course_key=course.id).enabled - site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long + site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) # lint-amnesty, pylint: disable=line-too-long site_config.save() RequestCache.clear_all_namespaces() @@ -384,7 +389,7 @@ def test_caching_course(self): with self.assertNumQueries(0): assert not CourseDurationLimitConfig.current(course_key=course.id).enabled - org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=pytz.UTC)) # lint-amnesty, pylint: disable=line-too-long + org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone())) # lint-amnesty, pylint: disable=line-too-long org_config.save() RequestCache.clear_all_namespaces() diff --git a/openedx/features/course_experience/tests/views/test_course_updates.py b/openedx/features/course_experience/tests/views/test_course_updates.py index 379be52ed40f..ad365df2046a 100644 --- a/openedx/features/course_experience/tests/views/test_course_updates.py +++ b/openedx/features/course_experience/tests/views/test_course_updates.py @@ -5,7 +5,7 @@ from datetime import datetime from django.urls import reverse -from pytz import UTC +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES from openedx.features.content_type_gating.models import ContentTypeGatingConfig @@ -41,7 +41,9 @@ def test_view(self): self.assertContains(response, 'Second Message') def test_queries(self): - ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=UTC)) + ContentTypeGatingConfig.objects.create( + enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=get_utc_timezone()) + ) self.create_course_update('First Message') # Pre-fetch the view to populate any caches diff --git a/openedx/features/discounts/applicability.py b/openedx/features/discounts/applicability.py index 97d6f74403bd..c3f52237bd78 100644 --- a/openedx/features/discounts/applicability.py +++ b/openedx/features/discounts/applicability.py @@ -11,7 +11,7 @@ from datetime import datetime, timedelta -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from crum import get_current_request, impersonate from django.conf import settings from django.utils import timezone @@ -197,7 +197,7 @@ def _is_in_holdback_and_bucket(user): Return whether the specified user is in the first-purchase-discount holdback group. This will also stable bucket the user. """ - if datetime(2020, 8, 1, tzinfo=pytz.UTC) <= datetime.now(tz=pytz.UTC): + if datetime(2020, 8, 1, tzinfo=get_utc_timezone()) <= datetime.now(tz=get_utc_timezone()): return False # Holdback is 10% diff --git a/openedx/features/discounts/tests/test_applicability.py b/openedx/features/discounts/tests/test_applicability.py index 60dbe7a67edf..4a83275120e4 100644 --- a/openedx/features/discounts/tests/test_applicability.py +++ b/openedx/features/discounts/tests/test_applicability.py @@ -6,7 +6,7 @@ import ddt import pytest -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.contrib.sites.models import Site from django.utils.timezone import now from edx_toggles.toggles.testutils import override_waffle_flag @@ -39,7 +39,7 @@ def setUp(self): self.user = UserFactory.create() self.course = CourseFactory.create(run='test', display_name='test') CourseModeFactory.create(course_id=self.course.id, mode_slug='verified') - now_time = datetime.now(tz=pytz.UTC).strftime("%Y-%m-%d %H:%M:%S%z") + now_time = datetime.now(tz=get_utc_timezone()).strftime("%Y-%m-%d %H:%M:%S%z") ExperimentData.objects.create( user=self.user, experiment_id=REV1008_EXPERIMENT_ID, key=str(self.course.id), value=now_time ) @@ -175,6 +175,6 @@ def test_holdback_expiry(self): with patch('openedx.features.discounts.applicability.stable_bucketing_hash_group', return_value=0): with patch( 'openedx.features.discounts.applicability.datetime', - Mock(now=Mock(return_value=datetime(2020, 8, 1, 0, 1, tzinfo=pytz.UTC)), wraps=datetime), + Mock(now=Mock(return_value=datetime(2020, 8, 1, 0, 1, tzinfo=get_utc_timezone())), wraps=datetime), ): assert not _is_in_holdback_and_bucket(self.user) diff --git a/openedx/features/discounts/utils.py b/openedx/features/discounts/utils.py index 92490f19bcb3..6335cc9f81bd 100644 --- a/openedx/features/discounts/utils.py +++ b/openedx/features/discounts/utils.py @@ -4,7 +4,7 @@ from datetime import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from django.conf import settings from django.utils.translation import get_language from django.utils.translation import gettext as _ @@ -89,7 +89,7 @@ def generate_offer_data(user, course): ExperimentData.objects.get_or_create( user=user, experiment_id=REV1008_EXPERIMENT_ID, key=str(course), defaults={ - 'value': datetime.now(tz=pytz.UTC).strftime('%Y-%m-%d %H:%M:%S%z'), + 'value': datetime.now(tz=get_utc_timezone()).strftime('%Y-%m-%d %H:%M:%S%z'), }, ) diff --git a/openedx/tests/completion_integration/test_handlers.py b/openedx/tests/completion_integration/test_handlers.py index 677a7514628e..3d7a39ddb6c0 100644 --- a/openedx/tests/completion_integration/test_handlers.py +++ b/openedx/tests/completion_integration/test_handlers.py @@ -11,7 +11,7 @@ from completion.models import BlockCompletion from completion.test_utils import CompletionSetUpMixin from django.test import TestCase -from pytz import utc +from openedx.core.lib.time_zone_utils import get_utc_timezone from xblock.completable import XBlockCompletionMode from xblock.core import XBlock @@ -66,7 +66,7 @@ def call_scorable_block_completion_handler(self, block_key, score_deleted=None): usage_id=str(block_key), weighted_earned=0.0, weighted_possible=3.0, - modified=datetime.utcnow().replace(tzinfo=utc), + modified=datetime.utcnow().replace(tzinfo=get_utc_timezone()), score_db_table='submissions', **params ) @@ -127,7 +127,7 @@ def test_signal_calls_handler(self): usage_id=str(self.block_key), weighted_earned=0.0, weighted_possible=3.0, - modified=datetime.utcnow().replace(tzinfo=utc), + modified=datetime.utcnow().replace(tzinfo=get_utc_timezone()), score_db_table='submissions', ) mock_handler.assert_called() @@ -153,7 +153,7 @@ def test_disabled_handler_does_not_submit_completion(self): usage_id=str(self.block_key), weighted_earned=0.0, weighted_possible=3.0, - modified=datetime.utcnow().replace(tzinfo=utc), + modified=datetime.utcnow().replace(tzinfo=get_utc_timezone()), score_db_table='submissions', ) with pytest.raises(BlockCompletion.DoesNotExist): diff --git a/openedx/tests/xblock_integration/xblock_testcase.py b/openedx/tests/xblock_integration/xblock_testcase.py index 6f598a342cb1..95e260f99a86 100644 --- a/openedx/tests/xblock_integration/xblock_testcase.py +++ b/openedx/tests/xblock_integration/xblock_testcase.py @@ -44,7 +44,7 @@ import html from unittest import mock -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from bs4 import BeautifulSoup from django.conf import settings from django.urls import reverse @@ -199,7 +199,7 @@ def capture_score(user_id, usage_key, score, max_score): 'score': score, 'max_score': max_score}) # Shim a return time, defaults to 1 hour before now - return datetime.now().replace(tzinfo=pytz.UTC) - timedelta(hours=1) + return datetime.now().replace(tzinfo=get_utc_timezone()) - timedelta(hours=1) self.scores = [] patcher = mock.patch("lms.djangoapps.grades.signals.handlers.set_score", capture_score) diff --git a/pylintrc b/pylintrc index 43f2b3bc9eb8..9a6870e2eebf 100644 --- a/pylintrc +++ b/pylintrc @@ -72,10 +72,10 @@ persistent = yes load-plugins = edx_lint.pylint,openedx.core.tests.pylint_django_settings,pylint_django,pylint_celery,pylint_pytest [MESSAGES CONTROL] -enable = +enable = blacklisted-name, line-too-long, - + abstract-class-instantiated, abstract-method, access-member-before-definition, @@ -184,26 +184,26 @@ enable = used-before-assignment, using-constant-test, yield-outside-function, - + astroid-error, fatal, method-check-failed, parse-error, raw-checker-failed, - + empty-docstring, invalid-characters-in-docstring, missing-docstring, wrong-spelling-in-comment, wrong-spelling-in-docstring, - + unused-argument, unused-import, unused-variable, - + eval-used, exec-used, - + bad-classmethod-argument, bad-mcs-classmethod-argument, bad-mcs-method-argument, @@ -234,30 +234,30 @@ enable = unneeded-not, useless-else-on-loop, wrong-assert-type, - + deprecated-method, deprecated-module, - + too-many-boolean-expressions, too-many-nested-blocks, too-many-statements, - + wildcard-import, wrong-import-order, wrong-import-position, - + missing-final-newline, mixed-line-endings, trailing-newlines, trailing-whitespace, unexpected-line-ending-format, - + bad-inline-option, bad-option-value, deprecated-pragma, unrecognized-inline-option, useless-suppression, -disable = +disable = bad-indentation, broad-exception-raised, consider-using-f-string, @@ -282,10 +282,10 @@ disable = unspecified-encoding, unused-wildcard-import, use-maxsplit-arg, - + feature-toggle-needs-doc, illegal-waffle-usage, - + logging-fstring-interpolation, import-outside-toplevel, inconsistent-return-statements, @@ -360,7 +360,7 @@ ignore-imports = no ignore-mixin-members = yes ignored-classes = SQLObject unsafe-load-any-extension = yes -generated-members = +generated-members = REQUEST, acl_users, aq_parent, @@ -386,7 +386,7 @@ generated-members = [VARIABLES] init-import = no dummy-variables-rgx = _|dummy|unused|.*_unused -additional-builtins = +additional-builtins = [CLASSES] defining-attr-methods = __init__,__new__,setUp @@ -407,9 +407,9 @@ max-public-methods = 20 [IMPORTS] deprecated-modules = regsub,TERMIOS,Bastion,rexec -import-graph = -ext-import-graph = -int-import-graph = +import-graph = +ext-import-graph = +int-import-graph = [EXCEPTIONS] overgeneral-exceptions = builtins.Exception diff --git a/themes/red-theme/lms/templates/footer.html b/themes/red-theme/lms/templates/footer.html index d253ac93f83a..780aacfc73b3 100755 --- a/themes/red-theme/lms/templates/footer.html +++ b/themes/red-theme/lms/templates/footer.html @@ -7,7 +7,7 @@ from django.conf import settings from datetime import datetime -import pytz +from openedx.core.lib.time_zone_utils import get_utc_timezone from openedx.core.djangoapps.lang_pref.api import footer_language_selector_is_enabled from openedx.core.djangolib.markup import HTML, Text @@ -65,7 +65,7 @@

- + ## Site operators: Please do not remove this paragraph! This attributes back to edX and makes your acknowledgement of edX's trademarks clear.