fuzzyworld

when-am-i / files / README.md

README.md

16.1 KB Β· history Β· blame

πŸš€ Live site

live demo version calendar data license

React TypeScript Vite Leaflet

When am I?

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.

Landing page: a map and date picker, no spot chosen yet

Pin a spot and a date, and see every valid answer a local could give β€” including when more than one calendar applies at once:

Boston, 1650: both the colonial Julian calendar and the Massachusett lunar reckoning shown as valid answers

Features

Supported calendars

The engine returns every valid answer for a place and date. A calendar is convertible when a deterministic rule yields a numbered local year, or descriptive-only when it has no continuous numbered year (we still place you in time β€” the moon/season, the named year, and "winters since" a recorded event β€” and show the Gregorian year for reference).

Tech Stack

Getting Started

  1. Install dependencies:

bash npm install

  1. Start the development server:

bash npm run dev

  1. Build for production:

bash npm run build

Development

Quality checks (also run in CI before any deploy):

npm run lint           # ESLint (flat config)
npm run typecheck      # tsc --noEmit
npm test               # Vitest unit tests
npm run test:watch     # Vitest in watch mode
npm run validate:data  # Validate every calendar data file against the schema

validate:data also runs automatically before every build (prebuild) and in CI, so malformed calendar data can never be deployed. See Contributing Calendar Data.

Badges

The README badge block (between the badges:start/badges:end markers above) is generated, not hand-edited. It pulls the app version from package.json and counts the calendar data files, so the badges never drift. Run it before opening a PR:

npm run gen:badges            # rewrite the badge block in README.md
npm run gen:badges -- --check # fail if the block is stale (no write) β€” for CI

Screenshots

Screenshots are generated with Playwright, which forces deterministic app state via URL params (?lat=&lng=&zoom=&year=&month=&day=&era=). The scripts reuse a running dev server on port 5173 or start one automatically. Set SCREENSHOT_PORT to use a different port β€” handy when another checkout (e.g. a parallel workspace) already holds 5173, so you capture this code, not theirs.

npm run screenshot                                   # app states β†’ screenshots/app/
npm run screenshot:calendar -- src/data/calendars/massachusett.json   # one calendar β†’ screenshots/calendars/<file>/
npm run screenshot:calendars                         # regenerate ALL calendars β†’ screenshots/calendars/
npm run screenshot:readme                            # the two README images β†’ screenshots/readme/

screenshot:calendar pins a point inside the region and a representative date in each era, capturing the answer panel and shaded map per era. screenshot:calendars does the same for every calendar data file at once (reusing one dev server + browser) β€” run it after adding or editing calendar data so the checked-in screenshots stay current.

