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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/Gemfile +6 -0
- data/LICENSE +504 -0
- data/README.md +124 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/zmanim.rb +6 -0
- data/lib/zmanim/astronomical_calendar.rb +105 -0
- data/lib/zmanim/hebrew_calendar/hebrew_date_formatter.rb +243 -0
- data/lib/zmanim/hebrew_calendar/jewish_calendar.rb +390 -0
- data/lib/zmanim/hebrew_calendar/jewish_date.rb +397 -0
- data/lib/zmanim/limudim/anchor/day_of_month_anchor.rb +49 -0
- data/lib/zmanim/limudim/anchor/day_of_week_anchor.rb +26 -0
- data/lib/zmanim/limudim/anchor/day_of_year_anchor.rb +27 -0
- data/lib/zmanim/limudim/calculators/daf_yomi_bavli.rb +53 -0
- data/lib/zmanim/limudim/calculators/daf_yomi_yerushalmi.rb +58 -0
- data/lib/zmanim/limudim/calculators/mishna_yomis.rb +48 -0
- data/lib/zmanim/limudim/calculators/parsha.rb +91 -0
- data/lib/zmanim/limudim/calculators/tehillim_monthly.rb +43 -0
- data/lib/zmanim/limudim/cycle.rb +40 -0
- data/lib/zmanim/limudim/interval.rb +37 -0
- data/lib/zmanim/limudim/limud.rb +42 -0
- data/lib/zmanim/limudim/limud_calculator.rb +137 -0
- data/lib/zmanim/limudim/limudim_formatter.rb +168 -0
- data/lib/zmanim/limudim/unit.rb +53 -0
- data/lib/zmanim/util/astronomical_calculations.rb +34 -0
- data/lib/zmanim/util/geo_location.rb +116 -0
- data/lib/zmanim/util/hebrew_numeric_formatter.rb +67 -0
- data/lib/zmanim/util/math_helper.rb +14 -0
- data/lib/zmanim/util/noaa_calculator.rb +180 -0
- data/lib/zmanim/util/sun_times_calculator.rb +123 -0
- data/lib/zmanim/util/text_helper.rb +7 -0
- data/lib/zmanim/util/time_zone_converter.rb +20 -0
- data/lib/zmanim/version.rb +3 -0
- data/lib/zmanim/zmanim_calendar.rb +106 -0
- data/zmanim.gemspec +37 -0
- 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
|