mihrab-methods 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +104 -287
  2. package/certification.json +31 -20
  3. package/data/corrections/indonesia.json +721 -28
  4. package/data/corrections/morocco.json +201 -914
  5. package/dist/cjs/authorities/morocco.js +7 -6
  6. package/dist/cjs/core/compute.js +1 -1
  7. package/dist/cjs/core/types.d.ts +4 -0
  8. package/dist/cjs/data/generated/algeriaCertification.js +1 -1
  9. package/dist/cjs/data/generated/diyanetCertification.js +1 -1
  10. package/dist/cjs/data/generated/diyanetWorldPoints.js +1 -1
  11. package/dist/cjs/data/generated/indonesiaCertification.js +1 -1
  12. package/dist/cjs/data/generated/indonesiaCorrections.js +1 -1
  13. package/dist/cjs/data/generated/moroccoCertification.js +1 -1
  14. package/dist/cjs/data/generated/moroccoCorrections.js +1 -1
  15. package/dist/cjs/data/generated/moroccoMethodPoints.js +1 -1
  16. package/dist/cjs/data/generated/swedenCertification.js +1 -1
  17. package/dist/cjs/methods/morocco.d.ts +43 -20
  18. package/dist/cjs/methods/morocco.js +59 -25
  19. package/dist/cjs/methods/revisions.d.ts +1 -1
  20. package/dist/cjs/methods/revisions.js +1 -1
  21. package/dist/cjs/server.d.ts +12 -0
  22. package/dist/cjs/server.js +132 -0
  23. package/dist/esm/authorities/morocco.js +7 -6
  24. package/dist/esm/core/compute.js +1 -1
  25. package/dist/esm/core/types.d.ts +4 -0
  26. package/dist/esm/data/generated/algeriaCertification.js +1 -1
  27. package/dist/esm/data/generated/diyanetCertification.js +1 -1
  28. package/dist/esm/data/generated/diyanetWorldPoints.js +1 -1
  29. package/dist/esm/data/generated/indonesiaCertification.js +1 -1
  30. package/dist/esm/data/generated/indonesiaCorrections.js +1 -1
  31. package/dist/esm/data/generated/moroccoCertification.js +1 -1
  32. package/dist/esm/data/generated/moroccoCorrections.js +1 -1
  33. package/dist/esm/data/generated/moroccoMethodPoints.js +1 -1
  34. package/dist/esm/data/generated/swedenCertification.js +1 -1
  35. package/dist/esm/methods/morocco.d.ts +43 -20
  36. package/dist/esm/methods/morocco.js +54 -24
  37. package/dist/esm/methods/revisions.d.ts +1 -1
  38. package/dist/esm/methods/revisions.js +1 -1
  39. package/dist/esm/server.d.ts +12 -0
  40. package/dist/esm/server.js +126 -0
  41. package/docs/methods.md +24 -8
  42. package/package.json +5 -1
  43. package/schemas/certification.schema.json +119 -24
  44. package/dist/cjs/methods/scratch-congress.d.ts +0 -87
  45. package/dist/cjs/methods/scratch-congress.js +0 -539
  46. package/dist/esm/methods/scratch-congress.d.ts +0 -87
  47. package/dist/esm/methods/scratch-congress.js +0 -534
