-
Notifications
You must be signed in to change notification settings - Fork 7
/
5-high-lat-mecca.go
244 lines (211 loc) · 9.38 KB
/
5-high-lat-mecca.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package prayer
import (
"math"
"time"
)
// Mecca is adapter based on Fatwa from Dar Al Iftah Al Misrriyah number 2806 dated
// at 2010-08-08. They propose that area with higher latitude to follows the schedule
// in Mecca when abnormal days occured, using transit time as the common point. Here
// the day is considered "abnormal" when there are no true night, or the day length is
// less than 4 hours.
//
// To prevent sudden schedule changes, this method uses transition period for maximum
// one month before and after the abnormal periods.
//
// This adapter doesn't require the sunrise and sunset to be exist in a day, so it's
// usable for area in extreme latitudes (>=65 degrees).
//
// Reference: https://www.prayertimes.dk/fatawa.html
func Mecca() HighLatitudeAdapter {
return highLatMecca
}
func highLatMecca(cfg Config, year int, schedules []Schedule) []Schedule {
// Additional rule: day is normal if daylength is more than 4 hour
for i, s := range schedules {
if s.IsNormal {
var dayLength time.Duration
if !s.Maghrib.IsZero() && !s.Sunrise.IsZero() {
dayLength = s.Maghrib.Sub(s.Sunrise)
}
schedules[i].IsNormal = dayLength >= 4*time.Hour
}
}
// Extract abnormal schedules
abnormalSummer, abnormalWinter := extractAbnormalSchedules(schedules)
// Calculate schedule for Mecca
meccaTz, _ := time.LoadLocation("Asia/Riyadh")
meccaCfg := Config{
Latitude: 21.425506007708996,
Longitude: 39.8254579358597,
Timezone: meccaTz,
TwilightConvention: cfg.TwilightConvention,
AsrConvention: cfg.AsrConvention}
meccaSchedules, _ := calcNormal(meccaCfg, year)
// Apply Mecca schedules in abnormal period by matching it with duration
// in Mecca using transit time (noon) as the base.
for _, as := range []abnormalRange{abnormalSummer, abnormalWinter} {
for _, i := range as.Indexes {
// Calculate duration from Mecca schedule
ms := meccaSchedules[i]
msFajrTransit := ms.Zuhr.Sub(ms.Fajr)
msRiseTransit := ms.Zuhr.Sub(ms.Sunrise)
msTransitAsr := ms.Asr.Sub(ms.Zuhr)
msTransitMaghrib := ms.Maghrib.Sub(ms.Zuhr)
msTransitIsha := ms.Isha.Sub(ms.Zuhr)
// Apply Mecca duration
s := schedules[i]
s.Fajr = s.Zuhr.Add(-msFajrTransit)
s.Sunrise = s.Zuhr.Add(-msRiseTransit)
s.Asr = s.Zuhr.Add(msTransitAsr)
s.Maghrib = s.Zuhr.Add(msTransitMaghrib)
s.Isha = s.Zuhr.Add(msTransitIsha)
schedules[i] = s
}
}
schedules = applyMeccaTransition(schedules, abnormalSummer, abnormalWinter)
return schedules
}
func applyMeccaTransition(schedules []Schedule, abnormalSummer, abnormalWinter abnormalRange) []Schedule {
// If there are no abnormality, return as it is
if abnormalSummer.IsEmpty() && abnormalWinter.IsEmpty() {
return schedules
}
// Split schedules for each time
nSchedules := len(schedules)
fajrTimes := make([]time.Time, nSchedules)
sunriseTimes := make([]time.Time, nSchedules)
asrTimes := make([]time.Time, nSchedules)
maghribTimes := make([]time.Time, nSchedules)
ishaTimes := make([]time.Time, nSchedules)
for idx, s := range schedules {
fajrTimes[idx] = s.Fajr
sunriseTimes[idx] = s.Sunrise
asrTimes[idx] = s.Asr
maghribTimes[idx] = s.Maghrib
ishaTimes[idx] = s.Isha
}
// Check if there is only one abnormal period
onlySummer := abnormalWinter.IsEmpty() && !abnormalSummer.IsEmpty()
onlyWinter := abnormalSummer.IsEmpty() && !abnormalWinter.IsEmpty()
if onlySummer || onlyWinter {
// Merge into one abnormal period
abnormalPeriod := abnormalSummer
if abnormalPeriod.IsEmpty() {
abnormalPeriod = abnormalWinter
}
// Calculate transition duration from leftover days
leftoverDays := len(schedules) - len(abnormalPeriod.Indexes)
nTransitionDays := leftoverDays / 2
// Apply transition times
fajrTimes = createMeccaPreTransition(fajrTimes, abnormalPeriod, nTransitionDays)
fajrTimes = createMeccaPostTransition(fajrTimes, abnormalPeriod, nTransitionDays)
sunriseTimes = createMeccaPreTransition(sunriseTimes, abnormalPeriod, nTransitionDays)
sunriseTimes = createMeccaPostTransition(sunriseTimes, abnormalPeriod, nTransitionDays)
asrTimes = createMeccaPreTransition(asrTimes, abnormalPeriod, nTransitionDays)
asrTimes = createMeccaPostTransition(asrTimes, abnormalPeriod, nTransitionDays)
maghribTimes = createMeccaPreTransition(maghribTimes, abnormalPeriod, nTransitionDays)
maghribTimes = createMeccaPostTransition(maghribTimes, abnormalPeriod, nTransitionDays)
ishaTimes = createMeccaPreTransition(ishaTimes, abnormalPeriod, nTransitionDays)
ishaTimes = createMeccaPostTransition(ishaTimes, abnormalPeriod, nTransitionDays)
} else if !abnormalSummer.IsEmpty() && !abnormalWinter.IsEmpty() {
// Fetch indexes
summerIdxStart, _ := firstSliceItem(abnormalSummer.Indexes)
summerIdxEnd, _ := lastSliceItem(abnormalSummer.Indexes)
winterIdxStart, _ := firstSliceItem(abnormalWinter.Indexes)
winterIdxEnd, _ := lastSliceItem(abnormalWinter.Indexes)
// Calculate gap between period
winterSummerGap := int(math.Abs(float64(summerIdxStart-winterIdxEnd))) - 1 // after winter end, before summer start
summerWinterGap := int(math.Abs(float64(winterIdxStart-summerIdxEnd))) - 1 // after summer end, before winter start
// Calculate transition duration
winterSummerTransitionDays := winterSummerGap / 2 // for post-winter and pre-summer
summerWinterTransitionDays := summerWinterGap / 2 // for pre-winter and post-summer
preWinterTransitionDays := summerWinterTransitionDays
postWinterTransitionDays := winterSummerTransitionDays
preSummerTransitionDays := winterSummerTransitionDays
postSummerTransitionDays := summerWinterTransitionDays
// Create winter transition
fajrTimes = createMeccaPreTransition(fajrTimes, abnormalWinter, preWinterTransitionDays)
fajrTimes = createMeccaPostTransition(fajrTimes, abnormalWinter, postWinterTransitionDays)
sunriseTimes = createMeccaPreTransition(sunriseTimes, abnormalWinter, preWinterTransitionDays)
sunriseTimes = createMeccaPostTransition(sunriseTimes, abnormalWinter, postWinterTransitionDays)
asrTimes = createMeccaPreTransition(asrTimes, abnormalWinter, preWinterTransitionDays)
asrTimes = createMeccaPostTransition(asrTimes, abnormalWinter, postWinterTransitionDays)
maghribTimes = createMeccaPreTransition(maghribTimes, abnormalWinter, preWinterTransitionDays)
maghribTimes = createMeccaPostTransition(maghribTimes, abnormalWinter, postWinterTransitionDays)
ishaTimes = createMeccaPreTransition(ishaTimes, abnormalWinter, preWinterTransitionDays)
ishaTimes = createMeccaPostTransition(ishaTimes, abnormalWinter, postWinterTransitionDays)
// Create summer transition
fajrTimes = createMeccaPreTransition(fajrTimes, abnormalSummer, preSummerTransitionDays)
fajrTimes = createMeccaPostTransition(fajrTimes, abnormalSummer, postSummerTransitionDays)
sunriseTimes = createMeccaPreTransition(sunriseTimes, abnormalSummer, preSummerTransitionDays)
sunriseTimes = createMeccaPostTransition(sunriseTimes, abnormalSummer, postSummerTransitionDays)
asrTimes = createMeccaPreTransition(asrTimes, abnormalSummer, preSummerTransitionDays)
asrTimes = createMeccaPostTransition(asrTimes, abnormalSummer, postSummerTransitionDays)
maghribTimes = createMeccaPreTransition(maghribTimes, abnormalSummer, preSummerTransitionDays)
maghribTimes = createMeccaPostTransition(maghribTimes, abnormalSummer, postSummerTransitionDays)
ishaTimes = createMeccaPreTransition(ishaTimes, abnormalSummer, preSummerTransitionDays)
ishaTimes = createMeccaPostTransition(ishaTimes, abnormalSummer, postSummerTransitionDays)
}
// Put back times to schedule
for idx, s := range schedules {
s.Fajr = fajrTimes[idx]
s.Sunrise = sunriseTimes[idx]
s.Asr = asrTimes[idx]
s.Maghrib = maghribTimes[idx]
s.Isha = ishaTimes[idx]
schedules[idx] = s
}
return schedules
}
func createMeccaPreTransition(times []time.Time, abnormalPeriod abnormalRange, nTransitionDays int) []time.Time {
// Fix transition days
if nTransitionDays > 30 {
nTransitionDays = 30
}
// Get data where transition end, i.e. when abnormality start
endIdx, _ := firstSliceItem(abnormalPeriod.Indexes)
endTime := times[endIdx]
// Get data where transition begin
startIdx := sliceRealIdx(times, endIdx-nTransitionDays)
startTime := times[startIdx]
if startTime.After(endTime) {
startTime = startTime.AddDate(-1, 0, 0)
}
// Calculate duration step
durationDiff := endTime.Sub(startTime)
diffStep := durationDiff / time.Duration(nTransitionDays)
// Apply transition time
ci, ct := endIdx, endTime
for i := nTransitionDays - 1; i > 0; i-- { // minus 1 to exclude `startIdx`
ci = sliceRealIdx(times, ci-1)
ct = ct.Add(-diffStep)
times[ci] = ct
}
return times
}
func createMeccaPostTransition(times []time.Time, abnormalPeriod abnormalRange, nTransitionDays int) []time.Time {
// Fix transition days
if nTransitionDays > 30 {
nTransitionDays = 30
}
// Get data where transition start, i.e. when abnormality end
startIdx, _ := lastSliceItem(abnormalPeriod.Indexes)
startTime := times[startIdx]
// Get data where transition end
endIdx := sliceRealIdx(times, startIdx+nTransitionDays)
endTime := times[endIdx]
if endTime.Before(startTime) {
endTime = endTime.AddDate(1, 0, 0)
}
// Calculate duration step
durationDiff := endTime.Sub(startTime)
diffStep := durationDiff / time.Duration(nTransitionDays)
// Apply transition time
ci, ct := startIdx, startTime
for i := 1; i < nTransitionDays; i++ {
ci = sliceRealIdx(times, ci+1)
ct = ct.Add(diffStep)
times[ci] = ct
}
return times
}