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, and sorted joint projection across
calendar systems. pip install datex.
Every day is ultimately a JDN (an integer ≥ 1; the universe is infinite
above). A Date is a set of days described by constraints along any axes;
nothing is enumerated until you ask, and every answer is exact — there is no
heuristic mode. Zero runtime dependencies; all calendrical and astronomical
calculation is in-house.
from datex import Axis, CnDay, CnMonth, Date, Dizhi, Month, Tiangan, Weekday, Zodiac
next(iter(Date(year=2000, month=1, day=1).project(tuple(Axis))))
# (range(2451545, 2451546), 2000, Month.JAN, 1, Weekday.SAT, (1999, 52),
# 1, True, Zodiac.CAPRICORN, 1999, Tiangan.己, Dizhi.卯, CnMonth.十一月,
# Tiangan.丙, Dizhi.子, CnDay.廿五, Tiangan.戊, Dizhi.午)
list(Date(month=9, day=2).project((Axis.doy, Axis.leap)))
# joint, not a product of marginals: [(245, False), (246, True)]
Date(year=2000, month=1, day=1, weekday=Weekday.TUE) # falsy — never an exception
bool(Date(cn_month="正月", cn_month_dizhi="酉")) # False, by certificate
Date(cn_month="八月") | Date(cn_month="九月") # every set operation closes
The model
- Universe — days are JDNs ≥ 1, unbounded. No windows, no vendored tables: everything is calculated.
Date(**constraints)— a symbolic conjunction across axes; an iterable value is a disjunction within its axis. Internally a Date is a union of conjunctions of signed atoms (disjunctive normal form), so the algebra is closed with no gaps:&,|,-,^, complements, subset/equality comparisons (<=,<,==, …) all work, for arithmetic and astronomical constraints alike. Types gate form only: a wrongly-typed value or an unknown member of a closed vocabulary (month=13) raises, while any well-formed value no day carries (day=32, week 53 of a 52-week year) is simply the empty set — exactly like Feb 30. jdn values are plain unit-stepranges of days (a single day isrange(x, x + 1)).- Pythonic set protocols, nothing else — truthiness is non-emptiness
(
bool(d), like every Python container),j in dis day membership, and the operators above. There is nocount,len, oris_empty: day-count issum(len(r) for (r,) in d.project((Axis.jdn,))), value-count issum(1 for _ in it), emptiness isnot d. An infinite set yields an unending iterator, which is more honest than a number. project(axes)— the one read-out. Takes a single tuple ofAxismembers; returns an iterator of value tuples of the same arity — sorted, duplicate-free, each a combination the day-set actually attains, with jdn components as maximally contiguous ranges (in a joint, maximal over spans where the other projected components are constant). Sorted is truly sorted: over an infinite set, combinations after an infinitely-repeating prefix are never reached ((weekday, year)never leaves Monday) — put unbounded axes first. A set with an unbounded contiguous tail (e.g. the whole universe) cannot express its tail as arangeand raises when projected on jdn.
Axes
Eighteen, in canonical order: jdn, year, month, day, weekday,
week (ISO year-week pair), doy, leap, zodiac, cn_year,
cn_year_tiangan, cn_year_dizhi, cn_month, cn_month_tiangan,
cn_month_dizhi, cn_day, cn_day_tiangan, cn_day_dizhi.
Each axis is one row of a declaration table: its value kind, its
jdn-periodicity, its evaluator jdn2X (every evaluator is a step function
of the day), and its span end (the first day the value changes). The
lunisolar layer behind the cn axes computes truncated VSOP87 apparent solar
longitude and ELP-2000 lunar longitude (Meeus), new moons root-solved as
true conjunctions, the Espenak–Meeus ΔT model, UTC+8 day slicing,
winter-solstice anchoring with 無中氣置閏, the CNY-boundary year pillar, and
the 節氣-delimited continuous month pillar (立春 1984 = 丙寅月).
Value types: every enum doubles as a primitive — Weekday/Month are
IntEnums (interchange with ints; bools rejected), Zodiac/Tiangan/
Dizhi/CnMonth/CnDay are StrEnums whose values are the canonical forms
(Date(zodiac="Virgo"), Tiangan("甲")). year/cn_year/day/doy are
ints, leap a bool, week an (iso_year, week) int pair.
The engine
Enumeration span-jumps: a conjunction's runs are walked boundary-to-boundary (while any predicate is false, the walk jumps to the last failing predicate's span end), so cost is proportional to runs — and to the finest constrained axis — never to days at large. Emptiness of arithmetic conjunctions is decided exactly by scanning one lcm-period window per driver span (everything Gregorian repeats every 146,097 days, weekdays every 7, the day pillar every 60 — a full silent window proves emptiness). Astronomically-constrained sets over an unbounded day range answer truthiness by the drift-recurrence principle (part of the imposed standard: the Gregorian year outpaces the decreed tropical year, so the solar frame precesses through the whole calendar and every satisfiable combination recurs forever), with a lunisolar-month/節氣-branch incompatibility certificate proving the certain empties; the decree never overrides an arithmetically-empty skeleton. The one open frontier: a union of purely astronomical sets that happens to cover an unbounded contiguous tail has no finite certificate and streams unboundedly on jdn.
The imposed standard
Dates outside any calendar's historical span are decreed, not discovered:
proleptic Gregorian with astronomical year numbering (year 0 exists); ISO
weekdays and weeks; the customary tropical zodiac boundaries; the sexagenary
day count anchored at 1949-10-01 = 甲子 (index(jdn) = (jdn + 49) % 60);
the modern Chinese lunisolar rule applied uniformly (UTC+8 slicing also
before 1929, when historical calendars used Beijing local time), with
instants defined by the in-house truncated Meeus series extrapolated
proleptically; and the drift-recurrence principle above.
Verified at development time: 1929–2100 matches sxtwl day-for-day (lunar
year/month/leap/day and month pillar); solar longitude within 2″ and lunar
within 14″ of Swiss Ephemeris over 1600–2400; new moons within ~23 s over
1000–3000; suì invariants hold from −1000 to 3500. tests/golden/ holds the
generators (sxtwl is never a dependency).
Development
PYTHONPATH=src python3 -m unittest discover -s tests
Requires Python ≥ 3.11. Tests are stdlib-only; oracles are datetime
sweeps, literature anchors (JDN 0 = −4713-11-24, J2000.0 = JDN 2451545),
and the dev-time cross-checked goldens.
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.1.1.tar.gz.
File metadata
- Download URL: datex-0.1.1.tar.gz
- Upload date:
- Size: 34.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c421bc79d1af6a28b7545756de46fade733a875a00b0673ad25048836bc25b7
|
|
| MD5 |
94bfc03570b68b33dd44bdab2ff92793
|
|
| BLAKE2b-256 |
af0b054680361e79bc2b5e06db636d5456617702e2b1c0f33905329261ceb7d5
|
Provenance
The following attestation bundles were made for datex-0.1.1.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.1.1.tar.gz -
Subject digest:
1c421bc79d1af6a28b7545756de46fade733a875a00b0673ad25048836bc25b7 - Sigstore transparency entry: 2250041720
- Sigstore integration time:
-
Permalink:
cterdam/datex@128cf6f3b663569709af378ab2e9cab712b95e1a -
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@128cf6f3b663569709af378ab2e9cab712b95e1a -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file datex-0.1.1-py3-none-any.whl.
File metadata
- Download URL: datex-0.1.1-py3-none-any.whl
- Upload date:
- Size: 27.1 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 |
ab494ec2618ac7d03a3be03b464be0b09bde19735b34e3e321a7e90f0bc06183
|
|
| MD5 |
373bfdb5ae1c5f65689cede2c07f6fa9
|
|
| BLAKE2b-256 |
f070cb3f79a22726e8fb582d7d2d8af81372e3d167099e2b0e860007ae157f2a
|
Provenance
The following attestation bundles were made for datex-0.1.1-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.1.1-py3-none-any.whl -
Subject digest:
ab494ec2618ac7d03a3be03b464be0b09bde19735b34e3e321a7e90f0bc06183 - Sigstore transparency entry: 2250042022
- Sigstore integration time:
-
Permalink:
cterdam/datex@128cf6f3b663569709af378ab2e9cab712b95e1a -
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@128cf6f3b663569709af378ab2e9cab712b95e1a -
Trigger Event:
workflow_dispatch
-
Statement type: