diff --git a/agenda/types.py b/agenda/types.py index e3d5801..bf69cb7 100644 --- a/agenda/types.py +++ b/agenda/types.py @@ -1,8 +1,8 @@ """Types.""" -import dataclasses -import datetime import typing +from dataclasses import dataclass, field +from datetime import date, datetime, timezone from pycountry.db import Country @@ -12,23 +12,47 @@ from agenda import format_list_with_ampersand StrDict = dict[str, typing.Any] -@dataclasses.dataclass +def as_date(d: datetime | date) -> date: + """Convert datetime to date.""" + if isinstance(d, datetime): + return d.date() + assert isinstance(d, date) + return d + + +@dataclass class Trip: """Trip.""" - date: datetime.date - travel: list[StrDict] = dataclasses.field(default_factory=list) - accommodation: list[StrDict] = dataclasses.field(default_factory=list) - conferences: list[StrDict] = dataclasses.field(default_factory=list) + start: date + travel: list[StrDict] = field(default_factory=list) + accommodation: list[StrDict] = field(default_factory=list) + conferences: list[StrDict] = field(default_factory=list) @property def title(self) -> str: """Trip title.""" - names = ( + return ( format_list_with_ampersand([conf["name"] for conf in self.conferences]) - or "[no conferences in trip]" + or "[no conference]" ) - return f'{names} ({self.date.strftime("%b %Y")})' + + @property + def end(self) -> date | None: + """End date for trip.""" + max_conference_end = ( + max(as_date(item["end"]) for item in self.conferences) + if self.conferences + else date.min + ) + assert isinstance(max_conference_end, date) + + arrive = [item["arrive"].date() for item in self.travel if "arrive" in item] + travel_end = max(arrive) if arrive else date.min + assert isinstance(travel_end, date) + + max_date = max(max_conference_end, travel_end) + return max_date if max_date != date.min else None @property def countries(self) -> set[Country]: @@ -51,56 +75,54 @@ class Trip: ) -@dataclasses.dataclass +@dataclass class Holiday: """Holiay.""" name: str country: str - date: datetime.date + date: date -@dataclasses.dataclass +@dataclass class Event: """Event.""" name: str - date: datetime.date | datetime.datetime - end_date: datetime.date | datetime.datetime | None = None + date: date | datetime + end_date: date | datetime | None = None title: str | None = None url: str | None = None going: bool | None = None @property - def as_datetime(self) -> datetime.datetime: + def as_datetime(self) -> datetime: """Date/time of event.""" d = self.date - t0 = datetime.datetime.min.time() + t0 = datetime.min.time() return ( d - if isinstance(d, datetime.datetime) - else datetime.datetime.combine(d, t0).replace(tzinfo=datetime.timezone.utc) + if isinstance(d, datetime) + else datetime.combine(d, t0).replace(tzinfo=timezone.utc) ) @property def has_time(self) -> bool: """Event has a time associated with it.""" - return isinstance(self.date, datetime.datetime) + return isinstance(self.date, datetime) @property - def as_date(self) -> datetime.date: + def as_date(self) -> date: """Date of event.""" - return ( - self.date.date() if isinstance(self.date, datetime.datetime) else self.date - ) + return self.date.date() if isinstance(self.date, datetime) else self.date @property - def end_as_date(self) -> datetime.date: + def end_as_date(self) -> date: """Date of event.""" return ( ( self.end_date.date() - if isinstance(self.end_date, datetime.datetime) + if isinstance(self.end_date, datetime) else self.end_date ) if self.end_date @@ -110,22 +132,14 @@ class Event: @property def display_time(self) -> str | None: """Time for display on web page.""" - return ( - self.date.strftime("%H:%M") - if isinstance(self.date, datetime.datetime) - else None - ) + return self.date.strftime("%H:%M") if isinstance(self.date, datetime) else None @property def display_timezone(self) -> str | None: """Timezone for display on web page.""" - return ( - self.date.strftime("%z") - if isinstance(self.date, datetime.datetime) - else None - ) + return self.date.strftime("%z") if isinstance(self.date, datetime) else None - def delta_days(self, today: datetime.date) -> str: + def delta_days(self, today: date) -> str: """Return number of days from today as a string.""" delta = (self.as_date - today).days @@ -140,7 +154,7 @@ class Event: @property def display_date(self) -> str: """Date for display on web page.""" - if isinstance(self.date, datetime.datetime): + if isinstance(self.date, datetime): return self.date.strftime("%a, %d, %b %Y %H:%M %z") else: return self.date.strftime("%a, %d, %b %Y") diff --git a/templates/macros.html b/templates/macros.html index b279470..96651a9 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -1,5 +1,7 @@ {% macro display_datetime(dt) %}{{ dt.strftime("%a, %d, %b %Y %H:%M %z") }}{% endmacro %} {% macro display_time(dt) %}{{ dt.strftime("%H:%M %z") }}{% endmacro %} +{% macro display_date(dt) %}{{ dt.strftime("%a, %d, %b %Y") }}{% endmacro %} +{% macro display_date_no_year(dt) %}{{ dt.strftime("%a, %d, %b") }}{% endmacro %} {% macro conference_row(item, badge) %} {% set country = get_country(item.country) if item.country else None %} diff --git a/templates/trips.html b/templates/trips.html index 48005d3..7b4e2f4 100644 --- a/templates/trips.html +++ b/templates/trips.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% from "macros.html" import conference_row, accommodation_row, flight_row, train_row with context %} +{% from "macros.html" import display_date_no_year, conference_row, accommodation_row, flight_row, train_row with context %} {% block style %} {% set conference_column_count = 6 %} @@ -41,9 +41,15 @@

Trips

{% for trip in trips %} + {% set end = trip.end %}
-

{{ trip.title }}

-

Countries: {{ trip.countries_str }}

+

{{ trip.title }} ({{ trip.start.strftime("%b %Y") }})

+
Countries: {{ trip.countries_str }}
+ {% if end %} +
Dates: {{ display_date_no_year(trip.start) }} to {{ display_date_no_year(end) }}
+ {% else %} +
Start: {{ display_date_no_year(trip.start) }} (end date missing)
+ {% endif %}
{% for conf in trip.conferences %} {{ conference_row(conf, "going") }} diff --git a/web_view.py b/web_view.py index eedb29f..758ba3b 100755 --- a/web_view.py +++ b/web_view.py @@ -184,11 +184,11 @@ def trip_list() -> str: for key, item_list in data.items(): assert isinstance(item_list, list) for item in item_list: - if not (trip_id := item.get("trip")): + if not (start := item.get("trip")): continue - if trip_id not in trips: - trips[trip_id] = Trip(date=trip_id) - getattr(trips[trip_id], key).append(item) + if start not in trips: + trips[start] = Trip(start=start) + getattr(trips[start], key).append(item) trip_list = [trip for _, trip in sorted(trips.items(), reverse=True)]