Last active
May 11, 2026 13:49
-
-
Save adgedenkers/aa28a537b8328298f739a4d62869c1d4 to your computer and use it in GitHub Desktop.
Spiral Date Calculator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| spiral-date — look up the Nine Day Sun position for any date. | |
| Usage: | |
| spiral-date # today | |
| spiral-date 2026-04-15 # specific date (Sun 5) | |
| spiral-date 2020-06-15 # specific date (Sun 4) | |
| spiral-date -3113-08-11 # Sun 4 anchor (astronomical year notation) | |
| spiral-date tomorrow # relative | |
| spiral-date +7 # 7 days from now | |
| spiral-date --help # show framework reference | |
| spiral-date --verify # run self-tests | |
| Sun 4: August 11, 3114 BCE → March 19, 2026 | |
| Sun 5: March 20, 2026 → onward | |
| Dates before August 11, 3114 BCE (Sun 3 and earlier) are not yet defined. | |
| """ | |
| import sys | |
| from datetime import date, timedelta | |
| # --------------------------------------------------------------------------- | |
| # Framework constants — pure base-9 | |
| # --------------------------------------------------------------------------- | |
| DAYS_PER_CYCLE = 9 | |
| DAYS_PER_SPIRAL = 81 | |
| DAYS_PER_EPOCH = 729 | |
| DAYS_PER_AEON = 6_561 | |
| DAYS_PER_WATCH = 59_049 | |
| DAYS_PER_ERA = 531_441 | |
| META_CYCLE_LENGTH = 63 | |
| # --------------------------------------------------------------------------- | |
| # Sun anchors | |
| # | |
| # Sun 4: Maya Long Count creation date (0.0.0.0.0 = 4 Ajaw 8 Kumk'u) | |
| # August 11, 3114 BCE (proleptic Gregorian) | |
| # GMT correlation JDN = 584283 | |
| # | |
| # Sun 5: First equinox after the Saturn-Neptune conjunction at 0° Aries | |
| # March 20, 2026 | |
| # --------------------------------------------------------------------------- | |
| SUN5_ANCHOR = date(2026, 3, 20) | |
| # We can't represent 3114 BCE with datetime, so we use JDN for Sun 4. | |
| # JDN for the Sun 4 anchor (Maya creation date): | |
| SUN4_ANCHOR_JDN = 584283 | |
| # JDN for the Sun 5 anchor: | |
| SUN5_ANCHOR_JDN = _sun5_jdn = None # computed at module load | |
| WEEKDAY_PLANETS = { | |
| 0: ("Monday", "Moon", "☽"), | |
| 1: ("Tuesday", "Mars", "♂"), | |
| 2: ("Wednesday", "Mercury", "☿"), | |
| 3: ("Thursday", "Jupiter", "♃"), | |
| 4: ("Friday", "Venus", "♀"), | |
| 5: ("Saturday", "Saturn", "♄"), | |
| 6: ("Sunday", "Sun", "☉"), | |
| } | |
| DAYS = { | |
| 1: ("Aris", "Ignition", "The flame is lit"), | |
| 2: ("Selun", "Descent", "The flame descends"), | |
| 3: ("Valen", "Structure", "The flame builds form"), | |
| 4: ("Oran", "Motion", "The flame moves"), | |
| 5: ("Thael", "Vision", "The flame dreams"), | |
| 6: ("Riven", "Break / Severance", "The flame sheds"), | |
| 7: ("Miren", "Return", "The flame integrates"), | |
| 8: ("Kiren", "Service", "The flame offers"), | |
| 9: ("Sayel", "Sealing", "The flame rests"), | |
| } | |
| VERBS = { | |
| 1: "Start", 2: "Listen", 3: "Anchor", 4: "Act", 5: "Dream", | |
| 6: "Release", 7: "Integrate", 8: "Offer", 9: "Complete", | |
| } | |
| SABBATH_LABELS = [ | |
| "", | |
| "Day 9 — rest day", | |
| "Day 9 of Cycle 9 — deep rest", | |
| "Day 9 · Cycle 9 · Spiral 9 — season sabbath", | |
| "Depth 4 — quadruple nine", | |
| "Depth 5 — quintuple nine", | |
| "Depth 6 — sextuple nine", | |
| "Depth 7 — full sabbath (every level at 9)", | |
| ] | |
| # --------------------------------------------------------------------------- | |
| # JDN conversion | |
| # --------------------------------------------------------------------------- | |
| def gregorian_to_jdn(y, m, d): | |
| """ | |
| Convert a proleptic Gregorian date to Julian Day Number. | |
| Handles negative (astronomical) years for BCE dates. | |
| """ | |
| if m <= 2: | |
| y -= 1 | |
| m += 12 | |
| A = y // 100 | |
| B = 2 - A + A // 4 | |
| return int(365.25 * (y + 4716)) + int(30.6001 * (m + 1)) + d + B - 1524 | |
| def jdn_to_gregorian(jdn): | |
| """Convert JDN back to (year, month, day) in proleptic Gregorian.""" | |
| l = jdn + 68569 | |
| n = 4 * l // 146097 | |
| l = l - (146097 * n + 3) // 4 | |
| i = 4000 * (l + 1) // 1461001 | |
| l = l - 1461 * i // 4 + 31 | |
| j = 80 * l // 2447 | |
| d = l - 2447 * j // 80 | |
| l = j // 11 | |
| m = j + 2 - 12 * l | |
| y = 100 * (n - 49) + i + l | |
| return y, m, d | |
| def jdn_to_weekday(jdn): | |
| """Return weekday index (0=Monday, 6=Sunday) from JDN.""" | |
| return (jdn + 0) % 7 # JDN 0 = Monday | |
| # Compute Sun 5 anchor JDN at module load | |
| SUN5_ANCHOR_JDN = gregorian_to_jdn(2026, 3, 20) | |
| # --------------------------------------------------------------------------- | |
| # Position math | |
| # --------------------------------------------------------------------------- | |
| def position_from_delta(delta): | |
| """ | |
| Given a non-negative day offset from a Sun's anchor, | |
| return the base-9 stack coordinates. | |
| """ | |
| remainder = delta | |
| era_offset = remainder // DAYS_PER_ERA; remainder %= DAYS_PER_ERA | |
| watch_offset = remainder // DAYS_PER_WATCH; remainder %= DAYS_PER_WATCH | |
| aeon_offset = remainder // DAYS_PER_AEON; remainder %= DAYS_PER_AEON | |
| epoch_offset = remainder // DAYS_PER_EPOCH; remainder %= DAYS_PER_EPOCH | |
| spiral_offset = remainder // DAYS_PER_SPIRAL; remainder %= DAYS_PER_SPIRAL | |
| cycle_offset = remainder // DAYS_PER_CYCLE; remainder %= DAYS_PER_CYCLE | |
| return { | |
| "era": 1 + era_offset, | |
| "watch": 1 + watch_offset, | |
| "aeon": 1 + aeon_offset, | |
| "epoch": 1 + epoch_offset, | |
| "spiral": 1 + spiral_offset, | |
| "cycle": 1 + cycle_offset, | |
| "day": 1 + remainder, | |
| "days_since_anchor": delta, | |
| } | |
| def resolve_sun(jdn): | |
| """ | |
| Determine which Sun a JDN falls in and return | |
| (sun_number, anchor_jdn, delta_from_anchor). | |
| Returns None if before Sun 4. | |
| """ | |
| if jdn >= SUN5_ANCHOR_JDN: | |
| return 5, SUN5_ANCHOR_JDN, jdn - SUN5_ANCHOR_JDN | |
| elif jdn >= SUN4_ANCHOR_JDN: | |
| return 4, SUN4_ANCHOR_JDN, jdn - SUN4_ANCHOR_JDN | |
| else: | |
| return None, None, None | |
| def sabbath_depth(p): | |
| depth = 0 | |
| for level in [p["day"], p["cycle"], p["spiral"], p["epoch"], | |
| p["aeon"], p["watch"], p["era"]]: | |
| if level == 9: | |
| depth += 1 | |
| else: | |
| break | |
| return depth | |
| def cycle_seed_jdn(jdn, p): | |
| """JDN of Day 1 of the cycle containing this date.""" | |
| return jdn - (p["day"] - 1) | |
| # --------------------------------------------------------------------------- | |
| # Date parsing — supports both CE and BCE dates | |
| # --------------------------------------------------------------------------- | |
| def parse_input(arg): | |
| """ | |
| Parse input and return a JDN. | |
| Accepts: | |
| today, tomorrow, yesterday | |
| +N, -N (relative days) | |
| YYYY-MM-DD (CE dates, positive year) | |
| -YYYY-MM-DD (astronomical year for BCE; -3113 = 3114 BCE) | |
| """ | |
| if arg in ("today", ""): | |
| d = date.today() | |
| return gregorian_to_jdn(d.year, d.month, d.day) | |
| if arg == "tomorrow": | |
| d = date.today() + timedelta(days=1) | |
| return gregorian_to_jdn(d.year, d.month, d.day) | |
| if arg == "yesterday": | |
| d = date.today() - timedelta(days=1) | |
| return gregorian_to_jdn(d.year, d.month, d.day) | |
| # Relative days: +N or -N (but not -YYYY-MM-DD) | |
| if arg.startswith("+"): | |
| d = date.today() + timedelta(days=int(arg)) | |
| return gregorian_to_jdn(d.year, d.month, d.day) | |
| if arg.startswith("-") and arg.count("-") == 1: | |
| # Just a negative number like -7 | |
| d = date.today() + timedelta(days=int(arg)) | |
| return gregorian_to_jdn(d.year, d.month, d.day) | |
| # ISO-ish date: YYYY-MM-DD or -YYYY-MM-DD | |
| parts = arg.split("-") | |
| if arg.startswith("-"): | |
| # Negative/astronomical year: -3113-08-11 | |
| # parts = ['', '3113', '08', '11'] | |
| year = -int(parts[1]) | |
| month = int(parts[2]) | |
| day = int(parts[3]) | |
| else: | |
| # Positive year: 2026-03-20 | |
| year = int(parts[0]) | |
| month = int(parts[1]) | |
| day = int(parts[2]) | |
| return gregorian_to_jdn(year, month, day) | |
| def format_date_str(jdn): | |
| """Format a JDN as a human-readable date string.""" | |
| y, m, d = jdn_to_gregorian(jdn) | |
| months = [ | |
| "", "January", "February", "March", "April", "May", "June", | |
| "July", "August", "September", "October", "November", "December", | |
| ] | |
| weekdays = [ | |
| "Monday", "Tuesday", "Wednesday", "Thursday", | |
| "Friday", "Saturday", "Sunday", | |
| ] | |
| wd = weekdays[jdn_to_weekday(jdn)] | |
| if y <= 0: | |
| # Convert astronomical year to BCE | |
| bce_year = 1 - y | |
| return f"{wd}, {months[m]} {d}, {bce_year} BCE" | |
| else: | |
| return f"{wd}, {months[m]} {d}, {y} CE" | |
| # --------------------------------------------------------------------------- | |
| # Help — framework reference | |
| # --------------------------------------------------------------------------- | |
| def show_help(): | |
| print(""" | |
| ╔══════════════════════════════════════════════════════════════╗ | |
| ║ THE NINE DAY SUN — Framework Reference ║ | |
| ╚══════════════════════════════════════════════════════════════╝ | |
| SIGNATURE NOTATION | |
| ────────────────── | |
| Full: Sun : Era.Watch.Aeon.Epoch.Spiral.Cycle.Day | |
| e.g. 5 : 1.1.1.1.3.2.7 | |
| Short: Aeon.Epoch.Spiral.Cycle.Day (upper layers implicit) | |
| e.g. 1.1.3.2.7 | |
| ┌──────────┬────────┬────────────┬───────────────────────────────────┐ | |
| │ Level │ Power │ Duration │ Approximate Scale │ | |
| ├──────────┼────────┼────────────┼───────────────────────────────────┤ | |
| │ Day │ 9⁰ │ 1 day │ single day │ | |
| │ Cycle │ 9¹ │ 9 days │ just over a week │ | |
| │ Spiral │ 9² │ 81 days │ ~2.7 months — a season │ | |
| │ Epoch │ 9³ │ 729 days │ ~2 years │ | |
| │ Aeon │ 9⁴ │ 6,561 days │ ~17.97 years — lunar nodal cycle │ | |
| │ Watch │ 9⁵ │59,049 days │ ~161.7 years — 5 generations │ | |
| │ Era │ 9⁶ │531,441 days│ ~1,455 years — span of tradition │ | |
| └──────────┴────────┴────────────┴───────────────────────────────────┘ | |
| The framework is pure base-9. Every level is exactly 9 of the level | |
| below it. No liminal days — rest is fractal: Day 9 is the rest day | |
| of a Cycle, Cycle 9 is the rest cycle of a Spiral, and so on up. | |
| THE NINE DAYS | |
| ───────────── | |
| ┌─────┬────────┬──────────────────┬──────────────────────────────┐ | |
| │ Day │ Name │ Tone │ Flame │ | |
| ├─────┼────────┼──────────────────┼──────────────────────────────┤ | |
| │ 1 │ Aris │ Ignition │ The flame is lit │ | |
| │ 2 │ Selun │ Descent │ The flame descends │ | |
| │ 3 │ Valen │ Structure │ The flame builds form │ | |
| │ 4 │ Oran │ Motion │ The flame moves │ | |
| │ 5 │ Thael │ Vision │ The flame dreams │ | |
| │ 6 │ Riven │ Break/Severance │ The flame sheds │ | |
| │ 7 │ Miren │ Return │ The flame integrates │ | |
| │ 8 │ Kiren │ Service │ The flame offers │ | |
| │ 9 │ Sayel │ Sealing │ The flame rests │ | |
| └─────┴────────┴──────────────────┴──────────────────────────────┘ | |
| SABBATH DEPTH | |
| ───────────── | |
| When the 9th position stacks across levels, sabbath deepens: | |
| Depth 1: Day 9 — rest day | |
| Depth 2: Day 9 of Cycle 9 — deep rest | |
| Depth 3: Day 9 · Cycle 9 · Spiral 9 — season sabbath | |
| Depth 4+: Quadruple nine and beyond — rare cosmic rest | |
| PLANETARY WEATHER (Meta-Cycle) | |
| ────────────────────────────── | |
| The 7-day planetary week overlays the 9-day cycle. Since LCM(7,9) = 63, | |
| the pattern repeats every 63 days. Each cycle's "seed planet" is the | |
| planetary ruler of its Day 1. | |
| SUN ANCHORS | |
| ──────────── | |
| Sun 4: August 11, 3114 BCE Maya creation date (4 Ajaw 8 Kumk'u) | |
| Sun 5: March 20, 2026 First equinox after Saturn-Neptune 0° Aries | |
| USAGE | |
| ───── | |
| spiral-date today's position | |
| spiral-date 2026-04-15 specific date (Sun 5) | |
| spiral-date 2020-01-01 specific date (Sun 4) | |
| spiral-date -3113-08-11 Sun 4 anchor (3114 BCE) | |
| spiral-date tomorrow relative | |
| spiral-date +7 / -3 offset from today | |
| spiral-date --verify run self-tests | |
| spiral-date --help this reference | |
| """) | |
| # --------------------------------------------------------------------------- | |
| # Output | |
| # --------------------------------------------------------------------------- | |
| def display(jdn): | |
| sun, anchor_jdn, delta = resolve_sun(jdn) | |
| if sun is None: | |
| date_str = format_date_str(jdn) | |
| print() | |
| print(f" {date_str}") | |
| print(f" This date is before Sun 4 (August 11, 3114 BCE).") | |
| print(f" Sun 3 and earlier are not yet defined.") | |
| print() | |
| sys.exit(1) | |
| p = position_from_delta(delta) | |
| sig_full = ( | |
| f"{sun} : {p['era']}.{p['watch']}.{p['aeon']}." | |
| f"{p['epoch']}.{p['spiral']}.{p['cycle']}.{p['day']}" | |
| ) | |
| sig_short = ( | |
| f"{p['aeon']}.{p['epoch']}.{p['spiral']}.{p['cycle']}.{p['day']}" | |
| ) | |
| day_num = p["day"] | |
| ritual_name, tone, flame = DAYS[day_num] | |
| verb = VERBS[day_num] | |
| wd_idx = jdn_to_weekday(jdn) | |
| wd_name, wd_planet, wd_glyph = WEEKDAY_PLANETS[wd_idx] | |
| seed_jdn = cycle_seed_jdn(jdn, p) | |
| seed_wd_idx = jdn_to_weekday(seed_jdn) | |
| _, seed_planet, seed_glyph = WEEKDAY_PLANETS[seed_wd_idx] | |
| meta_day = (delta % META_CYCLE_LENGTH) + 1 | |
| depth = sabbath_depth(p) | |
| date_str = format_date_str(jdn) | |
| print() | |
| print(f" {date_str}") | |
| print(f" {'─' * 44}") | |
| print(f" {sig_full}") | |
| print(f" {sig_short} (short)") | |
| print() | |
| print(f" Sun {sun} Day {day_num} — {ritual_name}") | |
| print(f" Tone: {tone}") | |
| print(f" Flame: {flame}") | |
| print(f" Verb: {verb}") | |
| print() | |
| print(f" Weekday planet: {wd_planet} {wd_glyph} ({wd_name})") | |
| print(f" Cycle seed planet: {seed_planet} {seed_glyph}") | |
| print(f" Meta-cycle: {meta_day}/63") | |
| print(f" Days since Sun {sun} anchor: {delta:,}") | |
| if depth > 0: | |
| print() | |
| print(f" ✦ {SABBATH_LABELS[depth]}") | |
| print() | |
| # --------------------------------------------------------------------------- | |
| # Self-test | |
| # --------------------------------------------------------------------------- | |
| def verify(): | |
| """Verify against known reference points.""" | |
| tests = [ | |
| # (description, jdn_input, expected_sig) | |
| ( | |
| "Sun 4 anchor (Aug 11, 3114 BCE)", | |
| SUN4_ANCHOR_JDN, | |
| "4 : 1.1.1.1.1.1.1", | |
| ), | |
| ( | |
| "Sun 5 anchor (Mar 20, 2026)", | |
| SUN5_ANCHOR_JDN, | |
| "5 : 1.1.1.1.1.1.1", | |
| ), | |
| ( | |
| "Last day of Sun 4 (Mar 19, 2026)", | |
| SUN5_ANCHOR_JDN - 1, | |
| "4 : 4.5.8.1.5.8.4", | |
| ), | |
| ( | |
| "Sun 4 day 2 (Aug 12, 3114 BCE)", | |
| SUN4_ANCHOR_JDN + 1, | |
| "4 : 1.1.1.1.1.1.2", | |
| ), | |
| ( | |
| "Sun 5 day 2 (Mar 21, 2026)", | |
| SUN5_ANCHOR_JDN + 1, | |
| "5 : 1.1.1.1.1.1.2", | |
| ), | |
| ] | |
| all_pass = True | |
| for desc, jdn, expected_sig in tests: | |
| sun, anchor_jdn, delta = resolve_sun(jdn) | |
| p = position_from_delta(delta) | |
| sig = ( | |
| f"{sun} : {p['era']}.{p['watch']}.{p['aeon']}." | |
| f"{p['epoch']}.{p['spiral']}.{p['cycle']}.{p['day']}" | |
| ) | |
| ok = sig == expected_sig | |
| status = "✓" if ok else "✗" | |
| if not ok: | |
| all_pass = False | |
| print(f" {status} {desc}") | |
| print(f" got: {sig}") | |
| print(f" expected: {expected_sig}") | |
| else: | |
| print(f" {status} {desc}: {sig}") | |
| return all_pass | |
| # --------------------------------------------------------------------------- | |
| # Main | |
| # --------------------------------------------------------------------------- | |
| def main(): | |
| if len(sys.argv) > 1 and sys.argv[1] in ("--help", "-h"): | |
| show_help() | |
| return | |
| if len(sys.argv) > 1 and sys.argv[1] == "--verify": | |
| print() | |
| print(" Verifying against known reference points...") | |
| print() | |
| ok = verify() | |
| print() | |
| if ok: | |
| print(" All checks passed.") | |
| else: | |
| print(" SOME CHECKS FAILED.") | |
| sys.exit(1) | |
| print() | |
| return | |
| arg = sys.argv[1] if len(sys.argv) > 1 else "today" | |
| try: | |
| jdn = parse_input(arg) | |
| except (ValueError, TypeError, IndexError): | |
| print(f" Could not parse date: {arg}") | |
| print() | |
| print(f" Usage: spiral-date [YYYY-MM-DD | -YYYY-MM-DD | today | tomorrow | +N | -N]") | |
| print(f" spiral-date --verify") | |
| print(f" spiral-date --help") | |
| print() | |
| print(f" Examples:") | |
| print(f" spiral-date # today") | |
| print(f" spiral-date 2026-05-07 # Sun 5 date") | |
| print(f" spiral-date 2020-01-01 # Sun 4 date") | |
| print(f" spiral-date -3113-08-11 # Sun 4 anchor (3114 BCE)") | |
| print(f" spiral-date +9 # 9 days from now") | |
| sys.exit(1) | |
| display(jdn) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment