Skip to content

Commit b609f67

Browse files
authored
Merge pull request openedx#37554 from ttak-apphelix/ttak-apphelix/remove-pytz-3
chore: use zoneinfo instead of pytz
2 parents 2a69605 + f9d65aa commit b609f67

File tree

29 files changed

+127
-119
lines changed

29 files changed

+127
-119
lines changed

common/djangoapps/course_modes/admin.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"""Django admin for course_modes"""
22

33

4+
from zoneinfo import ZoneInfo
5+
46
from django import forms
57
from django.conf import settings
68
from django.contrib import admin
79
from django.http.request import QueryDict
810
from django.utils.translation import gettext_lazy as _
911
from opaque_keys.edx.keys import CourseKey
10-
from pytz import UTC, timezone
1112

1213
from common.djangoapps.course_modes.models import CourseMode, CourseModeExpirationConfig
1314
# Technically, we shouldn't be doing this, since verify_student is defined
@@ -73,15 +74,15 @@ def __init__(self, *args, **kwargs):
7374
# However, the args copy above before the super() call handles this case.
7475
pass
7576

76-
default_tz = timezone(settings.TIME_ZONE)
77+
default_tz = ZoneInfo(settings.TIME_ZONE)
7778

7879
if self.instance._expiration_datetime:
7980
# django admin is using default timezone. To avoid time conversion from db to form
8081
# convert the UTC object to naive and then localize with default timezone.
8182
_expiration_datetime = self.instance._expiration_datetime.replace(
8283
tzinfo=None
8384
)
84-
self.initial["_expiration_datetime"] = default_tz.localize(_expiration_datetime)
85+
self.initial["_expiration_datetime"] = _expiration_datetime.replace(tzinfo=default_tz)
8586
# Load the verification deadline
8687
# Since this is stored on a model in verify student, we need to load it from there.
8788
# 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):
9091
if self.instance.course_id and self.instance.mode_slug in CourseMode.VERIFIED_MODES:
9192
deadline = verification_models.VerificationDeadline.deadline_for_course(self.instance.course_id)
9293
self.initial["verification_deadline"] = (
93-
default_tz.localize(deadline.replace(tzinfo=None))
94+
deadline.replace(tzinfo=default_tz)
9495
if deadline is not None else None
9596
)
9697

@@ -107,14 +108,14 @@ def clean__expiration_datetime(self):
107108
# django admin saving the date with default timezone to avoid time conversion from form to db
108109
# changes its tzinfo to UTC
109110
if self.cleaned_data.get("_expiration_datetime"):
110-
return self.cleaned_data.get("_expiration_datetime").replace(tzinfo=UTC)
111+
return self.cleaned_data.get("_expiration_datetime").replace(tzinfo=ZoneInfo("UTC"))
111112

112113
def clean_verification_deadline(self):
113114
"""
114115
Ensure that the verification deadline we save uses the UTC timezone.
115116
"""
116117
if self.cleaned_data.get("verification_deadline"):
117-
return self.cleaned_data.get("verification_deadline").replace(tzinfo=UTC)
118+
return self.cleaned_data.get("verification_deadline").replace(tzinfo=ZoneInfo("UTC"))
118119

119120
def clean(self):
120121
"""

common/djangoapps/course_modes/tests/test_admin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import ddt
88
from django.conf import settings
99
from django.urls import reverse
10-
from pytz import UTC, timezone
10+
from pytz import timezone
11+
from zoneinfo import ZoneInfo
1112

1213
from common.djangoapps.course_modes.admin import CourseModeForm
1314
from common.djangoapps.course_modes.models import CourseMode
@@ -84,7 +85,7 @@ class AdminCourseModeFormTest(ModuleStoreTestCase):
8485
Test the course modes Django admin form validation and saving.
8586
"""
8687

87-
UPGRADE_DEADLINE = datetime.now(UTC)
88+
UPGRADE_DEADLINE = datetime.now(ZoneInfo("UTC"))
8889
VERIFICATION_DEADLINE = UPGRADE_DEADLINE + timedelta(days=5)
8990

9091
def setUp(self):

common/djangoapps/course_modes/tests/test_signals.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
from datetime import datetime, timedelta
77
from unittest.mock import patch
8+
from zoneinfo import ZoneInfo
89

910
import ddt
1011
from django.conf import settings
11-
from pytz import UTC
1212

1313
from common.djangoapps.course_modes.models import CourseMode
1414
from common.djangoapps.course_modes.signals import _listen_for_course_publish
@@ -26,7 +26,7 @@ class CourseModeSignalTest(ModuleStoreTestCase):
2626

2727
def setUp(self):
2828
super().setUp()
29-
self.end = datetime.now(tz=UTC).replace(microsecond=0) + timedelta(days=7)
29+
self.end = datetime.now(tz=ZoneInfo("UTC")).replace(microsecond=0) + timedelta(days=7)
3030
self.course = CourseFactory.create(end=self.end)
3131
CourseMode.objects.all().delete()
3232

common/djangoapps/course_modes/tests/test_views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
from datetime import datetime, timedelta
77
from unittest.mock import patch
88
from urllib.parse import urljoin
9+
from zoneinfo import ZoneInfo
910

1011
import ddt
1112
import freezegun
1213
import httpretty
13-
import pytz
1414
from django.conf import settings
1515
from django.test import override_settings
1616
from django.urls import reverse
@@ -50,7 +50,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
5050
@patch.dict(settings.FEATURES, {'MODE_CREATION_FOR_TESTING': True})
5151
def setUp(self):
5252
super().setUp()
53-
now = datetime.now(pytz.utc)
53+
now = datetime.now(ZoneInfo("UTC"))
5454
day = timedelta(days=1)
5555
tomorrow = now + day
5656
yesterday = now - day

common/djangoapps/entitlements/rest_api/v1/tests/test_views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import uuid
77
from datetime import datetime, timedelta
88
from unittest.mock import patch
9+
from zoneinfo import ZoneInfo
910

1011
from django.conf import settings
1112
from django.urls import reverse
1213
from django.utils.timezone import now
1314
from opaque_keys.edx.locator import CourseKey
14-
from pytz import UTC
1515

1616
from common.djangoapps.course_modes.models import CourseMode
1717
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
@@ -1023,7 +1023,7 @@ def test_already_enrolled_course_ended(self, mock_get_course_runs):
10231023
mock_get_course_runs.return_value = self.return_values
10241024

10251025
# Setup enrollment period to be in the past
1026-
utc_now = datetime.now(UTC)
1026+
utc_now = datetime.now(ZoneInfo("UTC"))
10271027
self.course.start = utc_now - timedelta(days=15)
10281028
self.course.end = utc_now - timedelta(days=1)
10291029
self.course = self.update_course(self.course, self.user.id)

common/djangoapps/entitlements/tests/test_tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
from datetime import datetime, timedelta
77
from unittest import mock
8+
from zoneinfo import ZoneInfo
89

910
import pytest
10-
import pytz
1111
from django.test import TestCase
1212

1313
from common.djangoapps.entitlements import tasks
@@ -19,7 +19,7 @@
1919

2020
def make_entitlement(expired=False): # lint-amnesty, pylint: disable=missing-function-docstring
2121
age = CourseEntitlementPolicy.DEFAULT_EXPIRATION_PERIOD_DAYS
22-
past_datetime = datetime.now(tz=pytz.UTC) - timedelta(days=age)
22+
past_datetime = datetime.now(tz=ZoneInfo("UTC")) - timedelta(days=age)
2323
expired_at = past_datetime if expired else None
2424
entitlement = CourseEntitlementFactory.create(created=past_datetime, expired_at=expired_at)
2525
return entitlement

common/djangoapps/student/management/commands/assigngroups.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
import random
66
import sys
77
from textwrap import dedent
8+
from zoneinfo import ZoneInfo
89

910
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
1011
from django.core.management.base import BaseCommand
11-
from pytz import UTC
1212

1313
from common.djangoapps.student.models import UserTestGroup
1414

@@ -75,7 +75,7 @@ def handle(self, *args, **options):
7575
utg = UserTestGroup()
7676
utg.name = group
7777
utg.description = json.dumps({"description": options['description']}, # lint-amnesty, pylint: disable=too-many-function-args
78-
{"time": datetime.datetime.now(UTC).isoformat()})
78+
{"time": datetime.datetime.now(ZoneInfo("UTC")).isoformat()})
7979
group_objects[group] = utg
8080
group_objects[group].save()
8181

common/djangoapps/student/models/course_enrollment.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from collections import defaultdict, namedtuple # lint-amnesty, pylint: disable=wrong-import-order
66
from datetime import date, datetime, timedelta # lint-amnesty, pylint: disable=wrong-import-order
77
from urllib.parse import urljoin
8+
from zoneinfo import ZoneInfo
89

910
from config_models.models import ConfigurationModel
1011
from django.conf import settings
@@ -29,7 +30,6 @@
2930
COURSE_UNENROLLMENT_COMPLETED,
3031
)
3132
from openedx_filters.learning.filters import CourseEnrollmentStarted, CourseUnenrollmentStarted
32-
from pytz import UTC
3333
from requests.exceptions import HTTPError, RequestException
3434
from simple_history.models import HistoricalRecords
3535

@@ -152,7 +152,7 @@ def with_certificates(self, username):
152152
"""
153153
return self.filter(course_id__in=self.get_user_course_ids_with_certificates(username))
154154

155-
def in_progress(self, username, time_zone=UTC):
155+
def in_progress(self, username, time_zone=ZoneInfo("UTC")):
156156
"""
157157
Returns a queryset of CourseEnrollment objects for courses that are currently in progress.
158158
"""
@@ -170,7 +170,7 @@ def completed(self, username):
170170
"""
171171
return self.active().with_certificates(username)
172172

173-
def expired(self, username, time_zone=UTC):
173+
def expired(self, username, time_zone=ZoneInfo("UTC")):
174174
"""
175175
Returns a queryset of CourseEnrollment objects for courses that have expired.
176176
"""
@@ -1087,7 +1087,7 @@ def refundable(self):
10871087
if refund_cutoff_date is None:
10881088
log.info("Refund cutoff date is null")
10891089
return False
1090-
if datetime.now(UTC) > refund_cutoff_date:
1090+
if datetime.now(ZoneInfo("UTC")) > refund_cutoff_date:
10911091
log.info(f"Refund cutoff date: {refund_cutoff_date} has passed")
10921092
return False
10931093

@@ -1133,7 +1133,10 @@ def refund_cutoff_date(self):
11331133
self.course_overview.start.replace(tzinfo=None)
11341134
)
11351135

1136-
return refund_window_start_date.replace(tzinfo=UTC) + EnrollmentRefundConfiguration.current().refund_window
1136+
return (
1137+
refund_window_start_date.replace(tzinfo=ZoneInfo("UTC"))
1138+
+ EnrollmentRefundConfiguration.current().refund_window
1139+
)
11371140

11381141
def is_order_voucher_refundable(self):
11391142
""" 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):
11421145
if not vouchers:
11431146
return False
11441147
voucher_end_datetime_str = vouchers[0]['end_datetime']
1145-
voucher_expiration_date = datetime.strptime(voucher_end_datetime_str, ECOMMERCE_DATE_FORMAT).replace(tzinfo=UTC)
1146-
return datetime.now(UTC) < voucher_expiration_date
1148+
voucher_expiration_date = (
1149+
datetime.strptime(voucher_end_datetime_str, ECOMMERCE_DATE_FORMAT)
1150+
.replace(tzinfo=ZoneInfo("UTC"))
1151+
)
1152+
return datetime.now(ZoneInfo("UTC")) < voucher_expiration_date
11471153

11481154
def get_order_attribute_from_ecommerce(self, attribute_name):
11491155
"""
@@ -1265,7 +1271,7 @@ def upgrade_deadline(self):
12651271
if self.dynamic_upgrade_deadline is not None:
12661272
# When course modes expire they aren't found any more and None would be returned.
12671273
# Replicate that behavior here by returning None if the personalized deadline is in the past.
1268-
if self.dynamic_upgrade_deadline <= datetime.now(UTC):
1274+
if self.dynamic_upgrade_deadline <= datetime.now(ZoneInfo("UTC")):
12691275
log.debug('Schedules: Returning None since dynamic upgrade deadline has already passed.')
12701276
return None
12711277

