freedays 0.3.0: WORKDAYS (Tue/Wed/Thu) as the one weekly-workday definition for every gate app

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-09-27 20:06:32 +02:00
parent 0af237af3d
commit 4bd6bce0df
5 changed files with 91 additions and 1 deletions

View File

@ -52,6 +52,15 @@ app launch.
| **Releasing refunds only the future** | Give back a day that has not arrived and it returns to the pool. Give back today, and it does not: those hours were already unguarded. |
| **Silent** | No notification, no low-budget warning, no prompt when a gate fires. You find out by asking. |
## Weekly workdays
`freedays.WORKDAYS` (`{1, 2, 3}`, Tue/Wed/Thu in `date.weekday()` numbering),
`NON_WORKDAYS` and `is_workday(day=None)` are the one definition of the weekly
workdays every gate app reads — screen-locker's relaxed days, leetcode-guard's
cheap days, wake-alarm's ramp hold and workday stick. Import it; never write
the set out again. Dart/Kotlin code that cannot import it keeps a literal copy
guarded by a drift test against this package.
## Free days are not sick days
screen-locker's sick days stay exactly where they are, with their rolling

View File

@ -52,9 +52,12 @@ from freedays._errors import (
from freedays._model import FreeDay
from freedays._paths import Paths
from freedays._sync import SyncUnavailableError, sync, sync_quietly
from freedays._workdays import NON_WORKDAYS, WORKDAYS, is_workday
__all__ = [
"DEFAULT_ANNUAL_BUDGET",
"NON_WORKDAYS",
"WORKDAYS",
"AlreadyFreeError",
"BudgetExhaustedError",
"FreeDay",
@ -68,6 +71,7 @@ __all__ = [
"free_days_in_year",
"is_exhausted",
"is_free_day",
"is_workday",
"load",
"lookup",
"mark",

View File

@ -0,0 +1,38 @@
# Copyright (c) 2026 Krzysztof Rudnicki
"""The weekly workdays: Tuesday, Wednesday and Thursday.
The one definition every gate app reads, so "which days are workdays" cannot
drift between them. Consumers each give it their own meaning: screen-locker
relaxes the lock, leetcode-guard charges the cheap price, wake-alarm's ramp
holds the wake time instead of stepping it earlier, and a missed workday
morning costs tomorrow its leniency.
Languages that cannot import this package (the Dart and Kotlin sides of the
phone apps) keep a literal copy and a test that fails when it drifts from
this one.
"""
from __future__ import annotations
from datetime import date
from typing import Final
from freedays._day import today
#: Python ``date.weekday()`` numbering: Mon=0 ... Sun=6.
WORKDAYS: Final[frozenset[int]] = frozenset({1, 2, 3})
#: The complement: Monday, Friday, Saturday and Sunday.
NON_WORKDAYS: Final[frozenset[int]] = frozenset(range(7)) - WORKDAYS
def is_workday(day: date | None = None) -> bool:
"""Return whether *day* (default: today, local) is a weekly workday.
Args:
day: The date to classify; ``None`` means today.
Returns:
True on Tuesday, Wednesday and Thursday.
"""
return today(day).weekday() in WORKDAYS

View File

@ -0,0 +1,39 @@
# Copyright (c) 2026 Krzysztof Rudnicki
"""The weekly workdays: the one Tue/Wed/Thu definition every app reads."""
from __future__ import annotations
from datetime import date
import pytest
import freedays
def test_workdays_are_tuesday_to_thursday() -> None:
assert freedays.WORKDAYS == frozenset({1, 2, 3})
def test_non_workdays_are_the_complement() -> None:
assert freedays.NON_WORKDAYS == frozenset({0, 4, 5, 6})
assert not freedays.WORKDAYS & freedays.NON_WORKDAYS
@pytest.mark.parametrize(
("day", "expected"),
[
(date(2026, 9, 28), False), # Monday
(date(2026, 9, 29), True), # Tuesday
(date(2026, 9, 30), True), # Wednesday
(date(2026, 10, 1), True), # Thursday
(date(2026, 10, 2), False), # Friday
(date(2026, 10, 3), False), # Saturday
(date(2026, 10, 4), False), # Sunday
],
)
def test_is_workday(day: date, expected: bool) -> None:
assert freedays.is_workday(day) is expected
def test_is_workday_defaults_to_today() -> None:
assert freedays.is_workday() is (date.today().weekday() in {1, 2, 3})

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "freedays"
version = "0.1.0"
version = "0.3.0"
description = "Shared pre-declared free-day pool: one global list of days on which every gate app stands down"
requires-python = ">=3.14"
# Deliberately NOT gatelock. Its __init__ imports tkinter for the lock-window