Symbolic day-sets over the unbounded Julian Day Number universe: exact constraints, a gapless set algebra, and sorted joint projection across calendar systems.
Project description
datex
Symbolic day-sets over the unbounded Julian Day Number universe: exact
constraints, a gapless set algebra, sorted joint projection across calendar
systems. Zero dependencies, Python ≥ 3.11. pip install datex.
A Date is a set of days (JDNs ≥ 1, infinite above) described by
constraints; nothing is enumerated until asked, every answer is exact.
Axes and types
Eighteen axes, in canonical order. Enums double as primitives:
Weekday/Month are IntEnums (ints accepted, bools rejected), the rest are
StrEnums whose values are the canonical strings (Date(zodiac="Virgo"),
Date(cn_day="初一")).
| axis | type | values |
|---|---|---|
jdn |
range (step 1) |
a single day is range(x, x+1) |
year |
int |
astronomical numbering — year 0 exists |
month |
Month |
JAN…DEC = 1…12 |
day |
int |
day of month |
weekday |
Weekday |
ISO: MON…SUN = 1…7 |
week |
(int, int) |
ISO (year, week) pair |
doy |
int |
day of year |
leap |
bool |
Gregorian leap year |
zodiac |
Zodiac |
"Aries"…"Pisces", customary tropical dates |
cn_year |
int |
lunar year, changes at 正月初一 |
cn_year_tiangan / cn_year_dizhi |
Tiangan / Dizhi |
year pillar 天干/地支 |
cn_month |
CnMonth |
正月…十二月 + 閏 forms |
cn_month_tiangan / cn_month_dizhi |
Tiangan / Dizhi |
節氣-delimited month pillar |
cn_day |
CnDay |
初一…三十 |
cn_day_tiangan / cn_day_dizhi |
Tiangan / Dizhi |
day pillar, 1949-10-01 = 甲子 |
Constructing sets
Keywords conjoin; an iterable value disjoins within its axis. Types gate
form only: a wrong type or unknown vocabulary member (month=13) raises;
a well-formed value no day carries (day=32, week 53 of a 52-week year) is
simply the empty set, exactly like Feb 30 — falsy, never an exception.
from datex import Axis, Date, Weekday
Date(year=2026, month=7, weekday=Weekday.FRI) # July 2026 Fridays
Date(year=[1999, 2001], month=1, day=1) # two New Year's Days
Date(jdn=range(2451545, 2451546)) # one concrete day
bool(Date(month=2, day=30)) # False
Set algebra
Closed under &, |, -, ^ (complement: Date() - d); compare with
==, <=, <, >=, >. Pythonic protocols only: truthiness is
non-emptiness, j in d is day membership. No len — an infinite set
yields an unending iterator, which is more honest than a number.
Date(cn_month="八月") | Date(cn_month="九月") # every operation closes
Date(month=2, day=29) <= Date(leap=True) # True — provably
Date(month=2, day=29) == Date(doy=60, leap=True) # True, across bases
2451545 in Date(month=1) # True (2000-01-01)
bool(Date(cn_month="正月", cn_month_dizhi="酉")) # False, by certificate
Projection
d.project(axes) — the one read-out. Takes a tuple of Axis members;
yields sorted, duplicate-free value tuples of the same arity, each a
combination the set actually attains (a joint, never a product of
marginals). Every direction works; what varies is the shape of the answer.
Onto periodic / finite-codomain axes — always terminates, even over infinite sets, in canonical value order:
list(Date(year=2026, month=7, weekday=Weekday.FRI).project((Axis.day,)))
# [(3,), (10,), (17,), (24,), (31,)]
list(Date().project((Axis.weekday,))) # whole universe: 7 rows
list(Date(cn_day_tiangan="甲").project((Axis.cn_day_dizhi,)))
# 6 rows (子寅辰午申戌) — stem/branch parity, decided without scanning
Onto unbounded axes (year, week, cn_year, jdn) — a lazy sorted
stream, infinite if the set is:
from itertools import islice
islice(Date(month=2, day=29).project((Axis.year,)), 4)
# (-4712,), (-4708,), (-4704,), (-4696,) — proleptic century rule intact
Sorted is truly sorted, so put unbounded axes first: (year, weekday)
interleaves forever, while (weekday, year) never leaves Monday — an
infinite prefix is honest, not a bug.
Onto jdn — maximally contiguous ranges, merged across constraint
boundaries, split exactly where any co-projected value changes:
list((Date(year=2023) | Date(year=2024)).project((Axis.jdn,)))
# [(range(2459946, 2460677),)] — one range, 731 days
list(Date(jdn=range(2460340, 2460345)).project((Axis.jdn, Axis.month)))
# [(range(2460340, 2460342), JAN), (range(2460342, 2460345), FEB)]
Date().project((Axis.jdn,)) # raises NotImplementedError:
# an unbounded contiguous tail cannot be a range — constrain any
# non-monotone axis first
Joint and cross-system — constraints and projection may mix arithmetic and astronomical axes freely:
list(Date(month=9, day=2).project((Axis.doy, Axis.leap)))
# [(245, False), (246, True)] — joint, not 2×2
islice(Date(cn_month="八月", weekday=Weekday.FRI, cn_day="十五")
.project((Axis.year, Axis.month, Axis.day)), 3) # ~0.1 s
Efficiency
Closed forms first, day-walking last. Emptiness is answered by
structural certificates where one exists (calendar shape, sexagenary
parity, the cn_year-pinned pillar, lunisolar-month/branch incompatibility)
— no scan at all. Enumeration seeks: a failing constraint jumps
straight to its next satisfying day (residue arithmetic for weekday and the
day pillars, O(1) civil conversions for month/day/doy, one month-table
lookup for cn_day), so cost tracks the output, not the days in between.
Only the astronomical evaluators, which have no inverse, walk span to span
— lunation-sized, cached per suì. Remaining exact scans are capped at one
lcm-period window (everything Gregorian repeats every 146,097 days).
Astronomical sets over an unbounded range answer truthiness by the
drift-recurrence principle (the decreed solar frame precesses through
the whole calendar, so every satisfiable combination recurs forever) —
instant, though the first witness may lie millennia away. One frontier: a
union of purely astronomical sets covering an unbounded contiguous tail has
no finite certificate and will not terminate when projected on jdn.
The imposed standard
Out-of-span dates are decreed, not discovered: proleptic Gregorian,
astronomical year numbering; ISO weeks; customary tropical zodiac; day
pillar (jdn + 49) % 60 anchored at 1949-10-01 = 甲子; the modern Chinese
lunisolar rule (truncated VSOP87/ELP-2000 instants per Meeus, Espenak–Meeus
ΔT, UTC+8 slicing, 無中氣置閏, CNY-boundary year pillar, 節氣-delimited
month pillar, 立春 1984 = 丙寅月) applied uniformly across all time.
Verified at development: matches sxtwl day-for-day 1929–2100; solar
longitude within 2″ and lunar within 14″ of Swiss Ephemeris 1600–2400;
tests/golden/ holds the generators (never runtime dependencies).
Development
PYTHONPATH=src python3 -m unittest discover -s tests
Stdlib-only tests: datetime sweeps, literature anchors, cross-checked
goldens, and day-for-day differential checks of the seek engine against
brute force.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file datex-0.2.0.tar.gz.
File metadata
- Download URL: datex-0.2.0.tar.gz
- Upload date:
- Size: 39.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73036745eb930f548098a88635bdf9b446acbcbd46ad130e34161a4789b9f25c
|
|
| MD5 |
cc3511231d00328b503e7ec9990194d4
|
|
| BLAKE2b-256 |
1cccd05583e4ba4426c7666ffc843506da87fcbe011a3ca21415cb9c337a4522
|
Provenance
The following attestation bundles were made for datex-0.2.0.tar.gz:
Publisher:
release.yml on cterdam/datex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
datex-0.2.0.tar.gz -
Subject digest:
73036745eb930f548098a88635bdf9b446acbcbd46ad130e34161a4789b9f25c - Sigstore transparency entry: 2255522227
- Sigstore integration time:
-
Permalink:
cterdam/datex@dd7a401b5a7caa18beaaa89a1d57bfe59a3784f5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cterdam
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@dd7a401b5a7caa18beaaa89a1d57bfe59a3784f5 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file datex-0.2.0-py3-none-any.whl.
File metadata
- Download URL: datex-0.2.0-py3-none-any.whl
- Upload date:
- Size: 30.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5f6391dcac7ef82740b6383557b80219f2f60dd92f9eb33ce43d8bc4ce15a56
|
|
| MD5 |
a42723fd456da96bd17cea5796f2b045
|
|
| BLAKE2b-256 |
d4f86e76cddc8503a2049638c403e071d8a8e1d93bb707527973583baebba1f5
|
Provenance
The following attestation bundles were made for datex-0.2.0-py3-none-any.whl:
Publisher:
release.yml on cterdam/datex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
datex-0.2.0-py3-none-any.whl -
Subject digest:
b5f6391dcac7ef82740b6383557b80219f2f60dd92f9eb33ce43d8bc4ce15a56 - Sigstore transparency entry: 2255522233
- Sigstore integration time:
-
Permalink:
cterdam/datex@dd7a401b5a7caa18beaaa89a1d57bfe59a3784f5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cterdam
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@dd7a401b5a7caa18beaaa89a1d57bfe59a3784f5 -
Trigger Event:
workflow_dispatch
-
Statement type: