zmanim 0.1.0

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE +504 -0
  6. data/README.md +124 -0
  7. data/Rakefile +2 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/lib/zmanim.rb +6 -0
  11. data/lib/zmanim/astronomical_calendar.rb +105 -0
  12. data/lib/zmanim/hebrew_calendar/hebrew_date_formatter.rb +243 -0
  13. data/lib/zmanim/hebrew_calendar/jewish_calendar.rb +390 -0
  14. data/lib/zmanim/hebrew_calendar/jewish_date.rb +397 -0
  15. data/lib/zmanim/limudim/anchor/day_of_month_anchor.rb +49 -0
  16. data/lib/zmanim/limudim/anchor/day_of_week_anchor.rb +26 -0
  17. data/lib/zmanim/limudim/anchor/day_of_year_anchor.rb +27 -0
  18. data/lib/zmanim/limudim/calculators/daf_yomi_bavli.rb +53 -0
  19. data/lib/zmanim/limudim/calculators/daf_yomi_yerushalmi.rb +58 -0
  20. data/lib/zmanim/limudim/calculators/mishna_yomis.rb +48 -0
  21. data/lib/zmanim/limudim/calculators/parsha.rb +91 -0
  22. data/lib/zmanim/limudim/calculators/tehillim_monthly.rb +43 -0
  23. data/lib/zmanim/limudim/cycle.rb +40 -0
  24. data/lib/zmanim/limudim/interval.rb +37 -0
  25. data/lib/zmanim/limudim/limud.rb +42 -0
  26. data/lib/zmanim/limudim/limud_calculator.rb +137 -0
  27. data/lib/zmanim/limudim/limudim_formatter.rb +168 -0
  28. data/lib/zmanim/limudim/unit.rb +53 -0
  29. data/lib/zmanim/util/astronomical_calculations.rb +34 -0
  30. data/lib/zmanim/util/geo_location.rb +116 -0
  31. data/lib/zmanim/util/hebrew_numeric_formatter.rb +67 -0
  32. data/lib/zmanim/util/math_helper.rb +14 -0
  33. data/lib/zmanim/util/noaa_calculator.rb +180 -0
  34. data/lib/zmanim/util/sun_times_calculator.rb +123 -0
  35. data/lib/zmanim/util/text_helper.rb +7 -0
  36. data/lib/zmanim/util/time_zone_converter.rb +20 -0
  37. data/lib/zmanim/version.rb +3 -0
  38. data/lib/zmanim/zmanim_calendar.rb +106 -0
  39. data/zmanim.gemspec +37 -0
  40. metadata +153 -0
