when-am-i / commit
Add Maya calendar (Long Count + Tzolk'in/Haab' Calendar Round)
.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 @@ [](https://gitlab.com/PavelSafronov/when-am-i/-/pipelines) [](https://pavelsafronov.gitlab.io/when-am-i)  - +   @@ -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) {