diff --git a/common/djangoapps/course_modes/admin.py b/common/djangoapps/course_modes/admin.py index c874a7892581..76eabd20bf80 100644 --- a/common/djangoapps/course_modes/admin.py +++ b/common/djangoapps/course_modes/admin.py @@ -1,13 +1,14 @@ """Django admin for course_modes""" +from zoneinfo import ZoneInfo + from django import forms from django.conf import settings from django.contrib import admin 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 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=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=ZoneInfo("UTC")) 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=ZoneInfo("UTC")) 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..7a01b6794987 100644 --- a/common/djangoapps/course_modes/tests/test_admin.py +++ b/common/djangoapps/course_modes/tests/test_admin.py @@ -7,7 +7,8 @@ import ddt from django.conf import settings from django.urls import reverse -from pytz import UTC, timezone +from pytz import timezone +from zoneinfo import ZoneInfo from common.djangoapps.course_modes.admin import CourseModeForm from common.djangoapps.course_modes.models import CourseMode @@ -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(ZoneInfo("UTC")) 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..e4323f825a25 100644 --- a/common/djangoapps/course_modes/tests/test_signals.py +++ b/common/djangoapps/course_modes/tests/test_signals.py @@ -5,10 +5,10 @@ from datetime import datetime, timedelta from unittest.mock import patch +from zoneinfo import ZoneInfo import ddt from django.conf import settings -from pytz import UTC 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=ZoneInfo("UTC")).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..65b4980eba01 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -6,11 +6,11 @@ from datetime import datetime, timedelta from unittest.mock import patch from urllib.parse import urljoin +from zoneinfo import ZoneInfo import ddt import freezegun import httpretty -import pytz 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(ZoneInfo("UTC")) day = timedelta(days=1) tomorrow = now + day yesterday = now - day 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..2f007ae6ed3b 100644 --- a/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py +++ b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py @@ -6,12 +6,12 @@ import uuid from datetime import datetime, timedelta from unittest.mock import patch +from zoneinfo import ZoneInfo from django.conf import settings from django.urls import reverse from django.utils.timezone import now from opaque_keys.edx.locator import CourseKey -from pytz import UTC 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(ZoneInfo("UTC")) 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..c9f705940437 100644 --- a/common/djangoapps/entitlements/tests/test_tasks.py +++ b/common/djangoapps/entitlements/tests/test_tasks.py @@ -5,9 +5,9 @@ from datetime import datetime, timedelta from unittest import mock +from zoneinfo import ZoneInfo import pytest -import pytz 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=ZoneInfo("UTC")) - 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/management/commands/assigngroups.py b/common/djangoapps/student/management/commands/assigngroups.py index acde7994a33e..eb9dc21d26d5 100644 --- a/common/djangoapps/student/management/commands/assigngroups.py +++ b/common/djangoapps/student/management/commands/assigngroups.py @@ -5,10 +5,10 @@ import random import sys from textwrap import dedent +from zoneinfo import ZoneInfo 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 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(ZoneInfo("UTC")).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 831915ae8f84..8a23f89a53b3 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,6 @@ COURSE_UNENROLLMENT_COMPLETED, ) from openedx_filters.learning.filters import CourseEnrollmentStarted, CourseUnenrollmentStarted -from pytz import UTC from requests.exceptions import HTTPError, RequestException from simple_history.models import HistoricalRecords @@ -152,7 +152,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=ZoneInfo("UTC")): """ Returns a queryset of CourseEnrollment objects for courses that are currently in progress. """ @@ -170,7 +170,7 @@ def completed(self, username): """ return self.active().with_certificates(username) - def expired(self, username, time_zone=UTC): + def expired(self, username, time_zone=ZoneInfo("UTC")): """ Returns a queryset of CourseEnrollment objects for courses that have expired. """ @@ -1087,7 +1087,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(ZoneInfo("UTC")) > refund_cutoff_date: log.info(f"Refund cutoff date: {refund_cutoff_date} has passed") return False @@ -1133,7 +1133,10 @@ 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. """ @@ -1142,8 +1145,11 @@ 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=ZoneInfo("UTC")) + ) + return datetime.now(ZoneInfo("UTC")) < voucher_expiration_date def get_order_attribute_from_ecommerce(self, attribute_name): """ @@ -1265,7 +1271,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(ZoneInfo("UTC")): 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 aa46de76c477..8d10db4b0f50 100644 --- a/common/djangoapps/student/models/user.py +++ b/common/djangoapps/student/models/user.py @@ -19,6 +19,7 @@ from functools import total_ordering from importlib import import_module from urllib.parse import urlencode +from zoneinfo import ZoneInfo import crum from config_models.models import ConfigurationModel @@ -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(ZoneInfo("UTC")).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(ZoneInfo("UTC")).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(ZoneInfo("UTC")).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(ZoneInfo("UTC")) 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(ZoneInfo("UTC")) + timedelta(seconds=lockout_period_secs) record.save() diff --git a/common/djangoapps/student/models_api.py b/common/djangoapps/student/models_api.py index 997f5454db53..c8740fd339fc 100644 --- a/common/djangoapps/student/models_api.py +++ b/common/djangoapps/student/models_api.py @@ -3,8 +3,8 @@ """ import datetime import logging +from zoneinfo import ZoneInfo -from pytz import UTC 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(ZoneInfo("UTC")).isoformat()] ) user_profile.set_meta(meta) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 27e245288dd6..21fa9a1b44f1 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -3,6 +3,7 @@ from datetime import datetime from uuid import uuid4 +from zoneinfo import ZoneInfo import factory from django.contrib.auth.models import AnonymousUser, Group, Permission @@ -10,7 +11,6 @@ from django.test.client import RequestFactory from factory.django import DjangoModelFactory from opaque_keys.edx.keys import CourseKey -from pytz import UTC 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=ZoneInfo("UTC")) + date_joined = datetime(2011, 1, 1, tzinfo=ZoneInfo("UTC")) @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 d001befe3bb1..da8376fbbe5e 100644 --- a/common/djangoapps/student/tests/test_admin_views.py +++ b/common/djangoapps/student/tests/test_admin_views.py @@ -6,6 +6,7 @@ import datetime import json from unittest.mock import Mock +from zoneinfo import ZoneInfo import ddt import pytest @@ -17,7 +18,6 @@ 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 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(ZoneInfo("UTC")) 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 881f5d6da02a..914e255df944 100644 --- a/common/djangoapps/student/tests/test_certificates.py +++ b/common/djangoapps/student/tests/test_certificates.py @@ -2,12 +2,12 @@ import datetime from unittest.mock import patch +from zoneinfo import ZoneInfo import ddt from django.conf import settings from django.test.utils import override_settings from django.urls import reverse -from pytz import UTC from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -22,8 +22,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(ZoneInfo("UTC")) - datetime.timedelta(days=2) +FUTURE_DATE = datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=2) class CertificateDisplayTestBase(SharedModuleStoreTestCase): @@ -92,7 +92,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(ZoneInfo("UTC")) 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..20ba9cd72ebc 100644 --- a/common/djangoapps/student/tests/test_credit.py +++ b/common/djangoapps/student/tests/test_credit.py @@ -4,9 +4,9 @@ import datetime from unittest.mock import patch +from zoneinfo import ZoneInfo import ddt -import pytz 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(ZoneInfo("UTC")) + 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..69f19da3dc18 100644 --- a/common/djangoapps/student/tests/test_models.py +++ b/common/djangoapps/student/tests/test_models.py @@ -3,9 +3,9 @@ import hashlib from unittest import mock from unittest.mock import MagicMock +from zoneinfo import ZoneInfo 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 +15,6 @@ 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 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")).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(ZoneInfo("UTC")) 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(ZoneInfo("UTC")) 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(ZoneInfo("UTC")) 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(ZoneInfo("UTC")) 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(ZoneInfo("UTC")) 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(ZoneInfo("UTC")) 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(ZoneInfo("UTC")) - 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..327bf6b46e89 100644 --- a/common/djangoapps/student/tests/test_recent_enrollments.py +++ b/common/djangoapps/student/tests/test_recent_enrollments.py @@ -3,12 +3,12 @@ """ import datetime +from zoneinfo import ZoneInfo import ddt from django.urls import reverse from django.utils.timezone import now from opaque_keys.edx import locator -from pytz import UTC 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=ZoneInfo("UTC")) enrollment.save() # New Course diff --git a/common/djangoapps/student/tests/test_refunds.py b/common/djangoapps/student/tests/test_refunds.py index 4544a54fef81..60979d8e80d5 100644 --- a/common/djangoapps/student/tests/test_refunds.py +++ b/common/djangoapps/student/tests/test_refunds.py @@ -6,10 +6,10 @@ import logging from datetime import datetime, timedelta from unittest.mock import patch +from zoneinfo import ZoneInfo import ddt import httpretty -import pytz # 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + timedelta(days=1) + self.verified_mode.expiration_datetime = datetime.now(ZoneInfo("UTC")) - 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + timedelta(days=1) assert self.enrollment.refundable() - cutoff_date.return_value = datetime.now(pytz.UTC) - timedelta(minutes=5) + cutoff_date.return_value = datetime.now(ZoneInfo("UTC")) - 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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")).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(ZoneInfo("UTC")) + timedelta(days=1), True), + (datetime.now(ZoneInfo("UTC")) - timedelta(days=1), False), + (datetime.now(ZoneInfo("UTC")) - 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(ZoneInfo("UTC")).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(ZoneInfo("UTC")).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..ed8c98188a50 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -8,9 +8,9 @@ import unittest from datetime import datetime, timedelta # lint-amnesty, pylint: disable=unused-import from unittest.mock import patch +from zoneinfo import ZoneInfo import ddt -import pytz 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(ZoneInfo("UTC")) + timedelta(days=1), sku=sku ) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 2d634207a8cf..c4e5635be75a 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -6,9 +6,9 @@ from datetime import datetime, timedelta from unittest.mock import Mock, patch from urllib.parse import quote +from zoneinfo import ZoneInfo import ddt -import pytz 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(ZoneInfo("UTC")) - timedelta(days=2) ) cert = GeneratedCertificateFactory.create( user=user, @@ -287,7 +287,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(ZoneInfo("UTC")) - timedelta(days=2), ) enrollment = CourseEnrollmentFactory(user=user, course_id=course.id, mode=CourseMode.VERIFIED) @@ -314,7 +314,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(ZoneInfo("UTC")) - timedelta(days=2), ) cert_status = {'status': 'generating', 'mode': 'honor', 'uuid': None} enrollment = CourseEnrollmentFactory(user=user, course_id=course.id, mode=CourseMode.VERIFIED) @@ -412,14 +412,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(ZoneInfo("UTC")) + 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(ZoneInfo("UTC")) + timedelta(days=-1) verified_mode.save() course_mode_info = complete_course_mode_info(self.course.id, enrollment) assert not course_mode_info['show_upsell'] @@ -434,13 +434,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(ZoneInfo("UTC")) - 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(ZoneInfo("UTC")) - timedelta(days=2) + self.course.end = datetime.now(ZoneInfo("UTC")) - timedelta(days=1) self.course.display_name = "Omega" self.course = self.update_course(self.course, self.user.id) @@ -482,12 +482,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(ZoneInfo("UTC")) - 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(ZoneInfo("UTC")) - timedelta(days=1) + self.course.start = datetime.now(ZoneInfo("UTC")) - timedelta(days=2) + self.course.end = datetime.now(ZoneInfo("UTC")) - timedelta(days=1) self.course.display_name = 'Omega' self.course.course_organization = 'Omega Org' self.course = self.update_course(self.course, self.user.id) @@ -593,7 +593,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(ZoneInfo("UTC")) + 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..be1f32160911 100644 --- a/common/djangoapps/student/views/dashboard.py +++ b/common/djangoapps/student/views/dashboard.py @@ -6,6 +6,7 @@ import datetime import logging from collections import defaultdict +from zoneinfo import ZoneInfo from django.conf import settings from django.contrib import messages @@ -20,7 +21,6 @@ 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 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(ZoneInfo("UTC")) - 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(ZoneInfo("UTC")).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..3af83742613d 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -9,6 +9,7 @@ import uuid from collections import namedtuple import re +from zoneinfo import ZoneInfo from django.conf import settings from django.contrib import messages @@ -35,7 +36,6 @@ # 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 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(ZoneInfo("UTC")) 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(ZoneInfo("UTC")).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..7cc4003bc496 100644 --- a/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py +++ b/common/djangoapps/third_party_auth/samlproviderdata/tests/test_samlproviderdata.py @@ -3,8 +3,8 @@ from datetime import datetime # lint-amnesty, pylint: disable=wrong-import-order from unittest import mock from uuid import uuid4 # lint-amnesty, pylint: disable=wrong-import-order +from zoneinfo import ZoneInfo -import pytz 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(ZoneInfo("UTC")).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 0d0a2cf7241d..66c57118bfb9 100644 --- a/common/djangoapps/third_party_auth/tests/test_pipeline_integration.py +++ b/common/djangoapps/third_party_auth/tests/test_pipeline_integration.py @@ -3,10 +3,10 @@ import datetime from unittest import mock +from zoneinfo import ZoneInfo import ddt import pytest -import pytz 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(ZoneInfo("UTC")) + 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..52474c343a5f 100644 --- a/common/djangoapps/third_party_auth/utils.py +++ b/common/djangoapps/third_party_auth/utils.py @@ -5,9 +5,9 @@ import datetime import logging from uuid import UUID +from zoneinfo import ZoneInfo import dateutil.parser -import pytz 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=ZoneInfo("UTC")) if expires_at is None or cache_expires < expires_at: expires_at = cache_expires diff --git a/common/djangoapps/track/tests/__init__.py b/common/djangoapps/track/tests/__init__.py index 0fd070c5a298..1a24fd72155f 100644 --- a/common/djangoapps/track/tests/__init__.py +++ b/common/djangoapps/track/tests/__init__.py @@ -2,15 +2,15 @@ from datetime import datetime +from zoneinfo import ZoneInfo from django.test import TestCase from django.test.utils import override_settings from eventtracking import tracker from eventtracking.django import DjangoTracker from freezegun import freeze_time -from pytz import UTC -FROZEN_TIME = datetime(2013, 10, 3, 8, 24, 55, tzinfo=UTC) +FROZEN_TIME = datetime(2013, 10, 3, 8, 24, 55, tzinfo=ZoneInfo("UTC")) 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..f0414da221c9 100644 --- a/common/djangoapps/track/tests/test_util.py +++ b/common/djangoapps/track/tests/test_util.py @@ -2,9 +2,9 @@ import json from datetime import datetime +from zoneinfo import ZoneInfo from django.test import TestCase -from pytz import UTC 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=ZoneInfo("UTC")) 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/util/file.py b/common/djangoapps/util/file.py index 9397cdff939d..57ac2c8534c4 100644 --- a/common/djangoapps/util/file.py +++ b/common/djangoapps/util/file.py @@ -5,6 +5,7 @@ import os from datetime import datetime +from zoneinfo import ZoneInfo from django.conf import settings from django.core.exceptions import PermissionDenied @@ -12,7 +13,6 @@ 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 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(ZoneInfo("UTC")).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..f5f26d6eff9a 100644 --- a/common/djangoapps/util/tests/test_date_utils.py +++ b/common/djangoapps/util/tests/test_date_utils.py @@ -5,12 +5,12 @@ import unittest from datetime import datetime, timedelta, tzinfo from unittest.mock import patch +from zoneinfo import ZoneInfo import crum import ddt import pytest from markupsafe import Markup -from pytz import utc 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=ZoneInfo("UTC")) 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=ZoneInfo("UTC")) 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=ZoneInfo("UTC")) 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=ZoneInfo("UTC")) 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=ZoneInfo("UTC")) + test_time_daylight = datetime(1992, 7, 12, 15, 3, 30, tzinfo=ZoneInfo("UTC")) 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..e90ac4e5b524 100644 --- a/common/djangoapps/util/tests/test_file.py +++ b/common/djangoapps/util/tests/test_file.py @@ -7,6 +7,7 @@ from datetime import datetime from io import StringIO from unittest.mock import Mock, patch +from zoneinfo import ZoneInfo import pytest import ddt @@ -17,7 +18,6 @@ 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 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=ZoneInfo("UTC")) def setUp(self): super().setUp()