diff --git a/biscuit/apps/chronos/templates/chronos/edit_substitution.html b/biscuit/apps/chronos/templates/chronos/edit_substitution.html
index 054b9117ae1f9235cf5a6a3746561767f4ae39a8..c205e848dffab59853326f3fb8e93630cd49515f 100644
--- a/biscuit/apps/chronos/templates/chronos/edit_substitution.html
+++ b/biscuit/apps/chronos/templates/chronos/edit_substitution.html
@@ -11,6 +11,7 @@
   <div class="d-flex justify-content-between">
     <div class="btn-group" role="group" aria-label="Day actions">
       {% if substitution %}
+        {# FIXME Respect year as well #}
         <a href="{% url 'delete_substitution' substitution.lesson_period.id substitution.week %}" class="btn btn-danger">
           {% fa 'trash-o' %}
         </a>
diff --git a/biscuit/apps/chronos/util.py b/biscuit/apps/chronos/util.py
index 9781ac55e9ee583316b36b112cfee652ad9da152..cfc5f8d1ebffe2197d09e08584f050084203f4bb 100644
--- a/biscuit/apps/chronos/util.py
+++ b/biscuit/apps/chronos/util.py
@@ -1,24 +1,67 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
 from datetime import date, datetime, timedelta
-from typing import Optional, Sequence
+from typing import Optional, Sequence, Tuple
 
 from django.apps import apps
 from django.db import models
 
 
-def current_week() -> int:
-    return int(datetime.now().strftime('%V'))
+@dataclass
+class CalendarWeek:
+    """ A calendar week defined by year and ISO week number. """
+
+    year: Optional[int] = None
+    week: Optional[int] = None
+
+    @classmethod
+    def from_date(cls, when: date):
+        return cls(year=when.strftime('%Y'), week=when.strftime('%V'))
+
+    def __post_init__(self) -> None:
+        today = date.today()
+
+        if not self.year:
+            self.year = today.year
+        if not self.week:
+            self.week = today.isoweekday()
+
+    def __len__(self) -> int:
+        return 7
+
+    def __getitem__(self, n: int) -> date:
+        if n < -7 or n > 6:
+            raise IndexError('Week day %d is out of range.' % n)
+
+        if n < 0:
+            n += 7
+
+        return datetime.strptime('%d-%d-%d' % (self.year, self.week, n + 1), '%G-%V-%u').date()
+
+    def __contains__(self, day: date) -> bool:
+        return self.__class__.form_date(day) == self
+
+    def __eq__(self, other: CalendarWeek) -> bool:
+        return self.year == other.year and self.week == other.week
+
+    def __lt__(self, other: CalendarWeek) -> bool:
+        return self[0] < other[0]
 
+    def __gt__(self, other: CalendarWeek) -> bool:
+        return self[0] > other[0]
 
-def week_days(week: Optional[int]) -> Sequence[date]:
-    # FIXME Make this aware of the school term concept
-    # cf. BiscuIT-ng#40
+    def __le__(self, other: CalendarWeek) -> bool:
+        return self[0] <= other[0]
 
-    year = date.today().year
-    wanted_week = week or current_week()
+    def __gr__(self, other: CalendarWeek) -> bool:
+        return self[0] >= other[0]
 
-    first_day = datetime.strptime('%d-%d-1' % (year, wanted_week), '%G-%V-%u')
+    def __add__(self, weeks: int) -> CalendarWeek:
+        return self.__class__.from_date(self[0] + timedelta(days=weeks * 7))
 
-    return [(first_day + timedelta(days=offset)).date() for offset in range(0, 7)]
+    def __sub__(self, weeks: int) -> CalendarWeek:
+        return self.__class__.from_date(self[0] - timedelta(days=weeks * 7))
 
 
 def current_lesson_periods(when: Optional[datetime] = None) -> models.query.QuerySet:
@@ -32,9 +75,9 @@ def current_lesson_periods(when: Optional[datetime] = None) -> models.query.Quer
                                        period__time_end__gte=now.time())
 
 
-def week_weekday_from_date(when: date) -> Sequence[int]:
-    return (int(when.strftime('%V')), int(when.strftime('%u')))
+def week_weekday_from_date(when: date) -> Tuple[CalendarWeek, int]:
+    return (CalendarWeek.from_date(when), when.isoweekday())
 
 
-def week_weekday_to_date(week: int, weekday: int) -> date:
-    return week_days(week)[weekday-1]
+def week_weekday_to_date(week: CalendarWeek, weekday: int) -> date:
+    return week[weekday - 1]
diff --git a/biscuit/apps/chronos/views.py b/biscuit/apps/chronos/views.py
index 03c639a30bf03916d1bda9079e315b8bbf0ba17f..67939a88148ca8c2c04c4dca9cf4e1dbaa647177 100644
--- a/biscuit/apps/chronos/views.py
+++ b/biscuit/apps/chronos/views.py
@@ -17,7 +17,7 @@ from biscuit.core.util import messages
 
 from .forms import SelectForm, LessonSubstitutionForm
 from .models import LessonPeriod, TimePeriod, LessonSubstitution
-from .util import current_week, week_weekday_from_date, week_days
+from .util import CalendarWeek, week_weekday_from_date
 from .tables import LessonsTable
 
 
@@ -25,17 +25,20 @@ from .tables import LessonsTable
 def timetable(request: HttpRequest, week: Optional[int] = None) -> HttpResponse:
     context = {}
 
