agenda/agenda/types.py
Edward Betts ce9faa654f Add trips page
Creating a new entity called a trip. This will group together any travel
accommodation and conferences that happen together on one trip.

A trip is assumed to start when leaving home and finish when returning
home.

The start date of a trip in is the trip ID. The date is written in ISO
format.

This assumes there cannot be multiple trips one one day. This assumption
might be wrong, for example a morning day trip by rail, then another
trip starts in the afternoon. I can change my choice of using dates as
trip IDs if that happens.

Sometimes during the planning of a trip the start date is unknown. For
now we make up a start date, we can always change it later. If we use
the start date in URLs then the URLs will change. Might need to keep a
file of redirects, or could think of a different style of identifier.

Trip ID have been added to accommodation, conferences, trains and
flights.

Later there will be a trips.yaml with notes about each trip.
2024-01-04 22:56:07 +00:00

152 lines
3.9 KiB
Python

"""Types."""
import dataclasses
import datetime
import typing
from pycountry.db import Country
import agenda
from agenda import format_list_with_ampersand
StrDict = dict[str, typing.Any]
@dataclasses.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)
@property
def title(self) -> str:
"""Trip title."""
names = (
format_list_with_ampersand([conf["name"] for conf in self.conferences])
or "[no conferences in trip]"
)
return f'{names} ({self.date.strftime("%b %Y")})'
@property
def countries(self) -> set[Country]:
"""Trip countries."""
found: set[Country] = set()
for item in self.conferences + self.accommodation:
if "country" not in item:
continue
country = agenda.get_country(item["country"])
assert country
found.add(country)
return found
@property
def countries_str(self) -> str:
"""List of countries visited on this trip."""
return format_list_with_ampersand(
[f"{c.flag} {c.name}" for c in self.countries]
)
@dataclasses.dataclass
class Holiday:
"""Holiay."""
name: str
country: str
date: datetime.date
@dataclasses.dataclass
class Event:
"""Event."""
name: str
date: datetime.date | datetime.datetime
end_date: datetime.date | datetime.datetime | None = None
title: str | None = None
url: str | None = None
going: bool | None = None
@property
def as_datetime(self) -> datetime.datetime:
"""Date/time of event."""
d = self.date
t0 = datetime.datetime.min.time()
return (
d
if isinstance(d, datetime.datetime)
else datetime.datetime.combine(d, t0).replace(tzinfo=datetime.timezone.utc)
)
@property
def has_time(self) -> bool:
"""Event has a time associated with it."""
return isinstance(self.date, datetime.datetime)
@property
def as_date(self) -> datetime.date:
"""Date of event."""
return (
self.date.date() if isinstance(self.date, datetime.datetime) else self.date
)
@property
def end_as_date(self) -> datetime.date:
"""Date of event."""
return (
(
self.end_date.date()
if isinstance(self.end_date, datetime.datetime)
else self.end_date
)
if self.end_date
else self.as_date
)
@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
)
@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
)
def delta_days(self, today: datetime.date) -> str:
"""Return number of days from today as a string."""
delta = (self.as_date - today).days
match delta:
case 0:
return "today"
case 1:
return "1 day"
case _:
return f"{delta:,d} days"
@property
def display_date(self) -> str:
"""Date for display on web page."""
if isinstance(self.date, datetime.datetime):
return self.date.strftime("%a, %d, %b %Y %H:%M %z")
else:
return self.date.strftime("%a, %d, %b %Y")
@property
def display_title(self) -> str:
"""Name for display."""
return self.title or self.name