chore: Replace pytz with zoneinfo for UTC handling - Part 1 (#37523)
First PR to replace pytz with zoneinfo for UTC handling across codebase. This PR migrates all UTC timezone handling from pytz to Python’s standard library zoneinfo. The pytz library is now deprecated, and its documentation recommends using zoneinfo for all new code. This update modernizes our codebase, removes legacy pytz usage, and ensures compatibility with current best practices for timezone management in Python 3.9+. No functional changes to timezone logic - just a direct replacement for UTC handling. https://github.com/openedx/edx-platform/issues/33980
This commit is contained in:
@@ -10,7 +10,7 @@ from unittest import mock
|
||||
import ddt
|
||||
import httpretty
|
||||
import pytest
|
||||
import pytz
|
||||
from zoneinfo import ZoneInfo
|
||||
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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + timedelta(days=90)
|
||||
self.credentials_api_config = self.create_credentials_config(enabled=False)
|
||||
|
||||
def tearDown(self):
|
||||
@@ -1135,7 +1135,7 @@ class UpdateCertificateAvailableDateOnCourseUpdateTestCase(CredentialsApiConfigM
|
||||
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(ZoneInfo("UTC")) + timedelta(days=120)
|
||||
|
||||
course_overview = self._create_course_overview(
|
||||
False,
|
||||
@@ -1168,7 +1168,7 @@ class UpdateCertificateAvailableDateOnCourseUpdateTestCase(CredentialsApiConfigM
|
||||
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(ZoneInfo("UTC")) + timedelta(days=120)
|
||||
|
||||
course_overview = self._create_course_overview(
|
||||
True,
|
||||
|
||||
@@ -15,7 +15,7 @@ from django.test.utils import override_settings
|
||||
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 zoneinfo import ZoneInfo
|
||||
from testfixtures import LogCapture
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
@@ -209,7 +209,7 @@ class TestProgramProgressMeter(ModuleStoreTestCase):
|
||||
CourseEntitlementFactory.create(
|
||||
user=self.user,
|
||||
course_uuid=course_uuid,
|
||||
expired_at=datetime.datetime.now(utc),
|
||||
expired_at=datetime.datetime.now(ZoneInfo("UTC")),
|
||||
mode=CourseMode.VERIFIED,
|
||||
enrollment_course_run=enrollment
|
||||
|
||||
@@ -308,7 +308,7 @@ class TestProgramProgressMeter(ModuleStoreTestCase):
|
||||
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(ZoneInfo("UTC"))
|
||||
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 @@ class TestProgramProgressMeter(ModuleStoreTestCase):
|
||||
|
||||
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(ZoneInfo("UTC"))
|
||||
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(ZoneInfo("UTC")) - datetime.timedelta(days=1)
|
||||
course.end = datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=1)
|
||||
course.instructor_info = self.instructors
|
||||
course = self.update_course(course, self.user.id)
|
||||
|
||||
@@ -899,8 +899,8 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
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(ZoneInfo("UTC")) - datetime.timedelta(days=1)
|
||||
self.course.end = datetime.datetime.now(ZoneInfo("UTC")) + 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 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
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(ZoneInfo("UTC")) + 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 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
"""
|
||||
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(ZoneInfo("UTC")) - datetime.timedelta(days=start_offset)
|
||||
self.course.enrollment_end = datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=end_offset)
|
||||
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
|
||||
@@ -1040,7 +1040,7 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
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(ZoneInfo("UTC")) - datetime.timedelta(days=1)
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
|
||||
data = ProgramDataExtender(self.program, self.user).extend()
|
||||
|
||||
@@ -14,7 +14,7 @@ from django.core.cache import cache
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import utc
|
||||
from zoneinfo import ZoneInfo
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from common.djangoapps.course_modes.api import get_paid_modes_for_course
|
||||
@@ -43,7 +43,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
|
||||
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=ZoneInfo("UTC"))
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -286,7 +286,7 @@ class ProgramProgressMeter:
|
||||
list of dict, each containing information about a user's progress
|
||||
towards completing a program.
|
||||
"""
|
||||
now = datetime.datetime.now(utc)
|
||||
now = datetime.datetime.now(ZoneInfo("UTC"))
|
||||
|
||||
progress = []
|
||||
programs = programs or self.engaged_programs
|
||||
@@ -598,15 +598,17 @@ class ProgramDataExtender:
|
||||
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=ZoneInfo("UTC"))
|
||||
run_mode["is_course_ended"] = end_date < datetime.datetime.now(ZoneInfo("UTC"))
|
||||
|
||||
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=ZoneInfo("UTC"))
|
||||
run_mode["is_enrollment_open"] = (
|
||||
self.enrollment_start <= datetime.datetime.now(ZoneInfo("UTC")) < enrollment_end
|
||||
)
|
||||
|
||||
def _attach_course_run_advertised_start(self, run_mode):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user