requirements: Upgrade to Django 4.0.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2022-06-27 15:43:57 -07:00 committed by Tim Abbott
parent 59174694af
commit 81892df176
24 changed files with 192 additions and 78 deletions

View File

@ -1,9 +1,9 @@
import re
import sys
from datetime import datetime
from html import escape
from typing import Any, Collection, Dict, List, Optional, Sequence
import pytz
from django.conf import settings
from django.db.backends.utils import CursorWrapper
from django.template import loader
@ -12,7 +12,12 @@ from markupsafe import Markup as mark_safe
from zerver.models import UserActivity
eastern_tz = pytz.timezone("US/Eastern")
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
eastern_tz = zoneinfo.ZoneInfo("America/New_York")
if settings.BILLING_ENABLED:

View File

@ -3,7 +3,7 @@
# and requirements/prod.txt.
# See requirements/README.md for more detail.
# Django itself
Django[argon2]==3.2.*
Django[argon2]==4.0.*
# needed for NotRequired, ParamSpec
typing-extensions
@ -77,7 +77,7 @@ django-bmemcached
python-dateutil
# Needed for time zone work
pytz
backports.zoneinfo ; python_version < "3.9"
# Needed for Redis
redis

View File

@ -93,6 +93,26 @@ backoff-stubs==1.11.1 \
--hash=sha256:3fd641261cfe9cd657ebb7fc8a1dc700efa3f1b63e82fe0235d74bb73f8b85da \
--hash=sha256:8b56cf2cfaf64abc1623544bd725b21566b5b92cf790a97d33e7437fb131251e
# via -r requirements/mypy.in
backports.zoneinfo==0.2.1 ; python_version < "3.9" \
--hash=sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf \
--hash=sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328 \
--hash=sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546 \
--hash=sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6 \
--hash=sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570 \
--hash=sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9 \
--hash=sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7 \
--hash=sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987 \
--hash=sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722 \
--hash=sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582 \
--hash=sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc \
--hash=sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b \
--hash=sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1 \
--hash=sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08 \
--hash=sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac \
--hash=sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2
# via
# -r requirements/common.in
# django
beautifulsoup4==4.11.1 \
--hash=sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30 \
--hash=sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693
@ -439,9 +459,9 @@ distro==1.7.0 \
--hash=sha256:151aeccf60c216402932b52e40ee477a939f8d58898927378a02abbe852c1c39 \
--hash=sha256:d596311d707e692c2160c37807f83e3820c5d539d5a83e87cfb6babd8ba3a06b
# via zulip
django[argon2]==3.2.14 \
--hash=sha256:677182ba8b5b285a4e072f3ac17ceee6aff1b5ce77fd173cc5b6a2d3dc022fcf \
--hash=sha256:a8681e098fa60f7c33a4b628d6fcd3fe983a0939ff1301ecacac21d0b38bad56
django[argon2]==4.0.6 \
--hash=sha256:a67a793ff6827fd373555537dca0da293a63a316fe34cb7f367f898ccca3c3ae \
--hash=sha256:ca54ebedfcbc60d191391efbf02ba68fb52165b8bf6ccd6fe71f098cac1fe59e
# via
# -r requirements/common.in
# django-auth-ldap
@ -456,9 +476,8 @@ django-auth-ldap==4.1.0 \
--hash=sha256:68870e7921e84b1a9867e268a9c8a3e573e8a0d95ea08bcf31be178f5826ff36 \
--hash=sha256:77f749d3b17807ce8eb56a9c9c8e5746ff316567f81d5ba613495d9c7495a949
# via -r requirements/common.in
django-bitfield==2.1.0 \
--hash=sha256:158f1056e22cce450d0a49633ea77bfd84b85a2294b1ef03faa775a485f4065d \
--hash=sha256:a55859fd16ce4269d5ceed3e20cf8fc3c2df866f0a78b90c60a19a0e76aa5fd8
django-bitfield==2.2.0 \
--hash=sha256:1b21262acc4ec0af3f82ed04498a056cd9d5452532ac02771e004835a34e0b1b
# via -r requirements/common.in
django-bmemcached==0.3.0 \
--hash=sha256:4e4b7d97216dbae331c1de10e699ca22804b94ec3a90d2762dd5d146e6986a8a
@ -1606,9 +1625,7 @@ pytz==2022.1 \
--hash=sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7 \
--hash=sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c
# via
# -r requirements/common.in
# babel
# django
# moto
# twilio
pyuca==1.2 \
@ -2136,10 +2153,6 @@ types-python-dateutil==2.8.18 \
--hash=sha256:8695c7d7a5b1aef4002f3ab4e1247e23b1d41cd7cc1286d4594c2d8c5593c991 \
--hash=sha256:fd5ed97262b76ae684695ea38ace8dd7c1bc9491aba7eb4edf6654b7ecabc870
# via -r requirements/mypy.in
types-pytz==2022.1.1 \
--hash=sha256:4e7add70886dc2ee6ee7535c8184a26eeb0ac9dbafae9962cb882d74b9f67330 \
--hash=sha256:581467742f32f15fff1098698b11fd511057a2a8a7568d33b604083f2b03c24f
# via -r requirements/mypy.in
types-pyyaml==6.0.9 \
--hash=sha256:33ae75c84b8f61fddf0c63e9c7e557db9db1694ad3c2ee8628ec5efebb5a5e9b \
--hash=sha256:b738e9ef120da0af8c235ba49d3b72510f56ef9bcc308fc8e7357100ff122284

