Skip to content

Instantly share code, notes, and snippets.

@adgedenkers
Last active May 7, 2026 23:34
Show Gist options
  • Select an option

  • Save adgedenkers/aa28a537b8328298f739a4d62869c1d4 to your computer and use it in GitHub Desktop.

Select an option

Save adgedenkers/aa28a537b8328298f739a4d62869c1d4 to your computer and use it in GitHub Desktop.
Spiral Date Calculator
#!/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
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"
# ---------------------------------------------------------------------------
# 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] == "--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()
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