common/djangoapps/student/models/user.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from functools import total_ordering
2020
from importlib import import_module
2121
from urllib.parse import urlencode
22+
from zoneinfo import ZoneInfo
2223

2324
import crum
2425
from config_models.models import ConfigurationModel
@@ -456,7 +457,7 @@ class Meta:
456457
location = models.CharField(blank=True, max_length=255, db_index=True)
457458

458459
# Optional demographic data we started capturing from Fall 2012
459-
this_year = datetime.now(UTC).year
460+
this_year = datetime.now(ZoneInfo("UTC")).year
460461
VALID_YEARS = list(range(this_year, this_year - 120, -1))
461462
year_of_birth = models.IntegerField(blank=True, null=True, db_index=True)
462463
GENDER_CHOICES = (
@@ -572,7 +573,7 @@ def has_profile_image(self):
572573
def age(self):
573574
""" Convenience method that returns the age given a year_of_birth. """
574575
year_of_birth = self.year_of_birth
575-
year = datetime.now(UTC).year
576+
year = datetime.now(ZoneInfo("UTC")).year
576577
if year_of_birth is not None:
577578
return self._calculate_age(year, year_of_birth)
578579

@@ -798,7 +799,7 @@ def user_post_save_callback(sender, **kwargs):
798799
'username': user.username,
799800
'name': profile.name,
800801
'age': profile.age or -1,
801-
'yearOfBirth': profile.year_of_birth or datetime.now(UTC).year,
802+
'yearOfBirth': profile.year_of_birth or datetime.now(ZoneInfo("UTC")).year,
802803
'education': profile.level_of_education_display,
803804
'address': profile.mailing_address,
804805
'gender': profile.gender_display,
@@ -982,7 +983,7 @@ def is_user_locked_out(cls, user):
982983
if not record.lockout_until:
983984
return False
984985

985-
now = datetime.now(UTC)
986+
now = datetime.now(ZoneInfo("UTC"))
986987
until = record.lockout_until
987988
is_locked_out = until and now < until
988989

@@ -1003,7 +1004,7 @@ def increment_lockout_counter(cls, user):
10031004
if record.failure_count >= max_failures_allowed:
10041005
# yes, then store when this account is locked out until
10051006
lockout_period_secs = settings.MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS
1006-
record.lockout_until = datetime.now(UTC) + timedelta(seconds=lockout_period_secs)
1007+
record.lockout_until = datetime.now(ZoneInfo("UTC")) + timedelta(seconds=lockout_period_secs)
10071008

10081009
record.save()
10091010

common/djangoapps/student/models_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"""
44
import datetime
55
import logging
6+
from zoneinfo import ZoneInfo
67

7-
from pytz import UTC
88

99
from common.djangoapps.student.models import CourseAccessRole as _CourseAccessRole
1010
from common.djangoapps.student.models import CourseEnrollment as _CourseEnrollment
@@ -158,7 +158,7 @@ def confirm_name_change(user, pending_name_change):
158158
if 'old_names' not in meta:
159159
meta['old_names'] = []
160160
meta['old_names'].append(
161-
[user_profile.name, pending_name_change.rationale, datetime.datetime.now(UTC).isoformat()]
161+
[user_profile.name, pending_name_change.rationale, datetime.datetime.now(ZoneInfo("UTC")).isoformat()]
162162
)
163163
user_profile.set_meta(meta)
164164

0 commit comments

Comments
 (0)