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