Skip to content

Calendar Module

The solcadre.calendar module generates localized Solcadre calendars and provides helpers to map arbitrary datetimes to Solcadre dates and times.

calendar

Implementation of the Solcadre calendar system.

This module provides the core calendar functionality: generating localized days, weeks, blocks, and years aligned to solar events, as well as helpers to map arbitrary datetimes to Solcadre Day, Week, Block, Year, and Time objects.

Classes

Calendar dataclass

Calendar(latitude=CANONICAL_LATITUDE, longitude=CANONICAL_LONGITUDE, timezone=CANONICAL_TIMEZONE)

Calendar system that calculates days based on solar events and geographic location.

The calendar generates a sequence of days based on sunrise/sunset times for a given location. Seasons and transitions are automatically adjusted based on the hemisphere.

Attributes:

Name Type Description
latitude float

The latitude for solar calculations (defaults to canonical).

longitude float

The longitude for solar calculations (defaults to canonical).

timezone ZoneInfo

The timezone for solar calculations (defaults to canonical).

observer ZoneInfo

Astral observer object for solar calculations.

hemisphere ZoneInfo

The hemisphere determined from latitude.

epoch ZoneInfo

The first day of the calendar.

Functions
__post_init__
__post_init__()

Initialize the calendar with observer, hemisphere, and cached iterators.

Source code in src/solcadre/calendar.py
def __post_init__(self):
    """Initialize the calendar with observer, hemisphere, and cached iterators."""
    self.observer = astral.Observer(self.latitude, self.longitude)
    self.hemisphere = Hemisphere.NORTHERN if self.latitude > 0 else Hemisphere.SOUTHERN
    self._days = LazyList(self._calc_localized_days())
    self._weeks = LazyList(_group_weeks(self.iter_days()))
    self._blocks = LazyList(_group_blocks(self.iter_weeks()))
    self._years = LazyList(_group_years(self.iter_blocks()))
    self.epoch = next(day for day in self.iter_days())
block_of
block_of(obj)

Get the Block object that contains the given day or week.

Parameters:

Name Type Description Default
obj Day | Week

The Day or Week object to find the block for.

required

Returns:

Name Type Description
Block Block

The Block object (season or transition) containing the given object.

Source code in src/solcadre/calendar.py
def block_of(self, obj: Day | Week) -> Block:
    """Get the Block object that contains the given day or week.

    Args:
        obj: The Day or Week object to find the block for.

    Returns:
        Block: The Block object (season or transition) containing the given object.
    """
    return self.year_of(obj).blocks[obj.index.block_of_year]
find_block
find_block(time)

Find the Block object that contains the given datetime.

Parameters:

Name Type Description Default
time datetime

The datetime to search for.

required

Returns:

Type Description
Block | None

Block | None: The Block object containing the time, or None if not found.

Source code in src/solcadre/calendar.py
def find_block(self, time: datetime) -> Block | None:
    """Find the Block object that contains the given datetime.

    Args:
        time: The datetime to search for.

    Returns:
        Block | None: The Block object containing the time, or None if not found.
    """
    for block in self.iter_blocks():
        if block.start <= time < block.end:
            return block
    return None
find_day
find_day(time)

Find the Day object that contains the given datetime.

Parameters:

Name Type Description Default
time datetime

The datetime to search for.

required

Returns:

Type Description
Day | None

Day | None: The Day object containing the time, or None if not found.

Source code in src/solcadre/calendar.py
def find_day(self, time: datetime) -> Day | None:
    """Find the Day object that contains the given datetime.

    Args:
        time: The datetime to search for.

    Returns:
        Day | None: The Day object containing the time, or None if not found.
    """
    for day in self.iter_days():
        if day.start <= time < day.end:
            return day
    return None
find_time
find_time(time)

Find the Time object that corresponds to the given time point.

Parameters:

Name Type Description Default
time datetime

A timezone-aware datetime to locate within the calendar.

required

Returns:

Type Description
Time | None

Time | None: A Time object representating the time point, or None if not found.

Source code in src/solcadre/calendar.py
def find_time(self, time: datetime) -> Time | None:
    """Find the Time object that corresponds to the given time point.

    Args:
        time: A timezone-aware datetime to locate within the calendar.

    Returns:
        Time | None: A Time object representating the time point, or None if not found.
    """
    day = self.find_day(time)
    if day is None:
        return None
    return Time(day, day.time_of_day(time.astimezone(self.timezone)))
find_week
find_week(time)

Find the Week object that contains the given datetime.

Parameters:

Name Type Description Default
time datetime

The datetime to search for.

required

Returns:

Type Description
Week | None

Week | None: The Week object containing the time, or None if not found.

Source code in src/solcadre/calendar.py
def find_week(self, time: datetime) -> Week | None:
    """Find the Week object that contains the given datetime.

    Args:
        time: The datetime to search for.

    Returns:
        Week | None: The Week object containing the time, or None if not found.
    """
    for week in self.iter_weeks():
        if week.start <= time < week.end:
            return week
    return None
find_year
find_year(time)

Find the Year object that contains the given datetime.

Parameters:

Name Type Description Default
time datetime

The datetime to search for.

required

Returns:

Type Description
Year | None

Year | None: The Year object containing the time, or None if not found.

Source code in src/solcadre/calendar.py
def find_year(self, time: datetime) -> Year | None:
    """Find the Year object that contains the given datetime.

    Args:
        time: The datetime to search for.

    Returns:
        Year | None: The Year object containing the time, or None if not found.
    """
    for year in self.iter_years():
        if year.start <= time < year.end:
            return year
    return None
iter_blocks
iter_blocks(start_block=None, end_block=None)

Iterate over blocks (seasons and transitions) in the calendar.

Parameters:

Name Type Description Default
start_block Block | None

Optional Block object to start iteration from (inclusive). If None, starts from the beginning.

None
end_block Block | None

Optional Block object to end iteration at (exclusive). If None, continues to the end.

None

Returns:

Type Description
Iterable[Block]

Iterable[Block]: An iterator over Block objects in chronological order.

Source code in src/solcadre/calendar.py
def iter_blocks(self, start_block : Block | None = None, end_block : Block | None = None) -> Iterable[Block]:
    """Iterate over blocks (seasons and transitions) in the calendar.

    Args:
        start_block: Optional Block object to start iteration from (inclusive).
            If None, starts from the beginning.
        end_block: Optional Block object to end iteration at (exclusive).
            If None, continues to the end.

    Returns:
        Iterable[Block]: An iterator over Block objects in chronological order.
    """
    start_index = None if start_block is None else start_block.index.block_of_calendar
    end_index = None if end_block is None else end_block.index.block_of_calendar
    return self._blocks.iter_items(start_index, end_index)
iter_days
iter_days(start_day=None, end_day=None)

Iterate over days in the calendar.

Parameters:

Name Type Description Default
start_day Day | None

Optional Day object to start iteration from (inclusive). If None, starts from the beginning.

None
end_day Day | None

Optional Day object to end iteration at (exclusive). If None, continues to the end.

None

Returns:

Type Description
Iterable[Day]

Iterable[Day]: An iterator over Day objects in chronological order.

Source code in src/solcadre/calendar.py
def iter_days(self, start_day : Day | None = None, end_day : Day | None = None) -> Iterable[Day]:
    """Iterate over days in the calendar.

    Args:
        start_day: Optional Day object to start iteration from (inclusive).
            If None, starts from the beginning.
        end_day: Optional Day object to end iteration at (exclusive).
            If None, continues to the end.

    Returns:
        Iterable[Day]: An iterator over Day objects in chronological order.
    """
    start_index = None if start_day is None else start_day.index.day_of_calendar
    end_index = None if end_day is None else end_day.index.day_of_calendar
    return self._days.iter_items(start_index, end_index)
iter_weeks
iter_weeks(start_week=None, end_week=None)

Iterate over weeks in the calendar.

Parameters:

Name Type Description Default
start_week Week | None

Optional Week object to start iteration from (inclusive). If None, starts from the beginning.

None
end_week Week | None

Optional Week object to end iteration at (exclusive). If None, continues to the end.

None

Returns:

Type Description
Iterable[Week]

Iterable[Week]: An iterator over Week objects in chronological order.

Source code in src/solcadre/calendar.py
def iter_weeks(self, start_week : Week | None = None, end_week : Week | None = None) -> Iterable[Week]:
    """Iterate over weeks in the calendar.

    Args:
        start_week: Optional Week object to start iteration from (inclusive).
            If None, starts from the beginning.
        end_week: Optional Week object to end iteration at (exclusive).
            If None, continues to the end.

    Returns:
        Iterable[Week]: An iterator over Week objects in chronological order.
    """
    start_index = None if start_week is None else start_week.index.week_of_calendar
    end_index = None if end_week is None else end_week.index.week_of_calendar
    return self._weeks.iter_items(start_index, end_index)
iter_years
iter_years(start_year=None, end_year=None)

Iterate over years in the calendar.

Parameters:

Name Type Description Default
start_year Year | None

Optional Year object to start iteration from (inclusive). If None, starts from the beginning.

None
end_year Year | None

