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.
152 lines
3.9 KiB
Python
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
|