mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +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 bs4 import BeautifulSoup
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_backends
|
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.timezone import now as timezone_now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.utils.translation import override as override_language
|
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.send_email import EMAIL_DATE_FORMAT, FromAddress, send_future_email
|
||||||
from zerver.lib.soft_deactivation import soft_reactivate_if_personal_notification
|
from zerver.lib.soft_deactivation import soft_reactivate_if_personal_notification
|
||||||
from zerver.lib.tex import change_katex_to_raw_latex
|
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.timezone import canonicalize_timezone
|
||||||
from zerver.lib.topic import get_topic_display_name, get_topic_resolution_and_bare_name
|
from zerver.lib.topic import get_topic_display_name, get_topic_resolution_and_bare_name
|
||||||
from zerver.lib.url_encoding import (
|
from zerver.lib.url_encoding import (
|
||||||
@@ -183,6 +185,25 @@ def fix_spoilers_in_text(content: str, language: str) -> str:
|
|||||||
return "\n".join(output)
|
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:
|
def add_quote_prefix_in_text(content: str) -> str:
|
||||||
"""
|
"""
|
||||||
We add quote prefix ">" to each line of the message in plain text
|
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_emojis(fragment, user.emojiset)
|
||||||
fix_spoilers_in_html(fragment, user.default_language)
|
fix_spoilers_in_html(fragment, user.default_language)
|
||||||
change_katex_to_raw_latex(fragment)
|
change_katex_to_raw_latex(fragment)
|
||||||
|
convert_time_to_local_timezone(fragment, user)
|
||||||
|
|
||||||
html = Markup(lxml.html.tostring(fragment, encoding="unicode"))
|
html = Markup(lxml.html.tostring(fragment, encoding="unicode"))
|
||||||
if sender:
|
if sender:
|
||||||
|
@@ -1404,6 +1404,58 @@ class TestMessageNotificationEmails(ZulipTestCase):
|
|||||||
mail.outbox[0].alternatives[0][0],
|
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:
|
def test_multiple_missed_personal_messages(self) -> None:
|
||||||
hamlet = self.example_user("hamlet")
|
hamlet = self.example_user("hamlet")
|
||||||
msg_id_1 = self.send_personal_message(
|
msg_id_1 = self.send_personal_message(
|
||||||
|
Reference in New Issue
Block a user