Optional Year object to end iteration at (exclusive). If None, continues to the end.

None

Returns:

Type Description
Iterable[Year]

Iterable[Year]: An iterator over Year objects in chronological order.

Source code in src/solcadre/calendar.py
def iter_years(self, start_year : Year | None = None, end_year : Year | None = None) -> Iterable[Year]:
    """Iterate over years in the calendar.

    Args:
        start_year: Optional Year object to start iteration from (inclusive).
            If None, starts from the beginning.
        end_year: Optional Year object to end iteration at (exclusive).
            If None, continues to the end.

    Returns:
        Iterable[Year]: An iterator over Year objects in chronological order.
    """
    start_index = None if start_year is None else start_year.index.year_of_calendar
    end_index = None if end_year is None else end_year.index.year_of_calendar
    return self._years.iter_items(start_index, end_index)
week_of
week_of(obj)

Get the Week object that contains the given day.

Parameters:

Name Type Description Default
obj Day

The Day object to find the week for.

required

Returns:

Name Type Description
Week Week

The Week object containing the given day.

Source code in src/solcadre/calendar.py
def week_of(self, obj: Day) -> Week:
    """Get the Week object that contains the given day.

    Args:
        obj: The Day object to find the week for.

    Returns:
        Week: The Week object containing the given day.
    """
    return self.block_of(obj).weeks[obj.index.week_of_block]
year_of
year_of(obj)

Get the Year object that contains the given day, week, or block.

Parameters:

Name Type Description Default
obj Day | Week | Block

The Day, Week, or Block object to find the year for.

required

Returns:

Name Type Description
Year Year

The Year object containing the given object.

Source code in src/solcadre/calendar.py
def year_of(self, obj: Day | Week | Block) -> Year:
    """Get the Year object that contains the given day, week, or block.

    Args:
        obj: The Day, Week, or Block object to find the year for.

    Returns:
        Year: The Year object containing the given object.
    """
    year = self._years[obj.index.year_of_calendar]
    assert year is not None
    return year

InvalidLatitude

InvalidLatitude(latitude, message)

Bases: Exception

Exception raised when a latitude value is invalid for Solcadre.

This occurs when the latitude is too close to the poles, resulting in days where sunrise or sunset cannot be calculated.

Parameters:

Name Type Description Default
latitude

The invalid latitude value.

required
message

Additional error message describing why the latitude is invalid.

required
Source code in src/solcadre/calendar.py
def __init__(self, latitude, message):
    super().__init__(f"latitude({latitude}) is invalid: {message}")

Calendar

Calendar dataclass

Calendar(latitude=CANONICAL_LATITUDE, longitude=CANONICAL_LONGITUDE, timezone=CANONICAL_TIMEZONE)

Calendar system that calculates days based on solar events and geographic location.

The calendar generates a sequence of days based on sunrise/sunset times for a given location. Seasons and transitions are automatically adjusted based on the hemisphere.

Attributes:

Name Type Description
latitude float

The latitude for solar calculations (defaults to canonical).

longitude float

The longitude for solar calculations (defaults to canonical).

timezone ZoneInfo

The timezone for solar calculations (defaults to canonical).

observer ZoneInfo

Astral observer object for solar calculations.

hemisphere ZoneInfo

The hemisphere determined from latitude.

epoch ZoneInfo

The first day of the calendar.

Functions

__post_init__

__post_init__()

Initialize the calendar with observer, hemisphere, and cached iterators.

Source code in src/solcadre/calendar.py
def __post_init__(self):
    """Initialize the calendar with observer, hemisphere, and cached iterators."""
    self.observer = astral.Observer(self.latitude, self.longitude)
    self.hemisphere = Hemisphere.NORTHERN if self.latitude > 0 else Hemisphere.SOUTHERN
    self._days = LazyList(self._calc_localized_days())
    self._weeks = LazyList(_group_weeks(self.iter_days()))
    self._blocks = LazyList(_group_blocks(self.iter_weeks()))
    self._years = LazyList(_group_years(self.iter_blocks()))
    self.epoch = next(day for day in self.iter_days())

block_of

block_of(obj)

Get the Block object that contains the given day or week.

Parameters:

Name Type Description Default
obj Day | Week

The Day or Week object to find the block for.

required

Returns:

Name Type Description
Block Block

The Block object (season or transition) containing the given object.

Source code in src/solcadre/calendar.py
def block_of(self, obj: Day | Week) -> Block:
    """Get the Block object that contains the given day or week.

    Args:
        obj: The Day or Week object to find the block for.

    Returns:
        Block: The Block object (season or transition) containing the given object.
    """
    return self.year_of(obj).blocks[obj.index.block_of_year]