-    wanted_week = week or current_week()
+    if week:
+        wanted_week = CalendarWeek(week)  # FIXME Respect year as well
+    else:
+        wanted_week = CalendarWeek()
 
     lesson_periods = LessonPeriod.objects.filter(
-        lesson__date_start__lte=week_days(wanted_week)[0],
-        lesson__date_end__gte=week_days(wanted_week)[-1]
+        lesson__date_start__lte=wanted_week[0],
+        lesson__date_end__gte=wanted_week[-1]
     ).select_related(
         'lesson', 'lesson__subject', 'period', 'room'
     ).prefetch_related(
         'lesson__groups', 'lesson__teachers', 'substitutions'
     ).extra(
-        select={'_week': wanted_week}
+        select={'_week': wanted_week.week}  # FIXME respect year as well
     )
 
     if request.GET.get('group', None) or request.GET.get('teacher', None) or request.GET.get('room', None):
@@ -45,7 +48,7 @@ def timetable(request: HttpRequest, week: Optional[int] = None) -> HttpResponse:
                 Q(lesson__groups__pk=int(request.GET['group'])) | Q(lesson__groups__parent_groups__pk=int(request.GET['group'])))
         if 'teacher' in request.GET and request.GET['teacher']:
             lesson_periods = lesson_periods.filter(
-                Q(substitutions__teachers__pk=int(request.GET['teacher']), substitutions__week=wanted_week) | Q(lesson__teachers__pk=int(request.GET['teacher'])))
+                Q(substitutions__teachers__pk=int(request.GET['teacher']), substitutions__week=wanted_week.week) | Q(lesson__teachers__pk=int(request.GET['teacher'])))  # FIXME Respect year as well
         if 'room' in request.GET and request.GET['room']:
             lesson_periods = lesson_periods.filter(
                 room__pk=int(request.GET['room']))
@@ -93,10 +96,13 @@ def timetable(request: HttpRequest, week: Optional[int] = None) -> HttpResponse:
     context['periods'] = TimePeriod.get_times_dict()
     context['weekdays'] = dict(TimePeriod.WEEKDAY_CHOICES)
     context['week'] = wanted_week
-    context['url_prev'] = '%s?%s' % (reverse('timetable_by_week', week=wanted_week - 1), request.GET.urlencode())
-    context['url_next'] = '%s?%s' % (reverse('timetable_by_week', week=wanted_week + 1), request.GET.urlencode())
     context['select_form'] = select_form
 
+    week_prev = wanted_week - 1
+    week_next = wanted_week + 1
+    context['url_prev'] = '%s?%s' % (reverse('timetable_by_week', year=week_prev.year, week=week_prev.week), request.GET.urlencode())
+    context['url_next'] = '%s?%s' % (reverse('timetable_by_week', year=week_next.year, week=week_next.week), request.GET.urlencode())
+
     return render(request, 'chronos/tt_week.html', context)
 
 
@@ -118,7 +124,7 @@ def lessons_day(request: HttpRequest, when: Optional[str] = None) -> HttpRespons
     )
 
     # Build table
-    lessons_table = LessonsTable(lesson_periods.extra(select={'_week': week}).all())
+    lessons_table = LessonsTable(lesson_periods.extra(select={'_week': week.week}).all())  # FIXME Respect year as well
     RequestConfig(request).configure(lessons_table)
 
     context['current_head'] = _('Lessons')
@@ -136,16 +142,18 @@ def lessons_day(request: HttpRequest, when: Optional[str] = None) -> HttpRespons
 def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
     context = {}
 
+    wanted_week = CalendarWeek(week=week)  # FIXME Respect year as well
+
     lesson_period = get_object_or_404(LessonPeriod, pk=id_)
 
     lesson_substitution = LessonSubstitution.objects.filter(
-        week=week, lesson_period=lesson_period).first()
+        week=wanted_week.week, lesson_period=lesson_period).first()  # FIXME Respect year as well
     if lesson_substitution:
         edit_substitution_form = LessonSubstitutionForm(
             request.POST or None, instance=lesson_substitution)
     else:
         edit_substitution_form = LessonSubstitutionForm(
-            request.POST or None, initial={'week': week, 'lesson_period': lesson_period})
+            request.POST or None, initial={'week': wanted_week.week, 'lesson_period': lesson_period})  # FIXME Respect year as well
 
     context['substitution'] = lesson_substitution
 
@@ -154,7 +162,7 @@ def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse
             edit_substitution_form.save(commit=True)
 
             messages.success(request, _('The substitution has been saved.'))
-            return redirect('lessons_day_by_date', when=week_days(week)[lesson_period.period.weekday - 1].strftime('%Y-%m-%d'))
+            return redirect('lessons_day_by_date', when=wanted_week[lesson_period.period.weekday - 1].strftime('%Y-%m-%d'))
 
     context['edit_substitution_form'] = edit_substitution_form
 
@@ -165,9 +173,11 @@ def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse
 def delete_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
     context = {}
 
+    wanted_week = CalendarWeek(week=week)  # FIXME Respect year as well
+
     LessonSubstitution.objects.filter(
-        week=week, lesson_period__id=id_
+        week=wanted_week.week, lesson_period__id=id_  # FIXME Respect year as well
     ).delete()
 
     messages.success(request, _('The substitution has been deleted.'))
-    return redirect('lessons_day_by_date', when=week_days(week)[lesson_period.period.weekday - 1].strftime('%Y-%m-%d'))
+    return redirect('lessons_day_by_date', when=wanted_week[lesson_period.period.weekday - 1].strftime('%Y-%m-%d'))