zmanim 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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