fuzzyworld

when-am-i / commit

Add Maya calendar (Long Count + Tzolk'in/Haab' Calendar Round)

64fc68ee9601928840a13972975d8e5d44313919 · Pavel Safronov · 2026-06-20 22:18:37 -0700 · parents 84d39e5c8dfd

Add Maya calendar (Long Count + Tzolk'in/Haab' Calendar Round)

Adds a hand-rolled Long Count converter (GMT correlation) alongside the
descriptive-only Tzolk'in/Haab' Calendar Round, covering the Yucatán
Peninsula, Chiapas, Guatemala, Belize, and western Honduras.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
 .claude/skills/new-calendar/SKILL.md               |   3 +-
 README.md                                          |   7 +--
 screenshots/calendars/maya/01-maya-long-count.png  | Bin 0 -> 862573 bytes
 .../maya/02-tzolk-in-and-haab-calendar-round.png   | Bin 0 -> 678666 bytes
 src/data/calendars/maya.json                       |  52 +++++++++++++++++++++
 src/utils/calendars.test.ts                        |  15 ++++++
 src/utils/calendars.ts                             |  26 +++++++++++
 7 files changed, 99 insertions(+), 4 deletions(-)

diff --git a/.claude/skills/new-calendar/SKILL.md b/.claude/skills/new-calendar/SKILL.md
index 8ab7256..398722a 100644
--- a/.claude/skills/new-calendar/SKILL.md
+++ b/.claude/skills/new-calendar/SKILL.md
@@ -54,7 +54,8 @@ This is **two independent gates** (see plan.md, Q3):
   year. This is decided by `src/utils/calendars.ts`:
   - **Convertible** strings hit a converter there: `Gregorian`, `Julian`, and the
     Temporal-backed `Islamic`, `Hebrew`, `Persian`, `Indian`/`Saka`, `Buddhist`,
-    `Japanese`, `Ethiopic`, plus the hand-rolled `Chinese sexagenary cycle`.
+    `Japanese`, `Ethiopic`, plus the hand-rolled `Chinese sexagenary cycle` and
+    `Maya Long Count`.
     Match an existing string **exactly** (the lookup is case/space-insensitive).
   - **Descriptive-only** — a calendar with no continuous numbered year (e.g. an
     observational lunar calendar). Use a clear `calendar` name; the engine shows
