Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import re
from typing import Optional, Sequence
from .dates_add_days import add_days, days_in_month, format_iso_date, parse_iso_date ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_add_days_types import CivilDate
from .education_academic_year_types import AcademicYear, Term
MONTH_DAY = re.compile(r"^(\d{2})-(\d{2})$")
def academic_year(on_date: str, year_start: Optional[str], terms: Sequence[Term]) -> AcademicYear:
"""The academic year and term for a date.
The end is the day before the next year's start, worked out by date
arithmetic, so a 1 March start ends on 29 February in a leap year.
"""
on = parse_iso_date(on_date)
start_text = "09-01" if year_start is None else year_start
match = MONTH_DAY.match(start_text) if isinstance(start_text, str) else None
month = int(match.group(1)) if match else 0
day = int(match.group(2)) if match else 0
if match is None or month < 1 or month > 12 or day < 1 or day > days_in_month(2001, month):
raise ValueError('yearStart must be MM-DD on a day every year has, received "%s"' % (start_text,))
ordered = sorted(terms, key=lambda t: t.start)
for t in ordered:
parse_iso_date(t.start)
parse_iso_date(t.end)
if t.end < t.start:
raise ValueError("term %s ends (%s) before it starts (%s)" % (t.name, t.end, t.start))
for i in range(1, len(ordered)):
if ordered[i].start <= ordered[i - 1].end:
raise ValueError("terms %s and %s overlap" % (ordered[i - 1].name, ordered[i].name))
before_start = (on.month, on.day) < (month, day)
start_year = on.year - 1 if before_start else on.year
start = format_iso_date(CivilDate(year=start_year, month=month, day=day))
end = add_days(format_iso_date(CivilDate(year=start_year + 1, month=month, day=day)), -1)
end_year = int(end[0:4])
label = str(start_year) if end_year == start_year else "%d/%02d" % (start_year, end_year % 100)
term = next((t for t in ordered if t.start <= on_date <= t.end), None)
return AcademicYear(
label=label,
start=start,
end=end,
term=None if term is None else term.name,
term_start=None if term is None else term.start,
term_end=None if term is None else term.end,
)