find_block

find_block(time)

Find the Block object that contains the given datetime.

Parameters:

Name Type Description Default
time datetime

The datetime to search for.

required

Returns:

Type Description
Block | None

Block | None: The Block object containing the time, or None if not found.

Source code in src/solcadre/calendar.py
def find_block(self, time: datetime) -> Block | None:
    """Find the Block object that contains the given datetime.

    Args:
        time: The datetime to search for.

    Returns:
        Block | None: The Block object containing the time, or None if not found.
    """
    for block in self.iter_blocks():
        if block.start <= time < block.end:
            return block
    return None

find_day

find_day(time)

Find the Day object that contains the given datetime.

Parameters:

Name Type Description Default
time datetime

The datetime to search for.

required

Returns:

Type Description
Day | None

Day | None: The Day object containing the time, or None if not found.

Source code in src/solcadre/calendar.py
def find_day(self, time: datetime) -> Day | None:
    """Find the Day object that contains the given datetime.

    Args:
        time: The datetime to search for.

    Returns:
        Day | None: The Day object containing the time, or None if not found.
    """
    for day in self.iter_days():
        if day.start <= time < day.end:
            return day
    return None

find_time

find_time(time)

Find the Time object that corresponds to the given time point.

Parameters:

Name Type Description Default
time datetime

A timezone-aware datetime to locate within the calendar.

required

Returns:

Type Description
Time | None

Time | None: A Time object representating the time point, or None if not found.

Source code in src/solcadre/calendar.py
def find_time(self, time: datetime) -> Time | None:
    """Find the Time object that corresponds to the given time point.

    Args:
        time: A timezone-aware datetime to locate within the calendar.

    Returns:
        Time | None: A Time object representating the time point, or None if not found.
    """
    day = self.find_day(time)
    if day is None:
        return None
    return Time(day, day.time_of_day(time.astimezone(self.timezone)))

find_week

find_week(time)

Find the Week object that contains the given datetime.

Parameters:

Name Type Description Default
time datetime

The datetime to search for.

required

Returns:

Type Description
Week | None

Week | None: The Week object containing the time, or None if not found.

Source code in src/solcadre/calendar.py
def find_week(self, time: datetime) -> Week | None:
    """Find the Week object that contains the given datetime.

    Args:
        time: The datetime to search for.

    Returns:
        Week | None: The Week object containing the time, or None if not found.
    """
    for week in self.iter_weeks():
        if week.start <= time < week.end:
            return week
    return None

find_year

find_year(time)

Find the Year object that contains the given datetime.

Parameters:

Name Type Description Default
time datetime

The datetime to search for.

required

Returns:

Type Description
Year | None

Year | None: The Year object containing the time, or None if not found.

Source code in src/solcadre/calendar.py
def find_year(self, time: datetime) -> Year | None:
    """Find the Year object that contains the given datetime.

    Args:
        time: The datetime to search for.

    Returns:
        Year | None: The Year object containing the time, or None if not found.
    """
    for year in self.iter_years():
        if year.start <= time < year.end:
            return year
    return None

iter_blocks

iter_blocks(start_block=None, end_block=None)

Iterate over blocks (seasons and transitions) in the calendar.

Parameters:

Name Type Description Default
start_block Block | None

Optional Block object to start iteration from (inclusive). If None, starts from the beginning.

None
end_block Block | None

Optional Block object to end iteration at (exclusive). If None, continues to the end.

None

Returns:

Type Description
Iterable[Block]

Iterable[Block]: An iterator over Block objects in chronological order.

Source code in src/solcadre/calendar.py
def iter_blocks(self, start_block : Block | None = None, end_block : Block | None = None) -> Iterable[Block]:
    """Iterate over blocks (seasons and transitions) in the calendar.

    Args:
        start_block: Optional Block object to start iteration from (inclusive).
            If None, starts from the beginning.
        end_block: Optional Block object to end iteration at (exclusive).
            If None, continues to the end.

    Returns:
        Iterable[Block]: An iterator over Block objects in chronological order.
    """
    start_index = None if start_block is None else start_block.index.block_of_calendar
    end_index = None if end_block is None else end_block.index.block_of_calendar
    return self._blocks.iter_items(start_index, end_index)

iter_days

iter_days(start_day=None, end_day=None)

Iterate over days in the calendar.

Parameters:

Name Type Description Default
start_day Day | None

Optional Day object to start iteration from (inclusive). If None, starts from the beginning.

None
end_day Day | None

Optional Day object to end iteration at (exclusive). If None, continues to the end.

None

Returns:

Type Description
Iterable[Day]