diff --git a/README.md b/README.md
index 5801796..d0dbc11 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 [![pipeline status](https://gitlab.com/PavelSafronov/when-am-i/badges/main/pipeline.svg)](https://gitlab.com/PavelSafronov/when-am-i/-/pipelines)
 [![live demo](https://img.shields.io/badge/demo-live-success)](https://pavelsafronov.gitlab.io/when-am-i)
 ![version](https://img.shields.io/badge/version-v0.1.0-blue)
-![calendar data](https://img.shields.io/badge/calendar%20data-10-orange)
+![calendar data](https://img.shields.io/badge/calendar%20data-11-orange)
 ![license](https://img.shields.io/badge/license-MIT-green)
 
 ![React](https://img.shields.io/badge/React-20232A?logo=react&logoColor=61DAFB)
@@ -50,10 +50,11 @@ and show the Gregorian year for reference).
 
 - **Convertible:** Gregorian, Julian, Islamic (Umm al-Qura), Hebrew,
   Persian (Solar Hijri), Indian (Saka), Buddhist, Japanese (nengō, Meiji onward),
-  Ethiopian.
+  Ethiopian, Maya Long Count.
 - **Descriptive-only:** the Chinese sexagenary cycle (the named 60-year
   stem-branch / zodiac-animal year), the Japanese pre-1873 lunisolar calendar,
-  and observational lunar calendars such as the Algonquian (Massachusett) reckoning.
+  the Tzolk'in/Haab' Calendar Round, and observational lunar calendars such as
+  the Algonquian (Massachusett) reckoning.
 
 ## Tech Stack
 
diff --git a/screenshots/calendars/maya/01-maya-long-count.png b/screenshots/calendars/maya/01-maya-long-count.png
new file mode 100644
index 0000000..4b06036
Binary files /dev/null and b/screenshots/calendars/maya/01-maya-long-count.png differ
diff --git a/screenshots/calendars/maya/02-tzolk-in-and-haab-calendar-round.png b/screenshots/calendars/maya/02-tzolk-in-and-haab-calendar-round.png
new file mode 100644
index 0000000..fd06691
Binary files /dev/null and b/screenshots/calendars/maya/02-tzolk-in-and-haab-calendar-round.png differ
diff --git a/src/data/calendars/maya.json b/src/data/calendars/maya.json
new file mode 100644
index 0000000..75a6dcc
--- /dev/null
+++ b/src/data/calendars/maya.json
@@ -0,0 +1,52 @@
+{
+    "region": {
+        "name": "Maya region (Yucatán Peninsula, Chiapas, Guatemala, Belize, western Honduras)",
+        "geo": {
+            "type": "Polygon",
+            "coordinates": [
+                [
+                    [-92.5, 17.8],
+                    [-90.5, 19.5],
+                    [-88.0, 21.6],
+                    [-86.7, 21.0],
+                    [-87.5, 18.5],
+                    [-88.3, 16.0],
+                    [-89.0, 15.0],
+                    [-88.0, 14.0],
+                    [-87.0, 13.8],
+                    [-89.5, 13.5],
+                    [-92.0, 14.5],
+                    [-93.0, 15.8],
+                    [-92.5, 17.8]
+                ]
+            ]
+        }
+    },
+    "eras": [
+        {
+            "name": "Long Count era",
+            "start": "-0036-01-01",
+            "end": "0909-01-01",
+            "calendar": "Maya Long Count",
+            "usedBy": "the Classic Maya",
+            "description": "The Long Count is a continuous day count from a fixed mythological creation date (0.0.0.0.0), giving an absolute, non-repeating reckoning unlike the cyclical Tzolk'in and Haab'. It appears in inscriptions from around 36 BCE (Stela 2 at Chiapa de Corzo) and was the primary monumental dating system through the Classic period, with the latest known Classic-era Long Count inscription around 909 CE (10.4.0.0.0 at Tzibanche), after which the abbreviated Short Count took over. This conversion uses the GMT (Goodman-Martinez-Thompson) correlation (584,283), the most widely accepted constant, under which 13.0.0.0.0 fell on 2012-12-21 — the well-documented completion of the 13th baktun, not an 'end of the world' as sometimes popularly claimed.",
+            "sources": [
+                "https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar",
+                "https://en.wikipedia.org/wiki/Maya_calendar"
+            ]
+        },
+        {
+            "name": "Calendar Round (Tzolk'in and Haab')",
+            "start": null,
+            "end": null,
+            "calendar": "Tzolk'in and Haab' Calendar Round",
+            "usedBy": "the Maya peoples",
+            "description": "The Tzolk'in is a 260-day cycle combining 20 day names with 13 numbers; the Haab' is a 365-day 'vague solar year' of eighteen 20-day months plus five end-of-year days (Wayeb'). Together they form the 52-Haab' (18,980-day) Calendar Round, which repeats rather than counting a continuous numbered year. Both predate and outlasted the Long Count, and the Tzolk'in and Calendar Round remain in active ceremonial and agricultural use today among Maya communities in the Guatemalan highlands and parts of Chiapas, Oaxaca, and Veracruz — so this era is left open-ended.",
+            "sources": [
+                "https://en.wikipedia.org/wiki/Maya_calendar",
+                "https://en.wikipedia.org/wiki/Tzolk%27in",
+                "https://en.wikipedia.org/wiki/Haab%27"
+            ]
+        }
+    ]
+}
diff --git a/src/utils/calendars.test.ts b/src/utils/calendars.test.ts
index 55701d4..9316402 100644
--- a/src/utils/calendars.test.ts
+++ b/src/utils/calendars.test.ts
@@ -84,6 +84,21 @@ describe('convert', () => {
         expect(convert({ year: 2024, month: 6, day: 1 }, 'Chinese').localDateLabel).toMatch(/Wood Dragon/);
     });
 
+    it('converts to the Maya Long Count (hand-rolled, GMT correlation)', () => {
+        // The well-documented completion of the 13th baktun.
+        const c = convert({ year: 2012, month: 12, day: 21 }, 'Maya Long Count');
+        expect(c).toMatchObject({ system: 'Maya Long Count', convertible: true });
+        expect(c.localYearLabel).toBe('13.0.0.0.0');
+        // The epoch date itself is 0.0.0.0.0.
+        expect(convert({ year: -3113, month: 8, day: 11 }, 'Maya Long Count').localYearLabel).toBe('0.0.0.0.0');
+    });
+
+    it('names the Tzolk\'in/Haab\' Calendar Round as descriptive-only', () => {
+        const c = convert({ year: 1500, month: 1, day: 1 }, "Tzolk'in and Haab' Calendar Round");
+        expect(c.convertible).toBe(false);
+        expect(c.localYearLabel).toBe('1500'); // Gregorian reference
+    });
+
     it('falls back to descriptive-only for unknown calendars', () => {
         const c = convert({ year: 1700, month: 4, day: 1 }, 'Some Unknown Calendar');
         expect(c.convertible).toBe(false);
diff --git a/src/utils/calendars.ts b/src/utils/calendars.ts
index ddc9121..02d15e8 100644
--- a/src/utils/calendars.ts
+++ b/src/utils/calendars.ts
@@ -243,6 +243,31 @@ function sexagenaryConverter(date: HistoricalDate): CalendarConversion {
     };
 }
 
+// ── Maya Long Count (hand-rolled) ────────────────────────────────────────────
+// The Long Count is the Maya system with a continuous day count from a fixed
+// epoch, unlike the cyclical Tzolk'in (260-day) and Haab' (365-day) calendars
+// it ran alongside (those have no continuous numbered year and are
+// descriptive-only). Epoch 0.0.0.0.0 = JDN 584283 under the GMT
+// (Goodman-Martinez-Thompson) correlation, the most widely accepted constant.
+const LONG_COUNT_EPOCH_JDN = 584283;
+const LONG_COUNT_UNITS = [144000, 7200, 360, 20, 1]; // baktun, katun, tun, winal, k'in
+
+function longCountConverter(date: HistoricalDate): CalendarConversion {
+    let r = gregorianToJulianDay(date) - LONG_COUNT_EPOCH_JDN;
+    const digits = LONG_COUNT_UNITS.map((unit) => {
+        const v = Math.floor(r / unit);
+        r -= v * unit;
+        return v;
+    });
+    const label = digits.join('.');
+    return {
+        system: 'Maya Long Count',
+        localYearLabel: label,
+        localDateLabel: `${label} (baktun.katun.tun.winal.kʼin since creation)`,
+        convertible: true,
+    };
+}
+
 const CONVERTERS: Record<string, Converter> = {
     gregorian: (date) => ({
         system: 'Gregorian',
@@ -264,6 +289,7 @@ const CONVERTERS: Record<string, Converter> = {
     ethiopic: ethiopicConverter,
     'chinese sexagenary cycle': sexagenaryConverter,
     chinese: sexagenaryConverter,
+    'maya long count': longCountConverter,
 };
 
 for (const { names, def } of TEMPORAL_CALENDARS) {