mirror of
https://github.com/zulip/zulip.git
synced 2025-10-22 20:42:14 +00:00
email_notifications: Convert datetime to local date string.
This commit is contained in:
@@ -16,6 +16,7 @@ import lxml.html
|
||||
from bs4 import BeautifulSoup
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_backends
|
||||
from django.utils.timezone import get_current_timezone_name as timezone_get_current_timezone_name
|
||||
from django.utils.timezone import now as timezone_now
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import override as override_language
|
||||
@@ -32,6 +33,7 @@ from zerver.lib.queue import queue_event_on_commit
|
||||
from zerver.lib.send_email import EMAIL_DATE_FORMAT, FromAddress, send_future_email
|
||||
from zerver.lib.soft_deactivation import soft_reactivate_if_personal_notification
|
||||
from zerver.lib.tex import change_katex_to_raw_latex
|
||||
from zerver.lib.timestamp import format_datetime_to_string
|
||||
from zerver.lib.timezone import canonicalize_timezone
|
||||
from zerver.lib.topic import get_topic_display_name, get_topic_resolution_and_bare_name
|
||||
from zerver.lib.url_encoding import (
|
||||
@@ -183,6 +185,25 @@ def fix_spoilers_in_text(content: str, language: str) -> str:
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
def convert_time_to_local_timezone(fragment: lxml.html.HtmlElement, user: UserProfile) -> None:
|
||||
user_tz = user.timezone or timezone_get_current_timezone_name()
|
||||
time_elements = fragment.findall(".//time")
|
||||
|
||||
for time_elem in time_elements:
|
||||
datetime_str = time_elem.get("datetime")
|
||||
if not datetime_str:
|
||||
# We expect there to always be a datetime attribute.
|
||||
continue # nocoverage
|
||||
try:
|
||||
dt_utc = timezone_now().strptime(datetime_str, "%Y-%m-%dT%H:%M:%S%z")
|
||||
dt_local = dt_utc.astimezone(zoneinfo.ZoneInfo(canonicalize_timezone(user_tz)))
|
||||
formatted_time = format_datetime_to_string(dt_local, user.twenty_four_hour_time)
|
||||
time_elem.text = formatted_time
|
||||
except Exception as e:
|
||||
logger.warning("Failed to convert time element '%s': %s", datetime_str, e)
|
||||
continue
|
||||
|
||||
|
||||
def add_quote_prefix_in_text(content: str) -> str:
|
||||
"""
|
||||
We add quote prefix ">" to each line of the message in plain text
|
||||
@@ -260,6 +281,7 @@ def build_message_list(
|
||||
fix_emojis(fragment, user.emojiset)
|
||||
fix_spoilers_in_html(fragment, user.default_language)
|
||||
change_katex_to_raw_latex(fragment)
|
||||
convert_time_to_local_timezone(fragment, user)
|
||||
|
||||
html = Markup(lxml.html.tostring(fragment, encoding="unicode"))
|
||||
if sender:
|
||||
|
@@ -1404,6 +1404,58 @@ class TestMessageNotificationEmails(ZulipTestCase):
|
||||
mail.outbox[0].alternatives[0][0],
|
||||
)
|
||||
|
||||
def test_datetime_conversion_in_missed_message_content(self) -> None:
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
get_or_create_direct_message_group(id_list=[hamlet.id])
|
||||
|
||||
# Normal message with timestamp.
|
||||
msg_id = self.send_personal_message(
|
||||
hamlet, hamlet, "Meeting at <time:2025-09-30T09:30:00-07:00>", read_by_sender=False
|
||||
)
|
||||
|
||||
self.handle_missedmessage_emails(
|
||||
hamlet.id,
|
||||
{msg_id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)},
|
||||
)
|
||||
|
||||
assert isinstance(mail.outbox[0], EmailMultiAlternatives)
|
||||
self.assertEqual(mail.outbox[0].subject, "DMs with King Hamlet")
|
||||
assert isinstance(mail.outbox[0].alternatives[0][0], str)
|
||||
# Sender name is not appended for missed 1:1 direct messages
|
||||
self.assertEqual(
|
||||
"> Meeting at <time:2025-09-30T09:30:00-07:00>\n\n--\n\nReply", mail.outbox[0].body[:56]
|
||||
)
|
||||
self.assertIn(
|
||||
'<p>Meeting at <time datetime="2025-09-30T16:30:00Z">Tuesday, September 30, 2025 at 04:30 PM UTC</time></p>',
|
||||
mail.outbox[0].alternatives[0][0],
|
||||
)
|
||||
|
||||
# The timestamp is not formatted correctly.
|
||||
msg_id = self.send_personal_message(
|
||||
hamlet, hamlet, "Meeting at <time:2025-09-30T09:30:00-07:00>", read_by_sender=False
|
||||
)
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"zerver.lib.email_notifications.format_datetime_to_string",
|
||||
side_effect=ValueError("Invalid datetime format"),
|
||||
),
|
||||
self.assertLogs(level="WARNING") as m,
|
||||
):
|
||||
self.handle_missedmessage_emails(
|
||||
hamlet.id,
|
||||
{msg_id: MissedMessageData(trigger=NotificationTriggers.DIRECT_MESSAGE)},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
m.output,
|
||||
[
|
||||
"WARNING:zerver.lib.email_notifications:Failed to convert time element "
|
||||
"'2025-09-30T16:30:00Z': Invalid datetime format",
|
||||
],
|
||||
)
|
||||
|
||||
def test_multiple_missed_personal_messages(self) -> None:
|
||||
hamlet = self.example_user("hamlet")
|
||||
msg_id_1 = self.send_personal_message(
|
||||
|
Reference in New Issue
Block a user