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,42 @@
1
+ module Zmanim::Limudim
2
+ class Limud
3
+ attr_reader :interval, :unit
4
+
5
+ def initialize(interval, unit)
6
+ @interval = interval
7
+ @unit = unit
8
+ end
9
+
10
+ def cycle
11
+ interval.cycle
12
+ end
13
+
14
+ def description
15
+ unit.to_s
16
+ end
17
+
18
+ def start_date
19
+ interval.start_date
20
+ end
21
+
22
+ def end_date
23
+ interval.end_date
24
+ end
25
+
26
+ def iteration
27
+ interval.iteration
28
+ end
29
+
30
+ def cycle_start_date
31
+ cycle.start_date
32
+ end
33
+
34
+ def cycle_end_date
35
+ cycle.end_date
36
+ end
37
+
38
+ def cycle_iteration
39
+ cycle.iteration
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,137 @@
1
+ require 'zmanim/hebrew_calendar/jewish_date'
2
+
3
+ module Zmanim::Limudim
4
+ module LimudCalculator
5
+ def limud(date)
6
+ jewish_date = jewish_date(date)
7
+ cycle = find_cycle(jewish_date)
8
+ return nil unless cycle
9
+ units = cycle_units_calculation.(cycle)
10
+ interval = cycle.first_interval(interval_end_calculation)
11
+ while !jewish_date.between?(interval.start_date, interval.end_date) do
12
+ interval = interval.next(interval_end_calculation)
13
+ while !jewish_date.between?(interval.start_date, interval.end_date) && skip_interval?(interval)
14
+ interval = interval.skip(interval_end_calculation)
15
+ end
16
+ end
17
+ unit = unit_for_interval(units, interval)
18
+ Zmanim::Limudim::Limud.new(interval, unit)
19
+ end
20
+
21
+ # Jewish Date on which the first cycle starts (if not perpetual)
22
+ def initial_cycle_date
23
+ nil
24
+ end
25
+
26
+ # Anchor on which a cycle resets (where relevant)
27
+ # e.g. for Parsha this would be a Day-of-Year anchor
28
+ def perpetual_cycle_anchor
29
+ nil
30
+ end
31
+
32
+ # Number of units to apply over an iteration
33
+ def unit_step
34
+ 1
35
+ end
36
+
37
+ # Are units components of some larger grouping? (e.g. pages or mishnayos)
38
+ def tiered_units?
39
+ true
40
+ end
41
+
42
+ # For tiered units, this would be a Hash in the format:
43
+ # `{some_name: last_page, ...}`
44
+ # or:
45
+ # `{maseches: {perek_number => mishnayos, ...}, ...}`.
46
+ #
47
+ # For simple units, use an Array in the format:
48
+ # `[:some_name, ...]`
49
+ def default_units
50
+ {}
51
+ end
52
+
53
+ # Set if units are applied fractionally (indicated by a fractional unit_step).
54
+ # For example, an amud yomi calculator would set `%w(a b)`
55
+ def fractional_units
56
+ nil
57
+ end
58
+
59
+ # Change this when using page numbers that do not generally start from one.
60
+ # (e.g. Talmud Bavli pages start from 2)
61
+ def default_starting_page
62
+ 1
63
+ end
64
+
65
+ def starting_page(units, unit_name)
66
+ default_starting_page
67
+ end
68
+
69
+ def cycle_end_calculation
70
+ ->(start_date, iteration){ start_date }
71
+ end
72
+
73
+ def interval_end_calculation
74
+ ->(cycle, start_date){ start_date }
75
+ end
76
+
77
+ def cycle_units_calculation
78
+ ->(cycle){ default_units }
79
+ end
80
+
81
+ def unit_for_interval(units, interval)
82
+ return tiered_units_for_interval(units, interval) if tiered_units?
83
+ Unit.new(*units[interval.iteration-1])
84
+ end
85
+
86
+ def skip_interval?(interval)
87
+ false
88
+ end
89
+
90
+ def tiered_units_for_interval(units, interval)
91
+ iteration = interval.iteration
92
+ offset = ((iteration - 1) * unit_step) + 1
93
+ offset2 = (offset - 1) + unit_step if unit_step > 1
94
+ offsets = [offset, offset2].compact
95
+ targets = offsets.map{|o| [o, []]}
96
+ results = find_offset_units(units, targets)
97
+ return nil unless results.map(&:first).uniq == [0]
98
+ paths = results.map(&:last)
99
+ Unit.new(*paths)
100
+ end
101
+
102
+ def find_offset_units(units, targets)
103
+ units.reduce(targets) do |t, (name, attributes)|
104
+ if attributes.is_a?(Numeric)
105
+ start = starting_page(units, name)
106
+ length = (attributes - start) + 1
107
+ t.select{|o, p| o == 0} + t.reject{|o,p| o == 0}.map do |o, p|
108
+ o <= length ?
109
+ [0, p + [name, (start + o) - 1]] :
110
+ [o - length, p]
111
+ end
112
+ else
113
+ t.select{|o, p| o == 0} +
114
+ find_offset_units(attributes, t.reject{|o,p| o == 0}.map{|o, p| [o, p + [name]]}).map do |o, p|
115
+ o == 0 ? [o, p] : [o, p[0..-2]]
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ def find_cycle(date)
122
+ if initial_cycle_date
123
+ Cycle.from_cycle_initiation(initial_cycle_date, cycle_end_calculation, date)
124
+ elsif perpetual_cycle_anchor
125
+ Cycle.from_perpetual_anchor(perpetual_cycle_anchor, cycle_end_calculation, date)
126
+ else
127
+ raise 'Cycle cannot be determined without an initial date or an anchor'
128
+ end
129
+ end
130
+
131
+ protected
132
+
133
+ def jewish_date(date)
134
+ date.respond_to?(:jewish_year) ? date : Zmanim::HebrewCalendar::JewishDate.new(date)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,168 @@
1
+ # encoding: UTF-8
2
+ module Zmanim::Limudim
3
+ class LimudimFormatter
4
+ include Zmanim::Util::TextHelper
5
+ include Zmanim::Util::HebrewNumericFormatter
6
+
7
+ attr_accessor :hebrew_format
8
+ PARSHIYOS = {
9
+ bereishis: 'בראשית',
10
+ noach: 'נח',
11
+ lech_lecha: 'לך לך',
12
+ vayeira: 'וירא',
13
+ chayei_sarah: 'חיי שרה',
14
+ toldos: 'תולדות',
15
+ vayeitzei: 'ויצא',
16
+ vayishlach: 'וישלח',
17
+ vayeishev: 'וישב',
18
+ mikeitz: 'מקץ',
19
+ vayigash: 'ויגש',
20
+ vayechi: 'ויחי',
21
+ shemos: 'שמות',
22
+ vaeirah: 'וארא',
23
+ bo: 'בא',
24
+ beshalach: 'בשלח',
25
+ yisro: 'יתרו',
26
+ mishpatim: 'משפטים',
27
+ terumah: 'תרומה',
28
+ tetzaveh: 'תצוה',
29
+ ki_sisa: 'כי תשא',
30
+ vayakheil: 'ויקהל',
31
+ pekudei: 'פקודי',
32
+ vayikra: 'ויקרא',
33
+ tzav: 'צו',
34
+ shemini: 'שמיני',
35
+ tazria: 'תזריע',
36
+ metzora: 'מצורע',
37
+ acharei: 'אחרי מות',
38
+ kedoshim: 'קדושים',
39
+ emor: 'אמר',
40
+ behar: 'בהר',
41
+ bechukosai: 'בחוקתי',
42
+ bamidbar: 'במדבר',
43
+ naso: 'נשא',
44
+ behaalosecha: 'בהעלותך',
45
+ shelach: 'שלח',
46
+ korach: 'קרח',
47
+ chukas: 'חקת',
48
+ balak: 'בלק',
49
+ pinchas: 'פינחס',
50
+ matos: 'מטות',
51
+ masei: 'מסעי',
52
+ devarim: 'דברים',
53
+ vaeschanan: 'ואתחנן',
54
+ eikev: 'עקב',
55
+ reei: 'ראה',
56
+ shoftim: 'שופטים',
57
+ ki_seitzei: 'כי תצא',
58
+ ki_savo: 'כי תבא',
59
+ nitzavim: 'נצבים',
60
+ vayeilech: 'וילך',
61
+ haazinu: 'האזינו',
62
+ vezos_haberacha: 'וזאת הברכה',
63
+ }
64
+
65
+ MASECHTOS = {
66
+ berachos: 'ברכות',
67
+ peah: 'פאה',
68
+ demai: 'דמאי',
69
+ kilayim: 'כלאים',
70
+ sheviis: 'שביעית',
71
+ terumos: 'תרומות',
72
+ maasros: 'מעשרות',
73
+ maaser_sheni: 'מעזר שני',
74
+ chalah: 'חלה',
75
+ orlah: 'ערלה',
76
+ bikurim: 'בכורים',
77
+ shabbos: 'שבת',
78
+ eruvin: 'ערובין',
79
+ pesachim: 'פסחים',
80
+ shekalim: 'שקלים',
81
+ yoma: 'יומא',
82
+ sukkah: 'סוכה',
83
+ beitzah: 'ביצה',
84
+ rosh_hashanah: 'ראש השנה',
85
+ taanis: 'תענית',
86
+ megillah: 'מגילה',
87
+ moed_katan: 'מועד קטן',
88
+ chagigah: 'חגיגה',
89
+ yevamos: 'יבמות',
90
+ kesubos: 'כתובות',
91
+ nedarim: 'נדרים',
92
+ nazir: 'נזיר',
93
+ sotah: 'סוטה',
94
+ gitin: 'גיטין',
95
+ kiddushin: 'קידושין',
96
+ bava_kamma: 'בבא קמא',
97
+ bava_metzia: 'בבא מציעא',
98
+ bava_basra: 'בבא בתרא',
99
+ sanhedrin: 'סנהדרין',
100
+ makkos: 'מכות',
101
+ shevuos: 'שבועות',
102
+ eduyos: 'עדיות',
103
+ avodah_zarah: 'עבודה זרה',
104
+ avos: 'אבות',
105
+ horiyos: 'הוריות',
106
+ zevachim: 'זבחים',
107
+ menachos: 'מנחות',
108
+ chullin: 'חולין',
109
+ bechoros: 'בכורות',
110
+ arachin: 'ערכין',
111
+ temurah: 'תמורה',
112
+ kerisos: 'כריתות',
113
+ meilah: 'מעילה',
114
+ tamid: 'תמיד',
115
+ midos: 'מידות',
116
+ kinnim: 'קינים',
117
+ keilim: 'כלים',
118
+ ohalos: 'אוהלות',
119
+ negaim: 'נגעים',
120
+ parah: 'פרה',
121
+ taharos: 'טהרות',
122
+ mikvaos: 'מקואות',
123
+ niddah: 'נדה',
124
+ machshirim: 'מכשירין',
125
+ zavim: 'זבים',
126
+ tevul_yom: 'טבול יום',
127
+ yadayim: 'ידים',
128
+ uktzin: 'עוקצין',
129
+ no_daf_today: 'אין דף היום',
130
+ }
131
+
132
+ def initialize
133
+ super
134
+ @use_geresh_gershayim = false
135
+ end
136
+
137
+ def format_parsha(limud)
138
+ prefix = hebrew_format ? 'פרשת ' : 'Parshas '
139
+ prefix + limud.unit.render do |parsha|
140
+ hebrew_format ? PARSHIYOS[parsha] : titleize(parsha)
141
+ end
142
+ end
143
+
144
+ def format_talmudic(limud)
145
+ return '' unless unit = (limud && limud.unit)
146
+ unit.render do |e|
147
+ if e.is_a?(Numeric)
148
+ format_number(e)
149
+ elsif hebrew_format
150
+ MASECHTOS[e]
151
+ else
152
+ titleize(e)
153
+ end
154
+ end
155
+ end
156
+
157
+ def format_tehillim(limud)
158
+ prefix = hebrew_format ? 'תהלים ' : 'Tehillim '
159
+ prefix + limud.unit.render {|e| format_number(e) }
160
+ end
161
+
162
+ private
163
+
164
+ def format_number(number)
165
+ hebrew_format ? format_hebrew_number(number) : number
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,53 @@
1
+ module Zmanim::Limudim
2
+ class Unit
3
+ attr_reader :components
4
+ def initialize(*components)
5
+ @components = components
6
+ end
7
+
8
+ def to_s
9
+ render{|value| value }
10
+ end
11
+
12
+ def render
13
+ primary, secondary = components.map do |component|
14
+ Array(component).map{|v| yield v}
15
+ end
16
+ render_with_root(primary) + render_secondary(secondary, primary)
17
+ end
18
+
19
+ private
20
+
21
+ def render_with_root(component)
22
+ root, extension = component.first, component[1..-1]
23
+ return root.to_s if extension.length == 0
24
+ "#{root} #{render_extension(extension)}"
25
+ end
26
+
27
+ def render_extension(extension)
28
+ extension.join(':')
29
+ end
30
+
31
+ def render_secondary(second_component, first_component)
32
+ if second_component.nil?
33
+ return ''
34
+ elsif second_component.first != first_component.first
35
+ ' - ' + render_with_root(second_component)
36
+ elsif diff = render_difference(second_component, first_component)
37
+ '-' + diff
38
+ else
39
+ ''
40
+ end
41
+ end
42
+
43
+ def render_difference(rendering, comparing)
44
+ if rendering.length == 0
45
+ return nil
46
+ elsif rendering.first != comparing.first
47
+ render_extension(rendering)
48
+ else
49
+ render_difference(rendering[1..-1], comparing[1..-1])
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'math_helper'
2
+
3
+ module Zmanim::Util
4
+ module AstronomicalCalculations
5
+ using Zmanim::Util::MathHelper
6
+
7
+ GEOMETRIC_ZENITH = 90.0
8
+
9
+ attr_writer :earth_radius,
10
+ :refraction,
11
+ :solar_radius
12
+
13
+ def refraction
14
+ @refraction ||= 34 / 60.0
15
+ end
16
+
17
+ def solar_radius
18
+ @solar_radius ||= 16 / 60.0
19
+ end
20
+
21
+ def earth_radius
22
+ @earth_radius ||= 6356.9 # km
23
+ end
24
+
25
+ def elevation_adjustment(elevation)
26
+ Math.acos(earth_radius / (earth_radius + (elevation / 1000.0))).to_degrees
27
+ end
28
+
29
+ def adjusted_zenith(zenith, elevation)
30
+ return zenith unless zenith == GEOMETRIC_ZENITH #only adjust for exact sunrise or sunset
31
+ zenith + solar_radius + refraction + elevation_adjustment(elevation)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,116 @@
1
+ module Zmanim::Util
2
+ class GeoLocation
3
+ attr_reader :elevation,
4
+ :latitude,
5
+ :location_name,
6
+ :longitude,
7
+ :time_zone
8
+
9
+ def initialize(name, latitude, longitude, time_zone, elevation:nil)
10
+ self.location_name = name
11
+ self.latitude = latitude
12
+ self.longitude = longitude
13
+ self.time_zone = time_zone
14
+ self.elevation = elevation
15
+ end
16
+
17
+ def self.GMT
18
+ new('Greenwich, England', 51.4772, 0, 'GMT')
19
+ end
20
+
21
+ def latitude=(args)
22
+ if args.respond_to?(:size) && args.size == 4
23
+ degrees, minutes, seconds, direction = *args
24
+ temp = degrees + ((minutes + (seconds / 60.0)) / 60.0)
25
+ raise ArgumentError unless %w{S N}.include? direction
26
+ raise ArgumentError unless temp >= 0
27
+ temp *= -1 if direction == 'S'
28
+ self.latitude = temp
29
+ else
30
+ raise ArgumentError unless (-90..90).include?(args)
31
+ @latitude = args
32
+ end
33
+ end
34
+
35
+ def longitude=(args)
36
+ if args.respond_to?(:size) && args.size == 4
37
+ degrees, minutes, seconds, direction = *args
38
+ temp = degrees + ((minutes + (seconds / 60.0)) / 60.0)
39
+ raise ArgumentError unless %w{W E}.include? direction
40
+ raise ArgumentError unless temp >= 0
41
+ temp *= -1 if direction == 'W'
42
+ self.longitude = temp
43
+ else
44
+ raise ArgumentError unless (-180..180).include?(args)
45
+ @longitude = args
46
+ end
47
+ end
48
+
49
+ def elevation=(e)
50
+ e ||= 0
51
+ raise ArgumentError unless e >= 0
52
+ @elevation = e
53
+ end
54
+
55
+ def location_name=(name)
56
+ @location_name = name
57
+ end
58
+
59
+ def time_zone=(tz)
60
+ tz = TZInfo::Timezone.get(tz) if tz.is_a?(String)
61
+ raise ArgumentError unless tz.is_a?(TZInfo::Timezone)
62
+ @time_zone = tz
63
+ end
64
+
65
+ # Number of Days to adjust due to antimeridian crossover
66
+ #
67
+ # The actual Time Zone offset may deviate from the expected offset based on the longitude
68
+ # But since the 'absolute time' calculations are always based on longitudinal offset from UTC
69
+ # for a given date, the date is presumed to only increase East of the Prime Meridian, and to
70
+ # only decrease West of it.
71
+ # For Time Zones that cross the antimeridian, the date will be artificially adjusted before calculation
72
+ # to conform with this presumption.
73
+ #
74
+ # For example, Samoa (located around 172W) uses a local offset of +14:00. When asking to calculate for
75
+ # 2017-03-15, the calculator should operate using 2017-03-14 since the expected zone is -11. After
76
+ # determining the UTC time, the local offset of +14:00 should be applied to bring the date back to 2017-03-15.
77
+ def antimeridian_adjustment
78
+ local_hours_offset = local_mean_time_offset / Zmanim::AstronomicalCalendar::HOUR_MILLIS.to_f
79
+ if local_hours_offset >= 20
80
+ 1
81
+ elsif local_hours_offset <= -20
82
+ -1
83
+ else
84
+ 0
85
+ end
86
+ end
87
+
88
+ # Local Mean Time offset for the expected time zone (in ms).
89
+ #
90
+ # The offset is the difference between Local Mean Time at the given
91
+ # longitude and Standard Time in effect for the given time zone.
92
+ def local_mean_time_offset
93
+ (longitude * 4 * Zmanim::AstronomicalCalendar::MINUTE_MILLIS) - standard_time_offset
94
+ end
95
+
96
+ # Standard Time offset from UTC based on the provided Time Zone (in ms).
97
+ #
98
+ # This will ignore DST transformations.
99
+ def standard_time_offset
100
+ time_zone.current_period.utc_offset * 1000
101
+ end
102
+
103
+ # Time Zone offset from UTC at a given point in time (in ms).
104
+ #
105
+ # This will take into account any DST transformation in effect
106
+ # for the given Time Zone at the given time
107
+ def time_zone_offset_at(utc_time)
108
+ time_zone.period_for_utc(utc_time).utc_total_offset * 1000
109
+ end
110
+
111
+ def clone
112
+ self.class.new(location_name.dup, latitude, longitude, time_zone.dup, elevation: elevation)
113
+ end
114
+
115
+ end
116
+ end