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,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