View File

@ -24,7 +24,6 @@ types-Pillow
types-psycopg2
types-Pygments
types-python-dateutil
types-pytz
types-PyYAML
types-redis
types-requests

View File

@ -245,10 +245,6 @@ types-python-dateutil==2.8.18 \
--hash=sha256:8695c7d7a5b1aef4002f3ab4e1247e23b1d41cd7cc1286d4594c2d8c5593c991 \
--hash=sha256:fd5ed97262b76ae684695ea38ace8dd7c1bc9491aba7eb4edf6654b7ecabc870
# via -r requirements/mypy.in
types-pytz==2022.1.1 \
--hash=sha256:4e7add70886dc2ee6ee7535c8184a26eeb0ac9dbafae9962cb882d74b9f67330 \
--hash=sha256:581467742f32f15fff1098698b11fd511057a2a8a7568d33b604083f2b03c24f
# via -r requirements/mypy.in
types-pyyaml==6.0.9 \
--hash=sha256:33ae75c84b8f61fddf0c63e9c7e557db9db1694ad3c2ee8628ec5efebb5a5e9b \
--hash=sha256:b738e9ef120da0af8c235ba49d3b72510f56ef9bcc308fc8e7357100ff122284

View File

@ -64,6 +64,26 @@ backoff==2.1.2 \
--hash=sha256:407f1bc0f22723648a8880821b935ce5df8475cf04f7b6b5017ae264d30f6069 \
--hash=sha256:b135e6d7c7513ba2bfd6895bc32bc8c66c6f3b0279b4c6cd866053cfd7d3126b
# via -r requirements/common.in
backports.zoneinfo==0.2.1 ; python_version < "3.9" \
--hash=sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf \
--hash=sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328 \
--hash=sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546 \
--hash=sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6 \
--hash=sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570 \
--hash=sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9 \
--hash=sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7 \
--hash=sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987 \
--hash=sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722 \
--hash=sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582 \
--hash=sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc \
--hash=sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b \
--hash=sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1 \
--hash=sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08 \
--hash=sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac \
--hash=sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2
# via
# -r requirements/common.in
# django
beautifulsoup4==4.11.1 \
--hash=sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30 \
--hash=sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693
@ -272,9 +292,9 @@ distro==1.7.0 \
--hash=sha256:151aeccf60c216402932b52e40ee477a939f8d58898927378a02abbe852c1c39 \
--hash=sha256:d596311d707e692c2160c37807f83e3820c5d539d5a83e87cfb6babd8ba3a06b
# via zulip
django[argon2]==3.2.14 \
--hash=sha256:677182ba8b5b285a4e072f3ac17ceee6aff1b5ce77fd173cc5b6a2d3dc022fcf \
--hash=sha256:a8681e098fa60f7c33a4b628d6fcd3fe983a0939ff1301ecacac21d0b38bad56
django[argon2]==4.0.6 \
--hash=sha256:a67a793ff6827fd373555537dca0da293a63a316fe34cb7f367f898ccca3c3ae \
--hash=sha256:ca54ebedfcbc60d191391efbf02ba68fb52165b8bf6ccd6fe71f098cac1fe59e
# via
# -r requirements/common.in
# django-auth-ldap
@ -289,9 +309,8 @@ django-auth-ldap==4.1.0 \
--hash=sha256:68870e7921e84b1a9867e268a9c8a3e573e8a0d95ea08bcf31be178f5826ff36 \
--hash=sha256:77f749d3b17807ce8eb56a9c9c8e5746ff316567f81d5ba613495d9c7495a949
# via -r requirements/common.in
django-bitfield==2.1.0 \
--hash=sha256:158f1056e22cce450d0a49633ea77bfd84b85a2294b1ef03faa775a485f4065d \
--hash=sha256:a55859fd16ce4269d5ceed3e20cf8fc3c2df866f0a78b90c60a19a0e76aa5fd8
django-bitfield==2.2.0 \
--hash=sha256:1b21262acc4ec0af3f82ed04498a056cd9d5452532ac02771e004835a34e0b1b
# via -r requirements/common.in
django-bmemcached==0.3.0 \
--hash=sha256:4e4b7d97216dbae331c1de10e699ca22804b94ec3a90d2762dd5d146e6986a8a
@ -1085,10 +1104,7 @@ python3-saml==1.14.0 \
pytz==2022.1 \
--hash=sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7 \
--hash=sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c
# via
# -r requirements/common.in
# django
# twilio
# via twilio
pyuca==1.2 \
--hash=sha256:8a382fe74627f08c0d18908c0713ca4a20aad5385f077579e56208beea2893b2 \
--hash=sha256:abaa12e1bd2c7c68ca8396ff8383bc0654a739cef3ae68fd7af58bf29af0a91e

