mirror of
https://github.com/ansible/awx.git
synced 2026-03-05 02:31:03 -03:30
Replace pytz with standard library timezone (#16197)
Refactored code to use Python's built-in datetime.timezone and zoneinfo instead of pytz for timezone handling. This modernizes the codebase and removes the dependency on pytz, aligning with current best practices for timezone-aware datetime objects.
This commit is contained in:
@@ -50,7 +50,7 @@ from rest_framework_yaml.renderers import YAMLRenderer
|
|||||||
# ansi2html
|
# ansi2html
|
||||||
from ansi2html import Ansi2HTMLConverter
|
from ansi2html import Ansi2HTMLConverter
|
||||||
|
|
||||||
import pytz
|
from datetime import timezone as dt_timezone
|
||||||
from wsgiref.util import FileWrapper
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
# django-ansible-base
|
# django-ansible-base
|
||||||
@@ -648,7 +648,7 @@ class SchedulePreview(GenericAPIView):
|
|||||||
continue
|
continue
|
||||||
schedule.append(event)
|
schedule.append(event)
|
||||||
|
|
||||||
return Response({'local': schedule, 'utc': [s.astimezone(pytz.utc) for s in schedule]})
|
return Response({'local': schedule, 'utc': [s.astimezone(dt_timezone.utc) for s in schedule]})
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
# Python
|
# Python
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import pytz
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ def partition_name_dt(part_name):
|
|||||||
if not m:
|
if not m:
|
||||||
return m
|
return m
|
||||||
dt_str = f"{m.group(3)}_{m.group(4)}"
|
dt_str = f"{m.group(3)}_{m.group(4)}"
|
||||||
dt = datetime.datetime.strptime(dt_str, '%Y%m%d_%H').replace(tzinfo=pytz.UTC)
|
dt = datetime.datetime.strptime(dt_str, '%Y%m%d_%H').replace(tzinfo=datetime.timezone.utc)
|
||||||
return dt
|
return dt
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ from awx.main.models.jobs import LaunchTimeConfig
|
|||||||
from awx.main.utils import ignore_inventory_computed_fields
|
from awx.main.utils import ignore_inventory_computed_fields
|
||||||
from awx.main.consumers import emit_channel_notification
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.schedule')
|
logger = logging.getLogger('awx.main.models.schedule')
|
||||||
|
|
||||||
@@ -255,7 +253,7 @@ class Schedule(PrimordialModel, LaunchTimeConfig):
|
|||||||
|
|
||||||
# Coerce the datetime to UTC and format it as a string w/ Zulu format
|
# Coerce the datetime to UTC and format it as a string w/ Zulu format
|
||||||
# utc_until = UNTIL=20200601T220000Z
|
# utc_until = UNTIL=20200601T220000Z
|
||||||
utc_until = 'UNTIL=' + localized_until.astimezone(pytz.utc).strftime('%Y%m%dT%H%M%SZ')
|
utc_until = 'UNTIL=' + localized_until.astimezone(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')
|
||||||
|
|
||||||
# rule was: DTSTART;TZID=America/New_York:20200601T120000 RRULE:...;UNTIL=20200601T170000
|
# rule was: DTSTART;TZID=America/New_York:20200601T120000 RRULE:...;UNTIL=20200601T170000
|
||||||
# rule is now: DTSTART;TZID=America/New_York:20200601T120000 RRULE:...;UNTIL=20200601T220000Z
|
# rule is now: DTSTART;TZID=America/New_York:20200601T120000 RRULE:...;UNTIL=20200601T220000Z
|
||||||
@@ -310,7 +308,7 @@ class Schedule(PrimordialModel, LaunchTimeConfig):
|
|||||||
# If we made it this far we should have an end date and can ask the ruleset what the last date is
|
# If we made it this far we should have an end date and can ask the ruleset what the last date is
|
||||||
# However, if the until/count is before dtstart we will get an IndexError when trying to get [-1]
|
# However, if the until/count is before dtstart we will get an IndexError when trying to get [-1]
|
||||||
try:
|
try:
|
||||||
return ruleset[-1].astimezone(pytz.utc)
|
return ruleset[-1].astimezone(datetime.timezone.utc)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -328,14 +326,14 @@ class Schedule(PrimordialModel, LaunchTimeConfig):
|
|||||||
if not datetime_exists(next_run_actual):
|
if not datetime_exists(next_run_actual):
|
||||||
# skip imaginary dates, like 2:30 on DST boundaries
|
# skip imaginary dates, like 2:30 on DST boundaries
|
||||||
next_run_actual = future_rs.after(next_run_actual)
|
next_run_actual = future_rs.after(next_run_actual)
|
||||||
next_run_actual = next_run_actual.astimezone(pytz.utc)
|
next_run_actual = next_run_actual.astimezone(datetime.timezone.utc)
|
||||||
else:
|
else:
|
||||||
next_run_actual = None
|
next_run_actual = None
|
||||||
|
|
||||||
self.next_run = next_run_actual
|
self.next_run = next_run_actual
|
||||||
if not self.dtstart:
|
if not self.dtstart:
|
||||||
try:
|
try:
|
||||||
self.dtstart = future_rs[0].astimezone(pytz.utc)
|
self.dtstart = future_rs[0].astimezone(datetime.timezone.utc)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.dtstart = None
|
self.dtstart = None
|
||||||
self.dtend = Schedule.get_end_date(future_rs)
|
self.dtend = Schedule.get_end_date(future_rs)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
import pytest
|
import pytest
|
||||||
import pytz
|
|
||||||
|
|
||||||
from awx.main.models import JobTemplate, Schedule, ActivityStream
|
from awx.main.models import JobTemplate, Schedule, ActivityStream
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ class TestComputedFields:
|
|||||||
with self.assert_no_unwanted_stuff(s):
|
with self.assert_no_unwanted_stuff(s):
|
||||||
# force update of next_run, as if schedule re-calculation had not happened
|
# force update of next_run, as if schedule re-calculation had not happened
|
||||||
# since this time
|
# since this time
|
||||||
old_next_run = datetime(2009, 3, 13, tzinfo=pytz.utc)
|
old_next_run = datetime(2009, 3, 13, tzinfo=timezone.utc)
|
||||||
Schedule.objects.filter(pk=s.pk).update(next_run=old_next_run)
|
Schedule.objects.filter(pk=s.pk).update(next_run=old_next_run)
|
||||||
s.next_run = old_next_run
|
s.next_run = old_next_run
|
||||||
prior_modified = s.modified
|
prior_modified = s.modified
|
||||||
@@ -259,7 +259,7 @@ def test_utc_until_in_the_past(job_template):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@mock.patch('awx.main.models.schedules.now', lambda: datetime(2030, 3, 5, tzinfo=pytz.utc))
|
@mock.patch('awx.main.models.schedules.now', lambda: datetime(2030, 3, 5, tzinfo=timezone.utc))
|
||||||
def test_dst_phantom_hour(job_template):
|
def test_dst_phantom_hour(job_template):
|
||||||
# The DST period in the United States begins at 02:00 (2 am) local time, so
|
# The DST period in the United States begins at 02:00 (2 am) local time, so
|
||||||
# the hour from 2:00:00 to 2:59:59 does not exist in the night of the
|
# the hour from 2:00:00 to 2:59:59 does not exist in the night of the
|
||||||
@@ -456,15 +456,15 @@ def test_skip_sundays():
|
|||||||
RRULE:INTERVAL=1;FREQ=DAILY
|
RRULE:INTERVAL=1;FREQ=DAILY
|
||||||
EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU
|
EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU
|
||||||
'''
|
'''
|
||||||
timezone = pytz.timezone("America/New_York")
|
tz = ZoneInfo("America/New_York")
|
||||||
friday_apr_29th = datetime(2022, 4, 29, 0, 0, 0, 0, timezone)
|
friday_apr_29th = datetime(2022, 4, 29, 0, 0, 0, 0, tz)
|
||||||
monday_may_2nd = datetime(2022, 5, 2, 23, 59, 59, 999, timezone)
|
monday_may_2nd = datetime(2022, 5, 2, 23, 59, 59, 999, tz)
|
||||||
ruleset = Schedule.rrulestr(rrule)
|
ruleset = Schedule.rrulestr(rrule)
|
||||||
gen = ruleset.between(friday_apr_29th, monday_may_2nd, True)
|
gen = ruleset.between(friday_apr_29th, monday_may_2nd, True)
|
||||||
# We should only get Fri, Sat and Mon (skipping Sunday)
|
# We should only get Fri, Sat and Mon (skipping Sunday)
|
||||||
assert len(list(gen)) == 3
|
assert len(list(gen)) == 3
|
||||||
saturday_night = datetime(2022, 4, 30, 23, 59, 59, 9999, timezone)
|
saturday_night = datetime(2022, 4, 30, 23, 59, 59, 9999, tz)
|
||||||
monday_morning = datetime(2022, 5, 2, 0, 0, 0, 0, timezone)
|
monday_morning = datetime(2022, 5, 2, 0, 0, 0, 0, tz)
|
||||||
gen = ruleset.between(saturday_night, monday_morning, True)
|
gen = ruleset.between(saturday_night, monday_morning, True)
|
||||||
assert len(list(gen)) == 0
|
assert len(list(gen)) == 0
|
||||||
|
|
||||||
@@ -476,17 +476,17 @@ def test_skip_sundays():
|
|||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
'DTSTART;TZID=America/New_York:20210310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20210430T150000Z EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
|
'DTSTART;TZID=America/New_York:20210310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20210430T150000Z EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
|
||||||
datetime(2021, 4, 29, 19, 0, 0, tzinfo=pytz.utc),
|
datetime(2021, 4, 29, 19, 0, 0, tzinfo=timezone.utc),
|
||||||
id="Single rule in rule set with UTC TZ aware until",
|
id="Single rule in rule set with UTC TZ aware until",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000 EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
|
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000 EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
|
||||||
datetime(2022, 4, 30, 19, 0, tzinfo=pytz.utc),
|
datetime(2022, 4, 30, 19, 0, tzinfo=timezone.utc),
|
||||||
id="Single rule in ruleset with naive until",
|
id="Single rule in ruleset with naive until",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;COUNT=4 EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
|
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;COUNT=4 EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
|
||||||
datetime(2022, 3, 12, 20, 0, tzinfo=pytz.utc),
|
datetime(2022, 3, 12, 20, 0, tzinfo=timezone.utc),
|
||||||
id="Single rule in ruleset with count",
|
id="Single rule in ruleset with count",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
@@ -501,12 +501,12 @@ def test_skip_sundays():
|
|||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000Z',
|
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000Z',
|
||||||
datetime(2022, 4, 29, 19, 0, tzinfo=pytz.utc),
|
datetime(2022, 4, 29, 19, 0, tzinfo=timezone.utc),
|
||||||
id="Single rule in rule with UTZ TZ aware until",
|
id="Single rule in rule with UTZ TZ aware until",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000',
|
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000',
|
||||||
datetime(2022, 4, 30, 19, 0, tzinfo=pytz.utc),
|
datetime(2022, 4, 30, 19, 0, tzinfo=timezone.utc),
|
||||||
id="Single rule in rule with naive until",
|
id="Single rule in rule with naive until",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
@@ -521,12 +521,12 @@ def test_skip_sundays():
|
|||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=SU;UNTIL=20220430T1500Z RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=MO;COUNT=4',
|
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=SU;UNTIL=20220430T1500Z RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=MO;COUNT=4',
|
||||||
datetime(2022, 4, 24, 19, 0, tzinfo=pytz.utc),
|
datetime(2022, 4, 24, 19, 0, tzinfo=timezone.utc),
|
||||||
id="Multi rule one with until and one with an count",
|
id="Multi rule one with until and one with an count",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
'DTSTART;TZID=America/New_York:20010430T1500 RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=SU;COUNT=1',
|
'DTSTART;TZID=America/New_York:20010430T1500 RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=SU;COUNT=1',
|
||||||
datetime(2001, 5, 6, 19, 0, tzinfo=pytz.utc),
|
datetime(2001, 5, 6, 19, 0, tzinfo=timezone.utc),
|
||||||
id="Rule with count but ends in the past",
|
id="Rule with count but ends in the past",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ filterwarnings =
|
|||||||
once:datetime.datetime.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC:DeprecationWarning
|
once:datetime.datetime.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC:DeprecationWarning
|
||||||
|
|
||||||
# NOTE: the following are present using python 3.11
|
# NOTE: the following are present using python 3.11
|
||||||
# FIXME: Set `USE_TZ` to `True`.
|
|
||||||
# Note: RemovedInDjango50Warning may not exist in newer Django versions
|
|
||||||
ignore:The default value of USE_TZ will change from False to True in Django 5.0. Set USE_TZ to False in your project settings if you want to keep the current default behavior.
|
|
||||||
|
|
||||||
# FIXME: Delete this entry once `pyparsing` is updated.
|
# FIXME: Delete this entry once `pyparsing` is updated.
|
||||||
once:module 'sre_constants' is deprecated:DeprecationWarning:_pytest.assertion.rewrite
|
once:module 'sre_constants' is deprecated:DeprecationWarning:_pytest.assertion.rewrite
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user