package/README.md CHANGED
@@ -2,319 +2,136 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/mihrab-methods)](https://www.npmjs.com/package/mihrab-methods)
4
4
  [![checks](https://github.com/MihrabHQ/mihrab-methods/actions/workflows/ci.yml/badge.svg)](https://github.com/MihrabHQ/mihrab-methods/actions/workflows/ci.yml)
5
+ [![feed](https://github.com/MihrabHQ/mihrab-methods/actions/workflows/feed.yml/badge.svg)](https://github.com/MihrabHQ/mihrab-methods/actions/workflows/feed.yml)
6
+ [![downloads](https://img.shields.io/npm/dm/mihrab-methods)](#usage)
7
+ [![certification](docs/certification.svg)](#how-verification-works)
5
8
 
6
- An open prayer-time library: the calculation methods of the national
7
- authorities, implemented to reproduce their official timetables to the
8
- minute, free for anyone to use. Accurate prayer times shouldn't be something
9
- only a few apps can offer.
9
+ **Prayer times that match the published timetable to the printed minute.**
10
+ Each national authority's own calculation method, worked out again from its
11
+ published times and checked day by day against its timetables: <!--fig:total.off-->0<!--/fig--> of
12
+ <!--fig:total.times-->72,013,209<!--/fig--> times off the printed minute. Free for any app, site or
13
+ mosque to use.
10
14
 
11
15
  ```sh
12
16
  npm install mihrab-methods
13
17
  ```
14
18
 
15
19
  ```ts
16
- import { nearestPlace, timesFor } from 'mihrab-methods/algeria'; // or 'mihrab-methods' for all five
17
-
18
- const { place } = nearestPlace('algeria', 36.75, 3.06)!; // Algiers
19
- const day = timesFor(place, '2026-10-15');
20
- day.status; // 'certified' — the ministry's own printed minute
21
- day.clock.label; // 'Africa/Algiers'
22
- day.times!.fajr; // { instant: 2026-10-15T04:29:00.000Z, printed: '05:29' }
23
- ```
24
-
25
- It is a library, in TypeScript, with a small server over it for everything
26
- else (`npm run serve`); see [Use](#use).
27
-
28
- Each method is the authority's own formula, worked out again from its
29
- published times, with a small, fixed correction list for the few times where the formula
30
- alone lands a fraction of a second on the wrong side of a minute. The aim is that
31
- any app, site or mosque can give the times its community prays by, without
32
- asking anyone.
33
-
34
- Methods today:
35
-
36
- | Authority | Method | On the printed minute | Within a minute | Checked over |
37
- |---|---|---|---|---|
38
- | **Sweden** — Diyanet's method, as used by Islamiska Förbundet | `src/methods/sweden.ts` — the Istanbul congress method as Diyanet computes it, with `data/corrections/sweden.json` | **100 %** of Diyanet's timetables (1.39 M times, 2024–2027) and of Islamiska Förbundet's (107 cities, 59 640 times, Oct 2026 → Nov 2027, with the rows set aside below) — by the method alone, 99.981 % and 99.966 % | 100 % | 2024 → 2027 |
39
- | **Diyanet** — Türkiye and worldwide | `src/methods/sweden.ts` with Isha's angle by country (16° or 17°), a point per prayer per year in Türkiye, with `data/corrections/diyanet.json` | **100 %** of Diyanet's timetables for all 880 districts of Türkiye and Northern Cyprus and 7 252 places in 197 other countries (70.0 M times) — 99.987 % by the method alone | 100 % | 2024 → 2027 |
40
- | **Morocco** — Ministry of Habous | `src/methods/morocco.ts` — each city's own sunrise and Maghrib horizon, the sun's semidiameter by its distance, the tables' own clock (GMT+1 until 17 Sep 2026), with `data/corrections/morocco.json` | **100 %** of the ministry's tables for its 164 located cities (75 768 times) — 99.122 % by the method alone | 100 % | 27 Aug → 11 Nov 2026 |
41
- | **Algeria** — Ministry of Religious Affairs and Wakfs | `src/methods/algeria.ts`, with `data/corrections/algeria.json` | **100 %** of the ministry's 1448 AH table for its 68 cities (101 184 times) — 99.366 % by the method alone | 100 % | 1 Oct 2026 → 5 Jun 2027 |
42
- | **Indonesia** — Kemenag | `src/methods/indonesia.ts`, with `data/corrections/indonesia.json` | **100 %** of the ministry's tables for all 517 regencies and cities (266 772 times) — 99.969 % by the method alone | 100 % | 7 Oct → 31 Dec 2026 |
43
-
44
- The goal is 100 % on the printed minute, for every time of a full year, for
45
- each method.
46
-
47
- ## Certification: 100 % or not at all
48
-
49
- A method may be used only once it reproduces **every** time of its
50
- authority's timetable on the printed minute — not one minute off, anywhere.
51
- `npm run certify` checks each method day by day and writes
52
- `certification.json`; `timesFor` throws `UncertifiedMethodError` for any
53
- method not certified there, and says `status: 'method-only'` for a date
54
- beyond the span it was checked over. `candidateTimes` runs a method
55
- regardless, for research and for the check itself — not for showing anyone
56
- their prayer times.
57
-
58
- Days the timetable itself gets wrong (another town's times under a name, a
59
- previous year's figures for the date, a figure that differs from the
60
- authority's own) are set aside and listed in the record, so each can be
61
- judged.
62
-
63
- Today (checked 2026-10-16) **every method is certified**: Sweden and Diyanet for 2024–2027, and Morocco, Algeria and Indonesia for the tables published so far:
64
-
65
- | Authority | Off the printed minute |
66
- |---|---|
67
- | Sweden | 0 of 59 640 (Islamiska Förbundet); 0 of 1 393 794 (Diyanet) |
68
- | Diyanet (Türkiye and worldwide) | 0 of 69 999 603 |
69
- | Morocco | 0 of 75 768 |
70
- | Algeria | 0 of 101 184 |
71
- | Indonesia | 0 of 266 772 |
72
-
73
- ## Status
74
-
75
- | Method | Where it stands |
76
- |---|---|
77
- | Sweden | Certified, 2024–2027, against Diyanet's tables for Sweden (159 places), and against Islamiska Förbundet's published tables (11 Oct 2026 → 19 Nov 2027) for 107 of its 108 cities. Set aside from Islamiska Förbundet's: Kiruna, printed with Umeå's times; 33 395 times that differ from Diyanet's own table for the day (its 2027 rows repeat 2026's figures); and 646 rows copied from another day. On each, the method gives Diyanet's own table. |
78
- | Diyanet | Certified, 2024–2027, for every district of Türkiye and Northern Cyprus and 7 252 places in 197 other countries: 8 291 of the 8 528 places Diyanet lists, with Sweden's 159. Each place is computed at a point of its own, checked to lie in the place's country. Not yet included: 147 places that could not be placed — seven of them Diyanet serves no table for (Grozny, Gudermes, Shali and Urus-Martan, Rožaje, Monte Carlo, Punta Arenas), the rest names the gazetteer does not match; 75 whose tables the method does not yet follow over all four years — about half of them in Mexico and Australia, whose tables keep a clock of their own, the rest scattered (Kazan, Fez, Ushuaia, Svalbard among them); 12 whose tables Diyanet computes at a point elsewhere — in another country, at sea or in the other hemisphere (Tupiza, Ribeirão das Neves, Montería, Hami, Kungälv and Galena among them); Syktyvkar, whose 2024 and 2027 tables print a Fajr hours from its 2025 and 2026 ones; and Ping Tung City and Rilchingen-Hanweiler, each listed again at the same point (as Pingtung and Kleinblittersdorf) with a table a minute apart on two days. |
79
- | Morocco | Certified for 27 Aug → 11 Nov 2026 (164 cities): the method gives 99.12 % alone, and a list of 665 corrections the rest. Certification covers only the dates in the table: each newly published month is checked against the method, and its corrections are added in a new list. |
80
- | Algeria | Certified for 1448 AH (1 Oct 2026 → 5 Jun 2027, 68 cities): the method gives 99.37 % alone, and a list of 642 corrections the rest. The method is at its limit: the angles, horizon, Maghrib margin, rounding, sun series, iterations and city points are each sharp optima, and the 642 are independent differences of about a second or less (no pattern by prayer, city, date or season; refitting the points to them only fits noise, and is worse on days held out). |
81
- | Indonesia | Certified for 7 Oct → 31 Dec 2026 (517 regencies and cities): the method gives 99.969 % alone, and a list of 84 corrections the rest. Karawang's Asr, Maghrib and Isha are computed at a second point, about 2 km north-east of its first (its afternoon fits it, and it holds on days held out). The 84 are independent differences at a minute's edge (median 0.2 s, nine in ten under 1.3 s, the largest 3.9 s) — eight at Sibolga, whose mornings no point fits — and refitting points to them only fits noise. Certification covers only the dates in the tables: each newly published month is checked against the method, and its corrections are added in a new list. |
82
-
83
- ## Formulas and correction lists: why this can be used freely
84
-
85
- Under EU and Swedish law, this approach is lawful use and "insubstantial
86
- extraction": an unprotected mathematical formula, worked out from published
87
- times, plus a very small, static list of factual data points that fixes edge
88
- cases.
89
-
90
- - **The methods are formulas.** A method of calculation is not protected as
91
- such. What is here is the arithmetic: the sun's position, the angles, the
92
- margins and the high-latitude rules each authority applies. It is written
93
- from scratch, not copied.
94
- - **The correction lists are microscopically small and static.** Each one
95
- holds only the few times where the authority's own rounding falls the
96
- other way: a point, a date, a prayer, a minute either way. Sweden's is 270
97
- entries against 1.39 million times (0.02 %). The aim is the same for every
98
- method: get the formula as close as possible, so the list that remains is
99
- insubstantial.
100
- - **No timetable is part of this repository.** It holds no copy of any
101
- authority's tables. The times are computed, for any date and any point.
102
-
103
- This is the project's position, not legal advice. The same rule applies to
104
- every method added here: the best formula first, and a correction list only
105
- for what is left.
106
-
107
- ## Sweden's and Diyanet's correction lists
108
-
109
- The method reproduces Diyanet's computation; what it cannot reproduce is the
110
- arithmetic of Diyanet's program, which puts about one time in five thousand on
111
- the other side of a minute's rounding point — a fraction of a second away,
112
- independently from place to place and day to day. `data/corrections/sweden.json`
113
- lists those times (point, date, prayer, a minute either way: 270 over
114
- 2024–2027) and `timesFor` applies them. The list covers the years the
115
- timetables do, and certification holds over those dates only: outside them
116
- `timesFor` answers with `status: 'method-only'` (or refuses, with
117
- `strict`). Diyanet's worldwide list
118
- (`data/corrections/diyanet.json`) is the same kind: 9 118 entries at 4 660 points against
119
- 70.0 million times. Each year, once the next year's timetables are out:
120
-
121
- ```sh
122
- npm run corrections && for a in diyanet morocco algeria indonesia; do npm run corrections -- $a; done && npm run certify
20
+ import { nearestPlace, timesFor } from 'mihrab-methods/diyanet'; // one authority's data, or 'mihrab-methods' for all
21
+
22
+ const { place } = nearestPlace('diyanet', 41.01, 28.98)!; // Istanbul's district
23
+ const day = timesFor(place, '2026-10-15'); // or { year: 2026, month: 10, day: 15 }
24
+ day.status; // 'certified': inside the dates the method was checked over
25
+ day.clock.label; // 'Europe/Istanbul': the clock the table prints the day in
26
+ day.times!.fajr; // { instant: Date, printed: 'HH:MM' } — show printed, schedule on instant
27
+ day.certifiedUntil; // the last date checked; past it, status is 'method-only'
123
28
  ```
124
29
 
125
- What Diyanet's worldwide tables add to the Swedish method:
126
-
127
- - **Isha by country:** 16° (the Congress's) in most of Europe, Russia,
128
- Ukraine, Belarus, Kazakhstan, Mongolia, China, the United States and
129
- Canada; 17° elsewhere. The high-latitude rules are worked at 16° either way.
130
- - **The takdir at midsummer:** where the dawn forms all summer but midsummer's
131
- true Isha falls later than the share carried from that night, midsummer is
132
- the takdir's day, with the twenty-minute ramps (from about 44.4° north).
133
- - **Türkiye's districts:** each prayer is computed at a point of its own in the
134
- district, and the points move by a few thousandths of a degree from one
135
- year's tables to the next (`data/places/diyanetTurkeyPoints.json`, a point
136
- per prayer per year; Northern Cyprus the same).
137
- - **Past local midnight:** where the first true dawn after the summer gap falls
138
- before local midnight on the table's clock (far east of a zone's meridian,
139
- as at Aihui in China), there is no transition: the gap's last day is printed
140
- twenty minutes after the takdir and the true times follow.
141
- - **Tables on their own clocks:** 192 of Diyanet's tables are printed on a
142
- clock other than their place's zone — Alberta's keep summer time from 2026
143
- on; Mexico's had it again in 2025; Queensland's and Western Australia's have
144
- it in 2024, 2025 and 2027; Paraguay's keep the summer time the country gave
145
- up in October 2024 until the end of 2025; Xinjiang's are Beijing time;
146
- western Brazil's, Armenia's and some of Russia's and Mongolia's are a zone
147
- or two away from their own; Alchevsk's are Moscow time with summer time.
148
- For those the table's clock is read off the table itself (`clock` in
149
- `data/places/diyanetWorldPoints.json`); the instants are the method's.
150
- - **The day's clock:** a table prints the whole day in the clock kept at noon,
151
- so on the morning the clocks go forward a Fajr before the change is printed
152
- in the new time.
30
+ The full API, the HTTP server and the per-authority imports are in
31
+ [docs/usage.md](docs/usage.md).
32
+
33
+ ## What is certified
34
+
35
+ <!-- certification-table:start -->
36
+ | Authority | Tables matched | Places | Times checked | Off the printed minute | By the method alone | Correction entries | Checked over |
37
+ |---|---|---:|---:|---:|---:|---:|---|
38
+ | **Sweden** | Islamiska Förbundet’s tables | 107 | 61,344 | **0** | 99.967 % | 270 | 11 Oct 2026 → 7 Dec 2027 |
39
+ | | Diyanet’s own tables | 159 | 1,393,794 | **0** | 99.981 % | | 1 Jan 2024 → 31 Dec 2027 |
40
+ | **Diyanet** | its tables worldwide | 8,132 | 69,999,603 | **0** | 99.986 % | 9,118 | 1 Jan 2024 → 31 Dec 2027 |
41
+ | **Morocco** | Habous | 164 | 94,350 | **0** | 99.879 % | 114 | 27 Aug 2026 → 30 Nov 2026 |
42
+ | **Algeria** | Ministry of Religious Affairs | 68 | 101,184 | **0** | 99.365 % | 642 | 1 Oct 2026 → 5 Jun 2027 |
43
+ | **Indonesia** | Kemenag | 517 | 362,934 | **0** | 99.888 % | 405 | 7 Oct 2026 → 31 Jan 2027 |
44
+
45
+ 5 of 5 methods certified; 0 of 72,013,209 times off the printed minute. Last checked 4 November 2026. Generated from [`certification.json`](certification.json) by `scripts/readme.mjs`.
46
+ <!-- certification-table:end -->
47
+
48
+ "By the method alone" is the formula without its correction list. The list
49
+ holds the few times the authority's own program puts a fraction of a second
50
+ on the other side of a minute's rounding point: a point, a date, a prayer, a
51
+ minute either way. Why that can be used freely is in
52
+ [docs/legal.md](docs/legal.md); what each method still leaves out is in
53
+ [docs/status.md](docs/status.md).
54
+
55
+ ## How verification works
56
+
57
+ 1. **The tables.** For each authority, its own published timetables for
58
+ every place it lists, every day it has published. They are not part of
59
+ this repository and are not redistributed; the check reads them from
60
+ `MIHRAB_TABLES`.
61
+ 2. **The check.** [`tools/verify.ts`](tools/verify.ts) computes every place on
62
+ every day and compares each of the six times with the printed minute, on
63
+ the clock the table prints that day in. Days the table itself gets wrong
64
+ (another town's times under a name, a previous year's rows) are set aside
65
+ and listed in the record, so each can be judged.
66
+ 3. **The record.** [`tools/certify.ts`](tools/certify.ts) writes
67
+ [`certification.json`](certification.json): per authority, the tables
68
+ checked, the times, how many are off with and without the correction
69
+ list, the dates, the method's revision and the list's SHA-256. The
70
+ tables are checked again whenever they change and every week, and each
71
+ check that finds a method still at 0 off dates its entry anew.
72
+ 4. **The gate.** A method is certified only at **0** off. `timesFor` refuses
73
+ an uncertified method, and says `'method-only'` for a date past the
74
+ checked span. Every number on this page, on the Mihrab website and in its
75
+ store listings is generated from that record (`scripts/readme.mjs`).
76
+
77
+ Times that differ from your mosque's?
78
+ [Open a report](https://github.com/MihrabHQ/mihrab-methods/issues/new?template=times-differ.yml)
79
+ with the city, the date, the prayer and the printed time.
153
80
 
154
81
  ## Keeping an app current
155
82
 
156
- `certification.json` is meant to be read by apps. For each authority it
157
- says whether the method is certified, the method's `revision`
158
- (`src/methods/revisions.ts`) and the correction list it certifies
159
- (`corrections`: the dates it covers and how many times it moves):
160
-
161
- ```json
162
- "morocco": { "certified": true, "revision": 1,
163
- "corrections": { "from": "2026-08-27", "to": "2026-11-11", "entries": 665 }, … }
164
- ```
165
-
166
- An app that computes revision 1 can take any newer list certified for
167
- revision 1 straight from `data/corrections/<authority>.json` — no release
168
- needed. A higher revision means the method itself changed and its lists no
169
- longer fit the older code: the app should ask its user to update.
170
- `certified: false` means the authority has published tables the method is
171
- not yet on every minute of. Past a list's `to`, the method alone applies.
172
-
173
- The check done right is in the library:
83
+ The record and the correction lists are a feed: an app that computes a
84
+ method's revision can take any newer list certified for that revision, with
85
+ no release.
174
86
 
175
87
  ```ts
176
88
  import { fetchLatestCorrections } from 'mihrab-methods/morocco';
177
89
  const r = await fetchLatestCorrections({ authority: 'morocco' });
178
- // r.status: 'current' | 'updated' | 'method-moved' | 'uncertified' | 'rejected'
179
- ```
180
-
181
- It reads `certification.json`, takes a list only for the revision this
182
- build computes, only when it is the list the certification names (dates,
183
- size, and the **SHA-256** of the file as published), and puts it in force
184
- (`applyCorrections` restores one an app has stored). Both files have a
185
- published schema: `schemas/certification.schema.json` and
186
- `schemas/corrections.schema.json`; `npm test` validates them.
187
-
188
- Addresses: `main` is the live feed (what the Mihrab app reads). Every
189
- release is also tagged, and the same paths under a tag are fixed for good:
190
- `https://raw.githubusercontent.com/MihrabHQ/mihrab-methods/v1.0.0/certification.json`.
191
- Pass a tag's base URL as `baseUrl` to pin.
192
-
193
- The revision is raised whenever a change to a method or to its places'
194
- points changes what it prints; `npm test` fails until certification.json
195
- names the current revision and list. `CHANGELOG.md` says what each
196
- revision changed in the output.
197
-
198
- ## Use
199
-
200
- ### As a package
201
-
202
- ```sh
203
- npm install mihrab-methods
204
- ```
205
-
206
- One entry point per authority, so an app ships only the data it needs
207
- (Indonesia's is 90 KB; Diyanet's worldwide points and list are 1.6 MB):
208
-
209
- | Import | Loads |
210
- |---|---|
211
- | `mihrab-methods` | all five |
212
- | `mihrab-methods/sweden`, `/diyanet`, `/morocco`, `/algeria`, `/indonesia` | one authority: its places, list and certification |
213
- | `mihrab-methods/locate` | `countryAt`, `defaultAuthorityFor` — the country at a point, and the authority whose table it prays by (470 KB of outlines) |
214
- | `mihrab-methods/core` | the calls and types with no data (load authorities yourself) |
215
-
216
- CommonJS and ES modules, with types. Node 18+, or any bundler; nothing is
217
- read from disk or fetched unless you call `fetchLatestCorrections`.
218
-
219
- ```ts
220
- import { placesOf, placeById, nearestPlace, timesFor, timesForRange, candidateTimes, methodInfo } from 'mihrab-methods';
221
- import { defaultAuthorityFor } from 'mihrab-methods/locate';
222
-
223
- defaultAuthorityFor(59.33, 18.07); // 'sweden' (null where none of the five is the authority)
224
- const { place, distanceKm } = nearestPlace('sweden', 59.33, 18.07)!;
225
- place.id; // 'sweden:stockholm' — stable; place.country 'SE'
226
- placeById('diyanet:9146'); // Adana
227
-
228
- timesFor(place, '2026-10-15'); // or { year: 2026, month: 10, day: 15 } — January is 1
229
- // {
230
- // status: 'certified', 'method-only' outside the certified dates: the method alone,
231
- // on the printed minute all but rarely, never a minute further
232
- // date: '2026-10-15', certifiedFrom: '2024-01-01', certifiedUntil: '2027-12-31', revision: 1,
233
- // clock: { zone: 'Europe/Stockholm', offsetMinutes: 120, label: 'Europe/Stockholm' },
234
- // times: { fajr: { instant: Date, printed: '05:09' }, sunrise: …, dhuhr: …, asr: …, maghrib: …, isha: … }
235
- // }
236
- timesFor(place, '2031-01-01', { strict: true }); // throws UncertifiedMethodError instead
237
- timesFor(place, '2026-10-15', { asrShadow: 2 }); // the Hanafi Asr (the tables print the standard one)
238
- timesForRange(place, '2026-10-01', '2026-10-31'); // a month, one DayTimes per day
239
- candidateTimes(place, '2026-10-15', { corrections: false }); // the formula alone — research
240
- methodInfo('morocco'); // angles, margins, rounding, revision, certification, sources, spec
90
+ // 'current' | 'updated' | 'method-moved' (update the app) | 'uncertified' | 'rejected'
241
91
  ```
242
92
 
243
- `printed` is the wall-clock minute on `clock`, which is the clock the
244
- authority prints the day in: the place's zone as kept at noon, or the
245
- table's own clock where it keeps one no zone does (Morocco's GMT+1 until
246
- September 2026; some of Diyanet's). An app shows `printed` and schedules
247
- on `instant`; nothing needs rounding or converting.
248
-
249
- ### As a server
250
-
251
- ```sh
252
- npm run serve # http://localhost:8787
253
- ```
93
+ It takes a list only for the revision the build computes, only when it is
94
+ the list the record names, and only when its SHA-256 matches. Schemas, tags
95
+ as fixed addresses and the details: [docs/feed.md](docs/feed.md).
254
96
 
255
- ```
256
- GET /v1/times?authority=diyanet&lat=41.01&lng=28.98&date=2026-10-15
257
- GET /v1/times?place=algeria:27&from=2026-10-01&to=2026-10-31
258
- GET /v1/nearest?authority=morocco&lat=33.57&lng=-7.59
259
- GET /v1/authority?lat=59.33&lng=18.07
260
- GET /v1/places?authority=indonesia
261
- GET /v1/methods
262
- ```
97
+ ## Usage
263
98
 
264
- Self-hostable, nothing stored (`tools/serve.ts`, eighty lines).
99
+ <!-- usage:start -->
100
+ ![Usage: npm downloads and API requests a week](docs/stats.svg)
265
101
 
266
- ### In another language
102
+ | | Last 7 days | Last 30 days | Since the first release |
103
+ |---|---:|---:|---:|
104
+ | npm downloads | 66 | 735 | 735 |
105
+ | API requests | — | — | not deployed yet |
267
106
 
268
- `docs/methods.md` states each method in prose and formulas, and
269
- `test/vectors.json` is a small sample of places, dates and the times to
270
- expect — enough to prove a port matches, too small to be a timetable.
107
+ Counts only, to 2026-11-03, updated weekly (`scripts/stats.mjs`, history in [docs/stats.json](docs/stats.json)): npm's public download counts — every install counts, CI and mirrors included — and Cloudflare's request counts for the API. No addresses, queries or places are recorded anywhere.
108
+ <!-- usage:end -->
271
109
 
272
- ### This repository
110
+ ## More
273
111
 
274
- ```sh
275
- npm install # NODE_ENV must not be "production", or the dev tools are skipped
276
- npm test # the library, the schemas, the vectors; the table checks when the tables are present
277
- npm run build # dist/ (CommonJS and ESM, with types)
278
- npm run verify # every method against the reference timetables
279
- npm run verify -- morocco indonesia
280
- npm run verify -- --alone # each method without its correction list
281
- npm run corrections # rewrites Sweden's correction list (`-- diyanet`, `-- morocco`, … for the others)
282
- npm run certify # rewrites certification.json — what may be used
283
- npm run vectors # rewrites test/vectors.json (after a method revision)
284
- ```
285
-
286
- The reference timetables are not part of this repository; point
287
- `MIHRAB_TABLES` at them (default `.tables`). Without them `candidateTimes`
288
- still runs, the checks and their tests skip, and `npm run certify` writes
289
- nothing.
290
-
291
- ## Layout
292
-
293
- | Path | What |
112
+ | | |
294
113
  |---|---|
295
- | `src/methods/` | The methods: Sweden and Diyanet (`sweden.ts`), Morocco, Algeria, Indonesia; their revisions |
296
- | `src/sun/preciseSun.ts` | VSOP87 apparent sun, after Astronomy Engine: within about 0.05 s and 0.5″ of JPL's DE421 |
297
- | `src/core/` | The calls (`api.ts`), the computation they share (`compute.ts`), dates, clocks, the correction-list index, `fetchLatestCorrections` |
298
- | `src/authorities/` | Each authority's places, at the point (and Morocco's horizon) its method is computed at, with its description |
299
- | `src/index.ts`, `src/<authority>.ts`, `src/locate.ts`, `src/core.ts` | The package's entry points |
300
- | `data/places/`, `data/corrections/`, `data/locate/` | The place lists and fitted points, the correction lists, the country outlines — embedded into the code by `scripts/embed.mjs` at build time |
301
- | `certification.json`, `schemas/` | The 100 % gate's record, and the schemas of the files apps read |
302
- | `tools/verify.ts`, `tools/certify.ts`, `tools/corrections.ts` | The day-by-day check against the timetables; the certification; the correction lists |
303
- | `tools/vectors.ts`, `test/vectors.json`, `docs/methods.md` | For ports: the sample and the written method |
304
- | `tools/serve.ts` | The HTTP server |
305
-
306
- ## Back into Mihrab
307
-
308
- A method that improves here goes back as the same file in Mihrab's
309
- `src/providers/` (and its points file in `src/providers/data/`). The app
310
- reads `certification.json` and the lists from this repository's `main`;
311
- that feed does not change shape between versions (`schemas/`).
114
+ | [docs/usage.md](docs/usage.md) | The API, the entry points, the HTTP server, this repository's commands and layout |
115
+ | [docs/methods.md](docs/methods.md) | Each method in words and formulas, for ports to other languages; `test/vectors.json` to check one |
116
+ | [docs/status.md](docs/status.md) | Per method: what is certified, what was set aside, what is not yet covered |
117
+ | [docs/diyanet.md](docs/diyanet.md) | What Diyanet's worldwide tables add: Isha by country, midsummer, Türkiye's points, tables on their own clocks |
118
+ | [docs/feed.md](docs/feed.md) | `certification.json` and the lists as a feed: schemas, hashes, revisions, tags |
119
+ | [docs/legal.md](docs/legal.md) | Formulas and correction lists: why this can be used freely |
120
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Adding an authority: 100 % or nothing |
121
+ | [CHANGELOG.md](CHANGELOG.md) | Each version, and what each method revision changed in the output |
122
+
123
+ The Mihrab app computes these methods on the phone; a method that improves
124
+ here goes back as the same file in Mihrab's `src/providers/`.
125
+
126
+ Mihrab and this library are independent: not affiliated with, endorsed by
127
+ or speaking for any of the authorities whose published timetables they
128
+ match.
312
129
 
313
130
  ## Licence
314
131
 
315
132
  [MPL-2.0](LICENSE). Use the library in any app, open or closed, on a phone
316
- or on a server; what the licence asks is that changes to *these files* —
317
- a method fitted closer to its table, a corrected point — are published
318
- under the same terms, so an improvement to a method reaches everyone. The
319
- Mihrab app itself stays AGPL-3.0; it includes this library as the MPL
320
- allows. Versions before 1.0.0 were AGPL-3.0-or-later.
133
+ or on a server; what the licence asks is that changes to *these files* — a
134
+ method fitted closer to its table, a corrected point — are published under
135
+ the same terms, so an improvement to a method reaches everyone. The Mihrab
136
+ app stays AGPL-3.0 and includes this library as the MPL allows. Versions
137
+ before 1.0.0 were AGPL-3.0-or-later.
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "sweden": {
3
3
  "certified": true,
4
- "checkedAt": "2026-10-16",
4
+ "checkedAt": "2026-11-04",
5
+ "places": 108,
5
6
  "checks": [
6
7
  {
7
8
  "tables": "Sweden — Islamiska Förbundet’s tables",
8
9
  "places": 107,
9
- "times": 59640,
10
+ "times": 61344,
10
11
  "off": 0,
12
+ "offWithoutCorrections": 20,
11
13
  "from": "2026-10-11",
12
- "to": "2027-11-19",
14
+ "to": "2027-12-07",
13
15
  "setAside": {
14
16
  "misplaced": [],
15
- "sourceFaults": 33395,
16
- "copiedRows": 646
17
+ "sourceFaults": 35037,
18
+ "copiedRows": 682
17
19
  }
18
20
  },
19
21
  {
@@ -21,6 +23,7 @@
21
23
  "places": 159,
22
24
  "times": 1393794,
23
25
  "off": 0,
26
+ "offWithoutCorrections": 259,
24
27
  "from": "2024-01-01",
25
28
  "to": "2027-12-31"
26
29
  }
@@ -35,13 +38,15 @@
35
38
  },
36
39
  "diyanet": {
37
40
  "certified": true,
38
- "checkedAt": "2026-10-16",
41
+ "checkedAt": "2026-11-04",
42
+ "places": 8132,
39
43
  "checks": [
40
44
  {
41
45
  "tables": "Diyanet — its tables worldwide",
42
46
  "places": 8132,
43
47
  "times": 69999603,
44
48
  "off": 0,
49
+ "offWithoutCorrections": 9129,
45
50
  "from": "2024-01-01",
46
51
  "to": "2027-12-31"
47
52
  }
@@ -56,34 +61,38 @@
56
61
  },
57
62
  "morocco": {
58
63
  "certified": true,
59
- "checkedAt": "2026-10-16",
64
+ "checkedAt": "2026-11-04",
65
+ "places": 164,
60
66
  "checks": [
61
67
  {
62
68
  "tables": "Morocco — Habous",
63
69
  "places": 164,
64
- "times": 75768,
70
+ "times": 94350,
65
71
  "off": 0,
72
+ "offWithoutCorrections": 114,
66
73
  "from": "2026-08-27",
67
- "to": "2026-11-11"
74
+ "to": "2026-11-30"
68
75
  }
69
76
  ],
70
77
  "corrections": {
71
78
  "from": "2026-08-27",
72
- "to": "2026-11-11",
73
- "entries": 665,
74
- "sha256": "26ccb0979ee3e632c5acd736fdba3f8272530b48577cb1c0f2897d061e695bd0"
79
+ "to": "2026-11-30",
80
+ "entries": 114,
81
+ "sha256": "2ae7161a7164f7ccebf1aa9f81628a810ce8fdd337e0645907e6c58d0be6aae9"
75
82
  },
76
- "revision": 1
83
+ "revision": 2
77
84
  },
78
85
  "algeria": {
79
86
  "certified": true,
80
- "checkedAt": "2026-10-16",
87
+ "checkedAt": "2026-11-04",
88
+ "places": 68,
81
89
  "checks": [
82
90
  {
83
91
  "tables": "Algeria — Ministry of Religious Affairs",
84
92
  "places": 68,
85
93
  "times": 101184,
86
94
  "off": 0,
95
+ "offWithoutCorrections": 642,
87
96
  "from": "2026-10-01",
88
97
  "to": "2027-06-05"
89
98
  }
@@ -98,22 +107,24 @@
98
107
  },
99
108
  "indonesia": {
100
109
  "certified": true,
101
- "checkedAt": "2026-10-16",
110
+ "checkedAt": "2026-11-04",
111
+ "places": 517,
102
112
  "checks": [
103
113
  {
104
114
  "tables": "Indonesia — Kemenag",
105
115
  "places": 517,
106
- "times": 266772,
116
+ "times": 362934,
107
117
  "off": 0,
118
+ "offWithoutCorrections": 405,
108
119
  "from": "2026-10-07",
109
- "to": "2026-12-31"
120
+ "to": "2027-01-31"
110
121
  }
111
122
  ],
112
123
  "corrections": {
113
124
  "from": "2026-10-07",
114
- "to": "2026-12-31",
115
- "entries": 84,
116
- "sha256": "7c7d3b09a5b01a4d77c605ad8a37fbdac4fd3ef65df483332d75c67891b2359e"
125
+ "to": "2027-01-31",
126
+ "entries": 405,
127
+ "sha256": "5d9dc4703832e0ae850aff0af79a6d2436e013f5e1e2203ace41c6e05b36978d"
117
128
  },
118
129
  "revision": 1
119
130
  }