View File

@ -16,7 +16,7 @@ import subprocess
import sys
import time
import uuid
from typing import Any, Dict, List, Sequence, Set
from typing import IO, Any, Dict, List, Sequence, Set
from urllib.parse import SplitResult
DEPLOYMENTS_DIR = "/home/zulip/deployments"
@ -445,6 +445,20 @@ def os_families() -> Set[str]:
return {distro_info["ID"], *distro_info.get("ID_LIKE", "").split()}
def get_tzdata_zi() -> IO[str]:
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
for path in zoneinfo.TZPATH:
filename = os.path.join(path, "tzdata.zi")
if os.path.exists(filename):
return open(filename)
else:
raise RuntimeError("Missing time zone data (tzdata.zi)")
def files_and_string_digest(filenames: Sequence[str], extra_strings: Sequence[str]) -> str:
# see is_digest_obsolete for more context
sha1sum = hashlib.sha1()

View File

@ -10,13 +10,13 @@ ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__f
sys.path.append(ZULIP_PATH)
import pygments
from pytz import VERSION as timezones_version
from scripts.lib import clean_unused_caches
from scripts.lib.zulip_tools import (
ENDC,
OKBLUE,
get_dev_uuid_var_path,
get_tzdata_zi,
is_digest_obsolete,
run,
run_as_root,
@ -28,6 +28,11 @@ from version import PROVISION_VERSION
VENV_PATH = "/srv/zulip-py3-venv"
UUID_VAR_PATH = get_dev_uuid_var_path()
with get_tzdata_zi() as f:
line = f.readline()
assert line.startswith("# version ")
timezones_version = line[len("# version ") :]
def create_var_directories() -> None:
# create var/coverage, var/log, etc.

View File

@ -3,7 +3,10 @@ import json
import os
import sys
import pytz
if sys.version_info < (3, 9):
from backports import zoneinfo
else:
import zoneinfo
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")
sys.path.insert(0, ZULIP_PATH)
@ -13,4 +16,13 @@ from zerver.lib.timezone import get_canonical_timezone_map
OUT_PATH = os.path.join(ZULIP_PATH, "static", "generated", "timezones.json")
with open(OUT_PATH, "w") as f:
json.dump({"timezones": sorted(pytz.all_timezones_set - set(get_canonical_timezone_map()))}, f)
json.dump(
{
"timezones": sorted(
zoneinfo.available_timezones()
- {"Factory", "localtime"}
- set(get_canonical_timezone_map())
)
},
f,
)

View File

@ -48,4 +48,4 @@ API_FEATURE_LEVEL = 132
# historical commits sharing the same major version, in which case a
# minor version bump suffices.
PROVISION_VERSION = "194.0"
PROVISION_VERSION = "195.0"

View File

@ -3,7 +3,6 @@ import os
import re
from typing import List, Optional, Tuple
import pytz
from django.conf import settings
from django.utils.timezone import now as timezone_now
@ -16,7 +15,7 @@ from zerver.signals import get_device_browser
if settings.PRODUCTION: # nocoverage
timestamp = os.path.basename(os.path.abspath(settings.DEPLOY_ROOT))
LAST_SERVER_UPGRADE_TIME = datetime.datetime.strptime(timestamp, "%Y-%m-%d-%H-%M-%S").replace(
tzinfo=pytz.utc
tzinfo=datetime.timezone.utc
)
else:
LAST_SERVER_UPGRADE_TIME = timezone_now()
@ -31,7 +30,7 @@ def is_outdated_server(user_profile: Optional[UserProfile]) -> bool:
git_version_path = os.path.join(settings.DEPLOY_ROOT, "version.py")
release_build_time = datetime.datetime.utcfromtimestamp(
os.path.getmtime(git_version_path)
).replace(tzinfo=pytz.utc)
).replace(tzinfo=datetime.timezone.utc)
version_no_newer_than = min(LAST_SERVER_UPGRADE_TIME, release_build_time)
deadline = version_no_newer_than + datetime.timedelta(

View File

@ -11,7 +11,6 @@ from email.headerregistry import Address
from typing import Any, Dict, Iterable, List, Optional, Tuple
import lxml.html
import pytz
from bs4 import BeautifulSoup
from django.conf import settings
from django.contrib.auth import get_backends
@ -46,6 +45,11 @@ from zerver.models import (
get_user_profile_by_id,
)
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
logger = logging.getLogger(__name__)
@ -620,7 +624,7 @@ def followup_day2_email_delay(user: UserProfile) -> timedelta:
user_tz = user.timezone
if user_tz == "":
user_tz = "UTC"
signup_day = user.date_joined.astimezone(pytz.timezone(user_tz)).isoweekday()
signup_day = user.date_joined.astimezone(zoneinfo.ZoneInfo(user_tz)).isoweekday()
if signup_day == 5:
# If the day is Friday then delay should be till Monday
days_to_delay = 3

View File

@ -102,7 +102,7 @@ def process_instrumented_calls(func: Callable[[Dict[str, Any]], None]) -> None:
SerializedSubsuite = Tuple[Type[TestSuite], List[str]]
SubsuiteArgs = Tuple[Type["RemoteTestRunner"], int, SerializedSubsuite, bool]
SubsuiteArgs = Tuple[Type["RemoteTestRunner"], int, SerializedSubsuite, bool, bool]
def run_subsuite(args: SubsuiteArgs) -> Tuple[int, Any]:
@ -110,8 +110,8 @@ def run_subsuite(args: SubsuiteArgs) -> Tuple[int, Any]:
test_helpers.INSTRUMENTED_CALLS = []
# The first argument is the test runner class but we don't need it
# because we run our own version of the runner class.
_, subsuite_index, subsuite, failfast = args
runner = RemoteTestRunner(failfast=failfast)
_, subsuite_index, subsuite, failfast, buffer = args
runner = RemoteTestRunner(failfast=failfast, buffer=buffer)
result = runner.run(deserialize_suite(subsuite))
# Now we send instrumentation related events. This data will be
# appended to the data structure in the main thread. For Mypy,
@ -237,8 +237,14 @@ class ParallelTestSuite(django_runner.ParallelTestSuite):
run_subsuite = run_subsuite
init_worker = init_worker
def __init__(self, suite: TestSuite, processes: int, failfast: bool) -> None:
super().__init__(suite, processes, failfast)
def __init__(
self,
subsuites: List[TestSuite],
processes: int,
failfast: bool = False,
buffer: bool = False,
) -> None:
super().__init__(subsuites=subsuites, processes=processes, failfast=failfast, buffer=buffer)
# We can't specify a consistent type for self.subsuites, since
# the whole idea here is to monkey-patch that so we can use
# most of django_runner.ParallelTestSuite with our own suite

View File

@ -1,16 +1,13 @@
from functools import lru_cache
from io import TextIOWrapper
from typing import Dict
import pytz
from scripts.lib.zulip_tools import get_tzdata_zi
@lru_cache(maxsize=None)
def get_canonical_timezone_map() -> Dict[str, str]:
canonical = {}
with TextIOWrapper(
pytz.open_resource("tzdata.zi") # type: ignore[attr-defined] # Unclear if this is part of the public pytz API
) as f:
with get_tzdata_zi() as f:
for line in f:
if line.startswith("L "):
l, name, alias = line.split()

View File

@ -28,6 +28,7 @@ for any particular type of object.
"""
import re
import sys
from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal
@ -49,7 +50,6 @@ from typing import (
)
import orjson
import pytz
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator, validate_email
from django.utils.translation import gettext as _
@ -58,6 +58,11 @@ from zerver.lib.exceptions import InvalidJSONError, JsonableError
from zerver.lib.timezone import canonicalize_timezone
from zerver.lib.types import ProfileFieldData, Validator
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
ResultT = TypeVar("ResultT")
@ -127,6 +132,17 @@ def check_long_string(var_name: str, val: object) -> str:
return check_capped_string(500)(var_name, val)
def check_timezone(var_name: str, val: object) -> str:
s = check_string(var_name, val)
try:
zoneinfo.ZoneInfo(s)
except (ValueError, zoneinfo.ZoneInfoNotFoundError):
raise ValidationError(
_("{var_name} is not a recognized time zone").format(var_name=var_name)
)
return s
def check_date(var_name: str, val: object) -> str:
if not isinstance(val, str):
raise ValidationError(_("{var_name} is not a string").format(var_name=var_name))
@ -564,10 +580,12 @@ def to_decimal(var_name: str, s: str) -> Decimal:
def to_timezone_or_empty(var_name: str, s: str) -> str:
if s in pytz.all_timezones_set:
return canonicalize_timezone(s)
else:
try:
zoneinfo.ZoneInfo(s)
except (ValueError, zoneinfo.ZoneInfoNotFoundError):
return ""
else:
return canonicalize_timezone(s)
def to_converted_or_fallback(

View File

@ -1810,7 +1810,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
)
default_all_public_streams: bool = models.BooleanField(default=False)
# A time zone name from the `tzdata` database, as found in pytz.all_timezones.
# A time zone name from the `tzdata` database, as found in zoneinfo.available_timezones().
#
# The longest existing name is 32 characters long, so max_length=40 seems
# like a safe choice.

View File

@ -1,6 +1,6 @@
import sys
from typing import Any, Optional
import pytz
from django.conf import settings
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.dispatch import receiver
@ -13,6 +13,11 @@ from zerver.lib.queue import queue_json_publish
from zerver.lib.send_email import FromAddress
from zerver.models import UserProfile
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
JUST_CREATED_THRESHOLD = 60
@ -81,7 +86,7 @@ def email_on_new_login(sender: Any, user: UserProfile, request: Any, **kwargs: A
user_tz = user.timezone
if user_tz == "":
user_tz = timezone_get_current_timezone_name()
local_time = timezone_now().astimezone(pytz.timezone(user_tz))
local_time = timezone_now().astimezone(zoneinfo.ZoneInfo(user_tz))
if user.twenty_four_hour_time:
hhmm_string = local_time.strftime("%H:%M")
else:

View File

@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Any
from unittest.mock import patch
import orjson
import pytz
from django.conf import settings
from django.test import override_settings
from django.utils.timezone import now as timezone_now
@ -878,7 +877,7 @@ class HomeTest(ZulipTestCase):
# Check when server_upgrade_nag_deadline > last_server_upgrade_time
hamlet = self.example_user("hamlet")
iago = self.example_user("iago")
now = LAST_SERVER_UPGRADE_TIME.replace(tzinfo=pytz.utc)
now = LAST_SERVER_UPGRADE_TIME.replace(tzinfo=datetime.timezone.utc)
with patch("zerver.lib.compatibility.timezone_now", return_value=now + timedelta(days=10)):
self.assertEqual(is_outdated_server(iago), False)
self.assertEqual(is_outdated_server(hamlet), False)

View File

@ -1,9 +1,9 @@
import datetime
import sys
from typing import TYPE_CHECKING, Any, List, Mapping, Optional, Set
from unittest import mock
import orjson
import pytz
from django.conf import settings
from django.db.models import Q
from django.test import override_settings
@ -65,6 +65,11 @@ from zerver.models import (
)
from zerver.views.message_send import InvalidMirrorInput
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
if TYPE_CHECKING:
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
@ -1432,8 +1437,8 @@ class ScheduledMessageTest(ZulipTestCase):
message = self.last_scheduled_message()
self.assert_json_success(result)
self.assertEqual(message.content, "Test message 6")
local_tz = pytz.timezone(tz_guess)
utz_defer_until = local_tz.normalize(local_tz.localize(defer_until))
local_tz = zoneinfo.ZoneInfo(tz_guess)
utz_defer_until = defer_until.replace(tzinfo=local_tz)
self.assertEqual(message.scheduled_timestamp, convert_to_UTC(utz_defer_until))
self.assertEqual(message.delivery_type, ScheduledMessage.SEND_LATER)
@ -1446,8 +1451,8 @@ class ScheduledMessageTest(ZulipTestCase):
message = self.last_scheduled_message()
self.assert_json_success(result)
self.assertEqual(message.content, "Test message 7")
local_tz = pytz.timezone(user.timezone)
utz_defer_until = local_tz.normalize(local_tz.localize(defer_until))
local_tz = zoneinfo.ZoneInfo(user.timezone)
utz_defer_until = defer_until.replace(tzinfo=local_tz)
self.assertEqual(message.scheduled_timestamp, convert_to_UTC(utz_defer_until))
self.assertEqual(message.delivery_type, ScheduledMessage.SEND_LATER)

View File

@ -1,8 +1,8 @@
import datetime
import sys
from typing import Sequence
from unittest import mock
import pytz
from django.conf import settings
from django.core import mail
from django.test import override_settings
@ -16,6 +16,11 @@ from zerver.lib.test_classes import ZulipTestCase
from zerver.models import Message, Realm, Recipient, Stream, UserProfile, get_realm
from zerver.signals import JUST_CREATED_THRESHOLD, get_device_browser, get_device_os
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
class SendLoginEmailTest(ZulipTestCase):
"""
@ -47,7 +52,7 @@ class SendLoginEmailTest(ZulipTestCase):
firefox_windows = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
)
user_tz = pytz.timezone(user.timezone)
user_tz = zoneinfo.ZoneInfo(user.timezone)
mock_time = datetime.datetime(year=2018, month=1, day=1, tzinfo=datetime.timezone.utc)
reference_time = mock_time.astimezone(user_tz).strftime("%A, %B %d, %Y at %I:%M%p %Z")
with mock.patch("zerver.signals.timezone_now", return_value=mock_time):

