email_notifications: Convert datetime to local date string.

This commit is contained in:
Aman Agrawal
2025-10-01 15:43:12 +05:30
committed by Tim Abbott
parent a694fe1865
commit 024559e1b9
2 changed files with 74 additions and 0 deletions

View File

@@ -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:

View File

@@ -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(