Created
May 7, 2026 23:17
-
-
Save adgedenkers/73dd8dda3a50ef76bdeaf92b3b5778b7 to your computer and use it in GitHub Desktop.
Maya 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 | |
| """ | |
| 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