Skip to content
Snippets Groups Projects
calendarweek.py 4.67 KiB
# Copyright 2019, 2020 Dominik George <dominik.george@teckids.org>
# Copyright 2020 Jonathan Weth <wethjo@katharineum.de>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

import calendar
from dataclasses import dataclass
from datetime import date, datetime, timedelta
import locale
from typing import Optional, Sequence, Tuple, Union

from .util import normalise_locale


@dataclass
class CalendarWeek:
    """ A calendar week defined by year and ISO week number. """

    year: Optional[int] = None
    week: Optional[int] = None

    @classmethod
    def day_names(cls, loc: Optional[str] = None) -> Tuple[str]:
        """ Return a tuple of day names for the selected locale. """

        with calendar.different_locale(normalise_locale(loc)):
            return tuple(calendar.day_name)

    @classmethod
    def day_abbrs(cls, loc: Optional[str] = None) -> Tuple[str]:
        """ Return a tuple of day name abbreviations for the selected locale. """

        with calendar.different_locale(normalise_locale(loc)):
            return tuple(calendar.day_abbr)

    @classmethod
    def month_names(cls, loc: Optional[str] = None) -> Tuple[str]:
        """ Return a tuple of month names for the selected locale. """

        with calendar.different_locale(normalise_locale(loc)):
            return tuple(calendar.month_name[1:])

    @classmethod
    def month_abbrs(cls, loc: Optional[str] = None) -> Tuple[str]:
        """ Return a tuple of month name abbreviations for the selected locale. """

        with calendar.different_locale(normalise_locale(loc)):
            return tuple(calendar.month_abbr[1:])

    @classmethod
    def from_date(cls, when: date):
        """ Get the calendar week by a date object (the week this date is in). """

        week = int(when.strftime("%V"))
        year = when.year

        if when.month == 12 and week == 1:
            year += 1
        elif when.month == 1 and (week == 52 or week == 53):
            year -= 1

        return cls(year=year, week=week)

    @classmethod
    def current_week(cls) -> int:
        """ Get the current week number. """

        return cls().week

    @classmethod
    def weeks_within(cls, start: date, end: date) -> Sequence[CalendarWeek]:
        """ Get all calendar weeks within a date range. """

        if start > end:
            raise ValueError("End date must be after start date.")

        current = start
        weeks = []
        while current < end:
            weeks.append(cls.from_date(current))
            current += timedelta(days=7)

        return weeks

    @classmethod
    def get_last_week_of_year(cls, year: int) -> CalendarWeek:
        """Get the last week of a year."""

        last_week = date(year, 12, 28).isocalendar()[1]
        return cls(week=last_week, year=year)

    def __post_init__(self) -> None:
        today = date.today()

        if not self.year:
            self.year = today.year
        if not self.week:
            self.week = int(today.strftime("%V"))

    def __str__(self) -> str:
        return "Week %d (%s to %s)" % (self.week, self[0], self[-1],)

    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__.from_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 __le__(self, other: CalendarWeek) -> bool:
        return self[0] <= other[0]

    def __gr__(self, other: CalendarWeek) -> bool:
        return self[0] >= other[0]

    def __add__(self, weeks: int) -> CalendarWeek:
        return self.__class__.from_date(self[0] + timedelta(days=weeks * 7))

    def __sub__(self, weeks: int) -> CalendarWeek:
        return self.__class__.from_date(self[0] - timedelta(days=weeks * 7))