-
Notifications
You must be signed in to change notification settings - Fork 7
/
5-high-lat-local-relative.go
184 lines (156 loc) · 5.54 KB
/
5-high-lat-local-relative.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
package prayer
import (
"time"
)
// LocalRelativeEstimation is adapter using method that created by cooperation between
// Fiqh Council of Muslim World League and Islamic Crescents' Observation Project (ICOP).
// In short, this method uses average percentage to calculate Fajr and Isha time for
// abnormal times.
//
// This adapter only estimates time for Isha and Fajr and require sunrise and sunset
// time. Therefore it's not suitable for area in extreme latitude (>=65 degrees).
//
// Reference: https://www.astronomycenter.net/latitude.html?l=en
func LocalRelativeEstimation() HighLatitudeAdapter {
return highLatLocalRelativeEstimation
}
func highLatLocalRelativeEstimation(cfg Config, year int, schedules []Schedule) []Schedule {
var (
nFajrSample int
nIshaSample int
sumFajrPercents float64
sumIshaPercents float64
)
for _, s := range schedules {
// This conventions only works if daytime exists (in other words, sunrise
// and Maghrib must exist). So if there are days where those time don't
// exist, stop and just return the schedule as it is.
// TODO: maybe put some warning log later.
if s.Sunrise.IsZero() || s.Maghrib.IsZero() {
return schedules
}
// Calculate percentage in normal days
if s.IsNormal {
// Calculate day and night
dayDuration := s.Maghrib.Sub(s.Sunrise).Seconds()
nightDuration := 24*60*60 - dayDuration
// Calculate Fajr percentage
if !s.Fajr.IsZero() {
fajrDuration := s.Sunrise.Sub(s.Fajr).Seconds()
sumFajrPercents += fajrDuration / nightDuration
nFajrSample++
}
// Calculate Isha percentage
if !s.Isha.IsZero() {
ishaDuration := s.Isha.Sub(s.Maghrib).Seconds()
sumIshaPercents += ishaDuration / nightDuration
nIshaSample++
}
}
}
// Calculate average percentage
avgFajrPercents := sumFajrPercents / float64(nFajrSample)
avgIshaPercents := sumIshaPercents / float64(nIshaSample)
// Extract abnormal schedules
abnormalSummer, abnormalWinter := extractAbnormalSchedules(schedules)
// Fix Fajr and Isha times in abnormal days
for _, as := range []abnormalRange{abnormalSummer, abnormalWinter} {
for _, i := range as.Indexes {
s := schedules[i]
dayDuration := s.Maghrib.Sub(s.Sunrise).Seconds()
nightDuration := 24*60*60 - dayDuration
if !s.IsNormal {
fajrDuration := nightDuration * avgFajrPercents * float64(time.Second)
schedules[i].Fajr = s.Sunrise.Add(-time.Duration(fajrDuration))
ishaDuration := nightDuration * avgIshaPercents * float64(time.Second)
schedules[i].Isha = s.Maghrib.Add(time.Duration(ishaDuration))
}
}
}
schedules = applyLocalRelativeTransition(schedules, abnormalSummer)
schedules = applyLocalRelativeTransition(schedules, abnormalWinter)
return schedules
}
func applyLocalRelativeTransition(schedules []Schedule, abnormalPeriod abnormalRange) []Schedule {
// If there are no abnormality, return as it is
if abnormalPeriod.IsEmpty() {
return schedules
}
// Split the abnormal period into two
nAbnormalDays := len(abnormalPeriod.Indexes)
maxTransitionDays := nAbnormalDays / 2
firstHalf := abnormalPeriod.Indexes[:maxTransitionDays]
secondHalf := abnormalPeriod.Indexes[nAbnormalDays-maxTransitionDays:]
// Fix the time in first half
for _, idx := range firstHalf {
today := sliceAt(schedules, idx)
yesterday := sliceAt(schedules, idx-1)
// If idx is zero, it means today is the first day of the year.
// Therefore, yesterday is occured last year.
if idx == 0 {
yesterday.Fajr = yesterday.Fajr.AddDate(-1, 0, 0)
yesterday.Isha = yesterday.Isha.AddDate(-1, 0, 0)
}
var fajrChanged, ishaChanged bool
schedules[idx].Fajr, fajrChanged = applyLocalRelativeTransitionTime(yesterday.Fajr, today.Fajr)
schedules[idx].Isha, ishaChanged = applyLocalRelativeTransitionTime(yesterday.Isha, today.Isha)
if !fajrChanged && !ishaChanged {
break
}
}
// Fix the time in second half, do it backward
for i := len(secondHalf) - 1; i >= 0; i-- {
idx := secondHalf[i]
today := sliceAt(schedules, idx)
tomorrow := sliceAt(schedules, idx+1)
// If idx is last, it means today is the last day of the year.
// Therefore, tomorrow will occur next year.
if idx == len(schedules)-1 {
tomorrow.Fajr = tomorrow.Fajr.AddDate(1, 0, 0)
tomorrow.Isha = tomorrow.Isha.AddDate(1, 0, 0)
}
var fajrChanged, ishaChanged bool
schedules[idx].Fajr, fajrChanged = applyLocalRelativeTransitionTime(tomorrow.Fajr, today.Fajr)
schedules[idx].Isha, ishaChanged = applyLocalRelativeTransitionTime(tomorrow.Isha, today.Isha)
if !fajrChanged && !ishaChanged {
break
}
}
return schedules
}
func applyLocalRelativeTransitionTime(reference, today time.Time) (time.Time, bool) {
// Calculate diff between today and reference
var diff time.Duration
var referenceIsForward bool
if today.After(reference) {
diff = today.Sub(reference)
referenceIsForward = false
} else {
diff = reference.Sub(today)
referenceIsForward = true
}
// Limit the difference
maxDiff := 24*time.Hour + 5*time.Minute
minDiff := 24*time.Hour - 5*time.Minute
if diff > maxDiff {
diff = maxDiff
} else if diff < minDiff {
diff = minDiff
} else {
return today, false // the diff is within limit, nothing to change
}
// Adjust the time
var newTime time.Time
if referenceIsForward {
newTime = reference.Add(-diff)
} else {
newTime = reference.Add(diff)
}
// Fix the year
if todayYear := today.Year(); newTime.Year() != todayYear {
newTime = time.Date(todayYear, newTime.Month(), newTime.Day(),
newTime.Hour(), newTime.Minute(), newTime.Second(), newTime.Nanosecond(),
newTime.Location())
}
return newTime, true
}