mihrab-methods 0.0.0-stage → 1.0.1

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 (217) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +319 -2
  3. package/certification.json +120 -0
  4. package/data/corrections/algeria.json +784 -0
  5. package/data/corrections/diyanet.json +18444 -0
  6. package/data/corrections/indonesia.json +210 -0
  7. package/data/corrections/morocco.json +993 -0
  8. package/data/corrections/sweden.json +516 -0
  9. package/dist/cjs/algeria.d.ts +4 -0
  10. package/dist/cjs/algeria.js +22 -0
  11. package/dist/cjs/authorities/algeria.d.ts +3 -0
  12. package/dist/cjs/authorities/algeria.js +57 -0
  13. package/dist/cjs/authorities/diyanet.d.ts +9 -0
  14. package/dist/cjs/authorities/diyanet.js +90 -0
  15. package/dist/cjs/authorities/indonesia.d.ts +4 -0
  16. package/dist/cjs/authorities/indonesia.js +82 -0
  17. package/dist/cjs/authorities/morocco.d.ts +5 -0
  18. package/dist/cjs/authorities/morocco.js +65 -0
  19. package/dist/cjs/authorities/slug.d.ts +2 -0
  20. package/dist/cjs/authorities/slug.js +17 -0
  21. package/dist/cjs/authorities/sweden.d.ts +3 -0
  22. package/dist/cjs/authorities/sweden.js +58 -0
  23. package/dist/cjs/core/api.d.ts +61 -0
  24. package/dist/cjs/core/api.js +177 -0
  25. package/dist/cjs/core/civilDate.d.ts +17 -0
  26. package/dist/cjs/core/civilDate.js +41 -0
  27. package/dist/cjs/core/clock.d.ts +22 -0
  28. package/dist/cjs/core/clock.js +67 -0
  29. package/dist/cjs/core/compute.d.ts +10 -0
  30. package/dist/cjs/core/compute.js +59 -0
  31. package/dist/cjs/core/correctionList.d.ts +25 -0
  32. package/dist/cjs/core/correctionList.js +62 -0
  33. package/dist/cjs/core/fetchLatest.d.ts +61 -0
  34. package/dist/cjs/core/fetchLatest.js +192 -0
  35. package/dist/cjs/core/index.d.ts +14 -0
  36. package/dist/cjs/core/index.js +45 -0
  37. package/dist/cjs/core/registry.d.ts +28 -0
  38. package/dist/cjs/core/registry.js +34 -0
  39. package/dist/cjs/core/types.d.ts +193 -0
  40. package/dist/cjs/core/types.js +22 -0
  41. package/dist/cjs/core.d.ts +2 -0
  42. package/dist/cjs/core.js +18 -0
  43. package/dist/cjs/data/generated/algeriaCertification.d.ts +3 -0
  44. package/dist/cjs/data/generated/algeriaCertification.js +4 -0
  45. package/dist/cjs/data/generated/algeriaCities.d.ts +8 -0
  46. package/dist/cjs/data/generated/algeriaCities.js +5 -0
  47. package/dist/cjs/data/generated/algeriaCorrections.d.ts +3 -0
  48. package/dist/cjs/data/generated/algeriaCorrections.js +4 -0
  49. package/dist/cjs/data/generated/algeriaMethodPoints.d.ts +2 -0
  50. package/dist/cjs/data/generated/algeriaMethodPoints.js +5 -0
  51. package/dist/cjs/data/generated/countryOutlines.d.ts +5 -0
  52. package/dist/cjs/data/generated/countryOutlines.js +5 -0
  53. package/dist/cjs/data/generated/diyanetCertification.d.ts +3 -0
  54. package/dist/cjs/data/generated/diyanetCertification.js +4 -0
  55. package/dist/cjs/data/generated/diyanetCorrections.d.ts +3 -0
  56. package/dist/cjs/data/generated/diyanetCorrections.js +4 -0
  57. package/dist/cjs/data/generated/diyanetCountries.d.ts +5 -0
  58. package/dist/cjs/data/generated/diyanetCountries.js +5 -0
  59. package/dist/cjs/data/generated/diyanetTurkeyPoints.d.ts +7 -0
  60. package/dist/cjs/data/generated/diyanetTurkeyPoints.js +5 -0
  61. package/dist/cjs/data/generated/diyanetWorldPoints.d.ts +10 -0
  62. package/dist/cjs/data/generated/diyanetWorldPoints.js +5 -0
  63. package/dist/cjs/data/generated/indonesiaCertification.d.ts +3 -0
  64. package/dist/cjs/data/generated/indonesiaCertification.js +4 -0
  65. package/dist/cjs/data/generated/indonesiaCorrections.d.ts +3 -0
  66. package/dist/cjs/data/generated/indonesiaCorrections.js +4 -0
  67. package/dist/cjs/data/generated/kemenagMethodPoints.d.ts +2 -0
  68. package/dist/cjs/data/generated/kemenagMethodPoints.js +5 -0
  69. package/dist/cjs/data/generated/kemenagPlaces.d.ts +8 -0
  70. package/dist/cjs/data/generated/kemenagPlaces.js +5 -0
  71. package/dist/cjs/data/generated/moroccoCertification.d.ts +3 -0
  72. package/dist/cjs/data/generated/moroccoCertification.js +4 -0
  73. package/dist/cjs/data/generated/moroccoCities.d.ts +8 -0
  74. package/dist/cjs/data/generated/moroccoCities.js +5 -0
  75. package/dist/cjs/data/generated/moroccoCorrections.d.ts +3 -0
  76. package/dist/cjs/data/generated/moroccoCorrections.js +4 -0
  77. package/dist/cjs/data/generated/moroccoMethodPoints.d.ts +2 -0
  78. package/dist/cjs/data/generated/moroccoMethodPoints.js +5 -0
  79. package/dist/cjs/data/generated/swedenCertification.d.ts +3 -0
  80. package/dist/cjs/data/generated/swedenCertification.js +4 -0
  81. package/dist/cjs/data/generated/swedenCorrections.d.ts +3 -0
  82. package/dist/cjs/data/generated/swedenCorrections.js +4 -0
  83. package/dist/cjs/data/generated/swedenPlaces.d.ts +5 -0
  84. package/dist/cjs/data/generated/swedenPlaces.js +5 -0
  85. package/dist/cjs/diyanet.d.ts +4 -0
  86. package/dist/cjs/diyanet.js +22 -0
  87. package/dist/cjs/index.d.ts +19 -0
  88. package/dist/cjs/index.js +37 -0
  89. package/dist/cjs/indonesia.d.ts +4 -0
  90. package/dist/cjs/indonesia.js +22 -0
  91. package/dist/cjs/locate.d.ts +21 -0
  92. package/dist/cjs/locate.js +204 -0
  93. package/dist/cjs/methods/algeria.d.ts +56 -0
  94. package/dist/cjs/methods/algeria.js +119 -0
  95. package/dist/cjs/methods/indonesia.d.ts +31 -0
  96. package/dist/cjs/methods/indonesia.js +94 -0
  97. package/dist/cjs/methods/morocco.d.ts +61 -0
  98. package/dist/cjs/methods/morocco.js +113 -0
  99. package/dist/cjs/methods/revisions.d.ts +15 -0
  100. package/dist/cjs/methods/revisions.js +18 -0
  101. package/dist/cjs/methods/scratch-congress.d.ts +87 -0
  102. package/dist/cjs/methods/scratch-congress.js +539 -0
  103. package/dist/cjs/methods/sweden.d.ts +88 -0
  104. package/dist/cjs/methods/sweden.js +568 -0
  105. package/dist/cjs/morocco.d.ts +4 -0
  106. package/dist/cjs/morocco.js +22 -0
  107. package/dist/cjs/sun/preciseSun.d.ts +19 -0
  108. package/dist/cjs/sun/preciseSun.js +185 -0
  109. package/dist/cjs/sweden.d.ts +4 -0
  110. package/dist/cjs/sweden.js +22 -0
  111. package/dist/esm/algeria.d.ts +4 -0
  112. package/dist/esm/algeria.js +4 -0
  113. package/dist/esm/authorities/algeria.d.ts +3 -0
  114. package/dist/esm/authorities/algeria.js +51 -0
  115. package/dist/esm/authorities/diyanet.d.ts +9 -0
  116. package/dist/esm/authorities/diyanet.js +83 -0
  117. package/dist/esm/authorities/indonesia.d.ts +4 -0
  118. package/dist/esm/authorities/indonesia.js +75 -0
  119. package/dist/esm/authorities/morocco.d.ts +5 -0
  120. package/dist/esm/authorities/morocco.js +59 -0
  121. package/dist/esm/authorities/slug.d.ts +2 -0
  122. package/dist/esm/authorities/slug.js +14 -0
  123. package/dist/esm/authorities/sweden.d.ts +3 -0
  124. package/dist/esm/authorities/sweden.js +52 -0
  125. package/dist/esm/core/api.d.ts +61 -0
  126. package/dist/esm/core/api.js +163 -0
  127. package/dist/esm/core/civilDate.d.ts +17 -0
  128. package/dist/esm/core/civilDate.js +36 -0
  129. package/dist/esm/core/clock.d.ts +22 -0
  130. package/dist/esm/core/clock.js +60 -0
  131. package/dist/esm/core/compute.d.ts +10 -0
  132. package/dist/esm/core/compute.js +56 -0
  133. package/dist/esm/core/correctionList.d.ts +25 -0
  134. package/dist/esm/core/correctionList.js +56 -0
  135. package/dist/esm/core/fetchLatest.d.ts +61 -0
  136. package/dist/esm/core/fetchLatest.js +184 -0
  137. package/dist/esm/core/index.d.ts +14 -0
  138. package/dist/esm/core/index.js +14 -0
  139. package/dist/esm/core/registry.d.ts +28 -0
  140. package/dist/esm/core/registry.js +28 -0
  141. package/dist/esm/core/types.d.ts +193 -0
  142. package/dist/esm/core/types.js +18 -0
  143. package/dist/esm/core.d.ts +2 -0
  144. package/dist/esm/core.js +2 -0
  145. package/dist/esm/data/generated/algeriaCertification.d.ts +3 -0
  146. package/dist/esm/data/generated/algeriaCertification.js +2 -0
  147. package/dist/esm/data/generated/algeriaCities.d.ts +8 -0
  148. package/dist/esm/data/generated/algeriaCities.js +3 -0
  149. package/dist/esm/data/generated/algeriaCorrections.d.ts +3 -0
  150. package/dist/esm/data/generated/algeriaCorrections.js +2 -0
  151. package/dist/esm/data/generated/algeriaMethodPoints.d.ts +2 -0
  152. package/dist/esm/data/generated/algeriaMethodPoints.js +3 -0
  153. package/dist/esm/data/generated/countryOutlines.d.ts +5 -0
  154. package/dist/esm/data/generated/countryOutlines.js +3 -0
  155. package/dist/esm/data/generated/diyanetCertification.d.ts +3 -0
  156. package/dist/esm/data/generated/diyanetCertification.js +2 -0
  157. package/dist/esm/data/generated/diyanetCorrections.d.ts +3 -0
  158. package/dist/esm/data/generated/diyanetCorrections.js +2 -0
  159. package/dist/esm/data/generated/diyanetCountries.d.ts +5 -0
  160. package/dist/esm/data/generated/diyanetCountries.js +3 -0
  161. package/dist/esm/data/generated/diyanetTurkeyPoints.d.ts +7 -0
  162. package/dist/esm/data/generated/diyanetTurkeyPoints.js +3 -0
  163. package/dist/esm/data/generated/diyanetWorldPoints.d.ts +10 -0
  164. package/dist/esm/data/generated/diyanetWorldPoints.js +3 -0
  165. package/dist/esm/data/generated/indonesiaCertification.d.ts +3 -0
  166. package/dist/esm/data/generated/indonesiaCertification.js +2 -0
  167. package/dist/esm/data/generated/indonesiaCorrections.d.ts +3 -0
  168. package/dist/esm/data/generated/indonesiaCorrections.js +2 -0
  169. package/dist/esm/data/generated/kemenagMethodPoints.d.ts +2 -0
  170. package/dist/esm/data/generated/kemenagMethodPoints.js +3 -0
  171. package/dist/esm/data/generated/kemenagPlaces.d.ts +8 -0
  172. package/dist/esm/data/generated/kemenagPlaces.js +3 -0
  173. package/dist/esm/data/generated/moroccoCertification.d.ts +3 -0
  174. package/dist/esm/data/generated/moroccoCertification.js +2 -0
  175. package/dist/esm/data/generated/moroccoCities.d.ts +8 -0
  176. package/dist/esm/data/generated/moroccoCities.js +3 -0
  177. package/dist/esm/data/generated/moroccoCorrections.d.ts +3 -0
  178. package/dist/esm/data/generated/moroccoCorrections.js +2 -0
  179. package/dist/esm/data/generated/moroccoMethodPoints.d.ts +2 -0
  180. package/dist/esm/data/generated/moroccoMethodPoints.js +3 -0
  181. package/dist/esm/data/generated/swedenCertification.d.ts +3 -0
  182. package/dist/esm/data/generated/swedenCertification.js +2 -0
  183. package/dist/esm/data/generated/swedenCorrections.d.ts +3 -0
  184. package/dist/esm/data/generated/swedenCorrections.js +2 -0
  185. package/dist/esm/data/generated/swedenPlaces.d.ts +5 -0
  186. package/dist/esm/data/generated/swedenPlaces.js +3 -0
  187. package/dist/esm/diyanet.d.ts +4 -0
  188. package/dist/esm/diyanet.js +4 -0
  189. package/dist/esm/index.d.ts +19 -0
  190. package/dist/esm/index.js +19 -0
  191. package/dist/esm/indonesia.d.ts +4 -0
  192. package/dist/esm/indonesia.js +4 -0
  193. package/dist/esm/locate.d.ts +21 -0
  194. package/dist/esm/locate.js +196 -0
  195. package/dist/esm/methods/algeria.d.ts +56 -0
  196. package/dist/esm/methods/algeria.js +114 -0
  197. package/dist/esm/methods/indonesia.d.ts +31 -0
  198. package/dist/esm/methods/indonesia.js +90 -0
  199. package/dist/esm/methods/morocco.d.ts +61 -0
  200. package/dist/esm/methods/morocco.js +107 -0
  201. package/dist/esm/methods/revisions.d.ts +15 -0
  202. package/dist/esm/methods/revisions.js +15 -0
  203. package/dist/esm/methods/scratch-congress.d.ts +87 -0
  204. package/dist/esm/methods/scratch-congress.js +534 -0
  205. package/dist/esm/methods/sweden.d.ts +88 -0
  206. package/dist/esm/methods/sweden.js +563 -0
  207. package/dist/esm/morocco.d.ts +4 -0
  208. package/dist/esm/morocco.js +4 -0
  209. package/dist/esm/package.json +1 -0
  210. package/dist/esm/sun/preciseSun.d.ts +19 -0
  211. package/dist/esm/sun/preciseSun.js +181 -0
  212. package/dist/esm/sweden.d.ts +4 -0
  213. package/dist/esm/sweden.js +4 -0
  214. package/docs/methods.md +190 -0
  215. package/package.json +178 -4
  216. package/schemas/certification.schema.json +59 -0
  217. package/schemas/corrections.schema.json +29 -0