screenshot:readme regenerates the two images embedded above: a full-viewport landing shot, and an example state cropped to just the map+answers column (skipping the header/intro, which a README image doesn't need) β€” narrower and more deliberately cropped than the full-page shots above, since a hero image needs to read at a glance.

Reporting issues

The app footer links to the source repository and a Report an issue link. The latter opens a pre-addressed email to the project's bug-report address (no account needed) prefilled with context β€” build version/commit, browser, URL, and the current selection. If the app crashes, the error report additionally includes the error message and stack.

Project Structure

Contributing Calendar Data

Calendar region and era data is stored as flat JSON files in src/data/calendars/. Each file describes a region (using GeoJSON geometry) and its calendar eras over time.

Example (abbreviated): based on src/data/calendars/massachusett.json

Note how answers can overlap β€” both in time and in space. A click in 1650s Boston returns the Massachusett reckoning (from massachusett.json, a tight homeland polygon around Massachusetts Bay) and the colonial Julian calendar (from colonial-america.json, a broad "British North America" polygon that also covers the same spot). The Julian β†’ Gregorian cutover was empire-wide, so it lives in the larger region rather than being duplicated into every town; overlapping regions are expected, not an error.

{
   "region": {
      "name": "Massachusett homeland",
      "geo": {
         "type": "Polygon",
         "coordinates": [
            [
               [-71.30, 42.25],
               [-71.05, 42.50],
               [-70.92, 42.41],
               [-71.12, 42.22],
               [-71.30, 42.25]
            ]
         ]
      }
   },
   "eras": [
      {
         "name": "Massachusett reckoning",
         "start": null,
         "end": null,
         "calendar": "Algonquian lunar calendar",
         "usedBy": "the Massachusett people",
         "description": "Lunar and seasonal reckoning of the Massachusett, the Algonquian people of the Massachusetts Bay area.",
         "sources": ["https://en.wikipedia.org/wiki/Massachusett"]
      }
   ]
}

The colonial Julian and Gregorian eras live in colonial-america.json, whose larger polygon overlaps this one β€” so a Boston click resolves against both files.

An era for a calendar with no numbered year may also carry anchorEvents β€” a list of documented, dated, sourced events a local reckons time from. The app reports the most recent one at or before the date as "about N winters since <event>" (e.g. the Massachusett reckoning counts winters since the 1616–1619 epidemic). Anchor events must be real and cited β€” the engine only counts the winters, it never invents the event.

Schema

See src/data/calendars/SCHEMA.md (human-readable field docs) and scripts/validate-data.mts (the authoritative, machine-checkable schema run by validate:data).

How to contribute

Contributions arrive by email: share a branch from any git host, or send a git format-patch, to contrib-68uq+when-am-i@fuzzyworld.net. Details: how to contribute.

Calendars we'd like to add

Ranked by rough "years in use Γ— area/population reached" β€” the systems that would let the most human-years of history resolve to a real local answer. Contributions for any of these (or anything else, sourced) are very welcome β€” see How to contribute above, and check src/utils/calendars.ts for whether a system needs new converter work or can ship as descriptive-only.

  1. Tibetan calendar (Bod rtsis) β€” a lunisolar calendar in continuous use across the Tibetan Plateau for over a thousand years, with its own 60-year cycle and year names.
  2. Mongolian calendar β€” the Mongol Empire and successor states' lunisolar calendar (related to but distinct from the Chinese and Tibetan systems), spanning a vast historical territory.
  3. Coptic calendar β€” Egypt's calendar since antiquity (descended from the ancient Egyptian civil calendar), still used liturgically by the Coptic Orthodox Church; distinct from but related to the Ethiopian calendar already supported. (Note: the Temporal coptic calendar id is broken in our polyfill version, same class of bug as chinese/ethiopic β€” would likely need a hand-rolled converter like Ethiopian's.)
  4. Ancient Egyptian civil calendar β€” a 365-day calendar used for roughly three millennia along the Nile; descriptive-only (no continuous numbered year survives in common use), but enormous time depth.
  5. Armenian calendar β€” in continuous use since antiquity, with its own epoch and a fixed 365-day year (no leap day), giving it a slow drift against the Gregorian year worth surfacing.
  6. Coast Salish / Puget Sound calendar β€” a sourced, people-specific lunar reckoning for the Pacific Northwest, paralleling the Massachusett entry; the place/people this skill's original brainstorm named as an example.
  7. Inca calendar β€” a lunisolar/solar calendar used across the Inca Empire's considerable territory in the Andes; mostly descriptive (reconstructions of its exact rules are debated, so sourcing needs care).
  8. Balinese Pawukon and Saka calendars β€” Bali's parallel 210-day ritual cycle and a Saka-derived solar count, both still in everyday use.
  9. BahΓ‘'Γ­ calendar β€” a 19-month, 19-day solar calendar in worldwide use since the mid-19th century; smaller area than the others here but a long enough span and clean enough arithmetic to be a good convertible candidate.
  10. Sikh Nanakshahi calendar β€” a solar calendar adopted by the SGPC in 2003 for Sikh religious dates, with a clear epoch (Guru Nanak's birth year) β€” likely a simple convertible offset like Dangi/Juche.
  11. A dedicated South Korea region β€” korea.json's polygon covers the whole peninsula, so it can't host South-only eras without colliding with north-korea.json (both would fire on the same point). South Korea's own 1948–1962 official use of the Dangi (Dangun) era was removed from korea.json for this reason and isn't documented anywhere yet; a South-Korea-only polygon could restore it.

License

MIT Β© Pavel Safronov

Attributions

Implementation

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/.

Source
1# πŸš€ [Live site](https://apps.fuzzyworld.net/when-am-i/)
2
3<!-- badges:start -->
4<!-- generated by `npm run gen:badges` β€” do not edit by hand -->
5[![live demo](https://img.shields.io/badge/demo-live-success)](https://apps.fuzzyworld.net/when-am-i/)
6![version](https://img.shields.io/badge/version-v0.1.0-blue)
7![calendar data](https://img.shields.io/badge/calendar%20data-20-orange)
8![license](https://img.shields.io/badge/license-MIT-green)
9
10![React](https://img.shields.io/badge/React-20232A?logo=react&logoColor=61DAFB)
11![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=fff)
12![Vite](https://img.shields.io/badge/Vite-646CFF?logo=vite&logoColor=fff)
13![Leaflet](https://img.shields.io/badge/Leaflet-199900?logo=leaflet&logoColor=fff)
14<!-- badges:end -->
15
16## When am I?
17
18Imagine 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.
19
20![Landing page: a map and date picker, no spot chosen yet](screenshots/readme/01-landing.png)
21
22Pin a spot and a date, and see every valid answer a local could give β€” including
23when more than one calendar applies at once:
24
25![Boston, 1650: both the colonial Julian calendar and the Massachusett lunar reckoning shown as valid answers](screenshots/readme/02-example.png)
26
27## Features
28
29- Interactive world map for selecting a location
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
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
40- **Shuffle** button that drops you at a random place and date on a non-default
41 (non-Gregorian) calendar β€” e.g. Chinese, Japanese, Saka, Buddhist, Ethiopian,
42 Islamic, Persian, Julian, or a lunar reckoning
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
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)
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
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 &lt;event&gt;"**
56- A global **modern Gregorian default** for dates from 1927 on, so most recent dates
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
60- Educational and fun way to explore world calendars and history
61
62### Supported calendars
63
64The 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
67in time β€” the moon/season, the named year, and "winters since" a recorded event β€”
68and 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),
72 Ethiopian, Maya Long Count, French Republican calendar (year only, from a
73 sourced table of each year's actual equinox-based start date), Dangi
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.
77- **Descriptive-only:** the Chinese sexagenary cycle (the named 60-year
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.
84
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
941. **Install dependencies:**
95
96 ```bash
97 npm install
98 ```
99
1002. **Start the development server:**
101
102 ```bash
103 npm run dev
104 ```
105
1063. **Build for production:**
107
108 ```bash
109 npm run build
110 ```
111
112## Development
113
114Quality checks (also run in CI before any deploy):
115
116```bash
117npm run lint # ESLint (flat config)
118npm run typecheck # tsc --noEmit
119npm test # Vitest unit tests
120npm run test:watch # Vitest in watch mode
121npm run validate:data # Validate every calendar data file against the schema
122```
123
124`validate:data` also runs automatically before every build (`prebuild`) and in
125CI, so malformed calendar data can never be deployed. See
126[Contributing Calendar Data](#contributing-calendar-data).
127
128### Badges
129
130The README badge block (between the `badges:start`/`badges:end` markers above) is
131generated, not hand-edited. It pulls the app version from `package.json` and counts
132the calendar data files, so the badges never drift. Run it before opening a PR:
133
134```bash
135npm run gen:badges # rewrite the badge block in README.md
136npm run gen:badges -- --check # fail if the block is stale (no write) β€” for CI
137```
138
139### Screenshots
140
141Screenshots are generated with Playwright, which forces deterministic app state
142via URL params (`?lat=&lng=&zoom=&year=&month=&day=&era=`). The scripts reuse a
143running 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
145parallel workspace) already holds 5173, so you capture *this* code, not theirs.
146
147```bash
148npm run screenshot # app states β†’ screenshots/app/
149npm run screenshot:calendar -- src/data/calendars/massachusett.json # one calendar β†’ screenshots/calendars/<file>/
150npm run screenshot:calendars # regenerate ALL calendars β†’ screenshots/calendars/
151npm run screenshot:readme # the two README images β†’ screenshots/readme/
152```
153
154`screenshot:calendar` pins a point inside the region and a representative date
155in each era, capturing the answer panel and shaded map per era.
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
158data so the checked-in screenshots stay current.
159
160`screenshot:readme` regenerates the two images embedded above: a full-viewport
161landing 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
163more deliberately cropped than the full-page shots above, since a hero image
164needs to read at a glance.
165
166## Reporting issues
167
168The app footer links to the [source repository](https://code.fuzzyworld.net/when-am-i/)
169and a **Report an issue** link. The latter opens a pre-addressed email to the
170project's bug-report address (no account needed) prefilled with context
171β€” build version/commit, browser, URL, and the current selection. If the app
172crashes, the error report additionally includes the error message and stack.
173
174## Project Structure
175
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
183
184## Contributing Calendar Data
185
186Calendar region and era data is stored as flat JSON files in `src/data/calendars/`.
187Each file describes a region (using GeoJSON geometry) and its calendar eras over time.
188
189### Example (abbreviated): based on `src/data/calendars/massachusett.json`
190
191Note how answers can **overlap** β€” both in time and in space. A click in 1650s
192Boston returns the Massachusett reckoning (from `massachusett.json`, a tight
193homeland polygon around Massachusetts Bay) **and** the colonial Julian calendar
194(from `colonial-america.json`, a broad "British North America" polygon that also
195covers the same spot). The Julian β†’ Gregorian cutover was empire-wide, so it
196lives in the larger region rather than being duplicated into every town;
197overlapping regions are expected, not an error.
198
199```json
200{
201 "region": {
202 "name": "Massachusett homeland",
203 "geo": {
204 "type": "Polygon",
205 "coordinates": [
206 [
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]
212 ]
213 ]
214 }
215 },
216 "eras": [
217 {
218 "name": "Massachusett reckoning",
219 "start": null,
220 "end": null,
221 "calendar": "Algonquian lunar calendar",
222 "usedBy": "the Massachusett people",
223 "description": "Lunar and seasonal reckoning of the Massachusett, the Algonquian people of the Massachusetts Bay area.",
224 "sources": ["https://en.wikipedia.org/wiki/Massachusett"]
225 }
226 ]
227}
228```
229
230The colonial Julian and Gregorian eras live in `colonial-america.json`, whose
231larger polygon overlaps this one β€” so a Boston click resolves against both files.
232
233An era for a calendar with no numbered year may also carry **`anchorEvents`** β€” a
234list of documented, dated, **sourced** events a local reckons time from. The app
235reports the most recent one at or before the date as "about N winters since
236&lt;event&gt;" (e.g. the Massachusett reckoning counts winters since the
2371616–1619 epidemic). Anchor events must be real and cited β€” the engine only
238counts the winters, it never invents the event.
239
240### Schema
241
242See `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`).
245
246### How to contribute
247
248Contributions 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/).
251
252- Add or edit files in `src/data/calendars/`, on a branch or as a patch.
253- Use [geojson.io](https://geojson.io/) or similar tools to draw and export region polygons.
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.
259- Run `npm run screenshot:calendar -- src/data/calendars/<your-file>.json` and
260 attach the images to your email, so the region, eras, and conversions can be
261 reviewed visually.
262- Run `npm run gen:badges` so the README's calendar-data count reflects your
263 addition, and commit the updated README.
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.
267
268### Calendars we'd like to add
269
270Ranked by rough "years in use Γ— area/population reached" β€” the systems that
271would let the most human-years of history resolve to a real local answer.
272Contributions for any of these (or anything else, sourced) are very welcome β€”
273see [How to contribute](#how-to-contribute) above, and check
274`src/utils/calendars.ts` for whether a system needs new converter work or can
275ship as descriptive-only.
276
2771. **Tibetan calendar (Bod rtsis)** β€” a lunisolar calendar in continuous use
278 across the Tibetan Plateau for over a thousand years, with its own 60-year
279 cycle and year names.
2802. **Mongolian calendar** β€” the Mongol Empire and successor states' lunisolar
281 calendar (related to but distinct from the Chinese and Tibetan systems),
282 spanning a vast historical territory.
2833. **Coptic calendar** β€” Egypt's calendar since antiquity (descended from the
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.)
2894. **Ancient Egyptian civil calendar** β€” a 365-day calendar used for roughly
290 three millennia along the Nile; descriptive-only (no continuous numbered
291 year survives in common use), but enormous time depth.
2925. **Armenian calendar** β€” in continuous use since antiquity, with its own
293 epoch and a fixed 365-day year (no leap day), giving it a slow drift against
294 the Gregorian year worth surfacing.
2956. **Coast Salish / Puget Sound calendar** β€” a sourced, people-specific lunar
296 reckoning for the Pacific Northwest, paralleling the Massachusett entry; the
297 place/people this skill's original brainstorm named as an example.
2987. **Inca calendar** β€” a lunisolar/solar calendar used across the Inca Empire's
299 considerable territory in the Andes; mostly descriptive (reconstructions of
300 its exact rules are debated, so sourcing needs care).
3018. **Balinese Pawukon and Saka calendars** β€” Bali's parallel 210-day ritual
302 cycle and a Saka-derived solar count, both still in everyday use.
3039. **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.
30610. **Sikh Nanakshahi calendar** β€” a solar calendar adopted by the SGPC in 2003
307 for Sikh religious dates, with a clear epoch (Guru Nanak's birth year) β€”
308 likely a simple convertible offset like Dangi/Juche.
30911. **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.
315
316## License
317
318[MIT](LICENSE) Β© Pavel Safronov
319
320## Attributions
321
322- Map tiles and data: [OpenStreetMap](https://www.openstreetmap.org/)
323- Map library: [Leaflet](https://leafletjs.com/)
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`.
325- Calendar icon: [Calendar icons created by Icongeek26 - Flaticon](https://www.flaticon.com/free-icons/calendar)
326
327## Implementation
328
329This 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/).