View File

@ -410,6 +410,8 @@ class ChangeSettingsTest(ZulipTestCase):
expected_error_msg = f"Invalid {setting_name}"
if setting_name == "notification_sound":
expected_error_msg = f"Invalid notification sound '{invalid_value}'"
elif setting_name == "timezone":
expected_error_msg = "timezone is not a recognized time zone"
self.assert_json_error(result, expected_error_msg)
hamlet = self.example_user("hamlet")
self.assertNotEqual(getattr(hamlet, setting_name), invalid_value)

View File

@ -1,11 +1,16 @@
import sys
from datetime import datetime
import pytz
from django.utils.timezone import now as timezone_now
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.timezone import canonicalize_timezone, common_timezones
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
class TimeZoneTest(ZulipTestCase):
def test_canonicalize_timezone(self) -> None:
@ -32,10 +37,11 @@ class TimeZoneTest(ZulipTestCase):
now = timezone_now()
dates = [datetime(now.year, 6, 21), datetime(now.year, 12, 21)]
extra = {*common_timezones.items(), *ambiguous_abbrevs}
for name in pytz.all_timezones:
tz = pytz.timezone(name)
for name in zoneinfo.available_timezones():
tz = zoneinfo.ZoneInfo(name)
for date in dates:
abbrev = tz.tzname(date)
assert abbrev is not None
if abbrev.startswith(("-", "+")):
continue
delta = tz.utcoffset(date)

