when-am-i / files / src / data / calendars / SCHEMA.md
SCHEMA.md
26a02b71 2026-06-19 Pavel Safronov | 1 | # Calendar Region/Era Schema for "When am I?" |
359276b3 2026-03-20 Pavel Safronov | 2 | |
| 3 | Place JSON files in `src/data/calendars/`. | |
| 4 | Each file describes a region and its calendar eras. | |
| 5 | ||
| 6 | ## Schema | |
| 7 | ||
| 8 | ```json | |
| 9 | { | |
| 10 | "region": { | |
| 11 | "name": "string (required)", | |
| 12 | "geo": { | |
| 13 | "type": "Polygon|MultiPolygon|Point|...", | |
| 14 | "coordinates": "GeoJSON coordinates array" | |
| 15 | } | |
| 16 | }, | |
| 17 | "eras": [ | |
| 18 | { | |
| 19 | "name": "string (required)", | |
| 20 | "start": "YYYY-MM-DD|null (start date, null for earliest)", | |
| 21 | "end": "YYYY-MM-DD|null (end date, null for present)", | |
| 22 | "calendar": "string (calendar system name)", | |
143b640c 2026-06-19 Pavel Safronov | 23 | "usedBy": "string (optional — whose calendar this is, e.g. 'English colonists')", |
359276b3 2026-03-20 Pavel Safronov | 24 | "description": "string (optional)", |
5c66fc6a 2026-06-21 Pavel Safronov | 25 | "sources": ["string (URL or citation)"], |
| 26 | "anchorEvents": [ | |
| 27 | { | |
| 28 | "name": "string (how a local names it, e.g. 'the great dying')", | |
| 29 | "date": "YYYY-MM-DD (proleptic Gregorian, when it happened)", | |
| 30 | "sources": ["string (URL or citation — required, ≥1)"] | |
| 31 | } | |
cedd4e9c 2026-06-23 Pavel Safronov | 32 | ], |
| 33 | "suppressModernDefault": "boolean (optional, default false — see Notes)" | |
359276b3 2026-03-20 Pavel Safronov | 34 | } |
| 35 | ] | |
| 36 | } | |
| 37 | ``` | |
143b640c 2026-06-19 Pavel Safronov | 38 | |
30de6faf 2026-06-20 Pavel Safronov | 39 | ## Validation |
| 40 | ||
| 41 | Every file here is checked by `npm run validate:data` | |
| 42 | ([`scripts/validate-data.mts`](../../../scripts/validate-data.mts)) — the | |
| 43 | authoritative, machine-checkable form of this schema. It runs automatically | |
| 44 | before every build (`prebuild`) and in CI, so invalid data can never deploy. It | |
| 45 | verifies required fields, that geometry rings are valid `[lng, lat]` and closed, | |
| 46 | that `start`/`end` are real proleptic-Gregorian dates, and that `start` precedes | |
| 47 | `end`. The block above is the human-readable companion; the script is the source | |
| 48 | of truth. | |
| 49 | ||
143b640c 2026-06-19 Pavel Safronov | 50 | ## Notes |
| 51 | ||
| 52 | - Dates (`start`/`end`) are **proleptic Gregorian** `YYYY-MM-DD`. They are | |
| 53 | compared as half-open intervals `[start, end)`; `null` means open-ended. | |
26a02b71 2026-06-19 Pavel Safronov | 54 | - **Eras may overlap in time, and regions may overlap in space.** Parallel |
| 55 | calendars coexisting at the same place and time is supported — each is shown as | |
| 56 | a separate answer. The overlap can be within one file (multiple eras active at | |
| 57 | once) or across files (overlapping region polygons): e.g. the Massachusett | |
| 58 | reckoning (`massachusett.json`) and the colonial Julian/Gregorian calendars | |
| 59 | (`colonial-america.json`, a larger polygon) both apply in 1650s Boston. | |
143b640c 2026-06-19 Pavel Safronov | 60 | - `usedBy` labels whose answer it is, shown as "Ask <usedBy>: …". |
| 61 | - A calendar with no continuous numeric year (e.g. an observational lunar | |
| 62 | calendar) is shown descriptively rather than converted to a number. | |
5c66fc6a 2026-06-21 Pavel Safronov | 63 | - **`anchorEvents`** (optional) let such a calendar place you in time the way a |
| 64 | local would: the engine reports the most recent one at/before the date as | |
| 65 | "about N winters since <event>". Each event needs a `name`, a proleptic- | |
| 66 | Gregorian `date`, and at least one **`source`** — anchors must be documented | |
| 67 | real events; the engine only counts the winters, it never invents an event. | |
cedd4e9c 2026-06-23 Pavel Safronov | 68 | - **`suppressModernDefault`** (optional, default `false`) opts a region out of |
| 69 | the app's modern global Gregorian default (shown everywhere from 1927 on, | |
| 70 | see `resolve.ts`) for any date this era covers. This is for the rare place | |
| 71 | where no outside civil calendar has ever applied at all — e.g. | |
| 72 | `north-sentinel-island.json`, where the Sentinelese have never adopted the | |
| 73 | Gregorian calendar — not for a region that simply has its own *different* | |
| 74 | calendar (those already suppress the default automatically once they supply | |
| 75 | their own Gregorian-calendar era, or coexist with it like modern Iran). | |
| 76 | Almost no region should set this. |