Iterable[Day]: An iterator over Day objects in chronological order.

Source code in src/solcadre/calendar.py
def iter_days(self, start_day : Day | None = None, end_day : Day | None = None) -> Iterable[Day]:
    """Iterate over days in the calendar.

    Args:
        start_day: Optional Day object to start iteration from (inclusive).
            If None, starts from the beginning.
        end_day: Optional Day object to end iteration at (exclusive).
            If None, continues to the end.

    Returns:
        Iterable[Day]: An iterator over Day objects in chronological order.
    """
    start_index = None if start_day is None else start_day.index.day_of_calendar
    end_index = None if end_day is None else end_day.index.day_of_calendar
    return self._days.iter_items(start_index, end_index)

iter_weeks

iter_weeks(start_week=None, end_week=None)

Iterate over weeks in the calendar.

Parameters:

Name Type Description Default
start_week Week | None

Optional Week object to start iteration from (inclusive). If None, starts from the beginning.

None
end_week Week | None

Optional Week object to end iteration at (exclusive). If None, continues to the end.

None

Returns:

Type Description
Iterable[Week]

Iterable[Week]: An iterator over Week objects in chronological order.

Source code in src/solcadre/calendar.py
def iter_weeks(self, start_week : Week | None = None, end_week : Week | None = None) -> Iterable[Week]:
    """Iterate over weeks in the calendar.

    Args:
        start_week: Optional Week object to start iteration from (inclusive).
            If None, starts from the beginning.
        end_week: Optional Week object to end iteration at (exclusive).
            If None, continues to the end.

    Returns:
        Iterable[Week]: An iterator over Week objects in chronological order.
    """
    start_index = None if start_week is None else start_week.index.week_of_calendar
    end_index = None if end_week is None else end_week.index.week_of_calendar
    return self._weeks.iter_items(start_index, end_index)

iter_years

iter_years(start_year=None, end_year=None)

Iterate over years in the calendar.

Parameters:

Name Type Description Default
start_year Year | None

Optional Year object to start iteration from (inclusive). If None, starts from the beginning.

None
end_year Year | None

Optional Year object to end iteration at (exclusive). If None, continues to the end.

None

Returns:

Type Description
Iterable[Year]

Iterable[Year]: An iterator over Year objects in chronological order.

Source code in src/solcadre/calendar.py
def iter_years(self, start_year : Year | None = None, end_year : Year | None = None) -> Iterable[Year]:
    """Iterate over years in the calendar.

    Args:
        start_year: Optional Year object to start iteration from (inclusive).
            If None, starts from the beginning.
        end_year: Optional Year object to end iteration at (exclusive).
            If None, continues to the end.

    Returns:
        Iterable[Year]: An iterator over Year objects in chronological order.
    """
    start_index = None if start_year is None else start_year.index.year_of_calendar
    end_index = None if end_year is None else end_year.index.year_of_calendar
    return self._years.iter_items(start_index, end_index)

week_of

week_of(obj)

Get the Week object that contains the given day.

Parameters:

Name Type Description Default
obj Day

The Day object to find the week for.

required

Returns:

Name Type Description
Week Week

The Week object containing the given day.

Source code in src/solcadre/calendar.py
def week_of(self, obj: Day) -> Week:
    """Get the Week object that contains the given day.

    Args:
        obj: The Day object to find the week for.

    Returns:
        Week: The Week object containing the given day.
    """
    return self.block_of(obj).weeks[obj.index.week_of_block]

year_of

year_of(obj)

Get the Year object that contains the given day, week, or block.

Parameters:

Name Type Description Default
obj Day | Week | Block

The Day, Week, or Block object to find the year for.

required

Returns:

Name Type Description
Year Year

The Year object containing the given object.

Source code in src/solcadre/calendar.py
def year_of(self, obj: Day | Week | Block) -> Year:
    """Get the Year object that contains the given day, week, or block.

    Args:
        obj: The Day, Week, or Block object to find the year for.

    Returns:
        Year: The Year object containing the given object.
    """
    year = self._years[obj.index.year_of_calendar]
    assert year is not None
    return year

Exceptions

InvalidLatitude

InvalidLatitude(latitude, message)

Bases: Exception

Exception raised when a latitude value is invalid for Solcadre.

This occurs when the latitude is too close to the poles, resulting in days where sunrise or sunset cannot be calculated.

Parameters:

Name Type Description Default
latitude

The invalid latitude value.

required
message

Additional error message describing why the latitude is invalid.

required
Source code in src/solcadre/calendar.py
def __init__(self, latitude, message):
    super().__init__(f"latitude({latitude}) is invalid: {message}")