diff --git a/xmodule/assetstore/__init__.py b/xmodule/assetstore/__init__.py index d18a1bd02029..82e4a2163e6c 100644 --- a/xmodule/assetstore/__init__.py +++ b/xmodule/assetstore/__init__.py @@ -5,9 +5,9 @@ import json from datetime import datetime +from zoneinfo import ZoneInfo import dateutil.parser -import pytz from lxml import etree from opaque_keys.edx.keys import AssetKey, CourseKey @@ -80,7 +80,7 @@ def __init__(self, asset_id, self.thumbnail = thumbnail self.curr_version = curr_version self.prev_version = prev_version - now = datetime.now(pytz.utc) + now = datetime.now(ZoneInfo("UTC")) self.edited_by = edited_by self.edited_by_email = edited_by_email self.edited_on = edited_on or now diff --git a/xmodule/capa/capa_problem.py b/xmodule/capa/capa_problem.py index 6c7a0dcffa78..5a397e8ee6dd 100644 --- a/xmodule/capa/capa_problem.py +++ b/xmodule/capa/capa_problem.py @@ -23,10 +23,10 @@ from datetime import datetime from typing import Optional from xml.sax.saxutils import unescape +from zoneinfo import ZoneInfo from django.conf import settings from lxml import etree -from pytz import UTC from openedx.core.djangolib.markup import HTML, Text from openedx.core.lib.safe_lxml.xmlparser import XML @@ -437,7 +437,10 @@ def get_recentmost_queuetime(self): if self.correct_map.is_queued(answer_id) ] queuetimes = [ - datetime.strptime(qt_str, xqueue_interface.DATEFORMAT).replace(tzinfo=UTC) for qt_str in queuetime_strs + datetime.strptime(qt_str, xqueue_interface.DATEFORMAT).replace( + tzinfo=ZoneInfo("UTC") + ) + for qt_str in queuetime_strs ] return max(queuetimes) diff --git a/xmodule/capa/responsetypes.py b/xmodule/capa/responsetypes.py index 6842547b5139..d37083cdafcd 100644 --- a/xmodule/capa/responsetypes.py +++ b/xmodule/capa/responsetypes.py @@ -20,6 +20,7 @@ from collections import namedtuple from datetime import datetime from sys import float_info +from zoneinfo import ZoneInfo import html5lib import numpy @@ -33,7 +34,6 @@ from lxml import etree from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME? from pyparsing import ParseException -from pytz import UTC from shapely.geometry import MultiPoint, Point from six.moves import map, range, zip from symmath import symmath_check @@ -2653,7 +2653,7 @@ def get_score(self, student_answers): # pylint: disable=too-many-locals # ------------------------------------------------------------ qinterface = self.capa_system.xqueue.interface - qtime = datetime.strftime(datetime.now(UTC), DATEFORMAT) + qtime = datetime.strftime(datetime.now(ZoneInfo("UTC")), DATEFORMAT) anonymous_student_id = self.capa_system.anonymous_student_id diff --git a/xmodule/capa/tests/test_responsetypes.py b/xmodule/capa/tests/test_responsetypes.py index 95d29022d756..b3dca8d67757 100644 --- a/xmodule/capa/tests/test_responsetypes.py +++ b/xmodule/capa/tests/test_responsetypes.py @@ -11,13 +11,13 @@ import zipfile from datetime import datetime from unittest import mock +from zoneinfo import ZoneInfo import calc import pyparsing import pytest import random2 as random import requests -from pytz import UTC from xmodule.capa.correctmap import CorrectMap from xmodule.capa.responsetypes import ( @@ -999,7 +999,7 @@ def test_is_queued(self): # Now we queue the LCP cmap = CorrectMap() for i, answer_id in enumerate(answer_ids): - queuestate = CodeResponseTest.make_queuestate(i, datetime.now(UTC)) + queuestate = CodeResponseTest.make_queuestate(i, datetime.now(ZoneInfo("UTC"))) cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate)) self.problem.correct_map.update(cmap) @@ -1015,7 +1015,7 @@ def test_update_score(self): # pylint: disable=too-many-locals old_cmap = CorrectMap() for i, answer_id in enumerate(answer_ids): queuekey = 1000 + i - queuestate = CodeResponseTest.make_queuestate(queuekey, datetime.now(UTC)) + queuestate = CodeResponseTest.make_queuestate(queuekey, datetime.now(ZoneInfo("UTC"))) old_cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate)) # Message format common to external graders @@ -1083,14 +1083,14 @@ def test_recentmost_queuetime(self): cmap = CorrectMap() for i, answer_id in enumerate(answer_ids): queuekey = 1000 + i - latest_timestamp = datetime.now(UTC) + latest_timestamp = datetime.now(ZoneInfo("UTC")) queuestate = CodeResponseTest.make_queuestate(queuekey, latest_timestamp) cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate)) self.problem.correct_map.update(cmap) # Queue state only tracks up to second latest_timestamp = datetime.strptime(datetime.strftime(latest_timestamp, DATEFORMAT), DATEFORMAT).replace( - tzinfo=UTC + tzinfo=ZoneInfo("UTC") ) assert self.problem.get_recentmost_queuetime() == latest_timestamp @@ -1153,7 +1153,7 @@ def test_parse_score_msg_of_responder(self): old_cmap = CorrectMap() for i, answer_id in enumerate(answer_ids): queuekey = 1000 + i - queuestate = CodeResponseTest.make_queuestate(queuekey, datetime.now(UTC)) + queuestate = CodeResponseTest.make_queuestate(queuekey, datetime.now(ZoneInfo("UTC"))) old_cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate)) for grader_msg in valid_grader_msgs: diff --git a/xmodule/capa_block.py b/xmodule/capa_block.py index 291020fe614e..4b67997586b8 100644 --- a/xmodule/capa_block.py +++ b/xmodule/capa_block.py @@ -15,6 +15,7 @@ import struct import sys import traceback +from zoneinfo import ZoneInfo import nh3 from django.conf import settings @@ -23,7 +24,6 @@ from django.utils.encoding import smart_str from django.utils.functional import cached_property from lxml import etree -from pytz import utc from web_fragments.fragment import Fragment from xblock.core import XBlock from xblock.exceptions import NotFoundError, ProcessingError @@ -931,7 +931,7 @@ def set_last_submission_time(self): """ Set the module's last submission time (when the problem was submitted) """ - self.last_submission_time = datetime.datetime.now(utc) + self.last_submission_time = datetime.datetime.now(ZoneInfo("UTC")) def get_progress(self): """ @@ -1448,7 +1448,7 @@ def is_past_due(self): """ Is it now past this problem's due date, including grace period? """ - return self.close_date is not None and datetime.datetime.now(utc) > self.close_date + return self.close_date is not None and datetime.datetime.now(ZoneInfo("UTC")) > self.close_date def closed(self): """ @@ -1774,7 +1774,7 @@ def submit_problem( # pylint: disable=too-many-statements,too-many-branches,too event_info["answers"] = answers_without_files # Can override current time - current_time = datetime.datetime.now(utc) + current_time = datetime.datetime.now(ZoneInfo("UTC")) if override_time is not False: current_time = override_time diff --git a/xmodule/course_block.py b/xmodule/course_block.py index 2a720baf41c7..f9b0df780159 100644 --- a/xmodule/course_block.py +++ b/xmodule/course_block.py @@ -6,6 +6,7 @@ import json import logging from datetime import datetime, timedelta +from zoneinfo import ZoneInfo import dateutil.parser import requests @@ -15,7 +16,6 @@ from lazy import lazy from lxml import etree from path import Path as path -from pytz import utc from xblock.fields import Boolean, Date, Dict, Float, Integer, List, Scope, String from openedx.core.djangoapps.video_pipeline.models import VideoUploadsEnabledByDefault from openedx.core.djangoapps.video_config.sharing import ( @@ -163,7 +163,7 @@ def table_of_contents(self): # see if we already fetched this if toc_url in _cached_toc: (table_of_contents, timestamp) = _cached_toc[toc_url] - age = datetime.now(utc) - timestamp + age = datetime.now(ZoneInfo("UTC")) - timestamp # expire every 10 minutes if age.seconds < 600: return table_of_contents @@ -1485,7 +1485,7 @@ def forum_posts_allowed(self): blackouts = self.get_discussion_blackout_datetimes() posting_restrictions = self.discussions_settings.get('posting_restrictions', 'disabled') - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) if posting_restrictions == 'enabled': return False @@ -1601,7 +1601,7 @@ def can_toggle_course_pacing(self): """ if not self.start: return False - return datetime.now(utc) <= self.start + return datetime.now(ZoneInfo("UTC")) <= self.start class CourseSummary: @@ -1674,5 +1674,5 @@ def has_ended(self): course_id=str(self.id), end_date=self.end, err=e ) ) - modified_end = self.end.replace(tzinfo=utc) + modified_end = self.end.replace(tzinfo=ZoneInfo("UTC")) return course_metadata_utils.has_course_ended(modified_end) diff --git a/xmodule/course_metadata_utils.py b/xmodule/course_metadata_utils.py index d8cccb7248c9..e0320e6043e7 100644 --- a/xmodule/course_metadata_utils.py +++ b/xmodule/course_metadata_utils.py @@ -7,14 +7,15 @@ """ +import dateutil.parser + from base64 import b32encode from datetime import datetime, timedelta from math import exp +from zoneinfo import ZoneInfo -import dateutil.parser -from pytz import utc -DEFAULT_START_DATE = datetime(2030, 1, 1, tzinfo=utc) +DEFAULT_START_DATE = datetime(2030, 1, 1, tzinfo=ZoneInfo("UTC")) """ Default grading policy for a course run. @@ -95,7 +96,7 @@ def has_course_started(start_date): start_date (datetime): The start datetime of the course in question. """ # TODO: This will throw if start_date is None... consider changing this behavior? - return datetime.now(utc) > start_date + return datetime.now(ZoneInfo("UTC")) > start_date def has_course_ended(end_date): @@ -107,7 +108,7 @@ def has_course_ended(end_date): Arguments: end_date (datetime): The end datetime of the course in question. """ - return datetime.now(utc) > end_date if end_date is not None else False + return datetime.now(ZoneInfo("UTC")) > end_date if end_date is not None else False def is_enrollment_open(enrollment_start_date, enrollment_end_date): @@ -118,9 +119,9 @@ def is_enrollment_open(enrollment_start_date, enrollment_end_date): enrollment_start_date (datetime): The enrollment start datetime of the course. enrollment_end_date (datetime): The enrollment end datetime of the course. """ - now = datetime.now(utc) - enrollment_start_date = enrollment_start_date or datetime.min.replace(tzinfo=utc) - enrollment_end_date = enrollment_end_date or datetime.max.replace(tzinfo=utc) + now = datetime.now(ZoneInfo("UTC")) + enrollment_start_date = enrollment_start_date or datetime.min.replace(tzinfo=ZoneInfo("UTC")) + enrollment_end_date = enrollment_end_date or datetime.max.replace(tzinfo=ZoneInfo("UTC")) return enrollment_start_date < now < enrollment_end_date @@ -133,7 +134,7 @@ def course_starts_within(start_date, look_ahead_days): start_date (datetime): The start datetime of the course in question. look_ahead_days (int): number of days to see in future for course start date. """ - return datetime.now(utc) + timedelta(days=look_ahead_days) > start_date + return datetime.now(ZoneInfo("UTC")) + timedelta(days=look_ahead_days) > start_date def course_start_date_is_default(start, advertised_start): @@ -179,10 +180,10 @@ def sorting_dates(start, advertised_start, announcement): try: start = dateutil.parser.parse(advertised_start) if start.tzinfo is None: - start = start.replace(tzinfo=utc) + start = start.replace(tzinfo=ZoneInfo("UTC")) except (TypeError, ValueError, AttributeError): start = start # lint-amnesty, pylint: disable=self-assigning-variable - now = datetime.now(utc) + now = datetime.now(ZoneInfo("UTC")) return announcement, start, now diff --git a/xmodule/lti_block.py b/xmodule/lti_block.py index 803986013549..2dfd258639fe 100644 --- a/xmodule/lti_block.py +++ b/xmodule/lti_block.py @@ -62,6 +62,7 @@ from unittest import mock from urllib import parse from xml.sax.saxutils import escape +from zoneinfo import ZoneInfo import nh3 import oauthlib.oauth1 @@ -69,7 +70,6 @@ from lxml import etree from oauthlib.oauth1.rfc5849 import signature from opaque_keys.edx.keys import CourseKey -from pytz import UTC from web_fragments.fragment import Fragment from webob import Response from xblock.core import List, Scope, String, XBlock @@ -989,7 +989,7 @@ def is_past_due(self): close_date = due_date + self.graceperiod # pylint: disable=no-member else: close_date = due_date - return close_date is not None and datetime.datetime.now(UTC) > close_date + return close_date is not None and datetime.datetime.now(ZoneInfo("UTC")) > close_date LTIBlock = ( diff --git a/xmodule/modulestore/__init__.py b/xmodule/modulestore/__init__.py index d52455641fb8..3446a4c8c61b 100644 --- a/xmodule/modulestore/__init__.py +++ b/xmodule/modulestore/__init__.py @@ -12,11 +12,11 @@ from collections import defaultdict from contextlib import contextmanager from operator import itemgetter -from django.db import transaction +from zoneinfo import ZoneInfo +from django.db import transaction from opaque_keys.edx.keys import AssetKey, CourseKey from opaque_keys.edx.locations import Location # For import backwards compatibility -from pytz import UTC from sortedcontainers import SortedKeyList from xblock.core import XBlock from xblock.plugin import default_select @@ -719,7 +719,7 @@ def _save_assets_by_type(self, course_key, asset_metadata_list, course_assets, u )) continue if not import_only: - asset_md.update({'edited_by': user_id, 'edited_on': datetime.datetime.now(UTC)}) + asset_md.update({'edited_by': user_id, 'edited_on': datetime.datetime.now(ZoneInfo("UTC"))}) asset_type = asset_md.asset_id.asset_type all_assets = assets_by_type[asset_type] all_assets.insert_or_update(asset_md) @@ -862,7 +862,7 @@ def _block_matches(self, block, qualifiers): For substring matching: pass a regex object. For arbitrary function comparison such as date time comparison: - pass the function as in start=lambda x: x < datetime.datetime(2014, 1, 1, 0, tzinfo=pytz.UTC) + pass the function as in start=lambda x: x < datetime.datetime(2014, 1, 1, 0, tzinfo=ZoneInfo("UTC")) Args: block (dict, XBlock, or BlockData): either the BlockData (transformed from the db) -or- diff --git a/xmodule/modulestore/mongo/base.py b/xmodule/modulestore/mongo/base.py index 66a9938825dd..4282dbd02ae8 100644 --- a/xmodule/modulestore/mongo/base.py +++ b/xmodule/modulestore/mongo/base.py @@ -19,6 +19,7 @@ from datetime import datetime from importlib import import_module from uuid import uuid4 +from zoneinfo import ZoneInfo import pymongo from bson.son import SON @@ -26,7 +27,6 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator, LibraryLocator from path import Path as path -from pytz import UTC from xblock.exceptions import InvalidScopeError from xblock.fields import Reference, ReferenceList, ReferenceValueDict, Scope, ScopeIds from xblock.runtime import KvsFieldData @@ -252,7 +252,7 @@ def load_item(self, location, for_parent=None): # lint-amnesty, pylint: disable if raw_metadata.get('published_date'): block._edit_info['published_date'] = datetime( *raw_metadata.get('published_date')[0:6] - ).replace(tzinfo=UTC) + ).replace(tzinfo=ZoneInfo("UTC")) block._edit_info['published_by'] = raw_metadata.get('published_by') for wrapper in self.modulestore.xblock_field_data_wrappers: diff --git a/xmodule/modulestore/split_mongo/mongo_connection.py b/xmodule/modulestore/split_mongo/mongo_connection.py index cb6cca612ebc..bc8513b002e1 100644 --- a/xmodule/modulestore/split_mongo/mongo_connection.py +++ b/xmodule/modulestore/split_mongo/mongo_connection.py @@ -11,12 +11,12 @@ import zlib from contextlib import contextmanager from time import time +from zoneinfo import ZoneInfo from ccx_keys.locator import CCXLocator from django.core.cache import caches, InvalidCacheBackendError from django.db.transaction import TransactionManagementError import pymongo -import pytz # Import this just to export it from pymongo.errors import DuplicateKeyError # pylint: disable=unused-import from edx_django_utils import monitoring @@ -488,7 +488,7 @@ def insert_course_index(self, course_index, course_context=None): with TIMER.timer("insert_course_index", course_context): # Set last_update which is used to avoid collisions, unless a subclass already set it before calling super() if not self.with_mysql_subclass: - course_index['last_update'] = datetime.datetime.now(pytz.utc) + course_index['last_update'] = datetime.datetime.now(ZoneInfo("UTC")) # Insert the new index: self.course_index.insert_one(course_index) @@ -515,7 +515,7 @@ def update_course_index(self, course_index, from_index=None, course_context=None } # Set last_update which is used to avoid collisions, unless a subclass already set it before calling super() if not self.with_mysql_subclass: - course_index['last_update'] = datetime.datetime.now(pytz.utc) + course_index['last_update'] = datetime.datetime.now(ZoneInfo("UTC")) # Update the course index: result = self.course_index.replace_one(query, course_index, upsert=False,) if result.modified_count == 0: @@ -729,7 +729,7 @@ def insert_course_index(self, course_index, course_context=None): # pylint: dis # This is a relatively large hammer for the problem, but we mostly only use one course at a time. RequestCache(namespace="course_index_cache").clear() - course_index['last_update'] = datetime.datetime.now(pytz.utc) + course_index['last_update'] = datetime.datetime.now(ZoneInfo("UTC")) new_index = SplitModulestoreCourseIndex(**SplitModulestoreCourseIndex.fields_from_v1_schema(course_index)) new_index.save() # Also write to MongoDB, so we can switch back to using it if this new MySQL version doesn't work well. @@ -751,7 +751,7 @@ def update_course_index(self, course_index, from_index=None, course_context=None # This code is just copying the behavior of the existing MongoPersistenceBackend # See https://github.com/openedx/edx-platform/pull/5200 for context RequestCache(namespace="course_index_cache").clear() - course_index['last_update'] = datetime.datetime.now(pytz.utc) + course_index['last_update'] = datetime.datetime.now(ZoneInfo("UTC")) # Find the SplitModulestoreCourseIndex entry that we'll be updating: index_obj = SplitModulestoreCourseIndex.objects.get(objectid=course_index["_id"]) diff --git a/xmodule/modulestore/split_mongo/split.py b/xmodule/modulestore/split_mongo/split.py index 74539fabed90..4804e9244db9 100644 --- a/xmodule/modulestore/split_mongo/split.py +++ b/xmodule/modulestore/split_mongo/split.py @@ -60,6 +60,7 @@ import logging from collections import defaultdict from importlib import import_module +from zoneinfo import ZoneInfo from bson.objectid import ObjectId from ccx_keys.locator import CCXBlockUsageLocator, CCXLocator @@ -72,7 +73,6 @@ LocalId, ) from path import Path as path -from pytz import UTC from xblock.core import XBlock from xblock.fields import Reference, ReferenceList, ReferenceValueDict, Scope @@ -481,7 +481,7 @@ def version_structure(self, course_key, structure, user_id): new_structure['_id'] = ObjectId() new_structure['previous_version'] = structure['_id'] new_structure['edited_by'] = user_id - new_structure['edited_on'] = datetime.datetime.now(UTC) + new_structure['edited_on'] = datetime.datetime.now(ZoneInfo("UTC")) new_structure['schema_version'] = self.SCHEMA_VERSION # If we're in a bulk write, update the structure used there, and mark it as dirty @@ -499,7 +499,7 @@ def version_block(self, block_data, user_id, update_version): original_usage = block_data.edit_info.original_usage original_usage_version = block_data.edit_info.original_usage_version - block_data.edit_info.edited_on = datetime.datetime.now(UTC) + block_data.edit_info.edited_on = datetime.datetime.now(ZoneInfo("UTC")) block_data.edit_info.edited_by = user_id block_data.edit_info.previous_version = block_data.edit_info.update_version block_data.edit_info.update_version = update_version @@ -1499,7 +1499,7 @@ def create_definition_from_data(self, course_key, new_def_data, category, user_i "fields": new_def_data, "edit_info": { "edited_by": user_id, - "edited_on": datetime.datetime.now(UTC), + "edited_on": datetime.datetime.now(ZoneInfo("UTC")), "previous_version": None, "original_version": new_id, }, @@ -1547,7 +1547,7 @@ def _update_definition_from_data(self, course_key, old_definition, new_def_data, new_definition['_id'] = ObjectId() new_definition['fields'] = new_def_data new_definition['edit_info']['edited_by'] = user_id - new_definition['edit_info']['edited_on'] = datetime.datetime.now(UTC) + new_definition['edit_info']['edited_on'] = datetime.datetime.now(ZoneInfo("UTC")) # previous version id new_definition['edit_info']['previous_version'] = old_definition['_id'] new_definition['schema_version'] = self.SCHEMA_VERSION @@ -1886,7 +1886,7 @@ def _create_courselike( # lint-amnesty, pylint: disable=too-many-statements new_fields.update(definition_fields) definition_id = self._update_definition_from_data(locator, old_def, new_fields, user_id).definition_id root_block.definition = definition_id - root_block.edit_info.edited_on = datetime.datetime.now(UTC) + root_block.edit_info.edited_on = datetime.datetime.now(ZoneInfo("UTC")) root_block.edit_info.edited_by = user_id root_block.edit_info.previous_version = root_block.edit_info.update_version root_block.edit_info.update_version = new_id @@ -1906,7 +1906,7 @@ def _create_courselike( # lint-amnesty, pylint: disable=too-many-statements 'course': get_library_or_course_attribute(locator), 'run': locator.run, 'edited_by': user_id, - 'edited_on': datetime.datetime.now(UTC), + 'edited_on': datetime.datetime.now(ZoneInfo("UTC")), 'versions': versions_dict, 'schema_version': self.SCHEMA_VERSION, 'search_targets': search_targets or {}, @@ -2414,7 +2414,7 @@ def copy_from_template(self, source_keys, dest_usage, user_id, head_validation=T dest_info.edit_info.previous_version = dest_info.edit_info.update_version dest_info.edit_info.update_version = old_dest_structure_version dest_info.edit_info.edited_by = user_id - dest_info.edit_info.edited_on = datetime.datetime.now(UTC) + dest_info.edit_info.edited_on = datetime.datetime.now(ZoneInfo("UTC")) orphans = orig_descendants - new_descendants for orphan in orphans: @@ -2483,7 +2483,7 @@ def _copy_from_template( # from draft to published as part of publishing workflow. # Setting it to the source_block_info structure version here breaks split_draft's has_changes() method. new_block_info.edit_info.edited_by = user_id - new_block_info.edit_info.edited_on = datetime.datetime.now(UTC) + new_block_info.edit_info.edited_on = datetime.datetime.now(ZoneInfo("UTC")) new_block_info.edit_info.original_usage = str(usage_key.replace(branch=None, version_guid=None)) new_block_info.edit_info.original_usage_version = source_block_info.edit_info.update_version dest_structure['blocks'][new_block_key] = new_block_info @@ -2541,7 +2541,7 @@ def delete_item(self, usage_locator, user_id, force=False): # lint-amnesty, pyl for parent_block_key in parent_block_keys: parent_block = new_blocks[parent_block_key] parent_block.fields['children'].remove(block_key) - parent_block.edit_info.edited_on = datetime.datetime.now(UTC) + parent_block.edit_info.edited_on = datetime.datetime.now(ZoneInfo("UTC")) parent_block.edit_info.edited_by = user_id parent_block.edit_info.previous_version = parent_block.edit_info.update_version parent_block.edit_info.update_version = new_id @@ -3056,7 +3056,7 @@ def _new_structure(self, user_id, root_block_key, block_fields=None, definition_ 'previous_version': None, 'original_version': new_id, 'edited_by': user_id, - 'edited_on': datetime.datetime.now(UTC), + 'edited_on': datetime.datetime.now(ZoneInfo("UTC")), 'blocks': blocks, 'schema_version': self.SCHEMA_VERSION, } @@ -3122,7 +3122,7 @@ def _copy_subdag(self, user_id, destination_version, block_key, source_blocks, d destination_block.edit_info.previous_version = previous_version destination_block.edit_info.update_version = destination_version destination_block.edit_info.edited_by = user_id - destination_block.edit_info.edited_on = datetime.datetime.now(UTC) + destination_block.edit_info.edited_on = datetime.datetime.now(ZoneInfo("UTC")) else: destination_block = self._new_block( user_id, new_block.block_type, @@ -3196,7 +3196,7 @@ def _new_block(self, user_id, category, block_fields, definition_id, new_id, raw 'fields': block_fields, 'asides': asides, 'edit_info': { - 'edited_on': datetime.datetime.now(UTC), + 'edited_on': datetime.datetime.now(ZoneInfo("UTC")), 'edited_by': user_id, 'previous_version': None, 'update_version': new_id diff --git a/xmodule/modulestore/tests/factories.py b/xmodule/modulestore/tests/factories.py index 4467215fac4a..ca2f838238b0 100644 --- a/xmodule/modulestore/tests/factories.py +++ b/xmodule/modulestore/tests/factories.py @@ -12,9 +12,9 @@ from contextlib import contextmanager from uuid import uuid4 from unittest.mock import patch +from zoneinfo import ZoneInfo import pymongo.message -import pytz from factory import Factory, Sequence, lazy_attribute, lazy_attribute_sequence from factory.errors import CyclicDefinitionError from opaque_keys.edx.keys import UsageKey @@ -207,7 +207,7 @@ def _create(cls, target_class, **kwargs): 'graded': True, 'discussion_topics': {"General": {"id": "i4x-edX-toy-course-2012_Fall"}}, 'graceperiod': datetime.timedelta(days=2, seconds=21599), - 'start': datetime.datetime(2015, 7, 17, 12, tzinfo=pytz.utc), + 'start': datetime.datetime(2015, 7, 17, 12, tzinfo=ZoneInfo("UTC")), 'xml_attributes': {"filename": ["course/2012_Fall.xml", "course/2012_Fall.xml"]}, 'pdf_textbooks': [ { diff --git a/xmodule/modulestore/tests/test_assetstore.py b/xmodule/modulestore/tests/test_assetstore.py index aaa3da3ed6e0..50a8008b62a3 100644 --- a/xmodule/modulestore/tests/test_assetstore.py +++ b/xmodule/modulestore/tests/test_assetstore.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta import pytest import ddt -import pytz +from zoneinfo import ZoneInfo from django.test import TestCase from opaque_keys.edx.keys import CourseKey @@ -30,7 +30,7 @@ class AssetStoreTestData: """ Shared data for constructing test assets. """ - now = datetime.now(pytz.utc) + now = datetime.now(ZoneInfo("UTC")) user_id = 144 user_id_long = int(user_id) @@ -131,7 +131,7 @@ def _make_asset_metadata(self, asset_loc): """ Make a single test asset metadata. """ - now = datetime.now(pytz.utc) + now = datetime.now(ZoneInfo("UTC")) return AssetMetadata( asset_loc, internal_name='EKMND332DDBK', pathname='pictures/historical', contenttype='image/jpeg', @@ -322,13 +322,13 @@ def test_lock_unlock_assets(self, storebuilder): ('curr_version', 'v1.01'), ('prev_version', 'v1.0'), ('edited_by', 'Mork'), - ('edited_on', datetime(1969, 1, 1, tzinfo=pytz.utc)), + ('edited_on', datetime(1969, 1, 1, tzinfo=ZoneInfo("UTC"))), ) DISALLOWED_ATTRS = ( ('asset_id', 'IAmBogus'), ('created_by', 'Smith'), - ('created_on', datetime.now(pytz.utc)), + ('created_on', datetime.now(ZoneInfo("UTC"))), ) UNKNOWN_ATTRS = ( diff --git a/xmodule/modulestore/tests/test_mixed_modulestore.py b/xmodule/modulestore/tests/test_mixed_modulestore.py index d6dd042ce39f..8c74419a0b7b 100644 --- a/xmodule/modulestore/tests/test_mixed_modulestore.py +++ b/xmodule/modulestore/tests/test_mixed_modulestore.py @@ -13,6 +13,7 @@ from tempfile import mkdtemp from uuid import uuid4 from unittest.mock import Mock, call, patch +from zoneinfo import ZoneInfo import ddt from openedx_events.content_authoring.data import CourseData, XBlockData @@ -32,7 +33,6 @@ from django.conf import settings from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator, LibraryLocator # pylint: disable=unused-import -from pytz import UTC from web_fragments.fragment import Fragment from xblock.core import XBlockAside from xblock.fields import Scope, ScopeIds, String @@ -2043,7 +2043,7 @@ def test_create_item_populates_edited_info(self, default_ms): 'problem' ) assert self.user_id == block.edited_by - assert datetime.datetime.now(UTC) > block.edited_on + assert datetime.datetime.now(ZoneInfo("UTC")) > block.edited_on @ddt.data(ModuleStoreEnum.Type.split) def test_create_item_populates_subtree_edited_info(self, default_ms): @@ -2054,7 +2054,7 @@ def test_create_item_populates_subtree_edited_info(self, default_ms): 'problem' ) assert self.user_id == block.subtree_edited_by - assert datetime.datetime.now(UTC) > block.subtree_edited_on + assert datetime.datetime.now(ZoneInfo("UTC")) > block.subtree_edited_on # Split: wildcard search of draft (find) and split (mysql) @ddt.data((ModuleStoreEnum.Type.split, 1, 1, 0)) @@ -2202,7 +2202,7 @@ def check_node(location_key, after, before, edited_by, subtree_after, subtree_be block_id='test_html_no_change' ) - after_create = datetime.datetime.now(UTC) + after_create = datetime.datetime.now(ZoneInfo("UTC")) # Verify that all nodes were last edited in the past by create_user for block in [component, child, sibling]: check_node(block.location, None, after_create, self.user_id, None, after_create, self.user_id) @@ -2213,7 +2213,7 @@ def check_node(location_key, after, before, edited_by, subtree_after, subtree_be editing_user = self.user_id - 2 with self.store.bulk_operations(test_course.id): # TNL-764 bulk ops disabled ancestor updates component = self.store.update_item(component, editing_user) - after_edit = datetime.datetime.now(UTC) + after_edit = datetime.datetime.now(ZoneInfo("UTC")) check_node(component.location, after_create, after_edit, editing_user, after_create, after_edit, editing_user) # but child didn't change check_node(child.location, None, after_create, self.user_id, None, after_create, self.user_id) @@ -2223,7 +2223,7 @@ def check_node(location_key, after, before, edited_by, subtree_after, subtree_be child.display_name = 'Changed Display Name' self.store.update_item(child, user_id=editing_user) - after_edit = datetime.datetime.now(UTC) + after_edit = datetime.datetime.now(ZoneInfo("UTC")) # Verify that child was last edited between after_create and after_edit by edit_user check_node(child.location, after_create, after_edit, editing_user, after_create, after_edit, editing_user) @@ -2283,7 +2283,7 @@ def test_update_published_info(self, default_ms): ) # Store the current time, then publish - old_time = datetime.datetime.now(UTC) + old_time = datetime.datetime.now(ZoneInfo("UTC")) self.store.publish(component.location, publish_user) updated_component = self.store.get_item(component.location) diff --git a/xmodule/seq_block.py b/xmodule/seq_block.py index 6e19e04b2f1d..c374ca356290 100644 --- a/xmodule/seq_block.py +++ b/xmodule/seq_block.py @@ -11,11 +11,11 @@ from datetime import datetime from functools import reduce from django.conf import settings +from zoneinfo import ZoneInfo from edx_django_utils.monitoring import set_custom_attribute from lxml import etree from opaque_keys.edx.keys import UsageKey -from pytz import UTC from web_fragments.fragment import Fragment from xblock.completable import XBlockCompletionMode from xblock.core import XBlock @@ -393,7 +393,7 @@ def verify_current_content_visibility(cls, date, hide_after_date): return ( not date or not hide_after_date or - datetime.now(UTC) < date + datetime.now(ZoneInfo("UTC")) < date ) def gate_entire_sequence_if_it_is_a_timed_exam_and_contains_content_type_gated_problems(self): diff --git a/xmodule/tests/test_capa_block.py b/xmodule/tests/test_capa_block.py index ba6f68158e27..e92c876a930f 100644 --- a/xmodule/tests/test_capa_block.py +++ b/xmodule/tests/test_capa_block.py @@ -10,6 +10,7 @@ import textwrap import unittest from unittest.mock import DEFAULT, Mock, PropertyMock, patch +from zoneinfo import ZoneInfo import ddt import pytest @@ -21,7 +22,6 @@ from django.utils.encoding import smart_str from lxml import etree from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator -from pytz import UTC from webob.multidict import MultiDict from xblock.exceptions import NotFoundError from xblock.field_data import DictFieldData @@ -213,7 +213,7 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me def setUp(self): super().setUp() - now = datetime.datetime.now(UTC) + now = datetime.datetime.now(ZoneInfo("UTC")) day_delta = datetime.timedelta(days=1) self.yesterday_str = str(now - day_delta) self.today_str = str(now) @@ -731,11 +731,11 @@ def test_closed_for_archive(self, mock_course_end_date): # Utility to create a datetime object in the past def past_datetime(days): - return datetime.datetime.now(UTC) - datetime.timedelta(days=days) + return datetime.datetime.now(ZoneInfo("UTC")) - datetime.timedelta(days=days) # Utility to create a datetime object in the future def future_datetime(days): - return datetime.datetime.now(UTC) + datetime.timedelta(days=days) + return datetime.datetime.now(ZoneInfo("UTC")) + datetime.timedelta(days=days) block = CapaFactory.create(max_attempts="1", attempts="0") @@ -1258,7 +1258,7 @@ def test_submit_problem_queued(self): ) with multipatch as values: values["is_queued"].return_value = True - values["get_recentmost_queuetime"].return_value = datetime.datetime.now(UTC) + values["get_recentmost_queuetime"].return_value = datetime.datetime.now(ZoneInfo("UTC")) get_request_dict = {CapaFactory.input_key(): "3.14"} result = block.submit_problem(get_request_dict) diff --git a/xmodule/tests/test_course_block.py b/xmodule/tests/test_course_block.py index c88b519d708e..68b46bf3d810 100644 --- a/xmodule/tests/test_course_block.py +++ b/xmodule/tests/test_course_block.py @@ -6,6 +6,7 @@ from datetime import datetime, timedelta import sys from unittest.mock import Mock, patch +from zoneinfo import ZoneInfo import ddt from dateutil import parser @@ -14,7 +15,6 @@ from fs.memoryfs import MemoryFS from opaque_keys.edx.keys import CourseKey import pytest -from pytz import utc from xblock.runtime import DictKeyValueStore, KvsFieldData from openedx.core.lib.teams_config import TeamsConfig, DEFAULT_COURSE_RUN_MAX_TEAM_SIZE @@ -27,9 +27,9 @@ ORG = 'test_org' COURSE = 'test_course' -NOW = datetime.strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00').replace(tzinfo=utc) +NOW = datetime.strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00').replace(tzinfo=ZoneInfo("UTC")) -_TODAY = datetime.now(utc) +_TODAY = datetime.now(ZoneInfo("UTC")) _LAST_WEEK = _TODAY - timedelta(days=7) _NEXT_WEEK = _TODAY + timedelta(days=7) diff --git a/xmodule/tests/test_course_metadata_utils.py b/xmodule/tests/test_course_metadata_utils.py index ab38490112e2..c9d7739a305d 100644 --- a/xmodule/tests/test_course_metadata_utils.py +++ b/xmodule/tests/test_course_metadata_utils.py @@ -6,8 +6,8 @@ from collections import namedtuple from datetime import datetime, timedelta from unittest import TestCase +from zoneinfo import ZoneInfo -from pytz import utc import pytest from xmodule.block_metadata_utils import ( display_name_with_default, @@ -29,7 +29,7 @@ VersioningModulestoreBuilder ) -_TODAY = datetime.now(utc) +_TODAY = datetime.now(ZoneInfo("UTC")) _LAST_WEEK = _TODAY - timedelta(days=7) _NEXT_WEEK = _TODAY + timedelta(days=7) @@ -111,7 +111,7 @@ def noop_gettext(text): # lint-amnesty, pylint: disable=unused-variable """Dummy implementation of gettext, so we don't need Django.""" return text - test_datetime = datetime(1945, 2, 6, 4, 20, 00, tzinfo=utc) + test_datetime = datetime(1945, 2, 6, 4, 20, 00, tzinfo=ZoneInfo("UTC")) advertised_start_parsable = "2038-01-19 03:14:07" FunctionTest = namedtuple('FunctionTest', 'function scenarios') diff --git a/xmodule/tests/test_delay_between_attempts.py b/xmodule/tests/test_delay_between_attempts.py index c0315dca80fc..680765bf154f 100644 --- a/xmodule/tests/test_delay_between_attempts.py +++ b/xmodule/tests/test_delay_between_attempts.py @@ -12,10 +12,10 @@ import textwrap import unittest from unittest.mock import Mock +from zoneinfo import ZoneInfo import pytest from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator -from pytz import UTC from xblock.exceptions import NotFoundError from xblock.field_data import DictFieldData from xblock.fields import ScopeIds @@ -163,7 +163,7 @@ def test_no_wait_time(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime.now(UTC), + last_submission_time=datetime.datetime.now(ZoneInfo("UTC")), submission_wait_seconds=0 ) # Successfully submitted and answered @@ -176,7 +176,7 @@ def test_submit_quiz_in_rapid_succession(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime.now(UTC), + last_submission_time=datetime.datetime.now(ZoneInfo("UTC")), submission_wait_seconds=123 ) # You should get a dialog that tells you to wait @@ -189,9 +189,9 @@ def test_submit_quiz_too_soon(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=180, - considered_now=datetime.datetime(2013, 12, 6, 0, 18, 36, tzinfo=UTC) + considered_now=datetime.datetime(2013, 12, 6, 0, 18, 36, tzinfo=ZoneInfo("UTC")) ) # You should get a dialog that tells you to wait 2 minutes # Also, the number of attempts should not be incremented @@ -203,9 +203,9 @@ def test_submit_quiz_1_second_too_soon(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=180, - considered_now=datetime.datetime(2013, 12, 6, 0, 20, 35, tzinfo=UTC) + considered_now=datetime.datetime(2013, 12, 6, 0, 20, 35, tzinfo=ZoneInfo("UTC")) ) # You should get a dialog that tells you to wait 2 minutes # Also, the number of attempts should not be incremented @@ -217,9 +217,9 @@ def test_submit_quiz_as_soon_as_allowed(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=180, - considered_now=datetime.datetime(2013, 12, 6, 0, 20, 36, tzinfo=UTC) + considered_now=datetime.datetime(2013, 12, 6, 0, 20, 36, tzinfo=ZoneInfo("UTC")) ) # Successfully submitted and answered # Also, the number of attempts should increment by 1 @@ -231,9 +231,9 @@ def test_submit_quiz_after_delay_expired(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=180, - considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=UTC) + considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=ZoneInfo("UTC")) ) # Successfully submitted and answered # Also, the number of attempts should increment by 1 @@ -247,17 +247,17 @@ def test_still_cannot_submit_after_max_attempts(self): with pytest.raises(NotFoundError): (block, unused_result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=180, - considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=UTC) + considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=ZoneInfo("UTC")) ) # Now try it without the submit_problem (block, unused_result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=180, - considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=UTC), + considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=ZoneInfo("UTC")), skip_submit_problem=True ) # Expect that number of attempts NOT incremented @@ -268,9 +268,9 @@ def test_submit_quiz_with_long_delay(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=60 * 60 * 2, - considered_now=datetime.datetime(2013, 12, 6, 2, 15, 35, tzinfo=UTC) + considered_now=datetime.datetime(2013, 12, 6, 2, 15, 35, tzinfo=ZoneInfo("UTC")) ) # You should get a dialog that tells you to wait 2 minutes # Also, the number of attempts should not be incremented @@ -282,9 +282,9 @@ def test_submit_quiz_with_involved_pretty_print(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=60 * 60 * 2 + 63, - considered_now=datetime.datetime(2013, 12, 6, 1, 15, 40, tzinfo=UTC) + considered_now=datetime.datetime(2013, 12, 6, 1, 15, 40, tzinfo=ZoneInfo("UTC")) ) # You should get a dialog that tells you to wait 2 minutes # Also, the number of attempts should not be incremented @@ -296,9 +296,9 @@ def test_submit_quiz_with_nonplural_pretty_print(self): num_attempts = 1 (block, result) = self.create_and_check( num_attempts=num_attempts, - last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC), + last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")), submission_wait_seconds=60, - considered_now=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC) + considered_now=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=ZoneInfo("UTC")) ) # You should get a dialog that tells you to wait 2 minutes # Also, the number of attempts should not be incremented diff --git a/xmodule/tests/test_export.py b/xmodule/tests/test_export.py index e4a5cbcdae4f..7e201d4c1d97 100644 --- a/xmodule/tests/test_export.py +++ b/xmodule/tests/test_export.py @@ -9,11 +9,11 @@ from tempfile import mkdtemp from textwrap import dedent from unittest import mock +from zoneinfo import ZoneInfo import pytest import ddt import lxml.etree -import pytz from django.utils.translation import gettext_lazy from fs.osfs import OSFS from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator @@ -196,7 +196,7 @@ def test_encode_naive_datetime(self): assert '2013-05-03T10:20:30' == self.encoder.default(datetime(2013, 5, 3, 10, 20, 30)) def test_encode_utc_datetime(self): - assert '2013-05-03T10:20:30+00:00' == self.encoder.default(datetime(2013, 5, 3, 10, 20, 30, 0, pytz.UTC)) + assert '2013-05-03T10:20:30+00:00' == self.encoder.default(datetime(2013, 5, 3, 10, 20, 30, 0, ZoneInfo("UTC"))) assert '2013-05-03T10:20:30+04:00' == self.encoder.default(datetime(2013, 5, 3, 10, 20, 30, 0, self.offset_tz)) diff --git a/xmodule/tests/test_import.py b/xmodule/tests/test_import.py index 83aae2d66432..c92801ad22e9 100644 --- a/xmodule/tests/test_import.py +++ b/xmodule/tests/test_import.py @@ -4,6 +4,7 @@ import datetime from tempfile import mkdtemp from unittest.mock import Mock, patch +from zoneinfo import ZoneInfo import ddt from django.test import TestCase @@ -11,7 +12,6 @@ from lxml import etree from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator -from pytz import UTC from xblock.core import XBlock from xblock.fields import Date, Integer, Scope, String from xblock.runtime import DictKeyValueStore, KvsFieldData @@ -334,7 +334,7 @@ def course_block_no_inheritance_check(self, block): assert child.due is None # Check that the child hasn't started yet - assert datetime.datetime.now(UTC) <= child.start + assert datetime.datetime.now(ZoneInfo("UTC")) <= child.start def override_metadata_check(self, block, child, course_due, child_due): """ diff --git a/xmodule/tests/test_lti20_unit.py b/xmodule/tests/test_lti20_unit.py index 3243c834f365..3d1ddb0c56b2 100644 --- a/xmodule/tests/test_lti20_unit.py +++ b/xmodule/tests/test_lti20_unit.py @@ -5,8 +5,8 @@ import textwrap import unittest from unittest.mock import Mock +from zoneinfo import ZoneInfo -from pytz import UTC from xblock.field_data import DictFieldData from xmodule.lti_2_util import LTIError @@ -380,7 +380,7 @@ def test_lti20_request_handler_grade_past_due(self): Test that we get a 404 when accept_grades_past_due is False and it is past due """ self.setup_system_xblock_mocks_for_lti20_request_test() - self.xblock.due = datetime.datetime.now(UTC) + self.xblock.due = datetime.datetime.now(ZoneInfo("UTC")) self.xblock.accept_grades_past_due = False mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT) response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd") diff --git a/xmodule/tests/test_lti_unit.py b/xmodule/tests/test_lti_unit.py index ae980faf12f5..be314cab7221 100644 --- a/xmodule/tests/test_lti_unit.py +++ b/xmodule/tests/test_lti_unit.py @@ -6,6 +6,7 @@ from copy import copy from unittest.mock import Mock, PropertyMock, patch from urllib import parse +from zoneinfo import ZoneInfo import pytest @@ -14,7 +15,6 @@ from lxml import etree from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import BlockUsageLocator -from pytz import UTC from webob.request import Request from xblock.field_data import DictFieldData from xblock.fields import ScopeIds, Timedelta @@ -198,7 +198,7 @@ def test_grade_past_due(self): Should fail if we do not accept past due grades, and it is past due. """ self.xblock.accept_grades_past_due = False - self.xblock.due = datetime.datetime.now(UTC) + self.xblock.due = datetime.datetime.now(ZoneInfo("UTC")) self.xblock.graceperiod = Timedelta().from_json("0 seconds") request = Request(self.environ) request.body = self.get_request_body() diff --git a/xmodule/tests/test_vertical.py b/xmodule/tests/test_vertical.py index a3c596d253ac..f1dfc9b4f8e0 100644 --- a/xmodule/tests/test_vertical.py +++ b/xmodule/tests/test_vertical.py @@ -9,9 +9,9 @@ from collections import namedtuple from datetime import datetime, timedelta from unittest.mock import Mock, patch +from zoneinfo import ZoneInfo import ddt -import pytz from django.contrib.auth.models import AnonymousUser from django.test import override_settings from fs.memoryfs import MemoryFS @@ -213,7 +213,7 @@ def test_render_student_preview_view(self, context, view, completion_value, days Test the rendering of the student and public view. """ self.course.runtime._services['bookmarks'] = Mock() - now = datetime.now(pytz.UTC) + now = datetime.now(ZoneInfo("UTC")) self.vertical.due = now + timedelta(days=days) if view == STUDENT_VIEW: self.course.runtime._services['user'] = StubUserService(user=Mock(username=self.username)) @@ -253,7 +253,7 @@ def test_render_problem_without_score(self, has_score): self.course.runtime._services['user'] = StubUserService(user=Mock()) self.course.runtime._services['completion'] = StubCompletionService(enabled=True, completion_value=0) - now = datetime.now(pytz.UTC) + now = datetime.now(ZoneInfo("UTC")) self.vertical.due = now + timedelta(days=-1) self.problem_block.has_score = has_score @@ -273,7 +273,7 @@ def test_render_access_denied_blocks(self, node_has_access_error, child_has_acce """ Tests access denied blocks are not rendered when hide_access_error_blocks is True """ self.course.runtime._services['bookmarks'] = Mock() self.course.runtime._services['user'] = StubUserService(user=Mock()) - self.vertical.due = datetime.now(pytz.UTC) + timedelta(days=-1) + self.vertical.due = datetime.now(ZoneInfo("UTC")) + timedelta(days=-1) self.problem_block.has_access_error = node_has_access_error self.nested_problem_block.has_access_error = child_has_access_error diff --git a/xmodule/vertical_block.py b/xmodule/vertical_block.py index 26aba7c50c7b..789bebd95746 100644 --- a/xmodule/vertical_block.py +++ b/xmodule/vertical_block.py @@ -7,8 +7,8 @@ from copy import copy from datetime import datetime from functools import reduce +from zoneinfo import ZoneInfo -import pytz from django.conf import settings from lxml import etree from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted @@ -137,7 +137,7 @@ def _student_or_public_view(self, context, view): # lint-amnesty, pylint: disab }) completed = self.is_block_complete_for_assignments(completion_service) - past_due = completed is False and self.due and self.due < datetime.now(pytz.UTC) + past_due = completed is False and self.due and self.due < datetime.now(ZoneInfo("UTC")) cta_service = self.runtime.service(self, 'call_to_action') vertical_banner_ctas = cta_service.get_ctas(self, 'vertical_banner', completed) if cta_service else [] diff --git a/xmodule/video_block/bumper_utils.py b/xmodule/video_block/bumper_utils.py index 297e0b7fbf32..64b02e915573 100644 --- a/xmodule/video_block/bumper_utils.py +++ b/xmodule/video_block/bumper_utils.py @@ -8,8 +8,8 @@ import logging from collections import OrderedDict from datetime import datetime, timedelta +from zoneinfo import ZoneInfo -import pytz from django.conf import settings from .video_utils import set_query_parameter @@ -48,7 +48,7 @@ def is_bumper_enabled(video): bool. """ bumper_last_view_date = getattr(video, 'bumper_last_view_date', None) - utc_now = datetime.utcnow().replace(tzinfo=pytz.utc) + utc_now = datetime.now(ZoneInfo("UTC")) periodicity = settings.FEATURES.get('SHOW_BUMPER_PERIODICITY', 0) has_viewed = any([ video.bumper_do_not_show_again,