@@ -0,0 +1,563 @@
1
+ /**
2
+ * The International Prayer Times Congress method (Istanbul, 2021 — the
3
+ * Diyanet and the European Council for Fatwa and Research) as Diyanet's
4
+ * tables realise it. Islamiska Förbundet's Swedish tables are those
5
+ * tables, row for row, so this is Sweden's method (id 24).
6
+ *
7
+ * The published rules (awqatsalah.com, "Tespit Kriterleri"):
8
+ * - Dhuhr five minutes after transit; Asr one shadow (Dhuhr when the
9
+ * shadow never forms), four minutes late; sunrise seven early,
10
+ * Maghrib seven late ("temkin").
11
+ * - Neither the day nor the night is shorter than five hours.
12
+ * - True Fajr at 18°, true Isha at 16°. Where the 18° dawn no longer
13
+ * forms, the share of the night the sharia night's third took on the
14
+ * last day it did is carried: Isha that share of the night after
15
+ * Maghrib, Fajr the same share scaled by 18/16 before sunrise.
16
+ * - The move from true times to the carried ones is gradual, starting
17
+ * twenty minutes before (Isha) or after (Fajr) the takdir.
18
+ *
19
+ * What the tables turn out to do with them, read off Diyanet's own tables
20
+ * for all 160 Swedish places, 2024–2027 (99.98 % of times on the printed
21
+ * minute, the rest a fraction of a second from the rounding point):
22
+ * - The sun is taken once a day, at 0h UT less 45 s: Meeus's mean
23
+ * elements and equation of the centre, the TRUE (geometric) longitude,
24
+ * the apparent obliquity (mean plus nutation), the equation of time as
25
+ * the mean longitude less the right ascension, 0.106 s later; and
26
+ * two small waves by the mean longitude the formula leaves out: a few
27
+ * hundredths of a second on the transit, a tenth of an arc-second on
28
+ * the declination.
29
+ * - Coordinates are kept to three decimals of a degree.
30
+ * - Asr that never forms is printed at Dhuhr's time.
31
+ * - The night a share is taken of is the day's own: 24 h less the
32
+ * printed day, sunrise to Maghrib.
33
+ * - The five-hour floors hold each end separately, 150 minutes from the
34
+ * printed Dhuhr or from the midnight after it. Where midsummer's
35
+ * night is floored at both ends (to within a minute), that night
36
+ * anchors the season, and the floored times never step back: before
37
+ * midsummer no sunrise earlier than midsummer's and a floored Maghrib
38
+ * no earlier than the day before; after it no sunrise earlier nor
39
+ * Maghrib later than on any day since midsummer — and Fajr and Isha
40
+ * carried the same way.
41
+ * - The gradual transition is a straight line across the days next to
42
+ * the gap on which the true time lies within twenty minutes of the
43
+ * takdir at the gap's edge: twenty minutes away at the far end, the
44
+ * takdir at the edge.
45
+ *
46
+ * Beyond Sweden the same code gives Diyanet's tables worldwide, read off
47
+ * them for places on six continents:
48
+ * - Where the dawn forms all summer but midsummer's true Isha (at 16°)
49
+ * falls later than the share carried from that night, midsummer is the
50
+ * takdir's day — a gap of one day, with its ramps (from about 44.4°
51
+ * north: Austria, central France, northern Bosnia).
52
+ * - Isha is printed at 16° in some countries and 17° in others; the
53
+ * high-latitude rules are worked at 16° either way (`ishaDeg`).
54
+ *
55
+ * Times are minutes past 0h UT of the date, a frame the clock changes
56
+ * cannot bend; `congressTimes` turns them into instants.
57
+ */
58
+ const RAD = Math.PI / 180;
59
+ const DAY_MS = 86_400_000;
60
+ const RISE_SET = -0.833;
61
+ const FAJR_ANGLE = -18;
62
+ /**
63
+ * Isha's depression: the Congress's 16° (Diyanet's in much of Europe,
64
+ * Russia, Central Asia, China and North America), or Diyanet's own 17°
65
+ * (elsewhere). The high-latitude rules are the Congress's at 16° either way;
66
+ * the angle sets only the true Isha printed.
67
+ */
68
+ export const CONGRESS_ISHA_DEG = 16;
69
+ export const DIYANET_ISHA_DEG = 17;
70
+ const TEMKIN = { sunrise: -7, dhuhr: 5, asr: 4, maghrib: 7 };
71
+ /** Minutes: the day and the night are never shorter. */
72
+ const FLOOR = 300;
73
+ /** Minutes: the gradual transition's reach. */
74
+ const BAND = 20;
75
+ /** Minutes: a midsummer night this close to the five-hour floor counts as floored. */
76
+ const ANCHOR_SLACK = 1;
77
+ /** Days to look for a dawn or a transition before giving up. */
78
+ const SEARCH_DAYS = 200;
79
+ const dayKeyOf = (y, m, d) => Date.UTC(y, m, d) / DAY_MS;
80
+ const yearOf = (k) => new Date(k * DAY_MS).getUTCFullYear();
81
+ const midsummerOf = (k) => dayKeyOf(yearOf(k), 5, 21);
82
+ /** Seconds before 0h UT at which Diyanet takes the day's sun. */
83
+ const SUN_INSTANT_S = -45;
84
+ /** Minutes Diyanet's equation of time runs ahead of the formula below. */
85
+ const EOT_ADD_MIN = 0.00176;
86
+ /**
87
+ * Seconds Diyanet's transit runs later than the formula below, by the sun's
88
+ * mean longitude: an annual wave of a few hundredths of a second, read off
89
+ * every Dhuhr of Diyanet's Swedish tables 2024–2027 (the largest margin that
90
+ * leaves none of them off; the two halves of the span give the same wave).
91
+ */
92
+ function eotResidualS(L) {
93
+ return (0.033377 * Math.sin(L) + 0.007721 * Math.cos(L) -
94
+ 0.000229 * Math.sin(2 * L) + 0.004198 * Math.cos(2 * L) -
95
+ 0.02346 * Math.sin(3 * L) - 0.026434 * Math.cos(3 * L));
96
+ }
97
+ /**
98
+ * Degrees Diyanet's declination runs above the formula below: a wave of a
99
+ * tenth of an arc-second by the sun's mean longitude, read off the times of
100
+ * Diyanet's Swedish tables 2024–2027 that sit at a minute's edge (fitted on
101
+ * either two years, it holds on the other two).
102
+ */
103
+ function decResidualDeg(L) {
104
+ return -1.1539e-5 - 3.6174e-5 * Math.sin(L) - 2.1101e-5 * Math.cos(L) + 2.0235e-5 * Math.sin(2 * L) - 2.2534e-5 * Math.cos(2 * L);
105
+ }
106
+ /**
107
+ * Declination (radians) and equation of time (minutes), as Diyanet's tables
108
+ * take them: Meeus's mean elements, the true longitude, the apparent obliquity.
109
+ */
110
+ function sunAt(utcMs) {
111
+ const T = (utcMs / DAY_MS - 10957.5) / 36525; // Julian centuries since J2000.0
112
+ const L0 = (((280.46646 + T * (36000.76983 + 0.0003032 * T)) % 360) + 360) % 360;
113
+ const M = (357.52911 + T * (35999.05029 - 0.0001537 * T)) * RAD;
114
+ const C = (1.914602 - T * (0.004817 + 0.000014 * T)) * Math.sin(M) +
115
+ (0.019993 - 0.000101 * T) * Math.sin(2 * M) +
116
+ 0.000289 * Math.sin(3 * M);
117
+ const om = (125.04 - 1934.136 * T) * RAD;
118
+ const Ls = (280.4665 + 36000.7698 * T) * RAD;
119
+ const Lm = (218.3165 + 481267.8813 * T) * RAD;
120
+ const eps = 23 +
121
+ (26 + (21.448 - T * (46.815 + T * (0.00059 - T * 0.001813))) / 60) / 60 +
122
+ (9.2 * Math.cos(om) + 0.57 * Math.cos(2 * Ls) + 0.1 * Math.cos(2 * Lm) - 0.09 * Math.cos(2 * om)) / 3600;
123
+ const lam = (L0 + C) * RAD;
124
+ const ra = ((((Math.atan2(Math.cos(eps * RAD) * Math.sin(lam), Math.cos(lam)) / RAD) % 360) + 360) % 360);
125
+ const dec = Math.asin(Math.sin(eps * RAD) * Math.sin(lam)) + decResidualDeg(L0 * RAD) * RAD;
126
+ const eot = (((((L0 - ra + 180) % 360) + 360) % 360) - 180) * 4 + EOT_ADD_MIN - eotResidualS(L0 * RAD) / 60;
127
+ return { dec, eot };
128
+ }
129
+ /** One day's sun: transit and the crossings, minutes past the day's 0h UT. */
130
+ class Solar {
131
+ day;
132
+ lat;
133
+ lon;
134
+ dec;
135
+ transit;
136
+ constructor(day, lat, lon) {
137
+ this.day = day;
138
+ this.lat = lat;
139
+ this.lon = lon;
140
+ const { dec, eot } = sunAt(day * DAY_MS + SUN_INSTANT_S * 1000);
141
+ this.dec = dec;
142
+ this.transit = 720 - 4 * lon - eot;
143
+ }
144
+ hourAngle(alt) {
145
+ const c = (Math.sin(alt * RAD) - Math.sin(this.lat * RAD) * Math.sin(this.dec)) /
146
+ (Math.cos(this.lat * RAD) * Math.cos(this.dec));
147
+ if (c < -1 || c > 1)
148
+ return null;
149
+ return (Math.acos(c) / RAD) * 4;
150
+ }
151
+ rising(alt) {
152
+ const h = this.hourAngle(alt);
153
+ return h === null ? null : this.transit - h;
154
+ }
155
+ setting(alt) {
156
+ const h = this.hourAngle(alt);
157
+ return h === null ? null : this.transit + h;
158
+ }
159
+ /** Asr: the shadow of a thing is its noon shadow plus `shadow` lengths. */
160
+ asr(shadow) {
161
+ const noonAlt = 90 - Math.abs(this.lat - this.dec / RAD);
162
+ if (noonAlt <= 0)
163
+ return null;
164
+ const alt = Math.atan(1 / (shadow + Math.tan((90 - noonAlt) * RAD))) / RAD;
165
+ return this.setting(alt);
166
+ }
167
+ }
168
+ /** The method for one place and one year; everything it needs of the year is worked out once. */
169
+ class CongressYear {
170
+ lat;
171
+ lon;
172
+ year;
173
+ clockOffset;
174
+ solars = new Map();
175
+ riseSets = new Map();
176
+ ratioValue;
177
+ gap;
178
+ floors;
179
+ ramps = new Map();
180
+ extremes = new Map();
181
+ carriedCache = new Map();
182
+ ishaAngle;
183
+ /**
184
+ * `clockOffset`: minutes east of UTC of the clock the table is printed in,
185
+ * when known. Diyanet walks the gradual transition on that clock, which
186
+ * matters only where the true time just past the gap falls on the other
187
+ * side of local midnight from the takdir (far east of a zone's meridian,
188
+ * as at Aihui): see `ramp`.
189
+ */
190
+ constructor(lat, lon, year, ishaDeg = CONGRESS_ISHA_DEG, clockOffset) {
191
+ this.lat = lat;
192
+ this.lon = lon;
193
+ this.year = year;
194
+ this.clockOffset = clockOffset;
195
+ this.ishaAngle = -ishaDeg;
196
+ }
197
+ solar(day) {
198
+ let s = this.solars.get(day);
199
+ if (!s) {
200
+ s = new Solar(day, this.lat, this.lon);
201
+ this.solars.set(day, s);
202
+ }
203
+ return s;
204
+ }
205
+ /** Printed (temkin) sunrise and Maghrib with the five-hour floors. */
206
+ riseSet(day) {
207
+ const cached = this.riseSets.get(day);
208
+ if (cached)
209
+ return cached;
210
+ const s = this.solar(day);
211
+ const dhuhr = s.transit + TEMKIN.dhuhr;
212
+ const half = FLOOR / 2;
213
+ const rawSr = s.rising(RISE_SET);
214
+ const rawSs = s.setting(RISE_SET);
215
+ // the night no shorter than five hours, 2.5 h either side of the midnight after the printed
216
+ // Dhuhr — or, where midsummer's night is floored at both ends, of the season's anchoring
217
+ // Dhuhr (dhuhrExtremes): the floored times never step back
218
+ let nsr;
219
+ let nss;
220
+ if (this.midsummerFloors(day)) {
221
+ const [hi, lo] = this.dhuhrExtremes(day);
222
+ nsr = hi - 720 + half;
223
+ nss = lo + 720 - half;
224
+ }
225
+ else {
226
+ nsr = dhuhr - 720 + half;
227
+ nss = dhuhr + 720 - half;
228
+ }
229
+ let sr;
230
+ let ss;
231
+ if (rawSr === null || rawSs === null) {
232
+ // the sun neither rises nor sets: midnight sun (up at noon) or polar night
233
+ const noonUp = 90 - Math.abs(this.lat - s.dec / RAD) > RISE_SET;
234
+ [sr, ss] = noonUp ? [nsr, nss] : [dhuhr - half, dhuhr + half];
235
+ }
236
+ else {
237
+ sr = rawSr + TEMKIN.sunrise;
238
+ ss = rawSs + TEMKIN.maghrib;
239
+ }
240
+ // the day no shorter than five hours about the printed Dhuhr, each end on its own; then the night
241
+ sr = Math.max(Math.min(sr, dhuhr - half), nsr);
242
+ ss = Math.min(Math.max(ss, dhuhr + half), nss);
243
+ // where midsummer anchors the season, Maghrib is held at the latest since midsummer until it
244
+ // falls of itself (Torsby, whose midsummer night is just over five hours: its sunset still
245
+ // gets later for a few days after the solstice, and the table holds midsummer's Maghrib)
246
+ let held;
247
+ if (this.midsummerFloors(day)) {
248
+ const mid = midsummerOf(day);
249
+ if (day === mid)
250
+ held = true;
251
+ else if (day > mid) {
252
+ const prev = this.riseSet(day - 1);
253
+ if (prev.held && ss > prev.ss) {
254
+ ss = prev.ss;
255
+ held = true;
256
+ }
257
+ }
258
+ }
259
+ const rs = { sr, ss, held };
260
+ this.riseSets.set(day, rs);
261
+ return rs;
262
+ }
263
+ dhuhrOf(day) {
264
+ return this.solar(day).transit + TEMKIN.dhuhr;
265
+ }
266
+ /** Does the day's Maghrib fall on the night's floor (or the sun not set at all)? */
267
+ maghribFloored(day) {
268
+ const s = this.solar(day);
269
+ const st = s.setting(RISE_SET);
270
+ return st === null || st + TEMKIN.maghrib > s.transit + TEMKIN.dhuhr + 720 - FLOOR / 2;
271
+ }
272
+ /**
273
+ * The Dhuhrs the anchored night floors go by: [latest, earliest]. Before
274
+ * midsummer, sunrise is held against midsummer's Dhuhr and a floored
275
+ * Maghrib against the latest Dhuhr since its floor began; after it, every
276
+ * Dhuhr from midsummer on — so the floored times never step back.
277
+ */
278
+ dhuhrExtremes(day) {
279
+ const cached = this.extremes.get(day);
280
+ if (cached)
281
+ return cached;
282
+ const mid = midsummerOf(day);
283
+ let out;
284
+ if (day <= mid) {
285
+ let maxRun = this.dhuhrOf(day);
286
+ if (this.maghribFloored(day)) {
287
+ for (let d = day - 1; d > day - 120 && this.maghribFloored(d); d -= 1)
288
+ maxRun = Math.max(maxRun, this.dhuhrOf(d));
289
+ }
290
+ out = [Math.max(this.dhuhrOf(day), this.dhuhrOf(mid)), Math.min(maxRun, this.dhuhrOf(mid))];
291
+ }
292
+ else {
293
+ let hi = -Infinity;
294
+ let lo = Infinity;
295
+ for (let d = mid; d <= day; d += 1) {
296
+ const v = this.dhuhrOf(d);
297
+ hi = Math.max(hi, v);
298
+ lo = Math.min(lo, v);
299
+ }
300
+ out = [hi, lo];
301
+ }
302
+ this.extremes.set(day, out);
303
+ return out;
304
+ }
305
+ /**
306
+ * Is midsummer's night floored at both ends — the day's own floors
307
+ * leaving it five hours, or within a minute of five (Torsby's 300.5 is
308
+ * anchored, Fagersta's 302 is not)? Then that night anchors the season.
309
+ */
310
+ midsummerFloors(day) {
311
+ if (this.floors === undefined) {
312
+ const s = this.solar(midsummerOf(day));
313
+ const sr = s.rising(RISE_SET);
314
+ const ss = s.setting(RISE_SET);
315
+ const dhuhr = s.transit + TEMKIN.dhuhr;
316
+ const half = FLOOR / 2;
317
+ if (sr === null || ss === null)
318
+ this.floors = true;
319
+ else {
320
+ const psr = Math.max(sr + TEMKIN.sunrise, dhuhr - 720 + half);
321
+ const pss = Math.min(ss + TEMKIN.maghrib, dhuhr + 720 - half);
322
+ this.floors = 1440 - (pss - psr) <= FLOOR + ANCHOR_SLACK;
323
+ }
324
+ }
325
+ return this.floors;
326
+ }
327
+ /** The day's night: 24 h less the printed day. */
328
+ night(day) {
329
+ const { sr, ss } = this.riseSet(day);
330
+ return 1440 - (ss - sr);
331
+ }
332
+ fajr18(day) {
333
+ return this.solar(day).rising(FAJR_ANGLE);
334
+ }
335
+ isha16(day) {
336
+ return this.solar(day).setting(this.ishaAngle);
337
+ }
338
+ trueTime(key, day) {
339
+ return key === 'fajr' ? this.fajr18(day) : this.isha16(day);
340
+ }
341
+ /** First and last day on which the 18° dawn does not form, or null when it forms all year. */
342
+ gapDays() {
343
+ if (this.gap !== undefined)
344
+ return this.gap;
345
+ const mid = dayKeyOf(this.year, 5, 21);
346
+ if (this.fajr18(mid) !== null) {
347
+ // The dawn forms all summer, but where midsummer's true Isha falls
348
+ // later than the share carried from that night would put it, Diyanet
349
+ // takes midsummer as the takdir's day and ramps to it as at a gap's
350
+ // edge (Austria, central France, northern Bosnia — from about 44.4°).
351
+ // The rule is the northern summer's; south of the equator the true
352
+ // times stand.
353
+ // (at the Congress's 16°, whatever Isha's printed angle)
354
+ const isha = this.solar(mid).setting(-CONGRESS_ISHA_DEG);
355
+ this.gap = this.lat > 0 && isha !== null && this.carriedRaw(mid)[1] < isha ? [mid, mid] : null;
356
+ return this.gap;
357
+ }
358
+ let a = mid;
359
+ while (this.fajr18(a - 1) === null && mid - a < SEARCH_DAYS)
360
+ a -= 1;
361
+ let b = mid;
362
+ while (this.fajr18(b + 1) === null && b - mid < SEARCH_DAYS)
363
+ b += 1;
364
+ this.gap = [a, b];
365
+ return this.gap;
366
+ }
367
+ /**
368
+ * The carried ratio: a third of the sharia night (this day's Maghrib to
369
+ * the next dawn — the last one, a day on) over the day's night, on the
370
+ * last day before midsummer on which the 18° dawn forms.
371
+ */
372
+ ratio() {
373
+ if (this.ratioValue !== undefined)
374
+ return this.ratioValue;
375
+ let d = dayKeyOf(this.year, 5, 21);
376
+ let f = this.fajr18(d);
377
+ let steps = 0;
378
+ while (f === null && steps < SEARCH_DAYS) {
379
+ d -= 1;
380
+ f = this.fajr18(d);
381
+ steps += 1;
382
+ }
383
+ if (f === null) {
384
+ this.ratioValue = null;
385
+ return null;
386
+ }
387
+ const { ss } = this.riseSet(d);
388
+ const S = f + 1440 - ss;
389
+ this.ratioValue = S / 3 / this.night(d);
390
+ return this.ratioValue;
391
+ }
392
+ /** Fajr and Isha from the carried ratio, with midsummer's as the bound where its floors are in force. */
393
+ /** Fajr and Isha from the carried share, before any season rule. */
394
+ carriedRaw(day) {
395
+ const r = this.ratio() ?? 0;
396
+ const { sr, ss } = this.riseSet(day);
397
+ const n = this.night(day);
398
+ return [sr - r * (FAJR_ANGLE / -CONGRESS_ISHA_DEG) * n, ss + r * n];
399
+ }
400
+ /**
401
+ * Fajr and Isha from the carried share. Where midsummer's night anchors
402
+ * the season: before midsummer never an earlier Fajr nor a later Isha than
403
+ * midsummer's; after it, than on any day since — as the floored sunrise.
404
+ */
405
+ carried(day) {
406
+ const cached = this.carriedCache.get(day);
407
+ if (cached)
408
+ return cached;
409
+ const mid = midsummerOf(day);
410
+ let out;
411
+ if (!this.midsummerFloors(day) || day === mid)
412
+ out = this.carriedRaw(day);
413
+ else if (day < mid) {
414
+ const [f, i] = this.carriedRaw(day);
415
+ const [mf, mi] = this.carried(mid);
416
+ out = [Math.max(f, mf), Math.min(i, mi)];
417
+ }
418
+ else {
419
+ // walk forward from midsummer so the running bound needs no deep recursion
420
+ let prev = this.carried(mid);
421
+ for (let d = mid + 1; d < day; d += 1) {
422
+ let c = this.carriedCache.get(d);
423
+ if (!c) {
424
+ const [f, i] = this.carriedRaw(d);
425
+ c = [Math.max(f, prev[0]), Math.min(i, prev[1])];
426
+ this.carriedCache.set(d, c);
427
+ }
428
+ prev = c;
429
+ }
430
+ const [f, i] = this.carriedRaw(day);
431
+ out = [Math.max(f, prev[0]), Math.min(i, prev[1])];
432
+ }
433
+ this.carriedCache.set(day, out);
434
+ return out;
435
+ }
436
+ /** The gradual transition between the true time and the takdir. */
437
+ ramp(day, side, key) {
438
+ const gap = this.gapDays();
439
+ if (!gap)
440
+ return this.trueTime(key, day);
441
+ const [d1, d2] = gap;
442
+ const idx = key === 'fajr' ? 0 : 1;
443
+ const cacheKey = side + key;
444
+ let ramp = this.ramps.get(cacheKey);
445
+ if (!ramp) {
446
+ const edge = side === 'in' ? d1 : d2;
447
+ const T = this.carried(edge)[idx];
448
+ const step = side === 'in' ? -1 : 1;
449
+ let far = null;
450
+ // On the table's clock, a true time just after the gap that lands on the
451
+ // other side of local midnight from the takdir is already past the band
452
+ // (Fajr after the gap only — before it, and for Isha, the transition is walked as ever):
453
+ // there is no transition, the gap's last day is printed the band away
454
+ // from the takdir, and the true times follow (Diyanet's tables at Aihui).
455
+ const first = this.trueTime(key, edge + step);
456
+ const localDay = (m) => Math.floor((m + (this.clockOffset ?? 0)) / 1440);
457
+ const wrapped = side === 'out' && key === 'fajr' && this.clockOffset !== undefined && first !== null && localDay(first) !== localDay(T);
458
+ // walking away from the gap the true time starts far beyond T, enters the
459
+ // band and leaves it again; the far end is the last day of that run
460
+ for (let d = edge + step; !wrapped && Math.abs(d - edge) < SEARCH_DAYS; d += step) {
461
+ const t = this.trueTime(key, d);
462
+ if (t !== null && Math.abs(t - T) <= BAND)
463
+ far = d;
464
+ else if (far !== null)
465
+ break;
466
+ }
467
+ ramp = { edge, T, far, bump: wrapped };
468
+ this.ramps.set(cacheKey, ramp);
469
+ }
470
+ const { edge, T, far } = ramp;
471
+ if (far === null)
472
+ return this.trueTime(key, day);
473
+ const inside = side === 'in' ? far <= day && day < edge : edge < day && day <= far;
474
+ if (!inside)
475
+ return this.trueTime(key, day);
476
+ const sign = key === 'fajr' ? 1 : -1; // Fajr sits later than the takdir, Isha earlier
477
+ return T + (sign * BAND * Math.abs(day - edge)) / Math.abs(far - edge);
478
+ }
479
+ /** The day's six times, minutes past its 0h UT. */
480
+ times(day, asrShadow) {
481
+ const s = this.solar(day);
482
+ const { sr, ss } = this.riseSet(day);
483
+ const dhuhr = s.transit + TEMKIN.dhuhr;
484
+ const rawAsr = s.asr(asrShadow);
485
+ // an Asr that never forms is printed at Dhuhr's time (the published rule)
486
+ let asr = rawAsr === null ? dhuhr : rawAsr + TEMKIN.asr;
487
+ asr = Math.max(dhuhr, Math.min(asr, ss));
488
+ let fajr = this.fajr18(day);
489
+ let isha = this.isha16(day);
490
+ const gap = this.gapDays();
491
+ if (gap) {
492
+ const [d1, d2] = gap;
493
+ if (d1 <= day && day <= d2) {
494
+ [fajr, isha] = this.carried(day);
495
+ // the gap's edge where the transition is skipped (`ramp`): the band away
496
+ if (day === d1 || day === d2) {
497
+ const side = day === d1 ? 'in' : 'out';
498
+ this.ramp(day, side, 'fajr');
499
+ this.ramp(day, side, 'isha');
500
+ if (this.ramps.get(side + 'fajr')?.bump && fajr !== null)
501
+ fajr += BAND;
502
+ if (this.ramps.get(side + 'isha')?.bump && isha !== null)
503
+ isha -= BAND;
504
+ }
505
+ }
506
+ else if (day < d1) {
507
+ fajr = this.ramp(day, 'in', 'fajr');
508
+ isha = this.ramp(day, 'in', 'isha');
509
+ }
510
+ else {
511
+ fajr = this.ramp(day, 'out', 'fajr');
512
+ isha = this.ramp(day, 'out', 'isha');
513
+ }
514
+ }
515
+ return {
516
+ fajr: fajr ?? sr - 60,
517
+ sunrise: sr,
518
+ dhuhr,
519
+ asr,
520
+ maghrib: ss,
521
+ isha: isha ?? ss + 60,
522
+ };
523
+ }
524
+ }
525
+ const years = new Map();
526
+ const MAX_CACHED_YEARS = 12;
527
+ function yearFor(lat, lon, year, ishaDeg = CONGRESS_ISHA_DEG, clockOffset) {
528
+ const key = `${lat.toFixed(5)},${lon.toFixed(5)},${year},${ishaDeg},${clockOffset ?? ''}`;
529
+ let cy = years.get(key);
530
+ if (!cy) {
531
+ if (years.size >= MAX_CACHED_YEARS)
532
+ years.delete(years.keys().next().value);
533
+ cy = new CongressYear(lat, lon, year, ishaDeg, clockOffset);
534
+ years.set(key, cy);
535
+ }
536
+ return cy;
537
+ }
538
+ /**
539
+ * The Congress method's times for a calendar date (year, month 0–11, day),
540
+ * as instants, unrounded. `asrShadow` is 1 for the standard Asr, 2 for the
541
+ * Hanafi one; the tables print the standard. `ishaDeg` is Isha's depression:
542
+ * 16° (the Congress's, Diyanet's in Europe and North America) or 17°
543
+ * (Diyanet's everywhere else). `clockOffset` is the table's clock, minutes
544
+ * east of UTC (its standard time), where known — see CongressYear.
545
+ */
546
+ export function congressTimes(latitude, longitude, year, month, day, asrShadow = 1, ishaDeg = CONGRESS_ISHA_DEG, clockOffset) {
547
+ const key = dayKeyOf(year, month, day);
548
+ const t = yearFor(latitude, longitude, year, ishaDeg, clockOffset).times(key, asrShadow);
549
+ const start = key * DAY_MS;
550
+ const at = (m) => new Date(start + m * 60_000);
551
+ return { fajr: at(t.fajr), sunrise: at(t.sunrise), dhuhr: at(t.dhuhr), asr: at(t.asr), maghrib: at(t.maghrib), isha: at(t.isha) };
552
+ }
553
+ /** For tests and the verification tool: the year's structure as the method sees it. */
554
+ export function congressYearInfo(latitude, longitude, year) {
555
+ const cy = yearFor(latitude, longitude, year);
556
+ const iso = (k) => new Date(k * DAY_MS).toISOString().slice(0, 10);
557
+ const gap = cy.gapDays();
558
+ return {
559
+ gap: gap ? [iso(gap[0]), iso(gap[1])] : null,
560
+ ratio: cy.ratio(),
561
+ midsummerFloors: cy.midsummerFloors(dayKeyOf(year, 5, 21)),
562
+ };
563
+ }
@@ -0,0 +1,4 @@
1
+ /** mihrab-methods/morocco — the morocco method with its places and correction list, and nothing else's. */
2
+ import './authorities/morocco.js';
3
+ export * from './core/index.js';
4
+ export { places } from './authorities/morocco.js';
@@ -0,0 +1,4 @@
1
+ /** mihrab-methods/morocco — the morocco method with its places and correction list, and nothing else's. */
2
+ import './authorities/morocco.js';
3
+ export * from './core/index.js';
4
+ export { places } from './authorities/morocco.js';
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A precise apparent Sun: the Earth's VSOP87 series (truncated), IAU 2006
3
+ * precession, IAU 2000B nutation and light time, after Don Cross's
4
+ * Astronomy Engine (MIT licence). It agrees with JPL DE421 to about 0.05 s
5
+ * in the equation of time and 0.5″ in declination, where Meeus's short
6
+ * series (`meeusApparentSun`) drifts by about a second over the year —
7
+ * enough to move a published minute.
8
+ */
9
+ /** ΔT (s), Espenak & Meeus, for UT days from J2000 — held at its 2017 value onward (about 69 s, as observed), as JPL Horizons does. */
10
+ export declare function deltaT(ut: number): number;
11
+ /**
12
+ * The apparent Sun at `d` days from J2000.0 (UT): declination (rad) and
13
+ * equation of time (minutes), such that the transit at longitude λ (east) is
14
+ * 720 − 4λ − eot minutes UT. Same contract as `meeusApparentSun`.
15
+ */
16
+ export declare function preciseApparentSun(d: number): {
17
+ dec: number;
18
+ eot: number;
19
+ };