fuzzyworld

when-am-i / files / src / utils / resolve.ts

resolve.ts

Blame: the commit that last changed each line · file · history

26a02b71 2026-06-19 Pavel Safronov1// The engine: "When am I?" at a point and date.
a71096e6 2026-06-18 Pavel Safronov2//
3// Composes the layers into the plural-answers model — every valid reply a local
4// could give. Pure (regions are passed in) so it is easy to unit-test; the UI
5// supplies the loaded calendar data.
6
b92a4280 2026-06-20 Pavel Safronov7import type { CalendarRegion, HistoricalDate, Answer, Resolution, Region, Era, Geometry } from '../types';
a71096e6 2026-06-18 Pavel Safronov8import { findRegions } from './geo';
9import { findEras } from './eras';
5c66fc6a 2026-06-21 Pavel Safronov10import { convert, wintersSince } from './calendars';
b92a4280 2026-06-20 Pavel Safronov11import { gregorianToJulianDay } from './julianDay';
12
13// ── Modern global default ─────────────────────────────────────────────────────
14// The Gregorian calendar became the world's near-universal *civil* calendar in
15// the early 20th century (the last major national adoption was Turkey, 1926).
16// From this year we add a Gregorian answer anywhere on Earth, so most modern
17// clicks resolve to "what almost everyone says today" even where the dataset has
18// no specific region. A single global cutoff is a deliberate simplification:
19// adoption was staggered (1582 Catholic Europe, 1752 Britain, 1918 Russia,
20// 1926 Turkey, 1949 China, 2016 Saudi civil), so this is "most of the world",
21// not a precise universal claim.
22export const MODERN_DEFAULT_START_YEAR = 1927;
23const MODERN_DEFAULT_START_JDN = gregorianToJulianDay({ year: MODERN_DEFAULT_START_YEAR, month: 1, day: 1 });
24
25// A whole-Earth geometry. The default is never added to the shaded map overlays
26// (that would flood the map), so this only exists to satisfy the Region shape.
27const WORLD_GEO: Geometry = {
28 type: 'Polygon',
29 coordinates: [[[-180, -90], [180, -90], [180, 90], [-180, 90], [-180, -90]]],
30};
31
32export const DEFAULT_MODERN_REGION: Region = { name: 'Most of the world', geo: WORLD_GEO };
33
34export const DEFAULT_MODERN_ERA: Era = {
35 name: 'Modern (global civil calendar)',
36 start: `${MODERN_DEFAULT_START_YEAR}-01-01`,
37 end: null,
38 calendar: 'Gregorian',
39 usedBy: 'most of the world',
40 description:
41 'By the early 20th century the Gregorian calendar had become the world’s ' +
42 'near-universal civil calendar; today almost every country uses it for civil ' +
43 'dating, often alongside a local religious or traditional calendar. This is a ' +
44 'global default, so its start year is a deliberate simplification — adoption ' +
45 'was staggered (e.g. 1582 Catholic Europe, 1752 Britain, 1918 Russia, 1926 Turkey).',
46 sources: ['https://en.wikipedia.org/wiki/Adoption_of_the_Gregorian_calendar'],
47};
48
49/** Whether `date` is recent enough for the global Gregorian default to apply. */
50export function isModernDefaultDate(date: HistoricalDate): boolean {
51 return gregorianToJulianDay(date) >= MODERN_DEFAULT_START_JDN;
52}
a71096e6 2026-06-18 Pavel Safronov53
54export function resolve(
55 lat: number,
56 lng: number,
57 date: HistoricalDate,
58 regions: CalendarRegion[],
59): Resolution {
60 const matched = findRegions(lat, lng, regions);
61
62 const answers: Answer[] = [];
34c4dd59 2026-06-18 Pavel Safronov63 for (const { region, eras, sourceFile } of matched) {
a71096e6 2026-06-18 Pavel Safronov64 for (const era of findEras(date, eras)) {
5c66fc6a 2026-06-21 Pavel Safronov65 const conversion = convert(date, era.calendar);
66 // Attach the era's "winters since <event>" reckoning when one applies
67 // (mainly for descriptive-only calendars with no numbered year).
68 const reckoning = wintersSince(date, era.anchorEvents);
69 answers.push({
70 region,
71 era,
72 conversion: reckoning ? { ...conversion, reckoning } : conversion,
73 sourceFile,
74 });
a71096e6 2026-06-18 Pavel Safronov75 }
76 }
77
b92a4280 2026-06-20 Pavel Safronov78 // Append the modern global Gregorian default — but only when no Gregorian
79 // answer already applies here (a region with its own Gregorian era covering
cedd4e9c 2026-06-23 Pavel Safronov80 // this date, so we never show the same Gregorian year twice), and no
81 // matched era explicitly opts out (suppressModernDefault — for the rare
82 // place, like North Sentinel Island, where no outside civil calendar
83 // applies at all, not just a different one).
84 const suppressed = answers.some((a) => a.era.suppressModernDefault);
85 if (isModernDefaultDate(date) && !suppressed && !answers.some((a) => a.conversion.system === 'Gregorian')) {
b92a4280 2026-06-20 Pavel Safronov86 answers.push({
87 region: DEFAULT_MODERN_REGION,
88 era: DEFAULT_MODERN_ERA,
89 conversion: convert(date, DEFAULT_MODERN_ERA.calendar),
90 });
91 }
92
93 if (answers.length === 0) {
94 return { answers: [], missReason: matched.length === 0 ? 'no-region' : 'no-era' };
95 }
a71096e6 2026-06-18 Pavel Safronov96 return { answers };
97}