33"""
44
55import logging
6+ from datetime import datetime
67from typing import Optional , Union
8+ from zoneinfo import ZoneInfo
79
810from bely_mqtt import (
911 LogEntryAddEvent ,
2325class NotificationFormatter :
2426 """Handles formatting of notification messages."""
2527
26- def __init__ (self , bely_url : Optional [str ], logger : logging .Logger ):
28+ def __init__ (
29+ self , bely_url : Optional [str ], logger : logging .Logger , timezone : Optional [str ] = None
30+ ):
2731 """
2832 Initialize the formatter.
2933
3034 Args:
3135 bely_url: Base URL for BELY instance
3236 logger: Logger instance for output
37+ timezone: Timezone string (e.g., 'America/New_York'). If None, uses system local timezone.
3338 """
3439 self .bely_url = bely_url
3540 self .logger = logger
3641
42+ # Set timezone - use provided timezone, or try to detect local timezone
43+ if timezone :
44+ try :
45+ self .timezone = ZoneInfo (timezone )
46+ except Exception as e :
47+ self .logger .warning (f"Invalid timezone '{ timezone } ': { e } . Using UTC." )
48+ self .timezone = ZoneInfo ("UTC" )
49+ else :
50+ # Try to detect local timezone
51+ try :
52+ import tzlocal
53+
54+ self .timezone = tzlocal .get_localzone ()
55+ except (ImportError , Exception ) as e :
56+ self .logger .debug (f"Could not detect local timezone: { e } . Using UTC." )
57+ self .timezone = ZoneInfo ("UTC" )
58+
59+ def _format_timestamp (self , timestamp : datetime ) -> str :
60+ """
61+ Format a timestamp for display in notifications.
62+
63+ Args:
64+ timestamp: The datetime object to format
65+
66+ Returns:
67+ Formatted timestamp string in local timezone
68+ """
69+ # Ensure timestamp is timezone-aware
70+ if timestamp .tzinfo is None :
71+ # Assume UTC if no timezone info
72+ timestamp = timestamp .replace (tzinfo = ZoneInfo ("UTC" ))
73+
74+ # Convert to local timezone
75+ local_timestamp = timestamp .astimezone (self .timezone )
76+
77+ # Format as readable string with timezone
78+ return local_timestamp .strftime ("%Y-%m-%d %H:%M:%S %Z" )
79+
3780 def format_entry_added (self , event : LogEntryAddEvent ) -> str :
3881 """Format notification body for new log entry."""
3982 body = (
4083 f"New entry added to { event .parent_log_document_info .name } <br/>"
4184 f"By: { event .event_triggered_by_username } <br/>"
42- f"Time: { event .event_timestamp } <br/>"
85+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
4386 f"Description: { event .description } <br/>"
4487 f"<br/>Entry markdown: { self ._format_text_diff_pre (event .text_diff )} "
4588 )
@@ -51,7 +94,7 @@ def format_entry_updated(self, event: LogEntryUpdateEvent) -> str:
5194 f"Entry updated in { event .parent_log_document_info .name } <br/>"
5295 f"Updated by: { event .event_triggered_by_username } <br/>"
5396 f"Original author: { event .log_info .entered_by_username } <br/>"
54- f"Time: { event .event_timestamp } <br/>"
97+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
5598 f"Description: { event .description } <br/>"
5699 f"<br/>Entry markdown changes: { self ._format_text_diff_pre (event .text_diff )} "
57100 )
@@ -63,7 +106,7 @@ def format_own_entry_edited(self, event: LogEntryUpdateEvent) -> str:
63106 f"Entry edited in { event .parent_log_document_info .name } <br/>"
64107 f"Original author: { event .log_info .entered_by_username } <br/>"
65108 f"Edited by: { event .event_triggered_by_username } <br/>"
66- f"Time: { event .event_timestamp } <br/>"
109+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
67110 f"Description: { event .description } <br/>"
68111 f"<br/>Entry markdown changes: { self ._format_text_diff_pre (event .text_diff )} "
69112 )
@@ -75,7 +118,7 @@ def format_reply_added(self, event: LogEntryReplyAddEvent) -> str:
75118 f"New reply to entry in { event .parent_log_document_info .name } <br/>"
76119 f"Entry by: { event .parent_log_info .entered_by_username } <br/>"
77120 f"Reply by: { event .event_triggered_by_username } <br/>"
78- f"Time: { event .event_timestamp } <br/>"
121+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
79122 f"<br/>Reply markdown: { self ._format_text_diff_pre (event .text_diff )} "
80123 )
81124 return self ._append_permalink_and_trigger (body , event )
@@ -86,7 +129,7 @@ def format_reply_updated(self, event: LogEntryReplyUpdateEvent) -> str:
86129 f"Reply updated in { event .parent_log_document_info .name } <br/>"
87130 f"Updated by: { event .event_triggered_by_username } <br/>"
88131 f"On entry by: { event .parent_log_info .entered_by_username } <br/>"
89- f"Time: { event .event_timestamp } <br/>"
132+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
90133 f"<br/>Reply markdown changes: { self ._format_text_diff_pre (event .text_diff )} "
91134 )
92135 return self ._append_permalink_and_trigger (body , event )
@@ -97,7 +140,7 @@ def format_own_reply_updated(self, event: LogEntryReplyUpdateEvent) -> str:
97140 f"Reply updated on entry in { event .parent_log_document_info .name } <br/>"
98141 f"Entry by: { event .parent_log_info .entered_by_username } <br/>"
99142 f"Updated by: { event .event_triggered_by_username } <br/>"
100- f"Time: { event .event_timestamp } <br/>"
143+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
101144 f"<br/>Reply markdown changes: { self ._format_text_diff_pre (event .text_diff )} "
102145 )
103146 return self ._append_permalink_and_trigger (body , event , "own_reply_update" )
@@ -108,7 +151,7 @@ def format_document_reply(self, event: LogEntryReplyAddEvent) -> str:
108151 f"New reply added in document { event .parent_log_document_info .name } <br/>"
109152 f"Reply by: { event .event_triggered_by_username } <br/>"
110153 f"To entry by: { event .parent_log_info .entered_by_username } <br/>"
111- f"Time: { event .event_timestamp } <br/>"
154+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
112155 f"<br/>Reply markdown: { self ._format_text_diff_pre (event .text_diff )} "
113156 )
114157 return self ._append_permalink_and_trigger (body , event , "document_owner" )
@@ -119,7 +162,7 @@ def format_reaction_added(self, event: LogReactionAddEvent) -> str:
119162 body = (
120163 f"New reaction added to entry in { event .parent_log_document_info .name } <br/>"
121164 f"By: { event .event_triggered_by_username } <br/>"
122- f"Time: { event .event_timestamp } <br/>"
165+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
123166 f"Reaction: { reaction_info .emoji } { reaction_info .name } <br/>"
124167 f"Description: { event .description } "
125168 )
@@ -131,7 +174,7 @@ def format_reaction_deleted(self, event: LogReactionDeleteEvent) -> str:
131174 body = (
132175 f"Reaction removed from entry in { event .parent_log_document_info .name } <br/>"
133176 f"By: { event .event_triggered_by_username } <br/>"
134- f"Time: { event .event_timestamp } <br/>"
177+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
135178 f"Reaction: { reaction_info .emoji } { reaction_info .name } <br/>"
136179 f"Description: { event .description } "
137180 )
@@ -143,7 +186,7 @@ def format_entry_deleted(self, event: LogEntryDeleteEvent) -> str:
143186 f"Entry deleted from { event .parent_log_document_info .name } <br/>"
144187 f"Deleted by: { event .event_triggered_by_username } <br/>"
145188 f"Original author: { event .log_info .entered_by_username } <br/>"
146- f"Time: { event .event_timestamp } <br/>"
189+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
147190 f"Description: { event .description } <br/>"
148191 f"<br/>Deleted entry content: { self ._format_text_diff_pre (event .text_diff )} "
149192 )
@@ -154,7 +197,7 @@ def format_own_entry_deleted(self, event: LogEntryDeleteEvent) -> str:
154197 body = (
155198 f"Entry was deleted from { event .parent_log_document_info .name } <br/>"
156199 f"Deleted by: { event .event_triggered_by_username } <br/>"
157- f"Time: { event .event_timestamp } <br/>"
200+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
158201 f"Description: { event .description } <br/>"
159202 f"<br/>Deleted entry content: { self ._format_text_diff_pre (event .text_diff )} "
160203 )
@@ -165,7 +208,7 @@ def format_reply_deleted(self, event: LogEntryReplyDeleteEvent) -> str:
165208 body = (
166209 f"Reply deleted from entry in { event .parent_log_document_info .name } <br/>"
167210 f"Deleted by: { event .event_triggered_by_username } <br/>"
168- f"Time: { event .event_timestamp } <br/>"
211+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
169212 f"<br/>Deleted reply content: { self ._format_text_diff_pre (event .text_diff )} "
170213 )
171214 return self ._append_permalink_and_trigger (body , event , "reply_delete" )
@@ -176,7 +219,7 @@ def format_document_reply_deleted(self, event: LogEntryReplyDeleteEvent) -> str:
176219 f"Reply deleted from document { event .parent_log_document_info .name } <br/>"
177220 f"Deleted by: { event .event_triggered_by_username } <br/>"
178221 f"On entry by: { event .parent_log_info .entered_by_username } <br/>"
179- f"Time: { event .event_timestamp } <br/>"
222+ f"Time: { self . _format_timestamp ( event .event_timestamp ) } <br/>"
180223 f"<br/>Deleted reply content: { self ._format_text_diff_pre (event .text_diff )} "
181224 )
182225 return self ._append_permalink_and_trigger (body , event , "document_owner" )
0 commit comments