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,390 @@
1
+ require_relative 'jewish_date'
2
+
3
+ module Zmanim::HebrewCalendar
4
+ class JewishCalendar < JewishDate
5
+ attr_accessor :in_israel, :use_modern_holidays
6
+
7
+ SIGNIFICANT_DAYS = %i(erev_rosh_hashana rosh_hashana tzom_gedalyah erev_yom_kippur yom_kippur
8
+ erev_succos succos chol_hamoed_succos hoshana_rabbah shemini_atzeres simchas_torah
9
+ chanukah tenth_of_teves tu_beshvat
10
+ taanis_esther purim shushan_purim purim_katan shushan_purim_katan
11
+ erev_pesach pesach chol_hamoed_pesach pesach_sheni erev_shavuos shavuos
12
+ seventeen_of_tammuz tisha_beav tu_beav
13
+ yom_hashoah yom_hazikaron yom_haatzmaut yom_yerushalayim)
14
+
15
+ SIGNIFICANT_TEFILOS = %i(yaaleh_veyavo al_hanissim begin_mashiv_haruach end_mashiv_haruach mashiv_haruach begin_morid_hatal morid_hatal vesein_tal_umatar vesein_beracha atah_yatzarta borchi_nafshi)
16
+
17
+ SIGNIFICANT_SHABBOS = %i(parshas_shekalim parshas_zachor parshas_parah parshas_hachodesh shabbos_hagadol shabbos_shuva)
18
+
19
+ def initialize(*args)
20
+ if args.count == 4
21
+ super(*args[0..2])
22
+ else
23
+ super
24
+ end
25
+ @in_israel = !!args[3]
26
+ @use_modern_holidays = false
27
+ end
28
+
29
+ def significant_day
30
+ send("#{jewish_month_name}_significant_day")
31
+ end
32
+
33
+ def yom_tov?
34
+ sd = significant_day
35
+ sd && !sd.to_s.start_with?('erev_') && (!taanis? || sd == :yom_kippur)
36
+ end
37
+
38
+ def yom_tov_assur_bemelacha?
39
+ %i(pesach shavuos rosh_hashana yom_kippur succos shemini_atzeres simchas_torah).include?(significant_day)
40
+ end
41
+
42
+ def chol_hamoed?
43
+ sd = significant_day
44
+ sd && (sd.to_s.start_with?('chol_hamoed_') || sd == :hoshana_rabbah)
45
+ end
46
+
47
+ def erev_yom_tov?
48
+ return false unless sd = significant_day
49
+ sd.to_s.start_with?('erev_') || sd == :hoshana_rabbah ||
50
+ (sd == :chol_hamoed_pesach && jewish_day == 20)
51
+ end
52
+
53
+ def taanis?
54
+ %i(seventeen_of_tammuz tisha_beav tzom_gedalyah yom_kippur tenth_of_teves taanis_esther).include?(significant_day)
55
+ end
56
+
57
+ def rosh_chodesh?
58
+ jewish_day == 30 || (jewish_day == 1 && jewish_month != 7)
59
+ end
60
+
61
+ def erev_rosh_chodesh?
62
+ jewish_day == 29 && jewish_month != 6
63
+ end
64
+
65
+ def chanukah?
66
+ significant_day == :chanukah
67
+ end
68
+
69
+ def day_of_chanukah
70
+ return unless chanukah?
71
+ if jewish_month_name == :kislev
72
+ jewish_day - 24
73
+ else
74
+ (kislev_short? ? 5 : 6) + jewish_day
75
+ end
76
+ end
77
+
78
+ def day_of_omer
79
+ case jewish_month_name
80
+ when :nissan
81
+ return (jewish_day - 15 if jewish_day > 15)
82
+ when :iyar
83
+ return jewish_day + 15
84
+ when :sivan
85
+ return (jewish_day + 44) if jewish_day < 6
86
+ end
87
+ end
88
+
89
+ # DateTime for molad as UTC
90
+ def molad_as_datetime
91
+ m = molad
92
+ location_name = 'Jerusalem, Israel'
93
+ latitude = 31.778 # Har Habayis latitude
94
+ longitude = 35.2354 # Har Habayis longitude
95
+ tz = TZInfo::Timezone.get('Asia/Jerusalem')
96
+
97
+ geo = Zmanim::Util::GeoLocation.new(location_name, latitude, longitude, tz)
98
+ seconds = (m.molad_chalakim * 10) / 3.0
99
+ # molad as local mean time
100
+ time = DateTime.new(m.gregorian_year, m.gregorian_month, m.gregorian_day,
101
+ m.molad_hours, m.molad_minutes, seconds, '+2')
102
+ offset_hours = geo.local_mean_time_offset / Zmanim::AstronomicalCalendar::HOUR_MILLIS.to_f
103
+ # molad as Jerusalem standard time
104
+ time -= offset_hours / 24.0
105
+ # molad as UTC
106
+ time.new_offset(0)
107
+ end
108
+
109
+ def techilas_zman_kiddush_levana_3_days
110
+ molad_as_datetime + 3
111
+ end
112
+
113
+ def techilas_zman_kiddush_levana_7_days
114
+ molad_as_datetime + 7
115
+ end
116
+
117
+ def sof_zman_kiddush_levana_between_moldos
118
+ half_molad_in_days = CHALAKIM_PER_MONTH.to_f / (1080 * 24 * 2)
119
+ molad_as_datetime + half_molad_in_days
120
+ end
121
+
122
+ def sof_zman_kiddush_levana_15_days
123
+ molad_as_datetime + 15
124
+ end
125
+
126
+ def daf_yomi_bavli
127
+ Zmanim::Limudim::Calculators::DafYomiBavli.new.limud(self)
128
+ end
129
+
130
+ def daf_yomi_yerushalmi
131
+ Zmanim::Limudim::Calculators::DafYomiYerushalmi.new.limud(self)
132
+ end
133
+
134
+ def parshas_hashavua
135
+ Zmanim::Limudim::Calculators::Parsha.new(in_israel: in_israel).limud(self)
136
+ end
137
+
138
+ def tehillim_portion
139
+ Zmanim::Limudim::Calculators::TehillimMonthly.new.limud(self)
140
+ end
141
+
142
+ def mishna_yomis
143
+ Zmanim::Limudim::Calculators::MishnaYomis.new.limud(self)
144
+ end
145
+
146
+ def tefilah_additions(walled_city: false, nusach: :ashkenaz)
147
+ additions = []
148
+ if mashiv_haruach_starts?
149
+ additions << :begin_mashiv_haruach
150
+ elsif mashiv_haruach_ends?
151
+ additions << (nusach == :sefard ? :begin_morid_hatal : :end_mashiv_haruach)
152
+ else
153
+ additions << :mashiv_haruach if mashiv_haruach?
154
+ additions << :morid_hatal if nusach == :sefard && morid_hatal?
155
+ end
156
+ additions << :vesein_beracha if vesein_beracha?
157
+ additions << :vesein_tal_umatar if vesein_tal_umatar?
158
+ additions << :atah_yatzarta if day_of_week == 7 && rosh_chodesh?
159
+ additions << :yaaleh_veyavo if yaaleh_veyavo?
160
+ additions << :al_hanissim if al_hanissim?(walled_city)
161
+ additions << :borchi_nafshi if rosh_chodesh?
162
+ additions
163
+ end
164
+
165
+ def significant_shabbos
166
+ return nil unless day_of_week == 7
167
+ if jewish_month == 1
168
+ if jewish_day == 1
169
+ :parshas_hachodesh
170
+ elsif jewish_day.between?(8,14)
171
+ :shabbos_hagadol
172
+ end
173
+ elsif jewish_month == 7 && jewish_day.between?(3,9)
174
+ :shabbos_shuva
175
+ elsif jewish_month == months_in_jewish_year - 1 && jewish_day.between?(25,30)
176
+ :parshas_shekalim
177
+ elsif jewish_month == months_in_jewish_year
178
+ if jewish_day == 1
179
+ :parshas_shekalim
180
+ elsif jewish_day.between?(7, 13)
181
+ :parshas_zachor
182
+ elsif jewish_day.between?(17,23)
183
+ :parshas_parah
184
+ elsif jewish_day.between?(24,29)
185
+ :parshas_hachodesh
186
+ end
187
+ end
188
+ end
189
+
190
+ def mashiv_haruach_starts?
191
+ jewish_month == 7 && jewish_day == 22
192
+ end
193
+
194
+ def mashiv_haruach_ends?
195
+ jewish_month == 1 && jewish_day == 15
196
+ end
197
+
198
+ def mashiv_haruach?
199
+ start_date = JewishDate.new(jewish_year, 7, 22)
200
+ end_date = JewishDate.new(jewish_year, 1, 15)
201
+ self.between?(start_date, end_date)
202
+ end
203
+
204
+ def morid_hatal?
205
+ !mashiv_haruach? || mashiv_haruach_starts? || mashiv_haruach_ends?
206
+ end
207
+
208
+ # This presumes the evenings of December 4/5 are always the initial start date outside of Israel
209
+ # Because the jewish date does not auto-increment in the evening, we use December 5/6 as the start date
210
+ # and rely on the user to increment the jewish date after nightfall.
211
+ # Note that according to many, the date for Vesein Tal Umatar is tied to the Julian calendar and has historically
212
+ # moved over time as the deviance from the Gregorian calendar increases. The date of December 4/5 is to be used
213
+ # for the 20th and 21st century.
214
+ def vesein_tal_umatar?
215
+ return false if day_of_week == 7 || yom_tov_assur_bemelacha?
216
+ start_date = JewishDate.new(jewish_year, 8, 7)
217
+ start_date.set_gregorian_date(start_date.gregorian_year, 12, gregorian_leap_year?(start_date.gregorian_year+1) ? 6 : 5) unless in_israel
218
+ end_date = JewishDate.new(jewish_year, 1, 15)
219
+ self.between?(start_date, end_date)
220
+ end
221
+
222
+ def vesein_beracha?
223
+ return false if day_of_week == 7 || yom_tov_assur_bemelacha?
224
+ !vesein_tal_umatar?
225
+ end
226
+
227
+ def yaaleh_veyavo?
228
+ rosh_chodesh? || chol_hamoed? || yom_tov_assur_bemelacha?
229
+ end
230
+
231
+ def al_hanissim?(walled_city=false)
232
+ purim_day = walled_city ? :shushan_purim : :purim
233
+ [:chanukah, purim_day].include?(significant_day)
234
+ end
235
+
236
+ private
237
+
238
+ def nissan_significant_day
239
+ pesach = [15, 21]
240
+ pesach |= [16, 22] unless in_israel
241
+ chol_hamoed_pesach = (16..20)
242
+ if jewish_day == 14
243
+ :erev_pesach
244
+ elsif pesach.include? jewish_day
245
+ :pesach
246
+ elsif chol_hamoed_pesach.include? jewish_day
247
+ :chol_hamoed_pesach
248
+ elsif use_modern_holidays
249
+ if (jewish_day == 26 && day_of_week == 5) ||
250
+ (jewish_day == 27 && ![1,6].include?(day_of_week)) ||
251
+ (jewish_day == 28 && day_of_week == 2)
252
+ :yom_hashoah
253
+ end
254
+ end
255
+ end
256
+
257
+ def iyar_significant_day
258
+ if jewish_day == 14
259
+ :pesach_sheni
260
+ elsif use_modern_holidays
261
+ # Note that this logic follows the current rules, which were last revised in 5764.
262
+ # The calculations for years prior may not reflect the actual dates observed at that time.
263
+ if ([2,3].include?(jewish_day) && day_of_week == 4) ||
264
+ (jewish_day == 4 && day_of_week == 3) ||
265
+ (jewish_day == 5 && day_of_week == 2)
266
+ :yom_hazikaron
267
+ elsif ([3,4].include?(jewish_day) && day_of_week == 5) ||
268
+ (jewish_day == 5 && day_of_week == 4) ||
269
+ (jewish_day == 6 && day_of_week == 3)
270
+ :yom_haatzmaut
271
+ elsif jewish_day == 28
272
+ :yom_yerushalayim
273
+ end
274
+ end
275
+ end
276
+
277
+ def sivan_significant_day
278
+ shavuos = [6]
279
+ shavuos << 7 unless in_israel
280
+ if jewish_day == 5
281
+ :erev_shavuos
282
+ elsif shavuos.include?(jewish_day)
283
+ :shavuos
284
+ end
285
+ end
286
+
287
+ def tammuz_significant_day
288
+ if (jewish_day == 17 && day_of_week != 7) ||
289
+ (jewish_day == 18 && day_of_week == 1)
290
+ :seventeen_of_tammuz
291
+ end
292
+ end
293
+
294
+ def av_significant_day
295
+ if (jewish_day == 9 && day_of_week != 7) ||
296
+ (jewish_day == 10 && day_of_week == 1)
297
+ :tisha_beav
298
+ elsif jewish_day == 15
299
+ :tu_beav
300
+ end
301
+ end
302
+
303
+ def elul_significant_day
304
+ if jewish_day == 29
305
+ :erev_rosh_hashana
306
+ end
307
+ end
308
+
309
+ def tishrei_significant_day
310
+ succos = [15]
311
+ succos << 16 unless in_israel
312
+ chol_hamoed_succos = (16..20)
313
+ if [1,2].include? jewish_day
314
+ :rosh_hashana
315
+ elsif (jewish_day == 3 && day_of_week != 7) ||
316
+ (jewish_day == 4 && day_of_week == 1)
317
+ :tzom_gedalyah
318
+ elsif jewish_day == 9
319
+ :erev_yom_kippur
320
+ elsif jewish_day == 10
321
+ :yom_kippur
322
+ elsif jewish_day == 14
323
+ :erev_succos
324
+ elsif succos.include?(jewish_day)
325
+ :succos
326
+ elsif chol_hamoed_succos.include?(jewish_day)
327
+ :chol_hamoed_succos
328
+ elsif jewish_day == 21
329
+ :hoshana_rabbah
330
+ elsif jewish_day == 22
331
+ :shemini_atzeres
332
+ elsif jewish_day == 23 && !in_israel
333
+ :simchas_torah
334
+ end
335
+ end
336
+
337
+ def cheshvan_significant_day
338
+ nil
339
+ end
340
+
341
+ def kislev_significant_day
342
+ if jewish_day >= 25
343
+ :chanukah
344
+ end
345
+ end
346
+
347
+ def teves_significant_day
348
+ chanukah = [1,2]
349
+ chanukah << 3 if kislev_short?
350
+ if chanukah.include?(jewish_day)
351
+ :chanukah
352
+ elsif jewish_day == 10
353
+ :tenth_of_teves
354
+ end
355
+ end
356
+
357
+ def shevat_significant_day
358
+ if jewish_day == 15
359
+ :tu_beshvat
360
+ end
361
+ end
362
+
363
+ def adar_significant_day
364
+ if jewish_leap_year?
365
+ if jewish_day == 14
366
+ :purim_katan
367
+ elsif jewish_day == 15
368
+ :shushan_purim_katan
369
+ end
370
+ else
371
+ purim_significant_day
372
+ end
373
+ end
374
+
375
+ def adar_ii_significant_day
376
+ purim_significant_day
377
+ end
378
+
379
+ def purim_significant_day
380
+ if (jewish_day == 13 && day_of_week != 7) ||
381
+ (jewish_day == 11 && day_of_week == 5)
382
+ :taanis_esther
383
+ elsif jewish_day == 14
384
+ :purim
385
+ elsif jewish_day == 15
386
+ :shushan_purim
387
+ end
388
+ end
389
+ end
390
+ end
@@ -0,0 +1,397 @@
1
+ module Zmanim::HebrewCalendar
2
+ class JewishDate
3
+ include Comparable
4
+
5
+ MONTHS = %i(nissan iyar sivan tammuz av elul tishrei cheshvan kislev teves shevat adar adar_ii)
6
+
7
+ RD = Date.new(1,1,1, Date::GREGORIAN)
8
+ JEWISH_EPOCH = -1373429 # 1 Tishrei of year 1 (Shnas Tohu)
9
+ # Note that while the Gregorian year will be calculated as -3760, the actual year is -3761BCE
10
+ # This discrepancy is due to the presence of a year 0 in the Ruby implementation
11
+
12
+ CHALAKIM_PER_MINUTE = 18
13
+ CHALAKIM_PER_HOUR = CHALAKIM_PER_MINUTE * 60
14
+ CHALAKIM_PER_DAY = CHALAKIM_PER_HOUR * 24
15
+ CHALAKIM_PER_MONTH = (CHALAKIM_PER_DAY * 29.5).to_i + 793
16
+
17
+ CHALAKIM_MOLAD_TOHU = CHALAKIM_PER_DAY + (CHALAKIM_PER_HOUR * 5) + 204
18
+
19
+ CHESHVAN_KISLEV_KEVIAH = %i(chaseirim kesidran shelaimim)
20
+
21
+ attr_reader :molad_hours, :molad_minutes, :molad_chalakim
22
+ attr_reader :jewish_year, :jewish_month, :jewish_day, :day_of_week, :gregorian_date
23
+
24
+ def initialize(*args)
25
+ if args.size == 0
26
+ reset_date!
27
+ elsif args.size == 3
28
+ set_jewish_date(*args)
29
+ elsif args.size == 1 && args.first.is_a?(Date)
30
+ self.date = args.first
31
+ elsif args.size == 1 && args.first.is_a?(Numeric)
32
+ set_from_molad(args.first)
33
+ else
34
+ raise ArgumentError
35
+ end
36
+ end
37
+
38
+ def self.from_molad(molad)
39
+ new(molad)
40
+ end
41
+
42
+ def self.from_jewish_date(year, month, day)
43
+ new(year, month, day)
44
+ end
45
+
46
+ def self.from_date(date)
47
+ new(date)
48
+ end
49
+
50
+ def reset_date!
51
+ self.date = Date.today
52
+ self
53
+ end
54
+
55
+ def date=(date)
56
+ @gregorian_date = date.gregorian
57
+ @absolute_date = gregorian_date_to_abs_date(gregorian_date)
58
+ reset_day_of_week
59
+ @molad_hours = @molad_minutes = @molad_chalakim = 0
60
+ @jewish_year, @jewish_month, @jewish_day = jewish_date_from_abs_date(@absolute_date)
61
+ end
62
+
63
+ def set_gregorian_date(year, month, day)
64
+ raise ArgumentError if year < -3760 || month < 1 || month > 12 || day < 1 || day > 31
65
+ if day > (max_days = days_in_gregorian_month(month, year))
66
+ day = max_days
67
+ end
68
+ self.date = Date.new(year, month, day, Date::GREGORIAN)
69
+ end
70
+
71
+ def set_jewish_date(year, month, day, hours=0, minutes=0, chalakim=0)
72
+ raise ArgumentError if year < 1 || month < 1 || month > 13 || day < 1 || day > 30 ||
73
+ hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || chalakim < 0 || chalakim > 17
74
+ if month > (max_months = months_in_jewish_year(year))
75
+ month = max_months
76
+ end
77
+ if day > (max_days = days_in_jewish_month(month, year))
78
+ day = max_days
79
+ end
80
+ self.date = gregorian_date_from_abs_date(jewish_date_to_abs_date(year, month, day))
81
+ @molad_hours, @molad_minutes, @molad_chalakim = hours, minutes, chalakim
82
+ end
83
+
84
+ def forward!(increment=1)
85
+ return back!(-increment) if increment < 0
86
+ if increment > 500
87
+ self.date = gregorian_date + increment
88
+ return self
89
+ end
90
+ days_of_year = sorted_days_in_jewish_year
91
+ y, m, d = jewish_year, jewish_month, jewish_day
92
+ d += increment
93
+ while d > (days_in_month = days_of_year.assoc(m)[1]) do
94
+ d -= days_in_month
95
+ m += 1
96
+ m = 1 if m > days_of_year.length
97
+ if m == 7
98
+ y += 1
99
+ days_of_year = sorted_days_in_jewish_year(y)
100
+ end
101
+ end
102
+ @gregorian_date += increment
103
+ @absolute_date += increment
104
+ reset_day_of_week
105
+ @jewish_year, @jewish_month, @jewish_day = y, m, d
106
+ self
107
+ end
108
+
109
+ def back!(decrement=1)
110
+ return forward!(-decrement) if decrement < 0
111
+ if decrement > 500
112
+ self.date = gregorian_date - decrement
113
+ return self
114
+ end
115
+ days_of_year = sorted_days_in_jewish_year
116
+ y, m, d = jewish_year, jewish_month, jewish_day
117
+ d -= decrement
118
+ while d <= 0 do
119
+ m -= 1
120
+ m = days_of_year.length if m == 0
121
+ if m == 6
122
+ y -= 1
123
+ days_of_year = sorted_days_in_jewish_year(y)
124
+ end
125
+ days_in_month = days_of_year.assoc(m)[1]
126
+ d += days_in_month
127
+ end
128
+ @gregorian_date -= decrement
129
+ @absolute_date -= decrement
130
+ reset_day_of_week
131
+ @jewish_year, @jewish_month, @jewish_day = y, m, d
132
+ self
133
+ end
134
+
135
+ def +(addend)
136
+ raise ArgumentError unless addend.is_a?(Numeric)
137
+ self.dup.forward!(addend)
138
+ end
139
+
140
+ def -(subtrahend)
141
+ if subtrahend.is_a?(Numeric)
142
+ self.dup.back!(subtrahend)
143
+ elsif subtrahend.is_a?(JewishDate)
144
+ absolute_date - subtrahend.send(:absolute_date)
145
+ elsif subtrahend.respond_to?(:to_date)
146
+ gregorian_date - subtrahend.to_date
147
+ else
148
+ raise ArgumentError
149
+ end
150
+ end
151
+
152
+ def <=>(other)
153
+ if other.is_a?(JewishDate)
154
+ gregorian_date <=> other.gregorian_date
155
+ else
156
+ gregorian_date <=> other
157
+ end
158
+ end
159
+
160
+ def gregorian_year=(year)
161
+ set_gregorian_date(year, gregorian_month, gregorian_day)
162
+ end
163
+
164
+ def gregorian_year
165
+ gregorian_date.year
166
+ end
167
+
168
+ def gregorian_month=(month)
169
+ set_gregorian_date(gregorian_year, month, gregorian_day)
170
+ end
171
+
172
+ def gregorian_month
173
+ gregorian_date.month
174
+ end
175
+
176
+ def gregorian_day=(day)
177
+ set_gregorian_date(gregorian_year, gregorian_month, day)
178
+ end
179
+
180
+ def gregorian_day
181
+ gregorian_date.day
182
+ end
183
+
184
+ def days_in_gregorian_year(year=gregorian_year)
185
+ gregorian_leap_year?(year) ? 366 : 365
186
+ end
187
+
188
+ def days_in_gregorian_month(month=gregorian_month, year=gregorian_year)
189
+ case month
190
+ when 2
191
+ gregorian_leap_year?(year) ? 29 : 28
192
+ when 4, 6, 9, 11
193
+ 30
194
+ else
195
+ 31
196
+ end
197
+ end
198
+
199
+ def gregorian_leap_year?(year=gregorian_year)
200
+ (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
201
+ end
202
+
203
+ def jewish_year=(year)
204
+ set_jewish_date(year, jewish_month, jewish_day)
205
+ end
206
+
207
+ def jewish_month=(month)
208
+ set_jewish_date(jewish_year, month, jewish_day)
209
+ end
210
+
211
+ def jewish_day=(day)
212
+ set_jewish_date(jewish_year, jewish_month, day)
213
+ end
214
+
215
+ def days_in_jewish_year(year=jewish_year)
216
+ jewish_calendar_elapsed_days(year+1) - jewish_calendar_elapsed_days(year)
217
+ end
218
+
219
+ def months_in_jewish_year(year=jewish_year)
220
+ jewish_leap_year?(year) ? 13 : 12
221
+ end
222
+
223
+ # Returns the list of jewish months for a given jewish year in chronological order
224
+ # sorted_months_in_jewish_year(5779)
225
+ # => [7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6]
226
+ def sorted_months_in_jewish_year(year=jewish_year)
227
+ (1..months_in_jewish_year(year)).sort_by{|y| [y >= 7 ? 0 : 1, y]}
228
+ end
229
+
230
+ # Returns the number of days in each jewish month for a given jewish year in chronological order
231
+ # sorted_days_in_jewish_year(5779)
232
+ # => [[7, 30], [8, 29], [9, 30], [10, 29], [11, 30], [12, 30], [13, 29], [1, 30], [2, 29], [3, 30], [4, 29], [5, 30], [6, 29]]
233
+ def sorted_days_in_jewish_year(year=jewish_year)
234
+ sorted_months_in_jewish_year(year).map{|month| [month, days_in_jewish_month(month, year)]}
235
+ end
236
+
237
+ def days_in_jewish_month(month=jewish_month, year=jewish_year)
238
+ m = jewish_month_name(month)
239
+ if %i(iyar tammuz elul teves adar_ii).include?(m) ||
240
+ (m == :cheshvan && cheshvan_short?(year)) ||
241
+ (m == :kislev && kislev_short?(year)) ||
242
+ (m == :adar && !jewish_leap_year?(year))
243
+ 29
244
+ else
245
+ 30
246
+ end
247
+ end
248
+
249
+ def day_number_of_jewish_year(year=jewish_year, month=jewish_month, day=jewish_day)
250
+ month_index = month_number_from_tishrei(year, month) - 1
251
+ day + sorted_months_in_jewish_year(year)[0...month_index].sum{|m| days_in_jewish_month(m, year)}
252
+ end
253
+
254
+ def jewish_leap_year?(year=jewish_year)
255
+ ((7 * year) + 1) % 19 < 7
256
+ end
257
+
258
+ def cheshvan_long?(year=jewish_year)
259
+ days_in_jewish_year(year) % 10 == 5
260
+ end
261
+
262
+ def cheshvan_short?(year=jewish_year)
263
+ !cheshvan_long?(year)
264
+ end
265
+
266
+ def kislev_long?(year=jewish_year)
267
+ !kislev_short?(year)
268
+ end
269
+
270
+ def kislev_short?(year=jewish_year)
271
+ days_in_jewish_year(year) % 10 == 3
272
+ end
273
+
274
+ def cheshvan_kislev_kviah(year=jewish_year)
275
+ CHESHVAN_KISLEV_KEVIAH[(days_in_jewish_year(year) % 10) - 3]
276
+ end
277
+
278
+ # Returns a new JewishDate as the molad for given month
279
+ def molad(month=jewish_month, year=jewish_year)
280
+ self.class.from_molad(chalakim_since_molad_tohu(year, month))
281
+ end
282
+
283
+ def jewish_month_from_name(month_name)
284
+ MONTHS.index(month_name) + 1
285
+ end
286
+
287
+ def jewish_month_name(month=jewish_month)
288
+ MONTHS[month - 1]
289
+ end
290
+
291
+ private
292
+
293
+ attr_accessor :absolute_date
294
+ attr_writer :gregorian_date
295
+
296
+ def set_from_molad(molad)
297
+ gregorian_date = gregorian_date_from_abs_date(molad_to_abs_date(molad))
298
+ remainder = molad % CHALAKIM_PER_DAY
299
+ molad_hours, remainder = remainder.divmod CHALAKIM_PER_HOUR
300
+ # molad hours start at 18:00, which means that
301
+ # we cross a secular date boundary if hours are 6 or greater
302
+ gregorian_date += 1 if molad_hours >= 6
303
+ self.date = gregorian_date
304
+ # Normalize hours to start at 00:00
305
+ @molad_hours = (molad_hours + 18) % 24
306
+ @molad_minutes, @molad_chalakim = remainder.divmod CHALAKIM_PER_MINUTE
307
+ self
308
+ end
309
+
310
+ def month_number_from_tishrei(year, month)
311
+ leap = jewish_leap_year?(year)
312
+ 1 + ((month + (leap ? 6 : 5)) % (leap ? 13 : 12))
313
+ end
314
+
315
+ def dechiyos_count(year, days, remainder)
316
+ count = 0
317
+ # 'days' is Monday-based due to start of Molad at BaHaRaD
318
+ # add 1 to convert to Sunday-based, '0' represents Shabbos
319
+ rosh_hashana_day = (days+1) % 7
320
+ if (remainder >= 19440) ||
321
+ ((rosh_hashana_day == 3) && (remainder >= 9924) && !jewish_leap_year?(year)) ||
322
+ ((rosh_hashana_day == 2) && (remainder >= 16789) && jewish_leap_year?(year-1))
323
+ count = 1
324
+ end
325
+ count += 1 if [1, 4, 6].include?((rosh_hashana_day+count) % 7)
326
+ count
327
+ end
328
+
329
+ def molad_components_for_year(year)
330
+ chalakim = chalakim_since_molad_tohu(year, 7) # chalakim up to tishrei of given year
331
+ chalakim.divmod(CHALAKIM_PER_DAY)
332
+ end
333
+
334
+ def jewish_calendar_elapsed_days(year)
335
+ days, remainder = molad_components_for_year(year)
336
+ days + dechiyos_count(year, days, remainder)
337
+ end
338
+
339
+ def chalakim_since_molad_tohu(year=jewish_year, month=jewish_month)
340
+ prev_year = year - 1
341
+ months = month_number_from_tishrei(year, month) - 1
342
+ cycles, remainder = prev_year.divmod(19)
343
+ months += (235 * cycles) +
344
+ (12 * remainder) +
345
+ (((7 * remainder) + 1) / 19)
346
+ CHALAKIM_MOLAD_TOHU + (CHALAKIM_PER_MONTH * months)
347
+ end
348
+
349
+ def jewish_date_to_abs_date(year, month, day)
350
+ day_number_of_jewish_year(year, month, day) +
351
+ jewish_year_start_to_abs_date(year) - 1
352
+ end
353
+
354
+ def jewish_year_start_to_abs_date(year)
355
+ jewish_calendar_elapsed_days(year) + JEWISH_EPOCH + 1
356
+ end
357
+
358
+ def jewish_date_from_abs_date(absolute_date)
359
+ jewish_year = (absolute_date - JEWISH_EPOCH) / 366
360
+
361
+ # estimate may be low for CE
362
+ while absolute_date >= jewish_year_start_to_abs_date(jewish_year+1) do
363
+ jewish_year += 1
364
+ end
365
+
366
+ # estimate may be high for BCE
367
+ while absolute_date < jewish_year_start_to_abs_date(jewish_year) do
368
+ jewish_year -= 1
369
+ end
370
+
371
+ months = sorted_months_in_jewish_year(jewish_year)
372
+ jewish_month = months[0..-2].detect.with_index do |m, i|
373
+ absolute_date < jewish_date_to_abs_date(jewish_year, months[i+1], 1)
374
+ end || months.last
375
+
376
+ jewish_day = absolute_date - jewish_date_to_abs_date(jewish_year, jewish_month, 1) + 1
377
+
378
+ [jewish_year, jewish_month, jewish_day]
379
+ end
380
+
381
+ def molad_to_abs_date(chalakim)
382
+ (chalakim / CHALAKIM_PER_DAY) + JEWISH_EPOCH
383
+ end
384
+
385
+ def gregorian_date_from_abs_date(absolute_date)
386
+ RD + (absolute_date - 1)
387
+ end
388
+
389
+ def gregorian_date_to_abs_date(date)
390
+ (date - RD).to_i + 1
391
+ end
392
+
393
+ def reset_day_of_week
394
+ @day_of_week = (gregorian_date.cwday % 7) + 1 # 1-based starting with Sunday
395
+ end
396
+ end
397
+ end