Skip to content

Instantly share code, notes, and snippets.

@adgedenkers
Created May 7, 2026 23:17
Show Gist options
  • Select an option

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

Select an option

Save adgedenkers/73dd8dda3a50ef76bdeaf92b3b5778b7 to your computer and use it in GitHub Desktop.
Maya Date Calculator
#!/usr/bin/env python3
"""
mlc.py — Mayan Long Count date calculator.
Returns the full Mayan calendar date for any Gregorian date:
- Long Count (Bak'tun.K'atun.Tun.Winal.K'in) + raw day count
- Tzolk'in (sacred 260-day cycle)
- Haab' (365-day solar cycle)
- Calendar Round (Tzolk'in + Haab' combined)
- Lord of the Night (9-night cycle, G1–G9)
Uses the GMT correlation constant (584283), which matches the
living Tzolk'in count maintained by K'iche' daykeepers in the
Guatemalan highlands and is supported by radiocarbon dating of
the Tikal Temple I lintel.
Usage:
mlc.py # today
mlc.py 2026-05-07 # specific date
mlc.py 2012-12-21 # 13.0.0.0.0 — the 13th Bak'tun rollover
mlc.py tomorrow
mlc.py +9
No dependencies beyond Python stdlib.
"""
import sys
from datetime import date, timedelta
# ---------------------------------------------------------------------------
# GMT correlation constant
# ---------------------------------------------------------------------------
# Julian Day Number for the Maya creation date 0.0.0.0.0 (= 13.0.0.0.0)
# 4 Ajaw 8 Kumk'u — August 11, 3114 BCE (proleptic Gregorian)
GMT_CORRELATION = 584283
# ---------------------------------------------------------------------------
# Long Count place values (mixed radix: 20-20-20-18-20)
# ---------------------------------------------------------------------------
KIN = 1 # 1 day
WINAL = 20 # 20 days
TUN = 360 # 18 × 20
KATUN = 7_200 # 20 × 360
BAKTUN = 144_000 # 20 × 7,200
# ---------------------------------------------------------------------------
# Tzolk'in — 20 day signs (venteina)
# ---------------------------------------------------------------------------
TZOLKIN_DAYS = [
"Imix'", # 0 — Water Lily / Crocodile
"Ik'", # 1 — Wind
"Ak'b'al", # 2 — Night / Darkness
"K'an", # 3 — Corn / Seed
"Chikchan", # 4 — Serpent
"Kimi", # 5 — Death
"Manik'", # 6 — Deer / Hand
"Lamat", # 7 — Star / Venus
"Muluk", # 8 — Water / Offering
"Ok", # 9 — Dog
"Chuwen", # 10 — Monkey / Thread
"Eb'", # 11 — Road / Grass
"B'en", # 12 — Reed / Corn Stalk
"Ix", # 13 — Jaguar
"Men", # 14 — Eagle
"K'ib'", # 15 — Owl / Vulture
"Kab'an", # 16 — Earth / Earthquake
"Etz'nab'", # 17 — Flint / Knife
"Kawak", # 18 — Storm / Rain
"Ajaw", # 19 — Lord / Sun
]
# ---------------------------------------------------------------------------
# Haab' — 18 months of 20 days + 5-day Wayeb'
# ---------------------------------------------------------------------------
HAAB_MONTHS = [
"Pop", # 0 — Mat
"Wo", # 1 — Black Conjunction
"Sip", # 2 — Red Conjunction
"Sotz'", # 3 — Bat
"Sek", # 4 — Sky
"Xul", # 5 — Dog
"Yaxk'in", # 6 — New Sun
"Mol", # 7 — Water
"Ch'en", # 8 — Black Storm
"Yax", # 9 — Green Storm
"Sak", # 10 — White Storm
"Keh", # 11 — Red Storm
"Mak", # 12 — Enclosed
"K'ank'in", # 13 — Yellow Sun
"Muwan", # 14 — Owl
"Pax", # 15 — Planting Time
"K'ayab'", # 16 — Turtle
"Kumk'u", # 17 — Granary
"Wayeb'", # 18 — Nameless Days (5 days)
]
# ---------------------------------------------------------------------------
# Lords of the Night (9-night cycle)
# ---------------------------------------------------------------------------
LORDS_OF_NIGHT = {
1: ("G1", "Xiuhtecuhtli", "Lord of Fire"),
2: ("G2", "Tlaltecuhtli", "Lord of Earth"),
3: ("G3", "Piltzintecuhtli", "Lord of the Young Sun"),
4: ("G4", "Centeotl", "Corn God"),
5: ("G5", "Mictlantecuhtli", "Lord of the Dead"),
6: ("G6", "Chalchiuhtlicue", "Lady of Jade Skirt"),
7: ("G7", "Tlazolteotl", "Goddess of Filth / Purification"),
8: ("G8", "Tepeyollotl", "Heart of the Mountain"),
9: ("G9", "Tlaloc", "Lord of Rain"),
}
# ---------------------------------------------------------------------------
# Gregorian date → Julian Day Number
# ---------------------------------------------------------------------------
def gregorian_to_jdn(y, m, d):
"""
Convert a Gregorian calendar date to Julian Day Number.
Valid for any date in the proleptic Gregorian calendar.
Algorithm: Meeus, Astronomical Algorithms (1991).
"""
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
# ---------------------------------------------------------------------------
# Core calculations
# ---------------------------------------------------------------------------
def long_count(jdn):
"""
Return (day_count, baktun, katun, tun, winal, kin) from a JDN.
day_count is the raw integer — total days since the creation date.
"""
day_count = jdn - GMT_CORRELATION
remainder = day_count
baktun = remainder // BAKTUN; remainder %= BAKTUN
katun = remainder // KATUN; remainder %= KATUN
tun = remainder // TUN; remainder %= TUN
winal = remainder // WINAL; remainder %= WINAL
kin = remainder
return day_count, baktun, katun, tun, winal, kin
def tzolkin(jdn):
"""
Return (number, day_name) for the Tzolk'in date.
The creation date 0.0.0.0.0 = 4 Ajaw.
Tzolk'in number cycles 1–13, day sign cycles through 20 names.
"""
day_count = jdn - GMT_CORRELATION
# On the creation date (day_count=0): number=4, sign=Ajaw (index 19)
number = (day_count + 3) % 13 + 1 # +3 because creation = 4
sign_idx = (day_count + 19) % 20 # +19 because creation = Ajaw
return number, TZOLKIN_DAYS[sign_idx]
def haab(jdn):
"""
Return (day_number, month_name) for the Haab' date.
The creation date 0.0.0.0.0 = 8 Kumk'u.
Haab' day numbering: 0 (seating) through 19 for regular months,
0–4 for Wayeb'.
"""
day_count = jdn - GMT_CORRELATION
# On creation date: 8 Kumk'u = day 348 of the Haab' year
# (17 months × 20 days + 8 = 348)
haab_day = (day_count + 348) % 365
month_idx = haab_day // 20
day_in_month = haab_day % 20
# Wayeb' is only 5 days (month index 18, days 0–4)
if month_idx > 18:
month_idx = 18
day_in_month = haab_day - 360
return day_in_month, HAAB_MONTHS[month_idx]
def lord_of_night(jdn):
"""
Return (glyph, name, title) for the Lord of the Night.
9-day cycle. On creation date (day_count=0): G9.
"""
day_count = jdn - GMT_CORRELATION
# Creation date = G9, so offset by 8
lord_num = (day_count + 8) % 9 + 1
return LORDS_OF_NIGHT[lord_num]
# ---------------------------------------------------------------------------
# Date parsing
# ---------------------------------------------------------------------------
def parse_date(arg):
if arg in ("today", ""):
return date.today()
if arg == "tomorrow":
return date.today() + timedelta(days=1)
if arg == "yesterday":
return date.today() - timedelta(days=1)
if arg.startswith("+") or arg.startswith("-"):
return date.today() + timedelta(days=int(arg))
return date.fromisoformat(arg)
# ---------------------------------------------------------------------------
# Output
# ---------------------------------------------------------------------------
def display(d):
jdn = gregorian_to_jdn(d.year, d.month, d.day)
day_count, baktun, katun, tun, winal, kin = long_count(jdn)
lc_str = f"{baktun}.{katun}.{tun}.{winal}.{kin}"
tz_num, tz_name = tzolkin(jdn)
h_day, h_month = haab(jdn)
g_code, g_name, g_title = lord_of_night(jdn)
cr_str = f"{tz_num} {tz_name} {h_day} {h_month}"
# Trecena ruler: the Tzolk'in day sign that started this 13-day period
# (i.e. the day sign when the trecena number was 1)
trecena_offset = tz_num - 1
trecena_jdn = jdn - trecena_offset
_, trecena_sign = tzolkin(trecena_jdn)
print()
print(f" {d.strftime('%A, %B %-d, %Y')}")
print(f" {'─' * 44}")
print()
print(f" Long Count: {lc_str}")
print(f" Day Count: {day_count:,}")
print()
print(f" Tzolk'in: {tz_num} {tz_name}")
print(f" Haab': {h_day} {h_month}")
print(f" Calendar Round: {cr_str}")
print()
print(f" Trecena: 1 {trecena_sign} (day {tz_num} of 13)")
print(f" Lord of Night: {g_code} — {g_name} ({g_title})")
print()
# ---------------------------------------------------------------------------
# Self-test against known dates
# ---------------------------------------------------------------------------
def verify():
"""Verify against well-established reference dates."""
tests = [
# (year, month, day, expected_lc, expected_tzolkin, expected_haab, expected_lord)
(2012, 12, 21, "13.0.0.0.0", "4 Ajaw", "3 K'ank'in", "G9"),
(2012, 12, 20, "12.19.19.17.19", "3 Kawak", "2 K'ank'in", "G8"),
(2026, 5, 7, "13.0.13.10.5", "1 Chikchan", "18 Wo", "G7"),
]
all_pass = True
for y, m, d, exp_lc, exp_tz, exp_h, exp_g in tests:
jdn = gregorian_to_jdn(y, m, d)
day_count, baktun, katun, tun, winal, kin = long_count(jdn)
lc = f"{baktun}.{katun}.{tun}.{winal}.{kin}"
tz_num, tz_name = tzolkin(jdn)
tz = f"{tz_num} {tz_name}"
h_day, h_month = haab(jdn)
h = f"{h_day} {h_month}"
g_code, _, _ = lord_of_night(jdn)
ok = (lc == exp_lc and tz == exp_tz and h == exp_h and g_code == exp_g)
status = "✓" if ok else "✗"
if not ok:
all_pass = False
print(f" {status} {y}-{m:02d}-{d:02d}: got {lc} {tz} {h} {g_code}")
print(f" expected: {exp_lc} {exp_tz} {exp_h} {exp_g}")
else:
print(f" {status} {y}-{m:02d}-{d:02d}: {lc} {tz} {h} {g_code}")
return all_pass
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
if len(sys.argv) > 1 and sys.argv[1] == "--verify":
print()
print(" Verifying against known reference dates...")
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:
d = parse_date(arg)
except (ValueError, TypeError):
print(f" Could not parse date: {arg}")
print(f" Usage: mlc.py [YYYY-MM-DD | today | tomorrow | yesterday | +N | -N]")
print(f" mlc.py --verify (run self-test)")
sys.exit(1)
display(d)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment