misc: use zoneinfo instead of pytz for timezones (#71918)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Benjamin Dauvergne 2023-03-08 13:00:20 +01:00
parent 39523e4e65
commit e86cdf15fc
43 changed files with 118 additions and 76 deletions

View File

@ -18,9 +18,9 @@ from datetime import timedelta
from django.core.management.base import BaseCommand
from django.db.models import F
from django.utils import timezone
from chrono.agendas.models import Booking
from chrono.utils import timezone
class Command(BaseCommand):

View File

@ -18,10 +18,10 @@ from datetime import timedelta
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils import timezone
from requests import RequestException
from chrono.agendas.models import Event, EventCancellationReport
from chrono.utils import timezone
class Command(BaseCommand):

View File

@ -20,9 +20,10 @@ import pytz
from django.conf import settings
from django.core.management.base import BaseCommand
from django.db.models import F
from django.utils import timezone, translation
from django.utils import translation
from chrono.agendas.models import Booking
from chrono.utils import timezone
from .utils import send_reminder

View File

@ -22,10 +22,11 @@ from django.core.mail import send_mail
from django.core.management.base import BaseCommand
from django.db.transaction import atomic
from django.template.loader import render_to_string
from django.utils import timezone, translation
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from chrono.agendas.models import Agenda
from chrono.utils import timezone
class Command(BaseCommand):

View File

@ -5,10 +5,10 @@ from django.core.mail import send_mail
from django.db.transaction import atomic
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from requests import RequestException
from chrono.utils import timezone
from chrono.utils.requests_wrapper import requests

View File

@ -1,7 +1,8 @@
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
from chrono.utils.timezone import utc
class Migration(migrations.Migration):

View File

@ -1,7 +1,8 @@
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
from chrono.utils.timezone import utc
class Migration(migrations.Migration):

View File

@ -1,7 +1,8 @@
import datetime
from django.db import migrations
from django.utils.timezone import localtime, make_aware
from chrono.utils.timezone import localtime, make_aware
def forwards(apps, schema_editor):

View File

@ -1,8 +1,9 @@
# Generated by Django 2.2.26 on 2022-06-29 13:14
import django.utils.timezone
from django.db import migrations, models
import chrono.utils.timezone
class Migration(migrations.Migration):
@ -14,7 +15,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='sharedcustodyagenda',
name='date_start',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Start'),
field=models.DateTimeField(default=chrono.utils.timezone.now, verbose_name='Start'),
preserve_default=False,
),
]

View File