@@ -0,0 +1,67 @@
1
+ # encoding: UTF-8
2
+ module Zmanim::Util
3
+ module HebrewNumericFormatter
4
+ attr_accessor :use_geresh_gershayim
5
+
6
+ GERESH = '׳'
7
+ GERSHAYIM = '״'
8
+
9
+ def initialize
10
+ @use_geresh_gershayim = true
11
+ end
12
+
13
+ def format_hebrew_number(number, include_thousands=false)
14
+ raise ArgumentError unless (0..9999).include?(number)
15
+ descriptors = {efes: 'אפס', alafim: 'אלפים'}
16
+ one_glyphs = [''] + %w(א ב ג ד ה ו ז ח ט)
17
+ ten_glyphs = [''] + %w(י כ ל מ נ ס ע פ צ)
18
+ final_ten_glyphs = [''] + %w(י ך ל ם ן ס ע ף ץ)
19
+ hundred_glyphs = [''] + %w(ק ר ש ת תק תר תש תת תתק)
20
+ tav_taz_glyphs = %w(טו טז)
21
+
22
+ return descriptors[:efes] if number == 0
23
+
24
+ thousands, remainder = number.divmod(1000)
25
+ hundreds, remainder = remainder.divmod(100)
26
+ tens, ones = remainder.divmod(10)
27
+
28
+ str = ''
29
+
30
+ if number % 1000 == 0
31
+ return add_geresh(one_glyphs[thousands]) + ' ' + descriptors[:alafim]
32
+ elsif thousands > 0 && include_thousands
33
+ str += add_geresh(one_glyphs[thousands]) + ' '
34
+ end
35
+
36
+ str += hundred_glyphs[hundreds]
37
+ if tens == 1 and ones == 5
38
+ str += tav_taz_glyphs[0]
39
+ elsif tens == 1 and ones == 6
40
+ str += tav_taz_glyphs[1]
41
+ else
42
+ if ones == 0 && hundreds != 0
43
+ str += final_ten_glyphs[tens]
44
+ else
45
+ str += ten_glyphs[tens]
46
+ end
47
+ str += one_glyphs[ones]
48
+ end
49
+
50
+ if use_geresh_gershayim
51
+ if [hundreds, tens, ones].select{|p| p > 0}.count == 1 && hundreds <= 4
52
+ str += GERESH
53
+ else
54
+ str.insert(-2, GERSHAYIM)
55
+ end
56
+ end
57
+
58
+ str
59
+ end
60
+
61
+ private
62
+
63
+ def add_geresh(str)
64
+ use_geresh_gershayim ? str + GERESH : str
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ module Zmanim::Util
2
+ module MathHelper
3
+ DEGREE_RADIAN_RATIO = 180.0/Math::PI
4
+ refine Numeric do
5
+ def to_radians
6
+ self / DEGREE_RADIAN_RATIO
7
+ end
8
+
9
+ def to_degrees
10
+ self * DEGREE_RADIAN_RATIO
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,180 @@
1
+ require_relative 'astronomical_calculations'
2
+ require_relative 'math_helper'
3
+
4
+ module Zmanim::Util
5
+ class NOAACalculator
6
+ include AstronomicalCalculations
7
+ using Zmanim::Util::MathHelper
8
+
9
+ JULIAN_DAY_JAN_1_2000 = 2451545.0
10
+ JULIAN_DAYS_PER_CENTURY = 36525.0
11
+
12
+ def name
13
+ 'US National Oceanic and Atmospheric Administration Algorithm'
14
+ end
15
+
16
+ def utc_sunrise(date, geo_location, zenith, adjust_for_elevation: false)
17
+ utc_sun_position(date, geo_location, zenith, adjust_for_elevation, :sunrise)
18
+ rescue Math::DomainError
19
+ nil
20
+ end
21
+
22
+ def utc_sunset(date, geo_location, zenith, adjust_for_elevation: false)
23
+ utc_sun_position(date, geo_location, zenith, adjust_for_elevation, :sunset)
24
+ rescue Math::DomainError
25
+ nil
26
+ end
27
+
28
+ private
29
+
30
+ def julian_centuries_from_julian_day(julian_day)
31
+ (julian_day - JULIAN_DAY_JAN_1_2000) / JULIAN_DAYS_PER_CENTURY
32
+ end
33
+
34
+ def julian_day_from_julian_centuries(julian_centuries)
35
+ (julian_centuries * JULIAN_DAYS_PER_CENTURY) + JULIAN_DAY_JAN_1_2000
36
+ end
37
+
38
+ def utc_sun_position(date, geo_location, zenith, adjust_for_elevation, mode)
39
+ elevation = adjust_for_elevation ? geo_location.elevation : 0.0
40
+ adjusted_zenith = adjusted_zenith(zenith, elevation)
41
+ utc_time = calculate_utc_sun_position(date.ajd,
42
+ geo_location.latitude,
43
+ -geo_location.longitude,
44
+ adjusted_zenith,
45
+ mode) # in minutes
46
+ utc_time /= 60.0 # in hours
47
+ utc_time % 24 # normalized (0...24)
48
+ end
49
+
50
+ def calculate_utc_sun_position(julian_day, latitude, longitude, zenith, mode)
51
+ julian_centuries = julian_centuries_from_julian_day(julian_day)
52
+
53
+ # first pass using solar noon
54
+ noonmin = solar_noon_utc(julian_centuries, longitude)
55
+ tnoon = julian_centuries_from_julian_day(julian_day + (noonmin / 1440.0))
56
+ first_pass = approximate_utc_sun_position(tnoon, latitude, longitude, zenith, mode)
57
+
58
+ # refine using output of first pass
59
+ trefinement = julian_centuries_from_julian_day(julian_day + (first_pass / 1440.0))
60
+ approximate_utc_sun_position(trefinement, latitude, longitude, zenith, mode)
61
+ end
62
+
63
+ def approximate_utc_sun_position(approx_julian_centuries, latitude, longitude, zenith, mode)
64
+ eq_time = equation_of_time(approx_julian_centuries)
65
+ solar_dec = solar_declination(approx_julian_centuries)
66
+ hour_angle = sun_hour_angle_at_horizon(latitude, solar_dec, zenith, mode)
67
+
68
+ delta = longitude - hour_angle.to_degrees
69
+ time_delta = delta * 4.0
70
+ 720 + time_delta - eq_time
71
+ end
72
+
73
+ def sun_hour_angle_at_horizon(latitude, solar_dec, zenith, mode)
74
+ lat_r = latitude.to_radians
75
+ solar_dec_r = solar_dec.to_radians
76
+ zenith_r = zenith.to_radians
77
+
78
+ hour_angle = Math.acos(
79
+ (Math.cos(zenith_r) / (Math.cos(lat_r) * Math.cos(solar_dec_r))) -
80
+ (Math.tan(lat_r) * Math.tan(solar_dec_r))
81
+ )
82
+
83
+ hour_angle *= -1 if mode == :sunset
84
+ hour_angle # in radians
85
+ end
86
+
87
+ def solar_declination(julian_centuries)
88
+ correction = obliquity_correction(julian_centuries).to_radians
89
+ lambda = sun_apparent_longitude(julian_centuries).to_radians
90
+ sint = Math.sin(correction) * Math.sin(lambda)
91
+ Math.asin(sint).to_degrees # in degrees
92
+ end
93
+
94
+ def sun_apparent_longitude(julian_centuries)
95
+ true_longitude = sun_true_longitude(julian_centuries)
96
+ omega = 125.04 - (1934.136 * julian_centuries)
97
+ true_longitude - 0.00569 - (0.00478 * Math.sin(omega.to_radians)) # in degrees
98
+ end
99
+
100
+ def sun_true_longitude(julian_centuries)
101
+ sgml = sun_geometric_mean_longitude(julian_centuries)
102
+ center = sun_equation_of_center(julian_centuries)
103
+ sgml + center # in degrees
104
+ end
105
+
106
+ def sun_equation_of_center(julian_centuries)
107
+ mrad = sun_geometric_mean_anomaly(julian_centuries).to_radians
108
+ sinm = Math.sin(mrad)
109
+ sin2m = Math.sin(2 * mrad)
110
+ sin3m = Math.sin(3 * mrad)
111
+
112
+ (sinm * (1.914602 - (julian_centuries * (0.004817 + (0.000014 * julian_centuries))))) +
113
+ (sin2m * (0.019993 - (0.000101 * julian_centuries))) +
114
+ (sin3m * 0.000289) # in degrees
115
+ end
116
+
117
+ def solar_noon_utc(julian_centuries, longitude)
118
+ century_start = julian_day_from_julian_centuries(julian_centuries)
119
+
120
+ # first pass to yield approximate solar noon
121
+ approx_tnoon = julian_centuries_from_julian_day(century_start + (longitude / 360.0))
122
+ approx_eq_time = equation_of_time(approx_tnoon)
123
+ approx_sol_noon = 720 + (longitude * 4) - approx_eq_time
124
+
125
+ # refinement using output of first pass
126
+ tnoon = julian_centuries_from_julian_day(century_start - 0.5 + (approx_sol_noon / 1440.0))
127
+ eq_time = equation_of_time(tnoon)
128
+ 720 + (longitude * 4) - eq_time
129
+ end
130
+
131
+ def equation_of_time(julian_centuries)
132
+ epsilon = obliquity_correction(julian_centuries).to_radians
133
+ sgml = sun_geometric_mean_longitude(julian_centuries).to_radians
134
+ sgma = sun_geometric_mean_anomaly(julian_centuries).to_radians
135
+ eoe = earth_orbit_eccentricity(julian_centuries)
136
+
137
+ y = Math.tan(epsilon / 2.0)
138
+ y *= y
139
+
140
+ sin2l0 = Math.sin(2.0 * sgml)
141
+ sin4l0 = Math.sin(4.0 * sgml)
142
+ cos2l0 = Math.cos(2.0 * sgml)
143
+ sinm = Math.sin(sgma)
144
+ sin2m = Math.sin( 2.0 * sgma)
145
+
146
+ eq_time = (y * sin2l0) - (2.0 * eoe * sinm) + (4.0 * eoe * y * sinm * cos2l0) - (0.5 * y * y * sin4l0) - (1.25 * eoe * eoe * sin2m)
147
+ eq_time.to_degrees * 4.0 # minutes of time
148
+ end
149
+
150
+
151
+ def earth_orbit_eccentricity(julian_centuries)
152
+ 0.016708634 - (julian_centuries * (0.000042037 + (0.0000001267 * julian_centuries))) # unitless
153
+ end
154
+
155
+ def sun_geometric_mean_anomaly(julian_centuries)
156
+ anomaly = 357.52911 + (julian_centuries * (35999.05029 - (0.0001537 * julian_centuries))) # in degrees
157
+
158
+ anomaly % 360 # normalized (0...360)
159
+ end
160
+
161
+ def sun_geometric_mean_longitude(julian_centuries)
162
+ longitude = 280.46646 + (julian_centuries * (36000.76983 + (0.0003032 * julian_centuries))) # in degrees
163
+
164
+ longitude % 360 # normalized (0...360)
165
+ end
166
+
167
+ def obliquity_correction(julian_centuries)
168
+ obliquity_of_ecliptic = mean_obliquity_of_ecliptic(julian_centuries)
169
+
170
+ omega = 125.04 - (1934.136 * julian_centuries)
171
+ correction = obliquity_of_ecliptic + (0.00256 * Math.cos(omega.to_radians))
172
+ correction % 360 # normalized (0...360)
173
+ end
174
+
175
+ def mean_obliquity_of_ecliptic(julian_centuries)
176
+ seconds = 21.448 - (julian_centuries * (46.8150 + (julian_centuries * (0.00059 - (julian_centuries * 0.001813)))))
177
+ 23.0 + ((26.0 + (seconds / 60)) / 60.0) # in degrees
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,123 @@
1
+ require_relative 'astronomical_calculations'
2
+ require_relative 'math_helper'
3
+
4
+ module Zmanim::Util
5
+ class SunTimesCalculator
6
+ include AstronomicalCalculations
7
+ using Zmanim::Util::MathHelper
8
+
9
+ def name
10
+ 'US Naval Almanac Algorithm'
11
+ end
12
+
13
+ def utc_sunrise(date, geo_location, zenith, adjust_for_elevation: false)
14
+ utc_sun_position(date, geo_location, zenith, adjust_for_elevation, :sunrise)
15
+ rescue Math::DomainError
16
+ nil
17
+ end
18
+
19
+ def utc_sunset(date, geo_location, zenith, adjust_for_elevation: false)
20
+ utc_sun_position(date, geo_location, zenith, adjust_for_elevation, :sunset)
21
+ rescue Math::DomainError
22
+ nil
23
+ end
24
+
25
+ private
26
+
27
+ DEG_PER_HOUR = 360.0 / 24.0
28
+
29
+ def sin_deg(deg)
30
+ Math.sin(deg.to_radians)
31
+ end
32
+
33
+ def cos_deg(deg)
34
+ Math.cos(deg.to_radians)
35
+ end
36
+
37
+ def tan_deg(deg)
38
+ Math.tan(deg.to_radians)
39
+ end
40
+
41
+ def acos_deg(x)
42
+ Math.acos(x).to_degrees
43
+ end
44
+
45
+ def asin_deg(x)
46
+ Math.asin(x).to_degrees
47
+ end
48
+
49
+ def atan_deg(x)
50
+ Math.atan(x).to_degrees
51
+ end
52
+
53
+ def utc_sun_position(date, geo_location, zenith, adjust_for_elevation, mode)
54
+ elevation = adjust_for_elevation ? geo_location.elevation : 0
55
+ adjusted_zenith = adjusted_zenith(zenith, elevation)
56
+ utc_time = calculate_utc_sun_position(date,
57
+ geo_location.latitude,
58
+ geo_location.longitude,
59
+ adjusted_zenith,
60
+ mode) # in hours
61
+ utc_time % 24 # normalized (0...24)
62
+ end
63
+
64
+ def calculate_utc_sun_position(date, latitude, longitude, zenith, mode)
65
+ day_of_year = date.yday
66
+ hours_offset = hours_from_meridian(longitude)
67
+ time_days = approx_time_days(day_of_year, hours_offset, mode)
68
+
69
+ mean_anomaly = sun_mean_anomaly(time_days)
70
+ true_long = sun_true_longitude(mean_anomaly)
71
+ right_ascension_hours = sun_right_ascension_hours(true_long)
72
+ cos_local_hour_angle = cos_local_hour_angle(true_long, latitude, zenith)
73
+
74
+ local_hour_angle = acos_deg(cos_local_hour_angle)
75
+ local_hour_angle = 360.0 - local_hour_angle if mode == :sunrise
76
+
77
+ local_hour = local_hour_angle / DEG_PER_HOUR
78
+
79
+ mean_time = local_mean_time(local_hour, right_ascension_hours, time_days)
80
+ mean_time - hours_offset
81
+ end
82
+
83
+ def local_mean_time(local_hour, right_ascension_hours, time_days)
84
+ local_hour + right_ascension_hours - (0.06571 * time_days) - 6.622
85
+ end
86
+
87
+ def cos_local_hour_angle(sun_true_long, latitude, zenith)
88
+ sin_dec = 0.39782 * sin_deg(sun_true_long)
89
+ cos_dec = cos_deg(asin_deg(sin_dec))
90
+ (cos_deg(zenith) - (sin_dec * sin_deg(latitude))) / (cos_dec * cos_deg(latitude))
91
+ end
92
+
93
+ def sun_right_ascension_hours(sun_true_long)
94
+ ra = atan_deg(0.91764 * tan_deg(sun_true_long))
95
+ l_quadrant = (sun_true_long / 90.0).floor * 90.0
96
+ ra_quadrant = (ra / 90.0).floor * 90.0
97
+ ra += (l_quadrant - ra_quadrant)
98
+
99
+ ra / DEG_PER_HOUR # in hours
100
+ end
101
+
102
+ def sun_true_longitude(sun_mean_anomaly)
103
+ true_longitude = sun_mean_anomaly +
104
+ (1.916 * sin_deg(sun_mean_anomaly)) +
105
+ (0.02 * sin_deg(2 * sun_mean_anomaly)) +
106
+ 282.634
107
+ true_longitude % 360
108
+ end
109
+
110
+ def sun_mean_anomaly(time_days)
111
+ (0.9856 * time_days) - 3.289
112
+ end
113
+
114
+ def approx_time_days(day_of_year, hours_offset, mode)
115
+ mode_offset = mode == :sunrise ? 6.0 : 18.0
116
+ day_of_year + ((mode_offset - hours_offset) / 24)
117
+ end
118
+
119
+ def hours_from_meridian(longitude)
120
+ longitude / DEG_PER_HOUR
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,7 @@
1
+ module Zmanim::Util
2
+ module TextHelper
3
+ def titleize(string)
4
+ string.to_s.gsub(/_/,' ').scan(/\w+/).map{|w| w[0].upcase + w[1..-1]}.join(' ')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ module Zmanim::Util
2
+ class TimeZoneConverter
3
+ attr_reader :time_zone
4
+
5
+ def initialize(time_zone)
6
+ @time_zone = time_zone
7
+ end
8
+
9
+ # Adjust a DateTime for the provided time zone
10
+ def modify_offset(time)
11
+ offset = offset_at(time)
12
+ time.new_offset(offset/24)
13
+ end
14
+
15
+ # offset in effect for time zone at the given time (in hours)
16
+ def offset_at(time)
17
+ time_zone.period_for_utc(time.new_offset(0)).utc_total_offset / (60 * 60).to_f
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Zmanim
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,106 @@
1
+ module Zmanim
2
+ class ZmanimCalendar < AstronomicalCalendar
3
+ attr_accessor :candle_lighting_offset
4
+
5
+ ZENITH_16_POINT_1 = GEOMETRIC_ZENITH + 16.1
6
+ ZENITH_8_POINT_5 = GEOMETRIC_ZENITH + 8.5
7
+
8
+ def initialize(opts={})
9
+ super
10
+ @candle_lighting_offset = opts[:candle_lighting_offset] || 18
11
+ end
12
+
13
+ def tzais(opts={degrees: 8.5})
14
+ degrees, offset = extract_degrees_offset(opts)
15
+ offset_by_minutes(sunset_offset_by_degrees(GEOMETRIC_ZENITH + degrees), offset)
16
+ end
17
+
18
+ def tzais_72
19
+ tzais(offset: 72)
20
+ end
21
+
22
+ def alos(opts={degrees: 16.1})
23
+ degrees, offset = extract_degrees_offset(opts)
24
+ offset_by_minutes(sunrise_offset_by_degrees(GEOMETRIC_ZENITH + degrees), offset)
25
+ end
26
+
27
+ def alos_72
28
+ alos(offset: -72)
29
+ end
30
+
31
+ alias_method :chatzos, :sun_transit
32
+
33
+ def sof_zman_shma(day_start, day_end)
34
+ shaos_into_day(day_start, day_end, 3)
35
+ end
36
+
37
+ def sof_zman_shma_gra
38
+ sof_zman_shma(sea_level_sunrise, sea_level_sunset)
39
+ end
40
+
41
+ def sof_zman_shma_mga
42
+ sof_zman_shma(alos_72, tzais_72)
43
+ end
44
+
45
+ def candle_lighting
46
+ offset_by_minutes(sea_level_sunset , -candle_lighting_offset)
47
+ end
48
+
49
+ def sof_zman_tfila(day_start, day_end)
50
+ shaos_into_day(day_start, day_end, 4)
51
+ end
52
+
53
+ def sof_zman_tfila_gra
54
+ sof_zman_tfila(sea_level_sunrise, sea_level_sunset)
55
+ end
56
+
57
+ def sof_zman_tfila_mga
58
+ sof_zman_tfila(alos_72, tzais_72)
59
+ end
60
+
61
+ def mincha_gedola(day_start=sea_level_sunrise, day_end=sea_level_sunset)
62
+ shaos_into_day(day_start, day_end, 6.5)
63
+ end
64
+
65
+ def mincha_ketana(day_start=sea_level_sunrise, day_end=sea_level_sunset)
66
+ shaos_into_day(day_start, day_end, 9.5)
67
+ end
68
+
69
+ def plag_hamincha(day_start=sea_level_sunrise, day_end=sea_level_sunset)
70
+ shaos_into_day(day_start, day_end, 10.75)
71
+ end
72
+
73
+ def shaah_zmanis(day_start, day_end)
74
+ temporal_hour(day_start, day_end)
75
+ end
76
+
77
+ def shaah_zmanis_gra
78
+ shaah_zmanis(sea_level_sunrise, sea_level_sunset)
79
+ end
80
+
81
+ def shaah_zmanis_mga
82
+ shaah_zmanis(alos_72, tzais_72)
83
+ end
84
+
85
+ def shaah_zmanis_by_degrees_and_offset(degrees, offset)
86
+ opts = {degrees: degrees, offset: offset}
87
+ shaah_zmanis(alos(opts.merge(offset: -offset)), tzais(opts))
88
+ end
89
+
90
+ private
91
+
92
+ def shaos_into_day(day_start, day_end, shaos)
93
+ return unless shaah_zmanis = temporal_hour(day_start, day_end)
94
+ offset_by_minutes(day_start, (shaah_zmanis / MINUTE_MILLIS) * shaos)
95
+ end
96
+
97
+ def extract_degrees_offset(opts)
98
+ [opts[:degrees] || 0.0, opts[:offset] || 0]
99
+ end
100
+
101
+ def offset_by_minutes(time, minutes)
102
+ return unless time
103
+ time + (minutes / (60 * 24).to_f)
104
+ end
105
+ end
106
+ end