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.
- package/LICENSE +373 -0
- package/README.md +319 -2
- package/certification.json +120 -0
- package/data/corrections/algeria.json +784 -0
- package/data/corrections/diyanet.json +18444 -0
- package/data/corrections/indonesia.json +210 -0
- package/data/corrections/morocco.json +993 -0
- package/data/corrections/sweden.json +516 -0
- package/dist/cjs/algeria.d.ts +4 -0
- package/dist/cjs/algeria.js +22 -0
- package/dist/cjs/authorities/algeria.d.ts +3 -0
- package/dist/cjs/authorities/algeria.js +57 -0
- package/dist/cjs/authorities/diyanet.d.ts +9 -0
- package/dist/cjs/authorities/diyanet.js +90 -0
- package/dist/cjs/authorities/indonesia.d.ts +4 -0
- package/dist/cjs/authorities/indonesia.js +82 -0
- package/dist/cjs/authorities/morocco.d.ts +5 -0
- package/dist/cjs/authorities/morocco.js +65 -0
- package/dist/cjs/authorities/slug.d.ts +2 -0
- package/dist/cjs/authorities/slug.js +17 -0
- package/dist/cjs/authorities/sweden.d.ts +3 -0
- package/dist/cjs/authorities/sweden.js +58 -0
- package/dist/cjs/core/api.d.ts +61 -0
- package/dist/cjs/core/api.js +177 -0
- package/dist/cjs/core/civilDate.d.ts +17 -0
- package/dist/cjs/core/civilDate.js +41 -0
- package/dist/cjs/core/clock.d.ts +22 -0
- package/dist/cjs/core/clock.js +67 -0
- package/dist/cjs/core/compute.d.ts +10 -0
- package/dist/cjs/core/compute.js +59 -0
- package/dist/cjs/core/correctionList.d.ts +25 -0
- package/dist/cjs/core/correctionList.js +62 -0
- package/dist/cjs/core/fetchLatest.d.ts +61 -0
- package/dist/cjs/core/fetchLatest.js +192 -0
- package/dist/cjs/core/index.d.ts +14 -0
- package/dist/cjs/core/index.js +45 -0
- package/dist/cjs/core/registry.d.ts +28 -0
- package/dist/cjs/core/registry.js +34 -0
- package/dist/cjs/core/types.d.ts +193 -0
- package/dist/cjs/core/types.js +22 -0
- package/dist/cjs/core.d.ts +2 -0
- package/dist/cjs/core.js +18 -0
- package/dist/cjs/data/generated/algeriaCertification.d.ts +3 -0
- package/dist/cjs/data/generated/algeriaCertification.js +4 -0
- package/dist/cjs/data/generated/algeriaCities.d.ts +8 -0
- package/dist/cjs/data/generated/algeriaCities.js +5 -0
- package/dist/cjs/data/generated/algeriaCorrections.d.ts +3 -0
- package/dist/cjs/data/generated/algeriaCorrections.js +4 -0
- package/dist/cjs/data/generated/algeriaMethodPoints.d.ts +2 -0
- package/dist/cjs/data/generated/algeriaMethodPoints.js +5 -0
- package/dist/cjs/data/generated/countryOutlines.d.ts +5 -0
- package/dist/cjs/data/generated/countryOutlines.js +5 -0
- package/dist/cjs/data/generated/diyanetCertification.d.ts +3 -0
- package/dist/cjs/data/generated/diyanetCertification.js +4 -0
- package/dist/cjs/data/generated/diyanetCorrections.d.ts +3 -0
- package/dist/cjs/data/generated/diyanetCorrections.js +4 -0
- package/dist/cjs/data/generated/diyanetCountries.d.ts +5 -0
- package/dist/cjs/data/generated/diyanetCountries.js +5 -0
- package/dist/cjs/data/generated/diyanetTurkeyPoints.d.ts +7 -0
- package/dist/cjs/data/generated/diyanetTurkeyPoints.js +5 -0
- package/dist/cjs/data/generated/diyanetWorldPoints.d.ts +10 -0
- package/dist/cjs/data/generated/diyanetWorldPoints.js +5 -0
- package/dist/cjs/data/generated/indonesiaCertification.d.ts +3 -0
- package/dist/cjs/data/generated/indonesiaCertification.js +4 -0
- package/dist/cjs/data/generated/indonesiaCorrections.d.ts +3 -0
- package/dist/cjs/data/generated/indonesiaCorrections.js +4 -0
- package/dist/cjs/data/generated/kemenagMethodPoints.d.ts +2 -0
- package/dist/cjs/data/generated/kemenagMethodPoints.js +5 -0
- package/dist/cjs/data/generated/kemenagPlaces.d.ts +8 -0
- package/dist/cjs/data/generated/kemenagPlaces.js +5 -0
- package/dist/cjs/data/generated/moroccoCertification.d.ts +3 -0
- package/dist/cjs/data/generated/moroccoCertification.js +4 -0
- package/dist/cjs/data/generated/moroccoCities.d.ts +8 -0
- package/dist/cjs/data/generated/moroccoCities.js +5 -0
- package/dist/cjs/data/generated/moroccoCorrections.d.ts +3 -0
- package/dist/cjs/data/generated/moroccoCorrections.js +4 -0
- package/dist/cjs/data/generated/moroccoMethodPoints.d.ts +2 -0
- package/dist/cjs/data/generated/moroccoMethodPoints.js +5 -0
- package/dist/cjs/data/generated/swedenCertification.d.ts +3 -0
- package/dist/cjs/data/generated/swedenCertification.js +4 -0
- package/dist/cjs/data/generated/swedenCorrections.d.ts +3 -0
- package/dist/cjs/data/generated/swedenCorrections.js +4 -0
- package/dist/cjs/data/generated/swedenPlaces.d.ts +5 -0
- package/dist/cjs/data/generated/swedenPlaces.js +5 -0
- package/dist/cjs/diyanet.d.ts +4 -0
- package/dist/cjs/diyanet.js +22 -0
- package/dist/cjs/index.d.ts +19 -0
- package/dist/cjs/index.js +37 -0
- package/dist/cjs/indonesia.d.ts +4 -0
- package/dist/cjs/indonesia.js +22 -0
- package/dist/cjs/locate.d.ts +21 -0
- package/dist/cjs/locate.js +204 -0
- package/dist/cjs/methods/algeria.d.ts +56 -0
- package/dist/cjs/methods/algeria.js +119 -0
- package/dist/cjs/methods/indonesia.d.ts +31 -0
- package/dist/cjs/methods/indonesia.js +94 -0
- package/dist/cjs/methods/morocco.d.ts +61 -0
- package/dist/cjs/methods/morocco.js +113 -0
- package/dist/cjs/methods/revisions.d.ts +15 -0
- package/dist/cjs/methods/revisions.js +18 -0
- package/dist/cjs/methods/scratch-congress.d.ts +87 -0
- package/dist/cjs/methods/scratch-congress.js +539 -0
- package/dist/cjs/methods/sweden.d.ts +88 -0
- package/dist/cjs/methods/sweden.js +568 -0
- package/dist/cjs/morocco.d.ts +4 -0
- package/dist/cjs/morocco.js +22 -0
- package/dist/cjs/sun/preciseSun.d.ts +19 -0
- package/dist/cjs/sun/preciseSun.js +185 -0
- package/dist/cjs/sweden.d.ts +4 -0
- package/dist/cjs/sweden.js +22 -0
- package/dist/esm/algeria.d.ts +4 -0
- package/dist/esm/algeria.js +4 -0
- package/dist/esm/authorities/algeria.d.ts +3 -0
- package/dist/esm/authorities/algeria.js +51 -0
- package/dist/esm/authorities/diyanet.d.ts +9 -0
- package/dist/esm/authorities/diyanet.js +83 -0
- package/dist/esm/authorities/indonesia.d.ts +4 -0
- package/dist/esm/authorities/indonesia.js +75 -0
- package/dist/esm/authorities/morocco.d.ts +5 -0
- package/dist/esm/authorities/morocco.js +59 -0
- package/dist/esm/authorities/slug.d.ts +2 -0
- package/dist/esm/authorities/slug.js +14 -0
- package/dist/esm/authorities/sweden.d.ts +3 -0
- package/dist/esm/authorities/sweden.js +52 -0
- package/dist/esm/core/api.d.ts +61 -0
- package/dist/esm/core/api.js +163 -0
- package/dist/esm/core/civilDate.d.ts +17 -0
- package/dist/esm/core/civilDate.js +36 -0
- package/dist/esm/core/clock.d.ts +22 -0
- package/dist/esm/core/clock.js +60 -0
- package/dist/esm/core/compute.d.ts +10 -0
- package/dist/esm/core/compute.js +56 -0
- package/dist/esm/core/correctionList.d.ts +25 -0
- package/dist/esm/core/correctionList.js +56 -0
- package/dist/esm/core/fetchLatest.d.ts +61 -0
- package/dist/esm/core/fetchLatest.js +184 -0
- package/dist/esm/core/index.d.ts +14 -0
- package/dist/esm/core/index.js +14 -0
- package/dist/esm/core/registry.d.ts +28 -0
- package/dist/esm/core/registry.js +28 -0
- package/dist/esm/core/types.d.ts +193 -0
- package/dist/esm/core/types.js +18 -0
- package/dist/esm/core.d.ts +2 -0
- package/dist/esm/core.js +2 -0
- package/dist/esm/data/generated/algeriaCertification.d.ts +3 -0
- package/dist/esm/data/generated/algeriaCertification.js +2 -0
- package/dist/esm/data/generated/algeriaCities.d.ts +8 -0
- package/dist/esm/data/generated/algeriaCities.js +3 -0
- package/dist/esm/data/generated/algeriaCorrections.d.ts +3 -0
- package/dist/esm/data/generated/algeriaCorrections.js +2 -0
- package/dist/esm/data/generated/algeriaMethodPoints.d.ts +2 -0
- package/dist/esm/data/generated/algeriaMethodPoints.js +3 -0
- package/dist/esm/data/generated/countryOutlines.d.ts +5 -0
- package/dist/esm/data/generated/countryOutlines.js +3 -0
- package/dist/esm/data/generated/diyanetCertification.d.ts +3 -0
- package/dist/esm/data/generated/diyanetCertification.js +2 -0
- package/dist/esm/data/generated/diyanetCorrections.d.ts +3 -0
- package/dist/esm/data/generated/diyanetCorrections.js +2 -0
- package/dist/esm/data/generated/diyanetCountries.d.ts +5 -0
- package/dist/esm/data/generated/diyanetCountries.js +3 -0
- package/dist/esm/data/generated/diyanetTurkeyPoints.d.ts +7 -0
- package/dist/esm/data/generated/diyanetTurkeyPoints.js +3 -0
- package/dist/esm/data/generated/diyanetWorldPoints.d.ts +10 -0
- package/dist/esm/data/generated/diyanetWorldPoints.js +3 -0
- package/dist/esm/data/generated/indonesiaCertification.d.ts +3 -0
- package/dist/esm/data/generated/indonesiaCertification.js +2 -0
- package/dist/esm/data/generated/indonesiaCorrections.d.ts +3 -0
- package/dist/esm/data/generated/indonesiaCorrections.js +2 -0
- package/dist/esm/data/generated/kemenagMethodPoints.d.ts +2 -0
- package/dist/esm/data/generated/kemenagMethodPoints.js +3 -0
- package/dist/esm/data/generated/kemenagPlaces.d.ts +8 -0
- package/dist/esm/data/generated/kemenagPlaces.js +3 -0
- package/dist/esm/data/generated/moroccoCertification.d.ts +3 -0
- package/dist/esm/data/generated/moroccoCertification.js +2 -0
- package/dist/esm/data/generated/moroccoCities.d.ts +8 -0
- package/dist/esm/data/generated/moroccoCities.js +3 -0
- package/dist/esm/data/generated/moroccoCorrections.d.ts +3 -0
- package/dist/esm/data/generated/moroccoCorrections.js +2 -0
- package/dist/esm/data/generated/moroccoMethodPoints.d.ts +2 -0
- package/dist/esm/data/generated/moroccoMethodPoints.js +3 -0
- package/dist/esm/data/generated/swedenCertification.d.ts +3 -0
- package/dist/esm/data/generated/swedenCertification.js +2 -0
- package/dist/esm/data/generated/swedenCorrections.d.ts +3 -0
- package/dist/esm/data/generated/swedenCorrections.js +2 -0
- package/dist/esm/data/generated/swedenPlaces.d.ts +5 -0
- package/dist/esm/data/generated/swedenPlaces.js +3 -0
- package/dist/esm/diyanet.d.ts +4 -0
- package/dist/esm/diyanet.js +4 -0
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.js +19 -0
- package/dist/esm/indonesia.d.ts +4 -0
- package/dist/esm/indonesia.js +4 -0
- package/dist/esm/locate.d.ts +21 -0
- package/dist/esm/locate.js +196 -0
- package/dist/esm/methods/algeria.d.ts +56 -0
- package/dist/esm/methods/algeria.js +114 -0
- package/dist/esm/methods/indonesia.d.ts +31 -0
- package/dist/esm/methods/indonesia.js +90 -0
- package/dist/esm/methods/morocco.d.ts +61 -0
- package/dist/esm/methods/morocco.js +107 -0
- package/dist/esm/methods/revisions.d.ts +15 -0
- package/dist/esm/methods/revisions.js +15 -0
- package/dist/esm/methods/scratch-congress.d.ts +87 -0
- package/dist/esm/methods/scratch-congress.js +534 -0
- package/dist/esm/methods/sweden.d.ts +88 -0
- package/dist/esm/methods/sweden.js +563 -0
- package/dist/esm/morocco.d.ts +4 -0
- package/dist/esm/morocco.js +4 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/sun/preciseSun.d.ts +19 -0
- package/dist/esm/sun/preciseSun.js +181 -0
- package/dist/esm/sweden.d.ts +4 -0
- package/dist/esm/sweden.js +4 -0
- package/docs/methods.md +190 -0
- package/package.json +178 -4
- package/schemas/certification.schema.json +59 -0
- package/schemas/corrections.schema.json +29 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.places = exports.indonesia = void 0;
|
|
7
|
+
exports.indonesiaZone = indonesiaZone;
|
|
8
|
+
/**
|
|
9
|
+
* Indonesia — Kemenag: every regency and city, at the point its table is
|
|
10
|
+
* computed at, in the zone it is printed in.
|
|
11
|
+
*/
|
|
12
|
+
const kemenagPlaces_js_1 = __importDefault(require("../data/generated/kemenagPlaces.js"));
|
|
13
|
+
const kemenagMethodPoints_js_1 = __importDefault(require("../data/generated/kemenagMethodPoints.js"));
|
|
14
|
+
const indonesiaCorrections_js_1 = __importDefault(require("../data/generated/indonesiaCorrections.js"));
|
|
15
|
+
const indonesiaCertification_js_1 = __importDefault(require("../data/generated/indonesiaCertification.js"));
|
|
16
|
+
const revisions_js_1 = require("../methods/revisions.js");
|
|
17
|
+
const registry_js_1 = require("../core/registry.js");
|
|
18
|
+
const AUTHORITY = 'indonesia';
|
|
19
|
+
/** Indonesia prints each regency in its own zone; the province says which. */
|
|
20
|
+
const WITA = new Set([
|
|
21
|
+
'Kalimantan Selatan', 'Kalimantan Timur', 'Kalimantan Utara', 'Bali',
|
|
22
|
+
'Nusa Tenggara Barat', 'Nusa Tenggara Timur', 'Sulawesi Barat', 'Sulawesi Selatan',
|
|
23
|
+
'Sulawesi Tengah', 'Sulawesi Tenggara', 'Sulawesi Utara', 'Gorontalo',
|
|
24
|
+
]);
|
|
25
|
+
const WIT = new Set(['Maluku', 'Maluku Utara', 'Papua', 'Papua Barat']);
|
|
26
|
+
function indonesiaZone(province) {
|
|
27
|
+
if (WIT.has(province))
|
|
28
|
+
return 'Asia/Jayapura';
|
|
29
|
+
if (WITA.has(province))
|
|
30
|
+
return 'Asia/Makassar';
|
|
31
|
+
return 'Asia/Jakarta';
|
|
32
|
+
}
|
|
33
|
+
function buildPlaces() {
|
|
34
|
+
return kemenagPlaces_js_1.default.map(c => {
|
|
35
|
+
const key = String(c.id);
|
|
36
|
+
const province = /\(([^)]*)\)\s*$/.exec(c.nameEn ?? '')?.[1] ?? '';
|
|
37
|
+
const p = kemenagMethodPoints_js_1.default[key];
|
|
38
|
+
return {
|
|
39
|
+
authority: AUTHORITY,
|
|
40
|
+
id: `${AUTHORITY}:${key}`,
|
|
41
|
+
key,
|
|
42
|
+
name: c.nameEn ?? c.name,
|
|
43
|
+
country: 'ID',
|
|
44
|
+
lat: p?.[0] ?? c.lat,
|
|
45
|
+
lon: p?.[1] ?? c.lon,
|
|
46
|
+
tz: indonesiaZone(province),
|
|
47
|
+
highland: p ? (p[2] & 1) !== 0 : false,
|
|
48
|
+
twilightDip: p ? (p[2] & 2) !== 0 : false,
|
|
49
|
+
...(p && p.length >= 5 ? { afternoon: [p[3], p[4]] } : {}),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
exports.indonesia = (0, registry_js_1.registerAuthority)({
|
|
54
|
+
authority: AUTHORITY,
|
|
55
|
+
revision: revisions_js_1.METHOD_REVISION.indonesia,
|
|
56
|
+
places: buildPlaces(),
|
|
57
|
+
corrections: indonesiaCorrections_js_1.default,
|
|
58
|
+
certification: indonesiaCertification_js_1.default,
|
|
59
|
+
description: {
|
|
60
|
+
name: 'Indonesia — Kemenag (Ministry of Religious Affairs)',
|
|
61
|
+
method: 'The precise apparent sun (VSOP87, as JPL’s to a tenth of a second) taken at each prayer’s own moment; Fajr 20°, Isha 18°; sunrise and Maghrib at −1° (−2° in the highland class); Asr when the shadow is its length plus the noon shadow, the noon shadow from the declination at 00:00 UT; every time rounded up after a margin of two minutes (three for Dhuhr, minus three for sunrise).',
|
|
62
|
+
countries: ['ID'],
|
|
63
|
+
parameters: {
|
|
64
|
+
fajrAngle: 20,
|
|
65
|
+
ishaAngle: 18,
|
|
66
|
+
sunriseAltitude: '−1°, or −2° in the highland class (Place.highland); the highland degree on Fajr and Isha too where Place.twilightDip',
|
|
67
|
+
asrShadow: 1,
|
|
68
|
+
asrNoonShadow: 'from the declination at 00:00 UT of the day',
|
|
69
|
+
'margin.fajr': 2,
|
|
70
|
+
'margin.sunrise': -3,
|
|
71
|
+
'margin.dhuhr': 3,
|
|
72
|
+
'margin.asr': 2,
|
|
73
|
+
'margin.maghrib': 2,
|
|
74
|
+
'margin.isha': 2,
|
|
75
|
+
rounding: 'up to the minute, after the margin',
|
|
76
|
+
sun: 'VSOP87 apparent sun (src/sun/preciseSun.ts), iterated at each prayer',
|
|
77
|
+
},
|
|
78
|
+
sources: [{ title: 'Kemenag — Jadwal Shalat', url: 'https://bimasislam.kemenag.go.id/jadwalshalat' }],
|
|
79
|
+
spec: 'docs/methods.md#indonesia',
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
exports.places = exports.indonesia.places;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Place } from '../core/types.js';
|
|
2
|
+
/** The ministry's clock: GMT+1, then GMT — its tables print GMT from 17 September 2026, three days before the clocks moved. */
|
|
3
|
+
export declare const MOROCCO_CLOCK: [string, number][];
|
|
4
|
+
export declare const morocco: import("../core/registry.js").AuthorityData;
|
|
5
|
+
export declare const places: readonly Place[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.places = exports.morocco = exports.MOROCCO_CLOCK = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Morocco — the Ministry of Habous and Islamic Affairs: its cities at the
|
|
9
|
+
* points and horizons its tables are computed with.
|
|
10
|
+
*/
|
|
11
|
+
const moroccoCities_js_1 = __importDefault(require("../data/generated/moroccoCities.js"));
|
|
12
|
+
const moroccoMethodPoints_js_1 = __importDefault(require("../data/generated/moroccoMethodPoints.js"));
|
|
13
|
+
const moroccoCorrections_js_1 = __importDefault(require("../data/generated/moroccoCorrections.js"));
|
|
14
|
+
const moroccoCertification_js_1 = __importDefault(require("../data/generated/moroccoCertification.js"));
|
|
15
|
+
const revisions_js_1 = require("../methods/revisions.js");
|
|
16
|
+
const registry_js_1 = require("../core/registry.js");
|
|
17
|
+
const AUTHORITY = 'morocco';
|
|
18
|
+
/**
|
|
19
|
+
* Morocco left GMT+1 for GMT on 2026-09-20 for good; the tz databases still
|
|
20
|
+
* move its clock, so the zone is pinned rather than named.
|
|
21
|
+
*/
|
|
22
|
+
const MOROCCO_TZ = 'Etc/GMT';
|
|
23
|
+
/** The ministry's clock: GMT+1, then GMT — its tables print GMT from 17 September 2026, three days before the clocks moved. */
|
|
24
|
+
exports.MOROCCO_CLOCK = [['2020-01-01', 60], ['2026-09-17', 0]];
|
|
25
|
+
const MOROCCO_DIP_PER_SQRT_M = 0.02;
|
|
26
|
+
function buildPlaces() {
|
|
27
|
+
return moroccoCities_js_1.default
|
|
28
|
+
.filter(c => typeof c.lat === 'number' && typeof c.lng === 'number')
|
|
29
|
+
.map(c => {
|
|
30
|
+
const key = String(c.id);
|
|
31
|
+
const base = { authority: AUTHORITY, id: `${AUTHORITY}:${key}`, key, name: c.name, country: 'MA', tz: MOROCCO_TZ, clock: exports.MOROCCO_CLOCK };
|
|
32
|
+
const p = moroccoMethodPoints_js_1.default[key];
|
|
33
|
+
return p
|
|
34
|
+
? { ...base, lat: p[0], lon: p[1], dipDeg: p[2], ...(p.length > 3 ? { dipMaghribDeg: p[3] } : {}) }
|
|
35
|
+
: { ...base, lat: c.lat, lon: c.lng, dipDeg: MOROCCO_DIP_PER_SQRT_M * Math.sqrt(c.heightM ?? 0) };
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
exports.morocco = (0, registry_js_1.registerAuthority)({
|
|
39
|
+
authority: AUTHORITY,
|
|
40
|
+
revision: revisions_js_1.METHOD_REVISION.morocco,
|
|
41
|
+
places: buildPlaces(),
|
|
42
|
+
corrections: moroccoCorrections_js_1.default,
|
|
43
|
+
certification: moroccoCertification_js_1.default,
|
|
44
|
+
description: {
|
|
45
|
+
name: 'Morocco — Ministry of Habous and Islamic Affairs',
|
|
46
|
+
method: 'Meeus’s apparent sun taken at each prayer’s own moment; Fajr 19°, Isha 17°, Asr one shadow; Dhuhr five minutes after transit; sunrise 2.85 minutes before and Maghrib 3.7 minutes after the upper limb on the horizon, less each city’s own horizon dip east and west; Fajr rounded down, the rest to the nearest minute; printed on the ministry’s clock (GMT+1 until 17 September 2026, then GMT).',
|
|
47
|
+
countries: ['MA'],
|
|
48
|
+
parameters: {
|
|
49
|
+
fajrAngle: 19,
|
|
50
|
+
ishaAngle: 17,
|
|
51
|
+
asrShadow: 1,
|
|
52
|
+
dhuhrAfterTransitMinutes: 5,
|
|
53
|
+
sunriseMarginMinutes: -2.85,
|
|
54
|
+
maghribMarginMinutes: 3.7,
|
|
55
|
+
refractionArcmin: 34,
|
|
56
|
+
sunSemidiameter: 'by the sun’s distance that day',
|
|
57
|
+
horizonDip: 'per city and per side, fitted (Place.dipDeg, Place.dipMaghribDeg); else 0.02°·√height, 0.86 of it at Maghrib',
|
|
58
|
+
rounding: 'Fajr down to the minute, the rest to the nearest',
|
|
59
|
+
sun: 'Meeus, apparent, with the full nutation; chapter-28 equation of time; iterated at each prayer',
|
|
60
|
+
},
|
|
61
|
+
sources: [{ title: 'Ministry of Habous and Islamic Affairs — horaires des prières', url: 'https://www.habous.gov.ma/' }],
|
|
62
|
+
spec: 'docs/methods.md#morocco',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
exports.places = exports.morocco.places;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.citySlug = citySlug;
|
|
4
|
+
/** Islamiska Förbundet's file names for its cities (the Swedish places' keys). */
|
|
5
|
+
function citySlug(name) {
|
|
6
|
+
return name
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[àáâã]/g, 'a')
|
|
10
|
+
.replace(/[å]/g, 'a')
|
|
11
|
+
.replace(/[ä]/g, 'a')
|
|
12
|
+
.replace(/[ö]/g, 'o')
|
|
13
|
+
.replace(/[ø]/g, 'o')
|
|
14
|
+
.replace(/[é]/g, 'e')
|
|
15
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
16
|
+
.replace(/^-+|-+$/g, '');
|
|
17
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.places = exports.sweden = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Sweden — Diyanet's Congress method, as Islamiska Förbundet's timetables
|
|
9
|
+
* print it: the towns at the points Diyanet computes them.
|
|
10
|
+
*/
|
|
11
|
+
const swedenPlaces_js_1 = __importDefault(require("../data/generated/swedenPlaces.js"));
|
|
12
|
+
const swedenCorrections_js_1 = __importDefault(require("../data/generated/swedenCorrections.js"));
|
|
13
|
+
const swedenCertification_js_1 = __importDefault(require("../data/generated/swedenCertification.js"));
|
|
14
|
+
const revisions_js_1 = require("../methods/revisions.js");
|
|
15
|
+
const registry_js_1 = require("../core/registry.js");
|
|
16
|
+
const slug_js_1 = require("./slug.js");
|
|
17
|
+
const AUTHORITY = 'sweden';
|
|
18
|
+
function buildPlaces() {
|
|
19
|
+
return Object.entries(swedenPlaces_js_1.default).map(([name, c]) => {
|
|
20
|
+
const key = (0, slug_js_1.citySlug)(name);
|
|
21
|
+
return { authority: AUTHORITY, id: `${AUTHORITY}:${key}`, key, name, country: 'SE', lat: c.lat, lon: c.lng, tz: 'Europe/Stockholm' };
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
exports.sweden = (0, registry_js_1.registerAuthority)({
|
|
25
|
+
authority: AUTHORITY,
|
|
26
|
+
revision: revisions_js_1.METHOD_REVISION.sweden,
|
|
27
|
+
places: buildPlaces(),
|
|
28
|
+
corrections: swedenCorrections_js_1.default,
|
|
29
|
+
certification: swedenCertification_js_1.default,
|
|
30
|
+
description: {
|
|
31
|
+
name: 'Sweden — Diyanet’s method, as used by Islamiska Förbundet',
|
|
32
|
+
method: 'The International Prayer Times Congress method (Istanbul, 2021) as Diyanet computes it: true Fajr at 18° and Isha at 16°, with the night’s share carried through the summer gap, five-hour floors on the day and the night, and gradual transitions; the sun taken once a day from Meeus’s mean elements.',
|
|
33
|
+
countries: ['SE'],
|
|
34
|
+
parameters: {
|
|
35
|
+
fajrAngle: 18,
|
|
36
|
+
ishaAngle: 16,
|
|
37
|
+
sunriseAltitude: -0.833,
|
|
38
|
+
asrShadow: 1,
|
|
39
|
+
'temkin.sunrise': -7,
|
|
40
|
+
'temkin.dhuhr': 5,
|
|
41
|
+
'temkin.asr': 4,
|
|
42
|
+
'temkin.maghrib': 7,
|
|
43
|
+
minimumDayMinutes: 300,
|
|
44
|
+
minimumNightMinutes: 300,
|
|
45
|
+
transitionBandMinutes: 20,
|
|
46
|
+
highLatitudeRule: 'the share of the night carried from the last day the 18° dawn formed; Fajr’s share scaled by 18/16',
|
|
47
|
+
rounding: 'to the nearest minute, after the method’s own temkin',
|
|
48
|
+
coordinatePrecision: '0.001°',
|
|
49
|
+
},
|
|
50
|
+
sources: [
|
|
51
|
+
{ title: 'Islamiska Förbundet i Sverige — bönetider', url: 'https://www.islamiskaforbundet.se/' },
|
|
52
|
+
{ title: 'Diyanet — Namaz Vakitleri', url: 'https://namazvakti.diyanet.gov.tr/' },
|
|
53
|
+
{ title: 'Diyanet — the Congress’s criteria (Tespit Kriterleri)', url: 'https://awqatsalah.com/' },
|
|
54
|
+
],
|
|
55
|
+
spec: 'docs/methods.md#sweden-and-diyanet',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
exports.places = exports.sweden.places;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The library's calls. Each works for whichever authorities are loaded —
|
|
3
|
+
* `mihrab-methods` loads all five, `mihrab-methods/<authority>` one.
|
|
4
|
+
*/
|
|
5
|
+
import { type ComputeOptions } from './compute.js';
|
|
6
|
+
import { type Authority, type Certification, type CivilDate, type DayTimes, type MethodInfo, type Place, type TimesOptions } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Certified: every check of the authority's tables has every time on the
|
|
9
|
+
* printed minute (and checked something). The flag alone is not trusted.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isCertified(authority: Authority): boolean;
|
|
12
|
+
/** The authority's record from certification.json, or null. */
|
|
13
|
+
export declare function certificationOf(authority: Authority): Certification | null;
|
|
14
|
+
/** The span of dates the method was checked over (and so is certified for), or null. */
|
|
15
|
+
export declare function certifiedSpan(authority: Authority): {
|
|
16
|
+
from: string;
|
|
17
|
+
to: string;
|
|
18
|
+
} | null;
|
|
19
|
+
/** The authority's places, each at the point its method is computed at. */
|
|
20
|
+
export declare function placesOf(authority: Authority): readonly Place[];
|
|
21
|
+
/** The authorities this build has loaded. */
|
|
22
|
+
export declare function loadedAuthorityIds(): Authority[];
|
|
23
|
+
/** A place by its stable id (`<authority>:<key>`), among the loaded authorities. */
|
|
24
|
+
export declare function placeById(id: string): Place | null;
|
|
25
|
+
/** Great-circle distance, km. */
|
|
26
|
+
export declare function distanceKm(lat1: number, lon1: number, lat2: number, lon2: number): number;
|
|
27
|
+
export type NearestPlace = {
|
|
28
|
+
place: Place;
|
|
29
|
+
distanceKm: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The authority's place nearest a point — how an app finds the table for a
|
|
33
|
+
* location. `maxKm` (default: no limit) returns null beyond that distance.
|
|
34
|
+
* Türkiye's districts and Diyanet's worldwide places are both 'diyanet'.
|
|
35
|
+
*/
|
|
36
|
+
export declare function nearestPlace(authority: Authority, lat: number, lon: number, opts?: {
|
|
37
|
+
maxKm?: number;
|
|
38
|
+
}): NearestPlace | null;
|
|
39
|
+
/**
|
|
40
|
+
* The place's prayer times on a civil date, as the authority prints them:
|
|
41
|
+
* each prayer's exact moment and its wall-clock minute on the table's clock.
|
|
42
|
+
*
|
|
43
|
+
* Inside the dates the method was certified over, `status` is 'certified':
|
|
44
|
+
* the authority's own printed minute. Outside them it is 'method-only': the
|
|
45
|
+
* method alone, which is on the printed minute all but rarely and never a
|
|
46
|
+
* minute further — the honest answer for a date the authority has not
|
|
47
|
+
* published yet. `{ strict: true }` throws `UncertifiedMethodError` there
|
|
48
|
+
* instead. A method not certified at all throws either way: it is not for
|
|
49
|
+
* showing anyone their prayer times (`candidateTimes` runs it regardless).
|
|
50
|
+
*/
|
|
51
|
+
export declare function timesFor(place: Place, date: CivilDate, opts?: TimesOptions): DayTimes;
|
|
52
|
+
/** `timesFor` for every date from `from` to `to`, both inclusive — a month's timetable, a year's. */
|
|
53
|
+
export declare function timesForRange(place: Place, from: CivilDate, to: CivilDate, opts?: TimesOptions): DayTimes[];
|
|
54
|
+
/**
|
|
55
|
+
* The method as it stands, certified or not, with or without its correction
|
|
56
|
+
* list (`corrections: false`): for research and for checking the method
|
|
57
|
+
* against the tables. `status` says what it would be served as.
|
|
58
|
+
*/
|
|
59
|
+
export declare function candidateTimes(place: Place, date: CivilDate, opts?: TimesOptions & ComputeOptions): DayTimes;
|
|
60
|
+
/** What the method is: its parameters, revision, certification, correction list and sources. */
|
|
61
|
+
export declare function methodInfo(authority: Authority): MethodInfo;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isCertified = isCertified;
|
|
4
|
+
exports.certificationOf = certificationOf;
|
|
5
|
+
exports.certifiedSpan = certifiedSpan;
|
|
6
|
+
exports.placesOf = placesOf;
|
|
7
|
+
exports.loadedAuthorityIds = loadedAuthorityIds;
|
|
8
|
+
exports.placeById = placeById;
|
|
9
|
+
exports.distanceKm = distanceKm;
|
|
10
|
+
exports.nearestPlace = nearestPlace;
|
|
11
|
+
exports.timesFor = timesFor;
|
|
12
|
+
exports.timesForRange = timesForRange;
|
|
13
|
+
exports.candidateTimes = candidateTimes;
|
|
14
|
+
exports.methodInfo = methodInfo;
|
|
15
|
+
/**
|
|
16
|
+
* The library's calls. Each works for whichever authorities are loaded —
|
|
17
|
+
* `mihrab-methods` loads all five, `mihrab-methods/<authority>` one.
|
|
18
|
+
*/
|
|
19
|
+
const compute_js_1 = require("./compute.js");
|
|
20
|
+
const civilDate_js_1 = require("./civilDate.js");
|
|
21
|
+
const clock_js_1 = require("./clock.js");
|
|
22
|
+
const registry_js_1 = require("./registry.js");
|
|
23
|
+
const types_js_1 = require("./types.js");
|
|
24
|
+
/**
|
|
25
|
+
* Certified: every check of the authority's tables has every time on the
|
|
26
|
+
* printed minute (and checked something). The flag alone is not trusted.
|
|
27
|
+
*/
|
|
28
|
+
function isCertified(authority) {
|
|
29
|
+
const c = certificationOf(authority);
|
|
30
|
+
return !!c && c.certified && c.checks.length > 0 && c.checks.every(k => k.times > 0 && k.off === 0);
|
|
31
|
+
}
|
|
32
|
+
/** The authority's record from certification.json, or null. */
|
|
33
|
+
function certificationOf(authority) {
|
|
34
|
+
return (0, registry_js_1.authorityData)(authority).certification;
|
|
35
|
+
}
|
|
36
|
+
/** The span of dates the method was checked over (and so is certified for), or null. */
|
|
37
|
+
function certifiedSpan(authority) {
|
|
38
|
+
if (!isCertified(authority))
|
|
39
|
+
return null;
|
|
40
|
+
const checks = certificationOf(authority).checks;
|
|
41
|
+
const tos = checks.map(c => c.to).sort();
|
|
42
|
+
return { from: checks.map(c => c.from).sort()[0], to: tos[tos.length - 1] };
|
|
43
|
+
}
|
|
44
|
+
/** The authority's places, each at the point its method is computed at. */
|
|
45
|
+
function placesOf(authority) {
|
|
46
|
+
return (0, registry_js_1.authorityData)(authority).places;
|
|
47
|
+
}
|
|
48
|
+
/** The authorities this build has loaded. */
|
|
49
|
+
function loadedAuthorityIds() {
|
|
50
|
+
return (0, registry_js_1.loadedAuthorities)();
|
|
51
|
+
}
|
|
52
|
+
/** A place by its stable id (`<authority>:<key>`), among the loaded authorities. */
|
|
53
|
+
function placeById(id) {
|
|
54
|
+
const i = id.indexOf(':');
|
|
55
|
+
if (i < 0)
|
|
56
|
+
return null;
|
|
57
|
+
const authority = id.slice(0, i);
|
|
58
|
+
if (!(0, registry_js_1.isLoaded)(authority))
|
|
59
|
+
return null;
|
|
60
|
+
return byId(authority).get(id) ?? null;
|
|
61
|
+
}
|
|
62
|
+
const idIndexes = new Map();
|
|
63
|
+
function byId(authority) {
|
|
64
|
+
let m = idIndexes.get(authority);
|
|
65
|
+
if (!m)
|
|
66
|
+
idIndexes.set(authority, (m = new Map(placesOf(authority).map(p => [p.id, p]))));
|
|
67
|
+
return m;
|
|
68
|
+
}
|
|
69
|
+
const RAD = Math.PI / 180;
|
|
70
|
+
/** Great-circle distance, km. */
|
|
71
|
+
function distanceKm(lat1, lon1, lat2, lon2) {
|
|
72
|
+
const dLat = (lat2 - lat1) * RAD;
|
|
73
|
+
const dLon = (lon2 - lon1) * RAD;
|
|
74
|
+
const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * RAD) * Math.cos(lat2 * RAD) * Math.sin(dLon / 2) ** 2;
|
|
75
|
+
return 2 * 6371.0088 * Math.asin(Math.min(1, Math.sqrt(a)));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The authority's place nearest a point — how an app finds the table for a
|
|
79
|
+
* location. `maxKm` (default: no limit) returns null beyond that distance.
|
|
80
|
+
* Türkiye's districts and Diyanet's worldwide places are both 'diyanet'.
|
|
81
|
+
*/
|
|
82
|
+
function nearestPlace(authority, lat, lon, opts = {}) {
|
|
83
|
+
if (!Number.isFinite(lat) || !Number.isFinite(lon))
|
|
84
|
+
return null;
|
|
85
|
+
let best = null;
|
|
86
|
+
for (const place of placesOf(authority)) {
|
|
87
|
+
const d = distanceKm(lat, lon, place.lat, place.lon);
|
|
88
|
+
if (!best || d < best.distanceKm)
|
|
89
|
+
best = { place, distanceKm: d };
|
|
90
|
+
}
|
|
91
|
+
if (best && opts.maxKm !== undefined && best.distanceKm > opts.maxKm)
|
|
92
|
+
return null;
|
|
93
|
+
return best;
|
|
94
|
+
}
|
|
95
|
+
function dayTimes(place, d, instants, status) {
|
|
96
|
+
const data = (0, registry_js_1.authorityData)(place.authority);
|
|
97
|
+
const span = certifiedSpan(place.authority);
|
|
98
|
+
const clock = (0, clock_js_1.tableClockAt)(place, d.year, d.month0, d.day);
|
|
99
|
+
let times = null;
|
|
100
|
+
if (instants) {
|
|
101
|
+
times = {};
|
|
102
|
+
for (const k of types_js_1.PRAYER_KEYS)
|
|
103
|
+
times[k] = { instant: instants[k], printed: (0, clock_js_1.printedAt)(instants[k], clock.offsetMinutes) };
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
authority: place.authority,
|
|
107
|
+
placeId: place.id,
|
|
108
|
+
date: d.iso,
|
|
109
|
+
status,
|
|
110
|
+
certifiedFrom: span?.from ?? null,
|
|
111
|
+
certifiedUntil: span?.to ?? null,
|
|
112
|
+
revision: data.revision,
|
|
113
|
+
clock,
|
|
114
|
+
times,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function statusFor(authority, iso) {
|
|
118
|
+
const span = certifiedSpan(authority);
|
|
119
|
+
if (!span)
|
|
120
|
+
return 'uncertified';
|
|
121
|
+
return iso < span.from || iso > span.to ? 'method-only' : 'certified';
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The place's prayer times on a civil date, as the authority prints them:
|
|
125
|
+
* each prayer's exact moment and its wall-clock minute on the table's clock.
|
|
126
|
+
*
|
|
127
|
+
* Inside the dates the method was certified over, `status` is 'certified':
|
|
128
|
+
* the authority's own printed minute. Outside them it is 'method-only': the
|
|
129
|
+
* method alone, which is on the printed minute all but rarely and never a
|
|
130
|
+
* minute further — the honest answer for a date the authority has not
|
|
131
|
+
* published yet. `{ strict: true }` throws `UncertifiedMethodError` there
|
|
132
|
+
* instead. A method not certified at all throws either way: it is not for
|
|
133
|
+
* showing anyone their prayer times (`candidateTimes` runs it regardless).
|
|
134
|
+
*/
|
|
135
|
+
function timesFor(place, date, opts = {}) {
|
|
136
|
+
if (!isCertified(place.authority))
|
|
137
|
+
throw new types_js_1.UncertifiedMethodError(place.authority);
|
|
138
|
+
const d = (0, civilDate_js_1.parseCivilDate)(date);
|
|
139
|
+
const status = statusFor(place.authority, d.iso);
|
|
140
|
+
if (status === 'method-only' && opts.strict)
|
|
141
|
+
throw new types_js_1.UncertifiedMethodError(place.authority, d.iso);
|
|
142
|
+
return dayTimes(place, d, (0, compute_js_1.candidateInstants)(place, d.year, d.month0, d.day, opts.asrShadow ?? 1), status);
|
|
143
|
+
}
|
|
144
|
+
/** `timesFor` for every date from `from` to `to`, both inclusive — a month's timetable, a year's. */
|
|
145
|
+
function timesForRange(place, from, to, opts = {}) {
|
|
146
|
+
const a = (0, civilDate_js_1.parseCivilDate)(from);
|
|
147
|
+
const b = (0, civilDate_js_1.parseCivilDate)(to);
|
|
148
|
+
if (a.iso > b.iso)
|
|
149
|
+
throw new RangeError(`The range runs backwards: ${a.iso} to ${b.iso}.`);
|
|
150
|
+
const out = [];
|
|
151
|
+
for (let d = a; d.iso <= b.iso; d = (0, civilDate_js_1.nextDay)(d))
|
|
152
|
+
out.push(timesFor(place, d.iso, opts));
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The method as it stands, certified or not, with or without its correction
|
|
157
|
+
* list (`corrections: false`): for research and for checking the method
|
|
158
|
+
* against the tables. `status` says what it would be served as.
|
|
159
|
+
*/
|
|
160
|
+
function candidateTimes(place, date, opts = {}) {
|
|
161
|
+
const d = (0, civilDate_js_1.parseCivilDate)(date);
|
|
162
|
+
return dayTimes(place, d, (0, compute_js_1.candidateInstants)(place, d.year, d.month0, d.day, opts.asrShadow ?? 1, { corrections: opts.corrections }), statusFor(place.authority, d.iso));
|
|
163
|
+
}
|
|
164
|
+
/** What the method is: its parameters, revision, certification, correction list and sources. */
|
|
165
|
+
function methodInfo(authority) {
|
|
166
|
+
const data = (0, registry_js_1.authorityData)(authority);
|
|
167
|
+
const cov = data.corrections.coverage();
|
|
168
|
+
return {
|
|
169
|
+
authority,
|
|
170
|
+
...data.description,
|
|
171
|
+
revision: data.revision,
|
|
172
|
+
certified: isCertified(authority),
|
|
173
|
+
certification: data.certification,
|
|
174
|
+
corrections: { from: cov.from, to: cov.to, entries: data.corrections.entries() },
|
|
175
|
+
places: data.places.length,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Civil dates as the library takes them: 'YYYY-MM-DD', or { year, month,
|
|
3
|
+
* day } with January = 1. The methods themselves count months from 0, as
|
|
4
|
+
* JavaScript's Date does; that habit stops at this file.
|
|
5
|
+
*/
|
|
6
|
+
import type { CivilDate } from './types.js';
|
|
7
|
+
export type Ymd = {
|
|
8
|
+
year: number;
|
|
9
|
+
month0: number;
|
|
10
|
+
day: number;
|
|
11
|
+
iso: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function parseCivilDate(date: CivilDate): Ymd;
|
|
14
|
+
/** 'YYYY-MM-DD' from a year, a 0-based month and a day. */
|
|
15
|
+
export declare function isoOf(year: number, month0: number, day: number): string;
|
|
16
|
+
/** The day after, as a civil date (UTC arithmetic: no zone, no summer time). */
|
|
17
|
+
export declare function nextDay(d: Ymd): Ymd;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseCivilDate = parseCivilDate;
|
|
4
|
+
exports.isoOf = isoOf;
|
|
5
|
+
exports.nextDay = nextDay;
|
|
6
|
+
const ISO = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
7
|
+
function parseCivilDate(date) {
|
|
8
|
+
let year, month, day;
|
|
9
|
+
if (typeof date === 'string') {
|
|
10
|
+
const m = ISO.exec(date);
|
|
11
|
+
if (!m)
|
|
12
|
+
throw new TypeError(`Not a date: ${JSON.stringify(date)} — expected 'YYYY-MM-DD' or { year, month, day }.`);
|
|
13
|
+
[year, month, day] = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
14
|
+
}
|
|
15
|
+
else if (date && typeof date === 'object' && typeof date.year === 'number' && typeof date.month === 'number' && typeof date.day === 'number') {
|
|
16
|
+
({ year, month, day } = date);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
throw new TypeError(typeof date === 'number'
|
|
20
|
+
? 'timesFor(place, year, month, day) is no longer the call: pass one date, as \'YYYY-MM-DD\' or { year, month, day } with January = 1.'
|
|
21
|
+
: `Not a date: ${JSON.stringify(date)} — expected 'YYYY-MM-DD' or { year, month, day }.`);
|
|
22
|
+
}
|
|
23
|
+
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day) || month < 1 || month > 12 || day < 1 || day > 31) {
|
|
24
|
+
throw new RangeError(`Not a calendar date: ${isoOf(year, month - 1, day)} (months run 1–12).`);
|
|
25
|
+
}
|
|
26
|
+
// 31 April, 30 February: a Date would roll these over without a word.
|
|
27
|
+
const probe = new Date(Date.UTC(year, month - 1, day));
|
|
28
|
+
if (probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) {
|
|
29
|
+
throw new RangeError(`Not a calendar date: ${isoOf(year, month - 1, day)}.`);
|
|
30
|
+
}
|
|
31
|
+
return { year, month0: month - 1, day, iso: isoOf(year, month - 1, day) };
|
|
32
|
+
}
|
|
33
|
+
/** 'YYYY-MM-DD' from a year, a 0-based month and a day. */
|
|
34
|
+
function isoOf(year, month0, day) {
|
|
35
|
+
return `${String(year).padStart(4, '0')}-${String(month0 + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
36
|
+
}
|
|
37
|
+
/** The day after, as a civil date (UTC arithmetic: no zone, no summer time). */
|
|
38
|
+
function nextDay(d) {
|
|
39
|
+
const n = new Date(Date.UTC(d.year, d.month0, d.day + 1));
|
|
40
|
+
return { year: n.getUTCFullYear(), month0: n.getUTCMonth(), day: n.getUTCDate(), iso: isoOf(n.getUTCFullYear(), n.getUTCMonth(), n.getUTCDate()) };
|
|
41
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clock a table prints a day in: the place's zone as kept at noon, or
|
|
3
|
+
* the table's own clock where it keeps one no zone does (`Place.clock`). A
|
|
4
|
+
* table prints the whole day in the clock kept at noon, so on the morning
|
|
5
|
+
* the clocks go forward a Fajr before the change is printed in the new time.
|
|
6
|
+
*/
|
|
7
|
+
import type { Place, TableClock } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Minutes east of UTC that a zone keeps at noon UTC of a date (month 0-based).
|
|
10
|
+
*
|
|
11
|
+
* Read from the zone's wall clock (year … minute, as every Intl has them),
|
|
12
|
+
* not from `timeZoneName: 'longOffset'`, which not every runtime supports:
|
|
13
|
+
* where it is missing the name comes back as 'GMT+1' or a zone's own
|
|
14
|
+
* abbreviation, and an offset parsed from that is silently wrong.
|
|
15
|
+
*/
|
|
16
|
+
export declare function clockOffsetAt(tz: string, year: number, month: number, day: number): number;
|
|
17
|
+
/** The clock a place's table prints a date in: its own (`Place.clock`) where it has one, else its zone's. Month 0-based. */
|
|
18
|
+
export declare function placeClockAt(place: Place, year: number, month: number, day: number): number;
|
|
19
|
+
export declare function offsetLabel(offsetMinutes: number): string;
|
|
20
|
+
export declare function tableClockAt(place: Place, year: number, month: number, day: number): TableClock;
|
|
21
|
+
/** 'HH:MM' of an instant on a clock, as a table prints it. */
|
|
22
|
+
export declare function printedAt(instant: Date, offsetMinutes: number): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clockOffsetAt = clockOffsetAt;
|
|
4
|
+
exports.placeClockAt = placeClockAt;
|
|
5
|
+
exports.offsetLabel = offsetLabel;
|
|
6
|
+
exports.tableClockAt = tableClockAt;
|
|
7
|
+
exports.printedAt = printedAt;
|
|
8
|
+
const formats = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Minutes east of UTC that a zone keeps at noon UTC of a date (month 0-based).
|
|
11
|
+
*
|
|
12
|
+
* Read from the zone's wall clock (year … minute, as every Intl has them),
|
|
13
|
+
* not from `timeZoneName: 'longOffset'`, which not every runtime supports:
|
|
14
|
+
* where it is missing the name comes back as 'GMT+1' or a zone's own
|
|
15
|
+
* abbreviation, and an offset parsed from that is silently wrong.
|
|
16
|
+
*/
|
|
17
|
+
function clockOffsetAt(tz, year, month, day) {
|
|
18
|
+
const at = Date.UTC(year, month, day, 12);
|
|
19
|
+
let f = formats.get(tz);
|
|
20
|
+
if (!f) {
|
|
21
|
+
f = new Intl.DateTimeFormat('en-US', {
|
|
22
|
+
timeZone: tz,
|
|
23
|
+
year: 'numeric',
|
|
24
|
+
month: 'numeric',
|
|
25
|
+
day: 'numeric',
|
|
26
|
+
hour: 'numeric',
|
|
27
|
+
minute: 'numeric',
|
|
28
|
+
hourCycle: 'h23',
|
|
29
|
+
});
|
|
30
|
+
formats.set(tz, f);
|
|
31
|
+
}
|
|
32
|
+
const parts = f.formatToParts(new Date(at));
|
|
33
|
+
const n = (type) => Number(parts.find(p => p.type === type)?.value);
|
|
34
|
+
const wall = Date.UTC(n('year'), n('month') - 1, n('day'), n('hour') % 24, n('minute'));
|
|
35
|
+
if (!Number.isFinite(wall))
|
|
36
|
+
throw new RangeError(`The zone ${tz} could not be read on this runtime.`);
|
|
37
|
+
return Math.round((wall - at) / 60_000);
|
|
38
|
+
}
|
|
39
|
+
/** The clock a place's table prints a date in: its own (`Place.clock`) where it has one, else its zone's. Month 0-based. */
|
|
40
|
+
function placeClockAt(place, year, month, day) {
|
|
41
|
+
if (place.clock) {
|
|
42
|
+
const date = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
43
|
+
let off = place.clock[0][1];
|
|
44
|
+
for (const [from, o] of place.clock)
|
|
45
|
+
if (from <= date)
|
|
46
|
+
off = o;
|
|
47
|
+
return off;
|
|
48
|
+
}
|
|
49
|
+
return clockOffsetAt(place.tz, year, month, day);
|
|
50
|
+
}
|
|
51
|
+
function offsetLabel(offsetMinutes) {
|
|
52
|
+
const sign = offsetMinutes < 0 ? '-' : '+';
|
|
53
|
+
const abs = Math.abs(offsetMinutes);
|
|
54
|
+
return `UTC${sign}${String(Math.floor(abs / 60)).padStart(2, '0')}:${String(abs % 60).padStart(2, '0')}`;
|
|
55
|
+
}
|
|
56
|
+
function tableClockAt(place, year, month, day) {
|
|
57
|
+
const offsetMinutes = placeClockAt(place, year, month, day);
|
|
58
|
+
return place.clock
|
|
59
|
+
? { zone: null, offsetMinutes, label: offsetLabel(offsetMinutes) }
|
|
60
|
+
: { zone: place.tz, offsetMinutes, label: place.tz };
|
|
61
|
+
}
|
|
62
|
+
/** 'HH:MM' of an instant on a clock, as a table prints it. */
|
|
63
|
+
function printedAt(instant, offsetMinutes) {
|
|
64
|
+
const local = Math.floor(instant.getTime() / 60_000) + offsetMinutes;
|
|
65
|
+
const m = ((local % 1440) + 1440) % 1440;
|
|
66
|
+
return `${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`;
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Instants, Place } from './types.js';
|
|
2
|
+
export type ComputeOptions = {
|
|
3
|
+
corrections?: boolean;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* The method as it stands, certified or not, for a civil date with the
|
|
7
|
+
* month 0-based. Null where the method gives no times for the day. Not for
|
|
8
|
+
* showing anyone their prayer times: `timesFor` is.
|
|
9
|
+
*/
|
|
10
|
+
export declare function candidateInstants(place: Place, year: number, month: number, day: number, asrShadow?: 1 | 2, opts?: ComputeOptions): Instants | null;
|