lib/prayer_times/calculation.rb in prayer_times-0.1.1 vs lib/prayer_times/calculation.rb in prayer_times-0.1.2
- old
+ new
@@ -3,12 +3,17 @@
# Calculation class used to do the times calculation in given settings
class Calculation
extend Forwardable
include MathHelpers
+ SunPosition = Struct.new(:declination, :equation)
+
+ MINUTE = 60
+ DEGREES_PER_HOUR = 15.0
+
def_delegators :@calculator, :time_suffixes, :time_format, :invalid_time, :iterations_count, :times_offsets
-
+
# Gets the prayers times
# @param [PrayTime] calculator parent class
# @param [Date] date the desired date
# It accepts an array as well [year, month, day]
# @param [Array] coords of type [long, lat [,alt]]
@@ -17,136 +22,156 @@
self.calculator = calculator
self.lat = coords[0]
self.lng = coords[1]
self.elv = coords.size > 2 ? coords[2] : 0
self.time_zone = time_zone
- date = Date.new(date[0], date[1], date[2]) unless date.is_a? Date
- self.jdate = date.ajd.to_f - lng / (15 * 24.0)
- end
+ self.jdate = julian_date(date) - lng / (360.0)
+ self.times = {
+ imsak: 5, fajr: 5, sunrise: 6, dhuhr: 12, asr: 13,
+ sunset: 18, maghrib: 18, isha: 18
+ }
+ end
# Compute prayer times
# @return [Hash] times
def compute
- times = {
- imsak: 5, fajr: 5, sunrise: 6, dhuhr: 12,
- asr: 13, sunset: 18, maghrib: 18, isha: 18
- }
# main iterations
- iterations_count.times{times = compute_prayer_times(times)}
- times = adjust_times(times)
+ iterations_count.times{compute_prayer_times}
- # add midnight time
- if method_settings[:midnight] == 'Jafari'
- times[:midnight] = times[:sunset] + time_diff(times[:sunset], times[:fajr]) / 2
- else
- times[:midnight] = times[:sunset] + time_diff(times[:sunset], times[:sunrise]) / 2
- end
+ adjust_times
- times = tune_times(times)
- modify_formats(times)
- set_names(times)
- end
+ tune_times
- private
+ modify_formats
- attr_accessor :calculator, :time_zone, :lat, :lng, :elv, :jdate
+ set_names
+ end
+ private
+ attr_accessor :calculator, :time_zone, :lat, :lng, :elv, :jdate, :times
+
+ # get julian date of date object or array
+ def julian_date(date)
+ date_obj = date.is_a?(Date) ? date : Date.new(date[0], date[1], date[2])
+ date_obj.ajd.to_f
+ end
+
# convert float time to the given format (see time_formats)
def get_formatted_time(time)
return invalid_time if time.nan?
return time if time_format == 'Float'
- time = fix_hour(time + 0.5 / 60) # add 0.5 minutes to round
+ time = fix_hour(time + 0.5 / self.class::MINUTE) # add 0.5 minutes to round
hours = time.floor
- minutes = ((time - hours) * 60).floor
+ minutes = ((time - hours) * self.class::MINUTE).floor
suffix = time_format == '12h' ? time_suffixes[hours < 12 ? :am : :pm ] : ''
formatted_time = (time_format == "24h") ? ("%02d:%02d" % [hours, minutes]) : "%d:%02d" % [((hours + 11) % 12)+1, minutes]
formatted_time + suffix
end
# compute mid-day time
def mid_day(time)
- eqt = sun_position(jdate + time)[1]
- fix_hour(12 - eqt)
+ eqt = sun_position(jdate + time).equation
+ fix_hour(12 - eqt)
end
-
- # compute the time at which sun reaches a specific angle below horizon
+
+ # compute the time at which sun reaches a specific angle below horizon
def sun_angle_time(angle, time, direction = nil)
- decl = sun_position(jdate + time)[0]
+ decl = sun_position(jdate + time).declination
noon = mid_day(time)
- t = 1/15.0 * darccos((-rsin(angle)- rsin(decl)* rsin(lat))/
+ t = 1.0 / self.class::DEGREES_PER_HOUR * darccos((-rsin(angle)- rsin(decl)* rsin(lat))/
(rcos(decl) * rcos(lat)))
return noon + (direction == 'ccw' ? -t : t)
rescue
return (0/0.0)
end
# compute asr time
- def asr_time(factor, time)
- decl = sun_position(jdate + time)[0]
+ def asr_time(factor, time)
+ decl = sun_position(jdate + time).declination
angle = -darccot(factor + rtan(lat - decl).abs)
- sun_angle_time(angle, time)
+ sun_angle_time(angle, time)
end
# compute declination angle of sun and equation of time
# Ref: http://aa.usno.navy.mil/faq/docs/SunApprox.php
- def sun_position(jd)
- d = jd - 2451545.0
+ def sun_position(julian_date)
+ d = julian_date - 2451545.0
g = fix_angle(357.529 + 0.98560028 * d)
q = fix_angle(280.459 + 0.98564736 * d)
l = fix_angle(q + 1.915* rsin(g) + 0.020* rsin(2*g))
#r = 1.00014 - 0.01671 * cos(g) - 0.00014 * cos(2*g)
e = 23.439 - 0.00000036 * d
- ra = darctan2(rcos(e)* rsin(l), rcos(l))/ 15.0
- eqt = q / 15.0 - fix_hour(ra)
- decl = darcsin(rsin(e)* rsin(l))
+ ra = darctan2(rcos(e)* rsin(l), rcos(l))/ self.class::DEGREES_PER_HOUR
+ equation = q / self.class::DEGREES_PER_HOUR - fix_hour(ra) # equation of time
+ declination = darcsin(rsin(e)* rsin(l)) # declination of the Sun
- [decl, eqt]
+ SunPosition.new(declination,equation)
end
# compute prayer times at given julian date
- def compute_prayer_times(times)
- day_portion(times)
- {
- imsak: sun_angle_time(method_settings[:imsak].to_f, times[:imsak], 'ccw'),
- fajr: sun_angle_time(method_settings[:fajr].to_f, times[:fajr], 'ccw'),
- sunrise: sun_angle_time(rise_set_angle(elv), times[:sunrise], 'ccw'),
- dhuhr: mid_day(times[:dhuhr]),
- asr: asr_time(asr_factor(method_settings[:asr]), times[:asr]),
- sunset: sun_angle_time(rise_set_angle(elv), times[:sunset]),
- maghrib: sun_angle_time(method_settings[:maghrib].to_f, times[:maghrib]),
- isha: sun_angle_time(method_settings[:isha].to_f, times[:isha])
- }
+ def compute_prayer_times
+ day_portion
+
+ initial_computation
end
+ # make the primary calculations
+ def initial_computation
+ times.merge!(
+ imsak: sun_angle_time(method_settings[:imsak].to_f, times[:imsak], 'ccw'),
+ fajr: sun_angle_time(method_settings[:fajr].to_f, times[:fajr], 'ccw'),
+ sunrise: sun_angle_time(rise_set_angle(elv), times[:sunrise], 'ccw'),
+ dhuhr: mid_day(times[:dhuhr]),
+ asr: asr_time(asr_factor(method_settings[:asr]), times[:asr]),
+ sunset: sun_angle_time(rise_set_angle(elv), times[:sunset]),
+ maghrib: sun_angle_time(method_settings[:maghrib].to_f, times[:maghrib]),
+ isha: sun_angle_time(method_settings[:isha].to_f, times[:isha])
+ )
+ end
+
# adjust times in a prayer time array
- def adjust_times(times)
- tz_adjust = time_zone - lng / 15.0
- times.keys.each{|k| times[k] += tz_adjust}
+ def adjust_times
+
+ adjust_time_zones
+
if !method_settings[:high_lats].nil?
- times = adjust_high_lats(times)
+ adjust_high_lats
end
if minute?(method_settings[:imsak])
- times[:imsak] = times[:fajr] - method_settings[:imsak].to_f / 60.0
+ times[:imsak] = times[:fajr] - method_settings[:imsak].to_f / self.class::MINUTE
end
+
# need to ask about 'min' settings
if minute?(method_settings[:maghrib])
- times[:maghrib] = times[:sunset] + method_settings[:maghrib].to_f / 60.0
+ times[:maghrib] = times[:sunset] + method_settings[:maghrib].to_f / self.class::MINUTE
end
+
if minute?(method_settings[:isha])
- times[:isha] = times[:maghrib] + method_settings[:isha].to_f / 60.0
+ times[:isha] = times[:maghrib] + method_settings[:isha].to_f / self.class::MINUTE
end
- times[:dhuhr] += method_settings[:dhuhr].to_f / 60.0
+ times[:dhuhr] += method_settings[:dhuhr].to_f / self.class::MINUTE
- return times
+ # add midnight time
+ if method_settings[:midnight] == 'Jafari'
+ times[:midnight] = times[:sunset] + time_diff(times[:sunset], times[:fajr]) / 2
+ else
+ times[:midnight] = times[:sunset] + time_diff(times[:sunset], times[:sunrise]) / 2
+ end
end
+ # adjust time zones
+ def adjust_time_zones
+ tz_adjust = time_zone - lng / self.class::DEGREES_PER_HOUR
+ times.keys.each{|k| times[k] += tz_adjust}
+ end
+
# get asr shadow factor
def asr_factor(asr)
calc_methods = {'Standard' => 1, 'Hanafi' => 2}
calc_methods.include?(asr) ? calc_methods[asr] : asr.to_f
end
@@ -155,33 +180,32 @@
def rise_set_angle(elevation = 0)
0.833 + 0.0347 * Math.sqrt(elevation) # an approximation
end
# apply offsets to the times
- def tune_times(times)
- times.each{|k,_| times[k] += (method_offsets[k] + times_offsets[k]) / 60.0}
+ def tune_times
+ times.each{|k,_| times[k] += (method_offsets[k] + times_offsets[k]).to_f / self.class::MINUTE}
end
# convert times to given time format
- def modify_formats(times)
+ def modify_formats
times.each{|k,_| times[k] = get_formatted_time(times[k])}
end
-
- def set_names(times)
+
+ def set_names
res = {}
calculator.times_names.each{|k,v| res[v] = times[k]}
res
end
# adjust times for locations in higher latitudes
- def adjust_high_lats(times)
+ def adjust_high_lats
night_time = time_diff(times[:sunset], times[:sunrise]) # sunset to sunrise
times[:imsak] = adjust_hl_time(times[:imsak], times[:sunrise], method_settings[:imsak].to_f, night_time, 'ccw')
times[:fajr] = adjust_hl_time(times[:fajr], times[:sunrise], method_settings[:fajr].to_f, night_time, 'ccw')
times[:isha] = adjust_hl_time(times[:isha], times[:sunset], method_settings[:isha].to_f, night_time)
times[:maghrib] = adjust_hl_time(times[:maghrib], times[:sunset], method_settings[:maghrib].to_f, night_time)
- times
end
# adjust a time for higher latitudes
def adjust_hl_time(time, base, angle, night, direction = nil)
portion = night_portion(angle, night)
@@ -193,45 +217,43 @@
end
# the night portion used for adjusting times in higher latitudes
def night_portion(angle, night)
hl_method = method_settings[:high_lats]
- portion = 1/2.0 # midnight
- portion = 1/60.0 * angle if hl_method == 'AngleBased'
- portion = 1/7.0 if hl_method == 'OneSeventh'
- portion * night
- end
+ portion = 1.0 / 2 # midnight
+ portion = 1.0 / self.class::MINUTE * angle if hl_method == 'AngleBased'
+ portion = 1.0 / 7 if hl_method == 'OneSeventh'
+ portion * night
+ end
# convert hours to day portions
- def day_portion(times)
+ def day_portion
times.each{|k,v| times[k] = times[k] / 24.0}
end
-
+
# compute the difference between two times
def time_diff(time1, time2); fix_hour(time2 - time1); end
-
# detect if input contains 'min'
def minute?(arg); arg.to_s.include? "min"; end
def fix_hour(hour); fix(hour, 24.0) ; end
-
def fix_angle(angle); fix(angle, 360.0) ; end
def fix(a, mode)
return a if a.nan?
a = a - mode * (a / mode).floor
a < 0 ? a + mode : a
end
-
+
def method_settings
@m_settings ||= calculator.calculation_method.settings
end
-
+
def method_offsets
@m_offsets ||= calculator.calculation_method.offsets
end
end
-end
\ No newline at end of file
+end