From 431a1df23b1dc648f05da72547787ef74eddd3c0 Mon Sep 17 00:00:00 2001 From: Ketan <73937490+devketanpro@users.noreply.github.com> Date: Fri, 22 Dec 2023 10:17:43 +0530 Subject: [PATCH] fix: The date in the downloaded Plain text file is not the event date [CPCN-388] (#687) * fix: The date in the downloaded Plain text file is not the event date [CPCN-388] * include timezone and format date time based on timezone * refactore code * add test and reformat code via black * add types and refactore code * fix black * add missing return statement * update logic and return type * address comment * refactore code * fix tests * remove unwanted date parsing and refactored code * use configured datetime for formatting * refactore event formatting for better readability * minor change * update default format * update datetime string output --- newsroom/gettext.py | 9 ++ newsroom/template_filters.py | 109 ++++++++++++++++++++++++- newsroom/templates/download_agenda.txt | 2 +- newsroom/web/factory.py | 2 + tests/core/test_template_filters.py | 103 ++++++++++++++++++++++- 5 files changed, 222 insertions(+), 3 deletions(-) diff --git a/newsroom/gettext.py b/newsroom/gettext.py index 7bdc3786a..20f8cc0b8 100644 --- a/newsroom/gettext.py +++ b/newsroom/gettext.py @@ -74,6 +74,15 @@ def set_session_timezone(timezone: str): current_app.session_timezone = timezone +def clear_session_timezone(): + try: + session.pop("timezone", None) + except RuntimeError: + pass + + current_app.config.pop("SESSION_TIMEZONE", None) + + def setup_babel(app): babel = Babel(app) diff --git a/newsroom/template_filters.py b/newsroom/template_filters.py index d7dd0efd9..1139b471f 100644 --- a/newsroom/template_filters.py +++ b/newsroom/template_filters.py @@ -8,12 +8,14 @@ from flask import current_app as app from eve.utils import str_to_date -from flask_babel import format_time, format_date, format_datetime, get_locale +from flask_babel import format_time, format_date, format_datetime, get_locale, lazy_gettext from flask_babel.speaklater import LazyString from jinja2.utils import htmlsafe_json_dumps # type: ignore from superdesk.text_utils import get_text, get_word_count, get_char_count from superdesk.utc import utcnow from datetime import datetime +from newsroom.gettext import set_session_timezone, get_session_timezone, clear_session_timezone +from enum import Enum from newsroom.user_roles import UserRole @@ -22,6 +24,13 @@ _hash_cache = {} +class ScheduleType(Enum): + ALL_DAY = "ALL_DAY" + MULTI_DAY = "MULTI_DAY" + NO_DURATION = "NO_DURATION" + REGULAR = "REGULAR" + + def get_client_format(key) -> Optional[str]: locale = str(get_locale()) try: @@ -61,6 +70,104 @@ def parse_date(datetime): return datetime +def get_schedule_type(start: datetime, end: datetime, all_day: bool, no_end_time: bool) -> ScheduleType: + """ + Determine the schedule type based on event start and end times. + + params : + - start (datetime): Start time of the event. + - end (datetime): End time of the event. + - all_day (bool): True if the event is an all-day event. + - no_end_time (bool): True if the event has no specified end time. + + return: + ScheduleType: The type of schedule (ALL_DAY, NO_DURATION, MULTI_DAY, REGULAR). + """ + duration = int((end - start).total_seconds() / 60) if not no_end_time and end else 0 + DAY_IN_MINUTES = 24 * 60 - 1 + + if all_day: + return ScheduleType.ALL_DAY if duration in (0, DAY_IN_MINUTES) else ScheduleType.MULTI_DAY + + if no_end_time: + return ScheduleType.NO_DURATION + + if duration >= DAY_IN_MINUTES and start.date() != (end.date() if end else None): + return ScheduleType.MULTI_DAY + + if duration <= DAY_IN_MINUTES and start.date() == (end.date() if end else None): + return ScheduleType.ALL_DAY + + if duration == 0 and start.time() == (end.time() if end else None): + return ScheduleType.NO_DURATION + + return ScheduleType.REGULAR + + +def is_item_tbc(item: dict) -> bool: + event_tbc = item.get("event", {}).get("_time_to_be_confirmed", False) + planning = item.get("planning_items", []) + return event_tbc or (planning and planning[0].get("_time_to_be_confirmed", False)) + + +def format_event_datetime(item: dict) -> str: + date_info = item.get("dates", {}) + + if not date_info: + return "" + + tz = date_info.get("tz", get_session_timezone()) + try: + # Set the session timezone + set_session_timezone(tz) + + start = parse_date(date_info.get("start")) + end = parse_date(date_info.get("end")) if date_info.get("end") else None + all_day = date_info.get("all_day") + no_end_time = date_info.get("no_end_time") + is_tbc_item = is_item_tbc(item) + + schedule_type = get_schedule_type(start, end, all_day, no_end_time) + + formatted_start = datetime_long(start) + formatted_end = datetime_long(end) if end else None + + if is_tbc_item: + if schedule_type == ScheduleType.MULTI_DAY: + return lazy_gettext("Date: {start} to {end} ({tz}) (Time to be confirmed)").format( + start=formatted_start, end=formatted_end, tz=tz + ) + + else: + return lazy_gettext("Date: {start} ({tz}) (Time to be confirmed)").format(start=formatted_start, tz=tz) + + elif schedule_type in (ScheduleType.ALL_DAY, ScheduleType.NO_DURATION): + return lazy_gettext("Date: {start} ({tz})").format(start=formatted_start, tz=tz) + + elif schedule_type == ScheduleType.MULTI_DAY: + return lazy_gettext("Date: {start} to {end} ({tz})").format(start=formatted_start, end=formatted_end, tz=tz) + elif schedule_type == ScheduleType.REGULAR: + return lazy_gettext("Time: {start_time} to {end_time} on Date: {date} ({tz})").format( + start_time=notification_time(start), + end_time=notification_time(end), + date=notification_date(start), + tz=tz, + ) + else: + # Default + return lazy_gettext("Date: {start_time} {start_date} to {end_time} {end_date} ({tz})").format( + start_time=notification_time(start), + start_date=notification_date(start), + end_time=notification_time(end), + end_date=notification_date(end), + tz=tz, + ) + + finally: + # clear session timezone + clear_session_timezone() + + def datetime_short(datetime): if datetime: return format_datetime(parse_date(datetime), app.config["DATETIME_FORMAT_SHORT"]) diff --git a/newsroom/templates/download_agenda.txt b/newsroom/templates/download_agenda.txt index cac9e2b5d..e312f3ef9 100644 --- a/newsroom/templates/download_agenda.txt +++ b/newsroom/templates/download_agenda.txt @@ -6,7 +6,7 @@ {% endif %} {%- if item.definition_long %}Definition: {{ item.definition_long }} {% endif -%} -On: {{ item.dates.start | datetime_long }} +{{ item | format_event_datetime }} {%- if item.subject %}Category: {{ item.subject|join(', ', attribute='name')}} {% endif %} diff --git a/newsroom/web/factory.py b/newsroom/web/factory.py index 2f9200196..53105886c 100644 --- a/newsroom/web/factory.py +++ b/newsroom/web/factory.py @@ -31,6 +31,7 @@ initials, get_highlighted_field, get_item_category_names, + format_event_datetime, ) from newsroom.template_loaders import LocaleTemplateLoader from newsroom.notifications.notifications import get_initial_notifications @@ -141,6 +142,7 @@ def _setup_jinja(self): self.add_template_filter(get_location_string, "location_string") self.add_template_filter(get_agenda_dates, "agenda_dates_string") self.add_template_filter(get_item_category_names, "category_names") + self.add_template_filter(format_event_datetime) self.jinja_loader = LocaleTemplateLoader(self._theme_folders) diff --git a/tests/core/test_template_filters.py b/tests/core/test_template_filters.py index dcb74fc89..9c2f3ff86 100644 --- a/tests/core/test_template_filters.py +++ b/tests/core/test_template_filters.py @@ -5,7 +5,7 @@ from datetime import datetime from flask_babel import lazy_gettext -from newsroom.template_filters import datetime_long, parse_date +from newsroom.template_filters import datetime_long, parse_date, format_event_datetime from newsroom.template_loaders import set_template_locale import newsroom.template_filters as template_filters @@ -50,3 +50,104 @@ def test_notification_date_time_filters(): assert "11:00" == template_filters.notification_time(d) assert "octobre 31, 2023" == template_filters.notification_date(d) assert "11:00 octobre 31, 2023" == template_filters.notification_datetime(d) + + +def test_format_event_datetime(): + # Case 1: Regular event with specific start and end times + event1 = { + "dates": { + "tz": "Asia/Calcutta", + "start": "2023-10-31T18:30:00+0000", + "end": "2023-11-01T20:45:00+0000", + "all_day": False, + "no_end_time": False, + }, + } + assert "Date: 01/11/2023 00:00 to 02/11/2023 02:15 (Asia/Calcutta)" == format_event_datetime(event1) + + # Case 2: All-day event + event2 = { + "dates": { + "tz": "Asia/Calcutta", + "end": "2023-12-18T18:29:59+0000", + "start": "2023-12-17T18:30:00+0000", + "all_day": True, + "no_end_time": False, + }, + } + assert "Date: 18/12/2023 00:00 (Asia/Calcutta)" == format_event_datetime(event2) + + # Case 3: Time-to-be-confirmed event + event3 = { + "dates": { + "tz": "Asia/Calcutta", + "start": "2023-10-31T18:30:00+0000", + "end": "2023-11-01T20:45:00+0000", + "all_day": False, + "no_end_time": False, + }, + "event": { + "_time_to_be_confirmed": True, + }, + } + assert "Date: 01/11/2023 00:00 to 02/11/2023 02:15 (Asia/Calcutta) (Time to be confirmed)" == format_event_datetime( + event3 + ) + + # Case 4: Event with no end time + event4 = { + "dates": { + "tz": "Asia/Calcutta", + "start": "2023-10-31T18:30:00+0000", + "all_day": False, + "no_end_time": True, + } + } + assert "Date: 01/11/2023 00:00 (Asia/Calcutta)" == format_event_datetime(event4) + + # Case 5: All-day event with no_end_time + event5 = { + "dates": { + "tz": "Asia/Calcutta", + "start": "2023-10-31T18:30:00+0000", + "end": "2023-11-01T18:29:59+0000", + "all_day": True, + "no_end_time": True, + }, + } + assert "Date: 01/11/2023 00:00 (Asia/Calcutta)" == format_event_datetime(event5) + + # Case 6: Multi-day event + event6 = { + "dates": { + "tz": "Asia/Calcutta", + "start": "2023-10-31T18:30:00+0000", + "end": "2023-11-02T20:45:00+0000", + "all_day": False, + "no_end_time": False, + }, + } + assert "Date: 01/11/2023 00:00 to 03/11/2023 02:15 (Asia/Calcutta)" == format_event_datetime(event6) + + # Case 7: REGULAR schedule_type with end_time + event7 = { + "dates": { + "tz": "Asia/Calcutta", + "start": "2023-11-01T19:29:59+0000", + "end": "2023-11-02T15:30:00+0000", + "all_day": False, + "no_end_time": False, + } + } + assert "Time: 00:59 AM to 21:00 PM on Date: November 2, 2023 (Asia/Calcutta)" == format_event_datetime(event7) + + # Case 8: REGULAR schedule_type with no end time + event8 = { + "dates": { + "tz": "Asia/Calcutta", + "start": "2023-11-01T18:30:00+0000", + "all_day": False, + "no_end_time": True, + }, + } + assert "Date: 02/11/2023 00:00 (Asia/Calcutta)" == format_event_datetime(event8)