@ -63,7 +63,6 @@ from django.utils.html import escape
from django.utils.module_loading import import_string
from django.utils.safestring import mark_safe
from django.utils.text import slugify
from django.utils.timezone import is_aware, localtime, make_aware, make_naive, now, utc
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext, pgettext_lazy
@ -74,6 +73,7 @@ from chrono.utils.db import ArraySubquery, SumCardinality
from chrono.utils.misc import AgendaImportError, ICSError, clean_import_data, generate_slug
from chrono.utils.publik_urls import translate_from_publik_url
from chrono.utils.requests_wrapper import requests as requests_wrapper
from chrono.utils.timezone import is_aware, localtime, make_aware, make_naive, now, utc
AGENDA_KINDS = (
('events', _('Events')),
@ -2973,7 +2973,7 @@ class TimePeriodException(models.Model):
def import_datetime(s):
'''Import datetime as a naive ISO8601 serialization'''
try:
return make_aware(datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S'), is_dst=True)
return make_aware(datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S'))
except ValueError:
raise AgendaImportError(_('Bad datetime format "%s"') % s)

View File

@ -32,7 +32,6 @@ from django.urls import reverse
from django.utils.dates import WEEKDAYS
from django.utils.encoding import force_str
from django.utils.formats import date_format
from django.utils.timezone import localtime, make_aware, now
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_noop as N_
@ -58,6 +57,7 @@ from chrono.api import serializers
from chrono.api.utils import APIError, APIErrorBadRequest, Response
from chrono.interval import IntervalSet
from chrono.utils.publik_urls import translate_to_publik_url
from chrono.utils.timezone import localtime, make_aware, now
def format_response_datetime(dt):

View File

@ -37,7 +37,6 @@ from django.template import Context, Template, TemplateSyntaxError, VariableDoes
from django.utils.encoding import force_str
from django.utils.formats import date_format
from django.utils.html import format_html, mark_safe
from django.utils.timezone import localtime, make_aware, now
from django.utils.translation import gettext_lazy as _
from chrono.agendas.models import (
@ -68,6 +67,7 @@ from chrono.agendas.models import (
generate_slug,
)
from chrono.utils.lingo import get_agenda_check_types
from chrono.utils.timezone import localtime, make_aware, now
from . import widgets
from .widgets import SplitDateTimeField, WeekdaysWidget

View File

@ -42,7 +42,6 @@ from django.utils.dates import MONTHS
from django.utils.encoding import force_str
from django.utils.formats import date_format
from django.utils.html import format_html
from django.utils.timezone import localtime, make_aware, make_naive, now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from django.views.generic import (
@ -88,6 +87,7 @@ from chrono.agendas.models import (
VirtualMember,
)
from chrono.utils.date import get_weekday_index
from chrono.utils.timezone import localtime, make_aware, make_naive, now
from .forms import (
AgendaAddForm,

66
chrono/utils/timezone.py Normal file
View File

@ -0,0 +1,66 @@
# chrono - agendas system
# Copyright (C) 2023 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import functools
import zoneinfo
import django.utils.timezone
from django.conf import settings
utc = zoneinfo.ZoneInfo('UTC')
@functools.lru_cache()
def get_default_timezone():
"""
Return the default time zone as a tzinfo instance.
This is the time zone defined by settings.TIME_ZONE.
"""
return zoneinfo.ZoneInfo(settings.TIME_ZONE)
def localtime(value=None, timezone=None):
if timezone is None:
timezone = get_default_timezone()
return django.utils.timezone.localtime(value=value, timezone=timezone)
is_aware = django.utils.timezone.is_aware
def make_aware(value, timezone=None, is_dst=None):
if timezone is None:
timezone = get_default_timezone()
return django.utils.timezone.make_aware(value, timezone=timezone, is_dst=is_dst)
def make_naive(value, timezone=None):
if timezone is None:
timezone = get_default_timezone()
return django.utils.timezone.make_naive(value, timezone=timezone)
def now():
"""
Return an aware or naive datetime.datetime, depending on settings.USE_TZ.
"""
if settings.USE_TZ:
# timeit shows that datetime.now(tz=utc) is 24% slower
return datetime.datetime.utcnow().replace(tzinfo=utc)
else:
return datetime.datetime.now()

View File

@ -2,9 +2,9 @@ import datetime
import pytest
from django.contrib.auth import get_user_model
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import Agenda, Desk, Event, MeetingType, TimePeriod, VirtualMember
from chrono.utils.timezone import localtime, make_aware, now
User = get_user_model()

View File

@ -5,9 +5,9 @@ import pytest
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, make_naive, now
from chrono.agendas.models import Agenda, Booking, Desk, Event, EventsType, TimePeriodException
from chrono.utils.timezone import localtime, make_aware, make_naive, now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -3,7 +3,6 @@ import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import (
Agenda,
@ -22,6 +21,7 @@ from chrono.agendas.models import (
TimePeriodExceptionGroup,
UnavailabilityCalendar,
)
from chrono.utils.timezone import localtime, make_aware, now
pytestmark = pytest.mark.django_db

View File

@ -3,7 +3,6 @@ import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import (
Agenda,
@ -17,6 +16,7 @@ from chrono.agendas.models import (
UnavailabilityCalendar,
VirtualMember,
)
from chrono.utils.timezone import localtime, make_aware, now
pytestmark = pytest.mark.django_db

View File

@ -3,7 +3,6 @@ import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import make_aware, now
from chrono.agendas.models import (
Agenda,
@ -22,6 +21,7 @@ from chrono.agendas.models import (
TimePeriodExceptionGroup,
UnavailabilityCalendar,
)
from chrono.utils.timezone import make_aware, now
pytestmark = pytest.mark.django_db

View File

@ -6,7 +6,6 @@ from unittest import mock
import pytest
from django.db import IntegrityError, connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, now
from chrono.agendas.models import (
Agenda,
@ -19,6 +18,7 @@ from chrono.agendas.models import (
TimePeriod,
VirtualMember,
)
from chrono.utils.timezone import localtime, now
pytestmark = pytest.mark.django_db

View File

@ -3,9 +3,9 @@ import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, now
from chrono.agendas.models import Agenda, Booking, Event, EventsType
from chrono.utils.timezone import localtime, now
pytestmark = pytest.mark.django_db

View File

@ -3,7 +3,6 @@ import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import (
Agenda,
@ -17,6 +16,7 @@ from chrono.agendas.models import (
SharedCustodyRule,
Subscription,
)
from chrono.utils.timezone import localtime, make_aware, now
pytestmark = pytest.mark.django_db

View File

@ -3,7 +3,6 @@ import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import now
from chrono.agendas.models import (
Agenda,
@ -17,6 +16,7 @@ from chrono.agendas.models import (
SharedCustodyRule,
Subscription,
)
from chrono.utils.timezone import now
pytestmark = pytest.mark.django_db

View File

@ -4,7 +4,6 @@ import pytest
from django.contrib.auth.models import Group
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, now
from chrono.agendas.models import (
Agenda,
@ -16,6 +15,7 @@ from chrono.agendas.models import (
Resource,
TimePeriodException,
)
from chrono.utils.timezone import localtime, now
pytestmark = pytest.mark.django_db

View File

@ -4,10 +4,10 @@ from unittest import mock
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import Agenda, Booking, BookingColor, Category, Desk, Event, MeetingType
from chrono.utils.lingo import CheckType
from chrono.utils.timezone import localtime, make_aware, now
pytestmark = pytest.mark.django_db

View File

@ -3,9 +3,9 @@ import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import Agenda, Booking, Event, EventsType, Subscription
from chrono.utils.timezone import localtime, make_aware, now
pytestmark = pytest.mark.django_db

View File

@ -1,10 +1,10 @@
import datetime
import pytest
from django.utils.timezone import now
from chrono.agendas.models import Agenda, Subscription
from chrono.api.serializers import AgendaOrSubscribedSlugsSerializer
from chrono.utils.timezone import now
pytestmark = pytest.mark.django_db

View File

@ -2,9 +2,9 @@ import datetime
import pytest
from django.core.files.base import ContentFile
from django.utils.timezone import now
from chrono.agendas.models import Person, SharedCustodyAgenda, SharedCustodySettings, UnavailabilityCalendar
from chrono.utils.timezone import now
pytestmark = pytest.mark.django_db

View File

@ -1,7 +1,7 @@
import pytest
from django.utils.timezone import now
from chrono.agendas.models import Agenda, Booking, Category, Event
from chrono.utils.timezone import now
pytestmark = pytest.mark.django_db

View File

@ -1,9 +1,9 @@
import datetime
import pytest
from django.utils.timezone import make_aware, now
from chrono.agendas.models import Agenda, Booking, Event, Subscription
from chrono.utils.timezone import make_aware, now
pytestmark = pytest.mark.django_db

View File

@ -10,7 +10,6 @@ from django.core.management import call_command
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import (
Agenda,
@ -26,6 +25,7 @@ from chrono.agendas.models import (
VirtualMember,
)
from chrono.utils.signature import check_query
from chrono.utils.timezone import localtime, make_aware, now
from tests.utils import login
pytestmark = pytest.mark.django_db
@ -1311,7 +1311,7 @@ def test_agenda_events_week_view(app, admin_user):
assert len(resp.pyquery.find('.event-title')) == 2
time = localtime(event.start_datetime).strftime('%H%M')
resp = resp.click('Dec. 9, 2020, 1 a.m.', index=1)
resp = resp.click('Dec. 9, 2020, 2 a.m.', index=1)
resp = resp.click('Options')
resp.form['start_datetime_1'] = '13:12'
resp = resp.form.submit(status=302).follow()
@ -1412,7 +1412,7 @@ def test_agenda_events_month_view(app, admin_user):
assert len(resp.pyquery.find('.event-title')) == 10
time = localtime(event.start_datetime).strftime('%H%M')
resp = resp.click('Dec. 9, 2020, 1 a.m.', index=1)
resp = resp.click('Dec. 9, 2020, 2 a.m.', index=1)
resp = resp.click('Options')
resp.form['start_datetime_1'] = '13:12'
resp = resp.form.submit(status=302).follow()

View File

@ -8,11 +8,11 @@ from django.core.management import call_command
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from webtest import Upload
from chrono.agendas.models import Agenda, Booking, Desk, Event, EventsType, Subscription
from chrono.utils.lingo import CheckType
from chrono.utils.timezone import localtime, make_aware, now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -4,9 +4,9 @@ import itertools
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import make_aware, now
from chrono.agendas.models import Agenda, Booking, Event, Subscription
from chrono.utils.timezone import make_aware, now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -10,7 +10,6 @@ from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.utils.timezone import localtime, make_aware, now
from webtest import Upload
from chrono.agendas.models import (
@ -25,6 +24,7 @@ from chrono.agendas.models import (
UnavailabilityCalendar,
)
from chrono.manager.forms import TimePeriodExceptionForm
from chrono.utils.timezone import localtime, make_aware, now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -4,7 +4,6 @@ import json
import freezegun
import pytest
from django.utils.encoding import force_str
from django.utils.timezone import now
from webtest import Upload
from chrono.agendas.models import (
@ -16,6 +15,7 @@ from chrono.agendas.models import (
SharedCustodySettings,
UnavailabilityCalendar,
)
from chrono.utils.timezone import now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -4,7 +4,6 @@ import pytest
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, now
from chrono.agendas.models import (
Agenda,
@ -17,6 +16,7 @@ from chrono.agendas.models import (
TimePeriodExceptionSource,
UnavailabilityCalendar,
)
from chrono.utils.timezone import localtime, now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -4,9 +4,9 @@ import freezegun
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import Agenda, Booking, Desk, Event, MeetingType, Resource, TimePeriod
from chrono.utils.timezone import localtime, make_aware, now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -4,7 +4,6 @@ import pytest
from django.core.files.base import ContentFile
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import now
from chrono.agendas.models import (
Person,
@ -16,6 +15,7 @@ from chrono.agendas.models import (
TimePeriodExceptionGroup,
UnavailabilityCalendar,
)
from chrono.utils.timezone import now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -1,7 +1,6 @@
import datetime
import pytest
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import (
Agenda,
@ -12,6 +11,7 @@ from chrono.agendas.models import (
TimePeriodException,
UnavailabilityCalendar,
)
from chrono.utils.timezone import localtime, make_aware, now
from tests.utils import login
pytestmark = pytest.mark.django_db

View File

@ -11,7 +11,6 @@ from django.core.management import call_command
from django.db import IntegrityError, connection, transaction
from django.db.models import Q
from django.test import override_settings
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import (
Agenda,
@ -37,6 +36,7 @@ from chrono.agendas.models import (
UnavailabilityCalendar,
VirtualMember,
)
from chrono.utils.timezone import localtime, make_aware, now
pytestmark = pytest.mark.django_db

View File

@ -13,7 +13,6 @@ from django.contrib.auth.models import Group
from django.core.management import CommandError, call_command
from django.test import override_settings
from django.utils.encoding import force_bytes
from django.utils.timezone import make_aware, now
from chrono.agendas.models import (
Agenda,
@ -34,6 +33,7 @@ from chrono.agendas.models import (
VirtualMember,
)
from chrono.manager.utils import import_site
from chrono.utils.timezone import make_aware, now
pytestmark = pytest.mark.django_db
@ -1102,36 +1102,6 @@ def test_export_deleted_meetingtype(app):
assert len(json.loads(output)['agendas'][0]['meetingtypes']) == 0
def test_import_export_exception_dst_change(app):
agenda_meetings = Agenda.objects.create(label='Meetings Agenda', kind='meetings')
desk = Desk.objects.create(agenda=agenda_meetings, label='Desk')
# create exception starting on dst change
start = make_aware(datetime.datetime(2022, 10, 30, 2, 0), is_dst=True)
end = make_aware(datetime.datetime(2022, 10, 31, 2, 0))
TimePeriodException.objects.create(desk=desk, start_datetime=start, end_datetime=end)
output = get_output_of_command('export_site')
payload = json.loads(output)
assert payload['agendas'][0]['desks'][0]['exceptions'][0]['start_datetime'] == '2022-10-30 02:00:00'
import_site(data=payload, overwrite=True)
exception = TimePeriodException.objects.get()
assert exception.start_datetime == start
assert exception.end_datetime == end
start = exception.start_datetime = make_aware(datetime.datetime(2022, 10, 30, 2, 0), is_dst=False)
exception.save()
output = get_output_of_command('export_site')
payload = json.loads(output)
assert payload['agendas'][0]['desks'][0]['exceptions'][0]['start_datetime'] == '2022-10-30 02:00:00'
import_site(data=payload, overwrite=True)
exception = TimePeriodException.objects.get()
# dst was forced on import
assert exception.start_datetime == start - datetime.timedelta(hours=1)
def test_import_export_agenda_update(app):
agenda = Agenda.objects.create(label='Foo Bar', kind='meetings')

View File

@ -4,9 +4,9 @@ import pytest
from django.db import IntegrityError, ProgrammingError, connection, transaction
from django.db.migrations.executor import MigrationExecutor
from django.test import override_settings
from django.utils.timezone import now
from chrono.agendas.models import Agenda, Booking, Desk, Event, MeetingType
from chrono.utils.timezone import now
pytestmark = pytest.mark.django_db

View File

@ -5,9 +5,9 @@ from django.db import IntegrityError, transaction
from django.db.models import Q
from django.test import override_settings
from django.utils.encoding import force_str
from django.utils.timezone import localtime, make_aware
from chrono.agendas.models import Agenda, Desk, MeetingType, TimePeriod, TimePeriodException
from chrono.utils.timezone import localtime, make_aware
pytestmark = pytest.mark.django_db