c2d75cfd 2026-10-27 Pavel Safronov | 1 | # 🚀 [Live site](https://apps.fuzzyworld.net/when-am-i/) |
3186bef2 2026-03-20 Pavel Safronov | 2 | |
6bcd5898 2026-06-19 Pavel Safronov | 3 | <!-- badges:start --> |
| 4 | <!-- generated by `npm run gen:badges` — do not edit by hand --> |
c2d75cfd 2026-10-27 Pavel Safronov | 5 | [](https://apps.fuzzyworld.net/when-am-i/) |
6bcd5898 2026-06-19 Pavel Safronov | 6 |  |
cedd4e9c 2026-06-23 Pavel Safronov | 7 |  |
6bcd5898 2026-06-19 Pavel Safronov | 8 |  |
| 9 | |
| 10 |  |
| 11 |  |
| 12 |  |
| 13 |  |
| 14 | <!-- badges:end --> |
| 15 | |
26a02b71 2026-06-19 Pavel Safronov | 16 | ## When am I? |
edbab6ce 2026-03-20 Pavel Safronov | 17 | |
26a02b71 2026-06-19 Pavel Safronov | 18 | Imagine you're a time traveler who ends up in a random place and time in history. What does a local say when you ask, "When am I?" This project is a fun and educational React app that lets you explore different calendar systems around the world by selecting a location on an interactive map and choosing any year in history. It then shows you the local calendar year and system for that place and time. |
edbab6ce 2026-03-20 Pavel Safronov | 19 | |
76ecf55e 2026-06-21 Pavel Safronov | 20 |  |
| 21 | |
| 22 | Pin a spot and a date, and see every valid answer a local could give — including |
| 23 | when more than one calendar applies at once: |
| 24 | |
| 25 |  |
| 26 | |
edbab6ce 2026-03-20 Pavel Safronov | 27 | ## Features |
| 28 | |
| 29 | - Interactive world map for selecting a location |
12bc9942 2026-06-23 Pavel Safronov | 30 | - A **year timeline** showing the picked spot's calendar eras as color-coded |
| 31 | bands (colors stay consistent across the map, timeline, and answers). Its |
| 32 | scale breaks at the region's **key dates** — the calendar changeovers — so |
| 33 | they're evenly spaced and labelled (e.g. 1954 for Vietnam) instead of crammed |
| 34 | into recent centuries; dashed lines mark where the calendar actually changes, |
| 35 | and each year label is **clickable to jump** straight to that date |
68dcf3ed 2026-06-25 Pavel Safronov | 36 | - A **day-of-year track** color-coded the same way for the *selected* year, so a |
| 37 | changeover that falls mid-year shows up as a color boundary within the year — |
| 38 | e.g. for Boston in 1752 it switches from Julian to Gregorian in mid-September |
| 39 | (the British calendar reform), with a dashed line on the exact day |
3adbd147 2026-06-20 Pavel Safronov | 40 | - **Shuffle** button that drops you at a random place and date on a non-default |
483f1c57 2026-06-21 Pavel Safronov | 41 | (non-Gregorian) calendar — e.g. Chinese, Japanese, Saka, Buddhist, Ethiopian, |
| 42 | Islamic, Persian, Julian, or a lunar reckoning |
f9fc6714 2026-06-21 Pavel Safronov | 43 | - Displays the local calendar year and system for the chosen place and time, |
| 44 | plus the modern geography of the spot — the country and nearest large city |
| 45 | (offline lookup, no network) |
| 46 | - A **two-column layout** (map + controls beside the answers) on wide screens |
| 47 | that stacks on mobile, and a **shareable URL** that captures the pinned spot, |
| 48 | date, and zoom so you can link someone to the exact view |
b92a4280 2026-06-20 Pavel Safronov | 49 | - Shows **all** valid answers at once — different people at the same place and time |
| 50 | reckoned dates differently (e.g. colonial Julian alongside an Indigenous lunar calendar) |
12bc9942 2026-06-23 Pavel Safronov | 51 | - Each answer notes **when that calendar was in use** at the spot (e.g. "in use |
| 52 | here since 1954"), so a changeover is called out plainly rather than buried |
483f1c57 2026-06-21 Pavel Safronov | 53 | - For calendars with no numbered year, gives the kind of answer a local would — |
| 54 | the moon and night ("a full moon, about night 15"), and, where the data |
| 55 | records a documented event, **"about N winters since <event>"** |
b92a4280 2026-06-20 Pavel Safronov | 56 | - A global **modern Gregorian default** for dates from 1927 on, so most recent dates |
cedd4e9c 2026-06-23 Pavel Safronov | 57 | resolve anywhere — shown alongside any local calendar (e.g. modern Iran → Persian *and* Gregorian), |
| 58 | with one deliberate exception: North Sentinel Island, where the uncontacted |
| 59 | Sentinelese have never adopted any outside calendar |
edbab6ce 2026-03-20 Pavel Safronov | 60 | - Educational and fun way to explore world calendars and history |
| 61 | |
483f1c57 2026-06-21 Pavel Safronov | 62 | ### Supported calendars |
| 63 | |
| 64 | The engine returns every valid answer for a place and date. A calendar is |
| 65 | **convertible** when a deterministic rule yields a numbered local year, or |
| 66 | **descriptive-only** when it has no continuous numbered year (we still place you |
| 67 | in time — the moon/season, the named year, and "winters since" a recorded event — |
| 68 | and show the Gregorian year for reference). |
| 69 | |
| 70 | - **Convertible:** Gregorian, Julian, Islamic (Umm al-Qura), Hebrew, |
| 71 | Persian (Solar Hijri), Indian (Saka), Buddhist, Japanese (nengō, Meiji onward), |
c571572f 2026-06-21 Pavel Safronov | 72 | Ethiopian, Maya Long Count, French Republican calendar (year only, from a |
5f04d90e 2026-06-21 Pavel Safronov | 73 | sourced table of each year's actual equinox-based start date), Dangi |
12bc9942 2026-06-23 Pavel Safronov | 74 | (Dangun era, South Korea), Juche (North Korea, 1997–2024), ab urbe condita |
| 75 | (AUC, from Rome's founding, 753 BC) and the Seleucid era (Anno Graecorum, |
| 76 | 312/311 BC) — both year-only arithmetic offsets. |
483f1c57 2026-06-21 Pavel Safronov | 77 | - **Descriptive-only:** the Chinese sexagenary cycle (the named 60-year |
f24ee458 2026-06-21 Pavel Safronov | 78 | stem-branch / zodiac-animal year), the Korean, Vietnamese, and Japanese |
| 79 | pre-modern lunisolar calendars, the Tzolk'in/Haab' Calendar Round, the |
| 80 | Aztec/Mexica year-bearer (52-year cycle) and Tōnalpōhualli (260-day ritual |
| 81 | cycle), the Ottoman Rumi fiscal calendar, the Māori maramataka, and |
| 82 | observational lunar calendars such as the Algonquian (Massachusett) |
| 83 | reckoning. |
483f1c57 2026-06-21 Pavel Safronov | 84 | |
edbab6ce 2026-03-20 Pavel Safronov | 85 | ## Tech Stack |
| 86 | |
| 87 | - React (SPA) |
| 88 | - Vite (modern, fast bundler) |
| 89 | - TypeScript |
| 90 | - Open-source map library (e.g., Leaflet or MapLibre) |
| 91 | |
| 92 | ## Getting Started |
| 93 | |
| 94 | 1. **Install dependencies:** |
| 95 | |
| 96 | ```bash |
| 97 | npm install |
| 98 | ``` |
| 99 | |
| 100 | 2. **Start the development server:** |
| 101 | |
| 102 | ```bash |
| 103 | npm run dev |
| 104 | ``` |
| 105 | |
| 106 | 3. **Build for production:** |
| 107 | |
| 108 | ```bash |
| 109 | npm run build |
| 110 | ``` |
| 111 | |
60741172 2026-06-17 Pavel Safronov | 112 | ## Development |
| 113 | |
| 114 | Quality checks (also run in CI before any deploy): |
| 115 | |
| 116 | ```bash |
30de6faf 2026-06-20 Pavel Safronov | 117 | npm run lint # ESLint (flat config) |
| 118 | npm run typecheck # tsc --noEmit |
| 119 | npm test # Vitest unit tests |
| 120 | npm run test:watch # Vitest in watch mode |
| 121 | npm run validate:data # Validate every calendar data file against the schema |
60741172 2026-06-17 Pavel Safronov | 122 | ``` |
| 123 | |
30de6faf 2026-06-20 Pavel Safronov | 124 | `validate:data` also runs automatically before every build (`prebuild`) and in |
| 125 | CI, so malformed calendar data can never be deployed. See |
| 126 | [Contributing Calendar Data](#contributing-calendar-data). |
| 127 | |
6bcd5898 2026-06-19 Pavel Safronov | 128 | ### Badges |
| 129 | |
| 130 | The README badge block (between the `badges:start`/`badges:end` markers above) is |
| 131 | generated, not hand-edited. It pulls the app version from `package.json` and counts |
| 132 | the calendar data files, so the badges never drift. Run it before opening a PR: |
| 133 | |
| 134 | ```bash |
| 135 | npm run gen:badges # rewrite the badge block in README.md |
| 136 | npm run gen:badges -- --check # fail if the block is stale (no write) — for CI |
| 137 | ``` |
| 138 | |
48a625b7 2026-06-19 Pavel Safronov | 139 | ### Screenshots |
| 140 | |
| 141 | Screenshots are generated with Playwright, which forces deterministic app state |
| 142 | via URL params (`?lat=&lng=&zoom=&year=&month=&day=&era=`). The scripts reuse a |
68dcf3ed 2026-06-25 Pavel Safronov | 143 | running dev server on port 5173 or start one automatically. Set |
| 144 | `SCREENSHOT_PORT` to use a different port — handy when another checkout (e.g. a |
| 145 | parallel workspace) already holds 5173, so you capture *this* code, not theirs. |
48a625b7 2026-06-19 Pavel Safronov | 146 | |
| 147 | ```bash |
| 148 | npm run screenshot # app states → screenshots/app/ |
26a02b71 2026-06-19 Pavel Safronov | 149 | npm run screenshot:calendar -- src/data/calendars/massachusett.json # one calendar → screenshots/calendars/<file>/ |
73912a47 2026-06-20 Pavel Safronov | 150 | npm run screenshot:calendars # regenerate ALL calendars → screenshots/calendars/ |
76ecf55e 2026-06-21 Pavel Safronov | 151 | npm run screenshot:readme # the two README images → screenshots/readme/ |
48a625b7 2026-06-19 Pavel Safronov | 152 | ``` |
| 153 | |
| 154 | `screenshot:calendar` pins a point inside the region and a representative date |
| 155 | in each era, capturing the answer panel and shaded map per era. |
73912a47 2026-06-20 Pavel Safronov | 156 | `screenshot:calendars` does the same for every calendar data file at once |
| 157 | (reusing one dev server + browser) — run it after adding or editing calendar |
| 158 | data so the checked-in screenshots stay current. |
48a625b7 2026-06-19 Pavel Safronov | 159 | |
76ecf55e 2026-06-21 Pavel Safronov | 160 | `screenshot:readme` regenerates the two images embedded above: a full-viewport |
| 161 | landing shot, and an example state cropped to just the map+answers column |
| 162 | (skipping the header/intro, which a README image doesn't need) — narrower and |
| 163 | more deliberately cropped than the full-page shots above, since a hero image |
| 164 | needs to read at a glance. |
| 165 | |
e6fe2bc7 2026-06-17 Pavel Safronov | 166 | ## Reporting issues |
| 167 | |
c2d75cfd 2026-10-27 Pavel Safronov | 168 | The app footer links to the [source repository](https://code.fuzzyworld.net/when-am-i/) |
e6fe2bc7 2026-06-17 Pavel Safronov | 169 | and a **Report an issue** link. The latter opens a pre-addressed email to the |
b9ae7fbb 2026-10-27 Pavel Safronov | 170 | project's bug-report address (no account needed) prefilled with context |
e6fe2bc7 2026-06-17 Pavel Safronov | 171 | — build version/commit, browser, URL, and the current selection. If the app |
| 172 | crashes, the error report additionally includes the error message and stack. |
| 173 | |
edbab6ce 2026-03-20 Pavel Safronov | 174 | ## Project Structure |
| 175 | |
30b4cdad 2026-03-20 Pavel Safronov | 176 | - `src/components/` — React components |
| 177 | - `src/utils/` — Utility functions |
| 178 | - `src/types/` — Shared TypeScript types |
| 179 | - `src/hooks/` — Custom React hooks |
| 180 | - `src/data/` — Data files |
| 181 | - `src/assets/` — App images and static assets |
| 182 | - `public/` — Static files served at root |
edbab6ce 2026-03-20 Pavel Safronov | 183 | |
359276b3 2026-03-20 Pavel Safronov | 184 | ## Contributing Calendar Data |
| 185 | |
| 186 | Calendar region and era data is stored as flat JSON files in `src/data/calendars/`. |
| 187 | Each file describes a region (using GeoJSON geometry) and its calendar eras over time. |
| 188 | |
26a02b71 2026-06-19 Pavel Safronov | 189 | ### Example (abbreviated): based on `src/data/calendars/massachusett.json` |
143b640c 2026-06-19 Pavel Safronov | 190 | |
26a02b71 2026-06-19 Pavel Safronov | 191 | Note how answers can **overlap** — both in time and in space. A click in 1650s |
| 192 | Boston returns the Massachusett reckoning (from `massachusett.json`, a tight |
| 193 | homeland polygon around Massachusetts Bay) **and** the colonial Julian calendar |
| 194 | (from `colonial-america.json`, a broad "British North America" polygon that also |
| 195 | covers the same spot). The Julian → Gregorian cutover was empire-wide, so it |
| 196 | lives in the larger region rather than being duplicated into every town; |
| 197 | overlapping regions are expected, not an error. |
359276b3 2026-03-20 Pavel Safronov | 198 | |
| 199 | ```json |
| 200 | { |
| 201 | "region": { |
26a02b71 2026-06-19 Pavel Safronov | 202 | "name": "Massachusett homeland", |
359276b3 2026-03-20 Pavel Safronov | 203 | "geo": { |
| 204 | "type": "Polygon", |
| 205 | "coordinates": [ |
| 206 | [ |
143b640c 2026-06-19 Pavel Safronov | 207 | [-71.30, 42.25], |
| 208 | [-71.05, 42.50], |
| 209 | [-70.92, 42.41], |
| 210 | [-71.12, 42.22], |
| 211 | [-71.30, 42.25] |
359276b3 2026-03-20 Pavel Safronov | 212 | ] |
| 213 | ] |
| 214 | } |
| 215 | }, |
| 216 | "eras": [ |
| 217 | { |
143b640c 2026-06-19 Pavel Safronov | 218 | "name": "Massachusett reckoning", |
| 219 | "start": null, |
359276b3 2026-03-20 Pavel Safronov | 220 | "end": null, |
143b640c 2026-06-19 Pavel Safronov | 221 | "calendar": "Algonquian lunar calendar", |
| 222 | "usedBy": "the Massachusett people", |
26a02b71 2026-06-19 Pavel Safronov | 223 | "description": "Lunar and seasonal reckoning of the Massachusett, the Algonquian people of the Massachusetts Bay area.", |
143b640c 2026-06-19 Pavel Safronov | 224 | "sources": ["https://en.wikipedia.org/wiki/Massachusett"] |
359276b3 2026-03-20 Pavel Safronov | 225 | } |
| 226 | ] |
| 227 | } |
| 228 | ``` |
| 229 | |
26a02b71 2026-06-19 Pavel Safronov | 230 | The colonial Julian and Gregorian eras live in `colonial-america.json`, whose |
| 231 | larger polygon overlaps this one — so a Boston click resolves against both files. |
| 232 | |
483f1c57 2026-06-21 Pavel Safronov | 233 | An era for a calendar with no numbered year may also carry **`anchorEvents`** — a |
| 234 | list of documented, dated, **sourced** events a local reckons time from. The app |
| 235 | reports the most recent one at or before the date as "about N winters since |
| 236 | <event>" (e.g. the Massachusett reckoning counts winters since the |
| 237 | 1616–1619 epidemic). Anchor events must be real and cited — the engine only |
| 238 | counts the winters, it never invents the event. |
| 239 | |
359276b3 2026-03-20 Pavel Safronov | 240 | ### Schema |
| 241 | |
483f1c57 2026-06-21 Pavel Safronov | 242 | See `src/data/calendars/SCHEMA.md` (human-readable field docs) and |
| 243 | `scripts/validate-data.mts` (the authoritative, machine-checkable schema run by |
| 244 | `validate:data`). |
359276b3 2026-03-20 Pavel Safronov | 245 | |
| 246 | ### How to contribute |
| 247 | |
7922a983 2026-10-27 Pavel Safronov | 248 | Contributions arrive by email: share a branch from any git host, or send a |
| 249 | `git format-patch`, to `contrib-68uq+when-am-i@fuzzyworld.net`. Details: |
| 250 | [how to contribute](https://code.fuzzyworld.net/contribute/). |
c2d75cfd 2026-10-27 Pavel Safronov | 251 | |
7922a983 2026-10-27 Pavel Safronov | 252 | - Add or edit files in `src/data/calendars/`, on a branch or as a patch. |
359276b3 2026-03-20 Pavel Safronov | 253 | - Use [geojson.io](https://geojson.io/) or similar tools to draw and export region polygons. |
483f1c57 2026-06-21 Pavel Safronov | 254 | - Include sources/references for each era (and for every anchor event). |
| 255 | - Working in Claude Code? The repo ships a **`new-calendar`** skill |
| 256 | (`.claude/skills/new-calendar/`) that walks the whole flow — researching a |
| 257 | place/people's calendar with citations, drafting the polygon and eras, picking |
| 258 | the right converter (or descriptive-only), then validating and screenshotting. |
48a625b7 2026-06-19 Pavel Safronov | 259 | - Run `npm run screenshot:calendar -- src/data/calendars/<your-file>.json` and |
7922a983 2026-10-27 Pavel Safronov | 260 | attach the images to your email, so the region, eras, and conversions can be |
48a625b7 2026-06-19 Pavel Safronov | 261 | reviewed visually. |
6bcd5898 2026-06-19 Pavel Safronov | 262 | - Run `npm run gen:badges` so the README's calendar-data count reflects your |
| 263 | addition, and commit the updated README. |
30de6faf 2026-06-20 Pavel Safronov | 264 | - Run `npm run validate:data` to check your file against the schema (required |
| 265 | fields, valid geometry, parseable dates). This runs automatically before every |
| 266 | build and in CI, so a malformed file will fail the pipeline rather than deploy. |
359276b3 2026-03-20 Pavel Safronov | 267 | |
5f04d90e 2026-06-21 Pavel Safronov | 268 | ### Calendars we'd like to add |
| 269 | |
| 270 | Ranked by rough "years in use × area/population reached" — the systems that |
| 271 | would let the most human-years of history resolve to a real local answer. |
| 272 | Contributions for any of these (or anything else, sourced) are very welcome — |
| 273 | see [How to contribute](#how-to-contribute) above, and check |
| 274 | `src/utils/calendars.ts` for whether a system needs new converter work or can |
| 275 | ship as descriptive-only. |
| 276 | |
f24ee458 2026-06-21 Pavel Safronov | 277 | 1. **Tibetan calendar (Bod rtsis)** — a lunisolar calendar in continuous use |
5f04d90e 2026-06-21 Pavel Safronov | 278 | across the Tibetan Plateau for over a thousand years, with its own 60-year |
| 279 | cycle and year names. |
f24ee458 2026-06-21 Pavel Safronov | 280 | 2. **Mongolian calendar** — the Mongol Empire and successor states' lunisolar |
5f04d90e 2026-06-21 Pavel Safronov | 281 | calendar (related to but distinct from the Chinese and Tibetan systems), |
| 282 | spanning a vast historical territory. |
f24ee458 2026-06-21 Pavel Safronov | 283 | 3. **Coptic calendar** — Egypt's calendar since antiquity (descended from the |
5f04d90e 2026-06-21 Pavel Safronov | 284 | ancient Egyptian civil calendar), still used liturgically by the Coptic |
| 285 | Orthodox Church; distinct from but related to the Ethiopian calendar already |
| 286 | supported. (Note: the Temporal `coptic` calendar id is broken in our |
| 287 | polyfill version, same class of bug as `chinese`/`ethiopic` — would likely |
| 288 | need a hand-rolled converter like Ethiopian's.) |
f24ee458 2026-06-21 Pavel Safronov | 289 | 4. **Ancient Egyptian civil calendar** — a 365-day calendar used for roughly |
5f04d90e 2026-06-21 Pavel Safronov | 290 | three millennia along the Nile; descriptive-only (no continuous numbered |
| 291 | year survives in common use), but enormous time depth. |
f24ee458 2026-06-21 Pavel Safronov | 292 | 5. **Armenian calendar** — in continuous use since antiquity, with its own |
5f04d90e 2026-06-21 Pavel Safronov | 293 | epoch and a fixed 365-day year (no leap day), giving it a slow drift against |
| 294 | the Gregorian year worth surfacing. |
f24ee458 2026-06-21 Pavel Safronov | 295 | 6. **Coast Salish / Puget Sound calendar** — a sourced, people-specific lunar |
5f04d90e 2026-06-21 Pavel Safronov | 296 | reckoning for the Pacific Northwest, paralleling the Massachusett entry; the |
| 297 | place/people this skill's original brainstorm named as an example. |
f24ee458 2026-06-21 Pavel Safronov | 298 | 7. **Inca calendar** — a lunisolar/solar calendar used across the Inca Empire's |
5f04d90e 2026-06-21 Pavel Safronov | 299 | considerable territory in the Andes; mostly descriptive (reconstructions of |
| 300 | its exact rules are debated, so sourcing needs care). |
f24ee458 2026-06-21 Pavel Safronov | 301 | 8. **Balinese Pawukon and Saka calendars** — Bali's parallel 210-day ritual |
9c2d2d06 2026-06-21 Pavel Safronov | 302 | cycle and a Saka-derived solar count, both still in everyday use. |
f24ee458 2026-06-21 Pavel Safronov | 303 | 9. **Bahá'í calendar** — a 19-month, 19-day solar calendar in worldwide use |
| 304 | since the mid-19th century; smaller area than the others here but a long |
| 305 | enough span and clean enough arithmetic to be a good convertible candidate. |
| 306 | 10. **Sikh Nanakshahi calendar** — a solar calendar adopted by the SGPC in 2003 |
5f04d90e 2026-06-21 Pavel Safronov | 307 | for Sikh religious dates, with a clear epoch (Guru Nanak's birth year) — |
| 308 | likely a simple convertible offset like Dangi/Juche. |
3cbfe9ad 2026-06-22 Pavel Safronov | 309 | 11. **A dedicated South Korea region** — `korea.json`'s polygon covers the whole |
| 310 | peninsula, so it can't host South-only eras without colliding with |
| 311 | `north-korea.json` (both would fire on the same point). South Korea's own |
| 312 | 1948–1962 official use of the Dangi (Dangun) era was removed from |
| 313 | `korea.json` for this reason and isn't documented anywhere yet; a |
| 314 | South-Korea-only polygon could restore it. |
5f04d90e 2026-06-21 Pavel Safronov | 315 | |
edbab6ce 2026-03-20 Pavel Safronov | 316 | ## License |
| 317 | |
6bcd5898 2026-06-19 Pavel Safronov | 318 | [MIT](LICENSE) © Pavel Safronov |
edbab6ce 2026-03-20 Pavel Safronov | 319 | |
30b4cdad 2026-03-20 Pavel Safronov | 320 | ## Attributions |
edbab6ce 2026-03-20 Pavel Safronov | 321 | |
30b4cdad 2026-03-20 Pavel Safronov | 322 | - Map tiles and data: [OpenStreetMap](https://www.openstreetmap.org/) |
| 323 | - Map library: [Leaflet](https://leafletjs.com/) |
f9fc6714 2026-06-21 Pavel Safronov | 324 | - City data (for the country / nearest-city readout): [GeoNames](https://www.geonames.org/), licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Regenerate the bundled `src/data/cities.json` with `npm run gen:cities`. |
359276b3 2026-03-20 Pavel Safronov | 325 | - Calendar icon: [Calendar icons created by Icongeek26 - Flaticon](https://www.flaticon.com/free-icons/calendar) |
dc97539a 2026-06-21 Pavel Safronov | 326 | |
| 327 | ## Implementation |
| 328 | |
| 329 | This entire app was written by Claude. Pavel supplied the ideas, Claude did all the heavy lifting. Pavel wrote this paragraph. In part, this is a continuation of my exploration of agentic coding. See my other similarly-developed projects: [https://fuzzyworld.net/projects/](https://fuzzyworld.net/projects/). |