Fixed #32366 -- Updated datetime module usage to recommended approach.

- Replaced datetime.utcnow() with datetime.now().
- Replaced datetime.utcfromtimestamp() with datetime.fromtimestamp().
- Replaced datetime.utctimetuple() with datetime.timetuple().
- Replaced calendar.timegm() and datetime.utctimetuple() with datetime.timestamp().
This commit is contained in:
Nick Pope 2021-05-07 10:42:59 +01:00 committed by Carlton Gibson
parent 69ffb1acf3
commit d06c5b3581
17 changed files with 75 additions and 77 deletions

View File

@ -59,10 +59,8 @@ class SessionStore(SessionBase):
Return the modification time of the file storing the session's content. Return the modification time of the file storing the session's content.
""" """
modification = os.stat(self._key_to_file()).st_mtime modification = os.stat(self._key_to_file()).st_mtime
if settings.USE_TZ: tz = timezone.utc if settings.USE_TZ else None
modification = datetime.datetime.utcfromtimestamp(modification) return datetime.datetime.fromtimestamp(modification, tz=tz)
return modification.replace(tzinfo=timezone.utc)
return datetime.datetime.fromtimestamp(modification)
def _expiry_date(self, session_data): def _expiry_date(self, session_data):
""" """

View File

@ -1,5 +1,4 @@
import datetime import datetime
from calendar import timegm
from functools import wraps from functools import wraps
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
@ -7,6 +6,7 @@ from django.core.paginator import EmptyPage, PageNotAnInteger
from django.http import Http404 from django.http import Http404
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from django.utils.http import http_date from django.utils.http import http_date
@ -72,10 +72,10 @@ def sitemap(request, sitemaps, section=None,
if all_sites_lastmod: if all_sites_lastmod:
site_lastmod = getattr(site, 'latest_lastmod', None) site_lastmod = getattr(site, 'latest_lastmod', None)
if site_lastmod is not None: if site_lastmod is not None:
site_lastmod = ( if not isinstance(site_lastmod, datetime.datetime):
site_lastmod.utctimetuple() if isinstance(site_lastmod, datetime.datetime) site_lastmod = datetime.datetime.combine(site_lastmod, datetime.time.min)
else site_lastmod.timetuple() if timezone.is_naive(site_lastmod):
) site_lastmod = timezone.make_aware(site_lastmod, timezone.utc)
lastmod = site_lastmod if lastmod is None else max(lastmod, site_lastmod) lastmod = site_lastmod if lastmod is None else max(lastmod, site_lastmod)
else: else:
all_sites_lastmod = False all_sites_lastmod = False
@ -88,5 +88,5 @@ def sitemap(request, sitemaps, section=None,
if all_sites_lastmod and lastmod is not None: if all_sites_lastmod and lastmod is not None:
# if lastmod is defined for all sites, set header so as # if lastmod is defined for all sites, set header so as
# ConditionalGetMiddleware is able to send 304 NOT MODIFIED # ConditionalGetMiddleware is able to send 304 NOT MODIFIED
response.headers['Last-Modified'] = http_date(timegm(lastmod)) response.headers['Last-Modified'] = http_date(lastmod.timestamp())
return response return response

View File

@ -1,5 +1,3 @@
from calendar import timegm
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
@ -42,8 +40,7 @@ class Feed:
if hasattr(self, 'item_pubdate') or hasattr(self, 'item_updateddate'): if hasattr(self, 'item_pubdate') or hasattr(self, 'item_updateddate'):
# if item_pubdate or item_updateddate is defined for the feed, set # if item_pubdate or item_updateddate is defined for the feed, set
# header so as ConditionalGetMiddleware is able to send 304 NOT MODIFIED # header so as ConditionalGetMiddleware is able to send 304 NOT MODIFIED
response.headers['Last-Modified'] = http_date( response.headers['Last-Modified'] = http_date(feedgen.latest_post_date().timestamp())
timegm(feedgen.latest_post_date().utctimetuple()))
feedgen.write(response, 'utf-8') feedgen.write(response, 'utf-8')
return response return response

View File

@ -123,10 +123,9 @@ class DatabaseCache(BaseDatabaseCache):
now = now.replace(microsecond=0) now = now.replace(microsecond=0)
if timeout is None: if timeout is None:
exp = datetime.max exp = datetime.max
elif settings.USE_TZ:
exp = datetime.utcfromtimestamp(timeout)
else: else:
exp = datetime.fromtimestamp(timeout) tz = timezone.utc if settings.USE_TZ else None
exp = datetime.fromtimestamp(timeout, tz=tz)
exp = exp.replace(microsecond=0) exp = exp.replace(microsecond=0)
if num > self._max_entries: if num > self._max_entries:
self._cull(db, cursor, now) self._cull(db, cursor, now)
@ -235,11 +234,7 @@ class DatabaseCache(BaseDatabaseCache):
connection = connections[db] connection = connections[db]
quote_name = connection.ops.quote_name quote_name = connection.ops.quote_name
if settings.USE_TZ: now = timezone.now().replace(microsecond=0, tzinfo=None)
now = datetime.utcnow()
else:
now = datetime.now()
now = now.replace(microsecond=0)
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute( cursor.execute(

View File

@ -347,11 +347,8 @@ class FileSystemStorage(Storage):
If timezone support is enabled, make an aware datetime object in UTC; If timezone support is enabled, make an aware datetime object in UTC;
otherwise make a naive one in the local timezone. otherwise make a naive one in the local timezone.
""" """
if settings.USE_TZ: tz = timezone.utc if settings.USE_TZ else None
# Safe to use .replace() because UTC doesn't have DST return datetime.fromtimestamp(ts, tz=tz)
return datetime.utcfromtimestamp(ts).replace(tzinfo=timezone.utc)
else:
return datetime.fromtimestamp(ts)
def get_accessed_time(self, name): def get_accessed_time(self, name):
return self._datetime_from_timestamp(os.path.getatime(self.path(name))) return self._datetime_from_timestamp(os.path.getatime(self.path(name)))

View File

@ -203,9 +203,9 @@ class HttpResponseBase:
self.cookies[key] = value self.cookies[key] = value
if expires is not None: if expires is not None:
if isinstance(expires, datetime.datetime): if isinstance(expires, datetime.datetime):
if timezone.is_aware(expires): if timezone.is_naive(expires):
expires = timezone.make_naive(expires, timezone.utc) expires = timezone.make_aware(expires, timezone.utc)
delta = expires - expires.utcnow() delta = expires - datetime.datetime.now(tz=timezone.utc)
# Add one second so the date matches exactly (a fraction of # Add one second so the date matches exactly (a fraction of
# time gets lost between converting to a timedelta and # time gets lost between converting to a timedelta and
# then the date string). # then the date string).

View File

@ -12,7 +12,6 @@ Usage:
""" """
import calendar import calendar
import datetime import datetime
import time
from email.utils import format_datetime as format_datetime_rfc5322 from email.utils import format_datetime as format_datetime_rfc5322
from django.utils.dates import ( from django.utils.dates import (
@ -20,7 +19,7 @@ from django.utils.dates import (
) )
from django.utils.regex_helper import _lazy_re_compile from django.utils.regex_helper import _lazy_re_compile
from django.utils.timezone import ( from django.utils.timezone import (
_datetime_ambiguous_or_imaginary, get_default_timezone, is_aware, is_naive, _datetime_ambiguous_or_imaginary, get_default_timezone, is_naive,
make_aware, make_aware,
) )
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -295,10 +294,10 @@ class DateFormat(TimeFormat):
def U(self): def U(self):
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)" "Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
if isinstance(self.data, datetime.datetime) and is_aware(self.data): value = self.data
return int(calendar.timegm(self.data.utctimetuple())) if not isinstance(value, datetime.datetime):
else: value = datetime.datetime.combine(value, datetime.time.min)
return int(time.mktime(self.data.timetuple())) return int(value.timestamp())
def w(self): def w(self):
"Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)" "Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)"

View File

@ -172,8 +172,7 @@ class SyndicationFeed:
if latest_date is None or item_date > latest_date: if latest_date is None or item_date > latest_date:
latest_date = item_date latest_date = item_date
# datetime.now(tz=utc) is slower, as documented in django.utils.timezone.now return latest_date or datetime.datetime.now(tz=utc)
return latest_date or datetime.datetime.utcnow().replace(tzinfo=utc)
class Enclosure: class Enclosure:

View File

@ -1,5 +1,4 @@
import base64 import base64
import calendar
import datetime import datetime
import re import re
import unicodedata import unicodedata
@ -112,9 +111,10 @@ def parse_http_date(date):
else: else:
raise ValueError("%r is not in a valid HTTP date format" % date) raise ValueError("%r is not in a valid HTTP date format" % date)
try: try:
tz = datetime.timezone.utc
year = int(m['year']) year = int(m['year'])
if year < 100: if year < 100:
current_year = datetime.datetime.utcnow().year current_year = datetime.datetime.now(tz=tz).year
current_century = current_year - (current_year % 100) current_century = current_year - (current_year % 100)
if year - (current_year % 100) > 50: if year - (current_year % 100) > 50:
# year that appears to be more than 50 years in the future are # year that appears to be more than 50 years in the future are
@ -127,8 +127,8 @@ def parse_http_date(date):
hour = int(m['hour']) hour = int(m['hour'])
min = int(m['min']) min = int(m['min'])
sec = int(m['sec']) sec = int(m['sec'])
result = datetime.datetime(year, month, day, hour, min, sec) result = datetime.datetime(year, month, day, hour, min, sec, tzinfo=tz)
return calendar.timegm(result.utctimetuple()) return int(result.timestamp())
except Exception as exc: except Exception as exc:
raise ValueError("%r is not a valid date" % date) from exc raise ValueError("%r is not a valid date" % date) from exc

View File

@ -194,11 +194,7 @@ def now():
""" """
Return an aware or naive datetime.datetime, depending on settings.USE_TZ. Return an aware or naive datetime.datetime, depending on settings.USE_TZ.
""" """
if settings.USE_TZ: return datetime.now(tz=utc if settings.USE_TZ else None)
# timeit shows that datetime.now(tz=utc) is 24% slower
return datetime.utcnow().replace(tzinfo=utc)
else:
return datetime.now()
# By design, these four functions don't perform any checks on their arguments. # By design, these four functions don't perform any checks on their arguments.

View File

@ -89,8 +89,9 @@ def get_git_changeset():
shell=True, cwd=repo_dir, universal_newlines=True, shell=True, cwd=repo_dir, universal_newlines=True,
) )
timestamp = git_log.stdout timestamp = git_log.stdout
tz = datetime.timezone.utc
try: try:
timestamp = datetime.datetime.utcfromtimestamp(int(timestamp)) timestamp = datetime.datetime.fromtimestamp(int(timestamp), tz=tz)
except ValueError: except ValueError:
return None return None
return timestamp.strftime('%Y%m%d%H%M%S') return timestamp.strftime('%Y%m%d%H%M%S')

View File

@ -2,11 +2,11 @@
Decorators for views based on HTTP headers. Decorators for views based on HTTP headers.
""" """
from calendar import timegm
from functools import wraps from functools import wraps
from django.http import HttpResponseNotAllowed from django.http import HttpResponseNotAllowed
from django.middleware.http import ConditionalGetMiddleware from django.middleware.http import ConditionalGetMiddleware
from django.utils import timezone
from django.utils.cache import get_conditional_response from django.utils.cache import get_conditional_response
from django.utils.decorators import decorator_from_middleware from django.utils.decorators import decorator_from_middleware
from django.utils.http import http_date, quote_etag from django.utils.http import http_date, quote_etag
@ -82,7 +82,9 @@ def condition(etag_func=None, last_modified_func=None):
if last_modified_func: if last_modified_func:
dt = last_modified_func(request, *args, **kwargs) dt = last_modified_func(request, *args, **kwargs)
if dt: if dt:
return timegm(dt.utctimetuple()) if not timezone.is_aware(dt):
dt = timezone.make_aware(dt, timezone.utc)
return int(dt.timestamp())
# The value from etag_func() could be quoted or unquoted. # The value from etag_func() could be quoted or unquoted.
res_etag = etag_func(request, *args, **kwargs) if etag_func else None res_etag = etag_func(request, *args, **kwargs) if etag_func else None

View File

@ -92,7 +92,7 @@ class MultiColumnFKTests(TestCase):
def test_reverse_query_filters_correctly(self): def test_reverse_query_filters_correctly(self):
timemark = datetime.datetime.utcnow() timemark = datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None)
timedelta = datetime.timedelta(days=1) timedelta = datetime.timedelta(days=1)
# Creating a to valid memberships # Creating a to valid memberships

View File

@ -479,8 +479,8 @@ class WriterTests(SimpleTestCase):
self.serialize_round_trip(models.SET(42)) self.serialize_round_trip(models.SET(42))
def test_serialize_datetime(self): def test_serialize_datetime(self):
self.assertSerializedEqual(datetime.datetime.utcnow()) self.assertSerializedEqual(datetime.datetime.now())
self.assertSerializedEqual(datetime.datetime.utcnow) self.assertSerializedEqual(datetime.datetime.now)
self.assertSerializedEqual(datetime.datetime.today()) self.assertSerializedEqual(datetime.datetime.today())
self.assertSerializedEqual(datetime.datetime.today) self.assertSerializedEqual(datetime.datetime.today)
self.assertSerializedEqual(datetime.date.today()) self.assertSerializedEqual(datetime.date.today())
@ -662,8 +662,8 @@ class WriterTests(SimpleTestCase):
Tests serializing a simple migration. Tests serializing a simple migration.
""" """
fields = { fields = {
'charfield': models.DateTimeField(default=datetime.datetime.utcnow), 'charfield': models.DateTimeField(default=datetime.datetime.now),
'datetimefield': models.DateTimeField(default=datetime.datetime.utcnow), 'datetimefield': models.DateTimeField(default=datetime.datetime.now),
} }
options = { options = {

View File

@ -19,7 +19,7 @@ class SetCookieTests(SimpleTestCase):
# evaluated expiration time and the time evaluated in set_cookie(). If # evaluated expiration time and the time evaluated in set_cookie(). If
# this difference doesn't exist, the cookie time will be 1 second # this difference doesn't exist, the cookie time will be 1 second
# larger. The sleep guarantees that there will be a time difference. # larger. The sleep guarantees that there will be a time difference.
expires = datetime.utcnow() + timedelta(seconds=10) expires = datetime.now(tz=utc).replace(tzinfo=None) + timedelta(seconds=10)
time.sleep(0.001) time.sleep(0.001)
response.set_cookie('datetime', expires=expires) response.set_cookie('datetime', expires=expires)
datetime_cookie = response.cookies['datetime'] datetime_cookie = response.cookies['datetime']
@ -28,7 +28,7 @@ class SetCookieTests(SimpleTestCase):
def test_aware_expiration(self): def test_aware_expiration(self):
"""set_cookie() accepts an aware datetime as expiration time.""" """set_cookie() accepts an aware datetime as expiration time."""
response = HttpResponse() response = HttpResponse()
expires = (datetime.utcnow() + timedelta(seconds=10)).replace(tzinfo=utc) expires = datetime.now(tz=utc) + timedelta(seconds=10)
time.sleep(0.001) time.sleep(0.001)
response.set_cookie('datetime', expires=expires) response.set_cookie('datetime', expires=expires)
datetime_cookie = response.cookies['datetime'] datetime_cookie = response.cookies['datetime']

View File

@ -54,8 +54,8 @@ class DateFormatTests(SimpleTestCase):
self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt) self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt)
# astimezone() is safe here because the target timezone doesn't have DST # astimezone() is safe here because the target timezone doesn't have DST
self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt.astimezone(ltz).replace(tzinfo=None)) self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt.astimezone(ltz).replace(tzinfo=None))
self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple(), dt.utctimetuple()) self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), tz).timetuple(), dt.astimezone(tz).timetuple())
self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple()) self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz).timetuple(), dt.astimezone(ltz).timetuple())
def test_epoch(self): def test_epoch(self):
udt = datetime(1970, 1, 1, tzinfo=utc) udt = datetime(1970, 1, 1, tzinfo=utc)

View File

@ -1,6 +1,6 @@
import platform import platform
import unittest import unittest
from datetime import datetime from datetime import datetime, timezone
from unittest import mock from unittest import mock
from django.test import SimpleTestCase from django.test import SimpleTestCase
@ -288,38 +288,52 @@ class HttpDateProcessingTests(unittest.TestCase):
def test_parsing_rfc1123(self): def test_parsing_rfc1123(self):
parsed = parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT') parsed = parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT')
self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37)) self.assertEqual(
datetime.fromtimestamp(parsed, timezone.utc),
datetime(1994, 11, 6, 8, 49, 37, tzinfo=timezone.utc),
)
@unittest.skipIf(platform.architecture()[0] == '32bit', 'The Year 2038 problem.') @unittest.skipIf(platform.architecture()[0] == '32bit', 'The Year 2038 problem.')
@mock.patch('django.utils.http.datetime.datetime') @mock.patch('django.utils.http.datetime.datetime')
def test_parsing_rfc850(self, mocked_datetime): def test_parsing_rfc850(self, mocked_datetime):
mocked_datetime.side_effect = datetime mocked_datetime.side_effect = datetime
mocked_datetime.utcnow = mock.Mock() mocked_datetime.now = mock.Mock()
utcnow_1 = datetime(2019, 11, 6, 8, 49, 37) now_1 = datetime(2019, 11, 6, 8, 49, 37, tzinfo=timezone.utc)
utcnow_2 = datetime(2020, 11, 6, 8, 49, 37) now_2 = datetime(2020, 11, 6, 8, 49, 37, tzinfo=timezone.utc)
utcnow_3 = datetime(2048, 11, 6, 8, 49, 37) now_3 = datetime(2048, 11, 6, 8, 49, 37, tzinfo=timezone.utc)
tests = ( tests = (
(utcnow_1, 'Tuesday, 31-Dec-69 08:49:37 GMT', datetime(2069, 12, 31, 8, 49, 37)), (now_1, 'Tuesday, 31-Dec-69 08:49:37 GMT', datetime(2069, 12, 31, 8, 49, 37, tzinfo=timezone.utc)),
(utcnow_1, 'Tuesday, 10-Nov-70 08:49:37 GMT', datetime(1970, 11, 10, 8, 49, 37)), (now_1, 'Tuesday, 10-Nov-70 08:49:37 GMT', datetime(1970, 11, 10, 8, 49, 37, tzinfo=timezone.utc)),
(utcnow_1, 'Sunday, 06-Nov-94 08:49:37 GMT', datetime(1994, 11, 6, 8, 49, 37)), (now_1, 'Sunday, 06-Nov-94 08:49:37 GMT', datetime(1994, 11, 6, 8, 49, 37, tzinfo=timezone.utc)),
(utcnow_2, 'Wednesday, 31-Dec-70 08:49:37 GMT', datetime(2070, 12, 31, 8, 49, 37)), (now_2, 'Wednesday, 31-Dec-70 08:49:37 GMT', datetime(2070, 12, 31, 8, 49, 37, tzinfo=timezone.utc)),
(utcnow_2, 'Friday, 31-Dec-71 08:49:37 GMT', datetime(1971, 12, 31, 8, 49, 37)), (now_2, 'Friday, 31-Dec-71 08:49:37 GMT', datetime(1971, 12, 31, 8, 49, 37, tzinfo=timezone.utc)),
(utcnow_3, 'Sunday, 31-Dec-00 08:49:37 GMT', datetime(2000, 12, 31, 8, 49, 37)), (now_3, 'Sunday, 31-Dec-00 08:49:37 GMT', datetime(2000, 12, 31, 8, 49, 37, tzinfo=timezone.utc)),
(utcnow_3, 'Friday, 31-Dec-99 08:49:37 GMT', datetime(1999, 12, 31, 8, 49, 37)), (now_3, 'Friday, 31-Dec-99 08:49:37 GMT', datetime(1999, 12, 31, 8, 49, 37, tzinfo=timezone.utc)),
) )
for utcnow, rfc850str, expected_date in tests: for now, rfc850str, expected_date in tests:
with self.subTest(rfc850str=rfc850str): with self.subTest(rfc850str=rfc850str):
mocked_datetime.utcnow.return_value = utcnow mocked_datetime.now.return_value = now
parsed = parse_http_date(rfc850str) parsed = parse_http_date(rfc850str)
self.assertEqual(datetime.utcfromtimestamp(parsed), expected_date) mocked_datetime.now.assert_called_once_with(tz=timezone.utc)
self.assertEqual(
datetime.fromtimestamp(parsed, timezone.utc),
expected_date,
)
mocked_datetime.reset_mock()
def test_parsing_asctime(self): def test_parsing_asctime(self):
parsed = parse_http_date('Sun Nov 6 08:49:37 1994') parsed = parse_http_date('Sun Nov 6 08:49:37 1994')
self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37)) self.assertEqual(
datetime.fromtimestamp(parsed, timezone.utc),
datetime(1994, 11, 6, 8, 49, 37, tzinfo=timezone.utc),
)
def test_parsing_year_less_than_70(self): def test_parsing_year_less_than_70(self):
parsed = parse_http_date('Sun Nov 6 08:49:37 0037') parsed = parse_http_date('Sun Nov 6 08:49:37 0037')
self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(2037, 11, 6, 8, 49, 37)) self.assertEqual(
datetime.fromtimestamp(parsed, timezone.utc),
datetime(2037, 11, 6, 8, 49, 37, tzinfo=timezone.utc),
)
class EscapeLeadingSlashesTests(unittest.TestCase): class EscapeLeadingSlashesTests(unittest.TestCase):