Skip to content

Weeks that do not start on Monday

The "standard" ISO week starts on Monday and ends on Sunday. Retail, payroll and reporting calendars often do not: a week may start on Saturday, or on Sunday, or on whatever day the business decided years ago.

This page shows how to shift the start of the week with a custom offset. For the reasoning behind the design, see why iso-week-date?.

Shift the start of an IsoWeek

Subclass IsoWeek and set the offset_ class attribute to a timedelta:

from datetime import date, timedelta

from iso_week_date import IsoWeek


class SaturdayWeek(IsoWeek):
    """An IsoWeek whose weeks start on the Saturday before the standard ISO week."""

    offset_ = timedelta(days=-2)

That is all that is required. A negative offset moves the start of the week earlier, a positive one moves it later.

The same date now maps to a different week depending on which class you ask:

reference_day = date(2023, 1, 1)  # a Sunday

print(IsoWeek.from_date(reference_day))
print(SaturdayWeek.from_date(reference_day))
2022-W52
2023-W01

And the same week label now starts on a different date:

print(IsoWeek("2023-W01").nth(1))
print(SaturdayWeek("2023-W01").nth(1))
2023-01-02
2022-12-31

Everything else keeps working as before, offset included:

print(SaturdayWeek("2023-W01").days)
(datetime.date(2022, 12, 31), datetime.date(2023, 1, 1), datetime.date(2023, 1, 2), datetime.date(2023, 1, 3), datetime.date(2023, 1, 4), datetime.date(2023, 1, 5), datetime.date(2023, 1, 6))

Shift the start of an IsoWeekDate

IsoWeekDate works the same way:

from iso_week_date import IsoWeekDate


class SaturdayWeekDate(IsoWeekDate):
    """An IsoWeekDate whose weeks start on the Saturday before the standard ISO week."""

    offset_ = timedelta(days=-2)


print(SaturdayWeekDate.from_date(date(2023, 1, 1)))
2023-W01-2

Operations stay within a class

Comparisons and arithmetic require both operands to be instances of the same class. Since offset_ is a class attribute, this also guarantees they share the same offset, so you cannot accidentally compare two weeks that mean different spans of days:

try:
    SaturdayWeek("2023-W01") < IsoWeek("2023-W02")
except TypeError as e:
    print(f"TypeError: {e}")
TypeError: Cannot compare `SaturdayWeek` with type `<class 'iso_week_date.isoweek.IsoWeek'>`, comparison is supported only with other `SaturdayWeek` objects

Equality returns False rather than raising:

print(SaturdayWeek("2023-W01") == IsoWeek("2023-W01"))
False

Same offset is not enough

Two different classes that happen to share the same offset_ value still refuse to be compared: the check is on the class, not on the offset value. Define one class per business calendar and reuse it.

Offsets on dataframe series

The dataframe helpers take the offset as a plain offset argument instead of a subclass, using the same sign convention:

import polars as pl

from iso_week_date.polars_utils import datetime_to_isoweek

series = pl.date_range(date(2023, 1, 1), date(2023, 1, 3), interval="1d", eager=True)

print(datetime_to_isoweek(series).to_list())
print(datetime_to_isoweek(series, offset=timedelta(days=-2)).to_list())
['2022-W52', '2023-W01', '2023-W01']
['2023-W01', '2023-W01', '2023-W01']

See working with dataframes for the full set of series helpers.