View File

@ -1,6 +1,6 @@
import sys
from typing import Iterable, Optional, Sequence, Union, cast
import pytz
from dateutil.parser import parse as dateparser
from django.core import validators
from django.core.exceptions import ValidationError
@ -36,6 +36,11 @@ from zerver.models import (
get_user_including_cross_realm,
)
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
else: # nocoverage
import zoneinfo
class InvalidMirrorInput(Exception):
pass
@ -159,8 +164,8 @@ def handle_deferred_message(
deliver_at_usertz = deliver_at
if deliver_at_usertz.tzinfo is None:
user_tz = pytz.timezone(local_tz)
deliver_at_usertz = user_tz.normalize(user_tz.localize(deliver_at))
user_tz = zoneinfo.ZoneInfo(local_tz)
deliver_at_usertz = deliver_at.replace(tzinfo=user_tz)
deliver_at = convert_to_UTC(deliver_at_usertz)
if deliver_at <= timezone_now():

View File

@ -1,6 +1,5 @@
from typing import Any, Dict, Optional
import pytz
from django.conf import settings
from django.contrib.auth import authenticate, update_session_auth_hash
from django.core.exceptions import ValidationError
@ -42,7 +41,13 @@ from zerver.lib.response import json_success
from zerver.lib.send_email import FromAddress, send_email
from zerver.lib.sounds import get_available_notification_sounds
from zerver.lib.upload import upload_avatar_image
from zerver.lib.validator import check_bool, check_int, check_int_in, check_string_in
from zerver.lib.validator import (
check_bool,
check_int,
check_int_in,
check_string_in,
check_timezone,
)
from zerver.models import (
EmailChangeStatus,
UserProfile,
@ -164,9 +169,7 @@ def json_change_settings(
demote_inactive_streams: Optional[int] = REQ(
json_validator=check_int_in(UserProfile.DEMOTE_STREAMS_CHOICES), default=None
),
timezone: Optional[str] = REQ(
str_validator=check_string_in(pytz.all_timezones_set), default=None
),
timezone: Optional[str] = REQ(str_validator=check_timezone, default=None),
email_notifications_batching_period_seconds: Optional[int] = REQ(
json_validator=check_int, default=None
),