Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: The date in the downloaded Plain text file is not the event date [CPCN-388] #687

Merged
merged 18 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions newsroom/gettext.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
109 changes: 108 additions & 1 deletion newsroom/template_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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:
devketanpro marked this conversation as resolved.
Show resolved Hide resolved
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"])
Expand Down
2 changes: 1 addition & 1 deletion newsroom/templates/download_agenda.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
2 changes: 2 additions & 0 deletions newsroom/web/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
103 changes: 102 additions & 1 deletion tests/core/test_template_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Loading