hebrew_date 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'hebrew_date'
3
+ s.require_paths = %w(. lib lib/hebrewdate)
4
+ s.version = '1.0.0'
5
+ s.date = '2014-01-09'
6
+ s.summary = 'Hebrew/Jewish dates, times, and holidays.'
7
+ s.description = <<-EOF
8
+ hebrewdate is a library designed to provide information about the Jewish
9
+ calendar. This includes dates, times, holidays, and parsha of the week.
10
+
11
+ EOF
12
+ s.authors = ['Daniel Orner']
13
+ s.email = 'dmorner@gmail.com'
14
+ s.files = `git ls-files`.split($/)
15
+ s.homepage = 'https://github.com/dmorner/hebrew_date'
16
+ s.license = 'MIT'
17
+
18
+ # s.add_dependency 'ruby-sun-times'
19
+ s.add_development_dependency 'yard'
20
+ s.add_development_dependency 'rspec'
21
+
22
+ end
@@ -0,0 +1,492 @@
1
+ require 'date'
2
+ require 'delegate'
3
+ require_relative 'support/holidays.rb'
4
+ require_relative 'support/parshiot.rb'
5
+
6
+ # This code is essentially a port of Avrom Finkelstein's RegularHebrewDate
7
+ # Java class (http://web.archive.org/web/20061207174551/http://www.bayt.org/calendar/hebdate.html)
8
+ # which itself implements algorithms presented by Edward M. Reingold and
9
+ # Nachum Dershowitz (http://emr.cs.iit.edu/~reingold/calendars.shtml).
10
+
11
+ # A class that represents a date in both the Gregorian and Hebrew calendars
12
+ # simultaneously. Note that you may call any Date methods on this class
13
+ # and it should respond accordingly.
14
+ class HebrewDate < Delegator
15
+ include HebrewDateSupport::Parshiot
16
+ include HebrewDateSupport::Holidays
17
+
18
+ # @private
19
+ HEBREW_EPOCH = -1373429
20
+
21
+ # @private
22
+ HEBREW_MONTH_NAMES = ['Nissan', 'Iyar', 'Sivan', 'Tammuz', 'Av', 'Elul',
23
+ 'Tishrei', 'Cheshvan', 'Kislev', 'Teves', 'Shvat',
24
+ 'Adar', 'Adar II']
25
+
26
+ class << self
27
+ # @return [Boolean] Whether to use Ashkenazi pronunciation.
28
+ attr_accessor :ashkenaz
29
+
30
+ # @return [Boolean] Whether to use Israeli parsha/holiday scheme.
31
+ attr_accessor :israeli
32
+
33
+ # @private
34
+ attr_accessor :debug
35
+
36
+ end
37
+
38
+ # @return [Integer] the current Gregorian year (e.g. 2008).
39
+ attr_reader :year
40
+
41
+ # @return [Integer] the current Gregorian month (1-12).
42
+ attr_reader :month
43
+
44
+ # @return [Integer] the current Gregorian day of the month (1-31).
45
+ attr_reader :date
46
+
47
+ # @return [Integer] the current Hebrew year (e.g. 5775).
48
+ attr_reader :hebrew_year
49
+
50
+ # @return [Integer] the current Hebrew month (1-13).
51
+ attr_reader :hebrew_month
52
+
53
+ # @return [Integer] the current Hebrew date (1-30).
54
+ attr_reader :hebrew_date
55
+
56
+ # @private
57
+ attr_reader :abs_date
58
+
59
+ def initialize(year_or_date_object=nil, month=nil, date=nil)
60
+ @abs_date = 0
61
+
62
+ if month && date
63
+ @year = year_or_date_object
64
+ @month = month
65
+ @date = date
66
+ else
67
+ date = year_or_date_object || Date.today
68
+ @year = date.year
69
+ @month = date.month
70
+ @date = date.mday
71
+ end
72
+ @israeli = self.class.israeli
73
+ @ashkenaz = self.class.ashkenaz
74
+ @debug = self.class.debug
75
+ set_date(@year, @month, @date)
76
+ super(Date.new)
77
+ end
78
+
79
+ # @private
80
+ def inspect
81
+ strftime('*Y *-m *-d')
82
+ end
83
+
84
+ # @private
85
+ def __getobj__
86
+ self.to_date
87
+ end
88
+
89
+ # @private
90
+ def __setobj__(obj)
91
+ # do nothing
92
+ end
93
+
94
+ # Get a clone of this object.
95
+ # @return [HebrewDate]
96
+ def clone
97
+ HebrewDate.new(self.to_date)
98
+ end
99
+
100
+ # Create a HebrewDate with initialized Hebrew date values.
101
+ # @param year [Integer] the Hebrew year (e.g. 5774)
102
+ # @param month [Integer] the Hebrew month (1-13)
103
+ # @param date [Integer] the day of the Hebrew month (1-30)
104
+ def self.new_from_hebrew(year, month, date)
105
+ d = self.new
106
+ d.set_hebrew_date(year, month, date)
107
+ d
108
+ end
109
+
110
+ # Set the Gregorian date of this HebrewDate.
111
+ # @param year [Integer] the Gregorian year (e.g. 2004)
112
+ # @param month [Integer] the Gregorian month (1-12)
113
+ # @param date [Integer] the Gregorian day of month (1-31)
114
+ # @return [void]
115
+ def set_date(year, month, date)
116
+ # precond should be 1->12 anyways, but just in case...
117
+ return "Illegal value for year: #{year}" if year < 0
118
+ return "Illegal value for month: #{month}" if month > 12 || month < 0
119
+ return "Illegal value for date: #{date}" if date < 0
120
+
121
+ # make sure date is a valid date for the given month.
122
+ # If not, set to last day of month
123
+ last_day = last_day_of_month(month)
124
+ date = last_day if date > last_day
125
+
126
+ @year = year
127
+ @month = month
128
+ @date = date
129
+
130
+ # init the Hebrew date
131
+ @abs_date = _date_to_abs_date(@year, @month, @date)
132
+ _abs_date_to_hebrew_date!
133
+ end
134
+
135
+ # Set the Hebrew date.
136
+ # @param year [Integer] the Hebrew year (e.g. 2008)
137
+ # @param month [Integer] the Hebrew month (1-13)
138
+ # @param date [Integer] the Hebrew day of month (1-30)
139
+ # @return [void]
140
+ def set_hebrew_date(year, month, date)
141
+ return "Illegal value for Hebrew year: #{year}" if year < 0
142
+ if month < 0 || month > last_month_of_hebrew_year
143
+ return "Illegal value for Hebrew month: #{month}"
144
+ end
145
+ return "Illegal value for Hebrew date: #{date}" if date < 0
146
+
147
+ # make sure date is valid for this month;
148
+ # otherwise, set to last day of month
149
+ last_day = last_day_of_hebrew_month(month)
150
+ date = last_day if date > last_day
151
+
152
+ @hebrew_year = year
153
+ @hebrew_month = month
154
+ @hebrew_date = date
155
+
156
+ # reset gregorian date
157
+ @abs_date =
158
+ _hebrew_date_to_abs_date(@hebrew_year, @hebrew_month, @hebrew_date)
159
+ _abs_date_to_date!
160
+
161
+ end
162
+
163
+ # Return a Ruby Date corresponding to the Gregorian date.
164
+ # @return [Date]
165
+ def to_date
166
+ Date.new(@year, @month, @date)
167
+ end
168
+
169
+ # Move forward one day.
170
+ # @return [void]
171
+ def forward
172
+ # Change Gregorian date
173
+ if @date == last_day_of_month
174
+ if @month == 12
175
+ # last day of year
176
+ @year += 1
177
+ @month = 1
178
+ @date = 1
179
+ else
180
+ @month += 1
181
+ @date = 1
182
+ end
183
+ else
184
+ # if not last day of month
185
+ @date += 1
186
+ end
187
+
188
+ # Change Hebrew date
189
+ if @hebrew_date == last_day_of_hebrew_month
190
+ if @hebrew_month == 6
191
+ # last day of Elul (i.e. last day of Hebrew year)
192
+ @hebrew_year += 1
193
+ @hebrew_month += 1
194
+ @hebrew_date = 1
195
+ elsif @hebrew_month == last_month_of_hebrew_year
196
+ # last day of Adar or Adar II
197
+ @hebrew_month = 1
198
+ @hebrew_date = 1
199
+ else
200
+ @hebrew_month += 1
201
+ @hebrew_date = 1
202
+ end
203
+ else
204
+ # not last day of month
205
+ @hebrew_date += 1
206
+ end
207
+
208
+ # increment the absolute date
209
+ @abs_date += 1
210
+ end
211
+
212
+ # Move back one day.
213
+ # @return [void]
214
+ def back
215
+ # Change Gregorian date
216
+ if @date == 1
217
+ if @month == 1
218
+ @month = 12
219
+ @year -= 1
220
+ else
221
+ @month -= 1
222
+ end
223
+
224
+ # change to last day of previous month
225
+ @date = last_day_of_month
226
+ else
227
+ @date -= 1
228
+ end
229
+
230
+ # Change Hebrew date
231
+ if @hebrew_date == 1
232
+ if @hebrew_month == 1 # Nisan
233
+ @hebrew_month = last_month_of_hebrew_year
234
+ elsif @hebrew_month == 7 # Rosh Hashana
235
+ @hebrew_year -= 1
236
+ @hebrew_month -= 1
237
+ else
238
+ @hebrew_month -= 1
239
+ end
240
+ @hebrew_date = last_day_of_hebrew_month
241
+ else
242
+ @hebrew_date -= 1
243
+ end
244
+
245
+ # Change the absolute date
246
+ @abs_date -= 1
247
+ end
248
+
249
+ # Get the name of the current Hebrew month.
250
+ # @return [String]
251
+ def hebrew_month_to_s
252
+ name = HEBREW_MONTH_NAMES[@hebrew_month - 1]
253
+ if name == 'Teves' && !@ashkenaz
254
+ name = 'Tevet'
255
+ end
256
+ name
257
+ end
258
+
259
+ # Extend the Date strftime method by replacing Hebrew fields. You can denote
260
+ # Hebrew fields by using the * flag. Supported flags are:
261
+ # * *Y - Hebrew year
262
+ # * *m - Hebrew month, zero-padded
263
+ # * *_m - Hebrew month, blank-padded
264
+ # * *-m - Hebrew month, no-padded
265
+ # * *B - Hebrew month, full name
266
+ # * *^B - Hebrew month, full name uppercase
267
+ # * *b - Hebrew month, 3 letters
268
+ # * *^b - Hebrew month, 3 letters uppercase
269
+ # * *h - same as %*b
270
+ # * *d - Hebrew day of month, zero-padded
271
+ # * *-d - Hebrew day of month, no-padded
272
+ # * *e - Hebrew day of month, blank-padded
273
+ # @param format [String]
274
+ # @return [String]
275
+ def strftime(format)
276
+ format.gsub!('*Y', @hebrew_year.to_s)
277
+ .gsub!('*m', @hebrew_month.to_s.rjust(2, '0'))
278
+ .gsub!('*_m', @hebrew_month.to_s.rjust(2, ' '))
279
+ .gsub!('*-m', @hebrew_month.to_s)
280
+ .gsub!('*B', hebrew_month_to_s)
281
+ .gsub!('*^B', hebrew_month_to_s.upcase)
282
+ .gsub!('*b', hebrew_month_to_s[0, 3])
283
+ .gsub!('*^b', hebrew_month_to_s[0, 3].upcase)
284
+ .gsub!('*h', hebrew_month_to_s[0, 3])
285
+ .gsub!('*d', @hebrew_date.to_s.rjust(2, '0'))
286
+ .gsub!('*-d', @hebrew_date.to_s)
287
+ .gsub!('*e', @hebrew_date.to_s.rjust(2, ' '))
288
+ end
289
+
290
+ # Get the day of the week.
291
+ # @return [Integer] the day of the week, 1-7.
292
+ def day
293
+ to_date.strftime('%w').to_i + 1
294
+ end
295
+
296
+ # The last day of the Gregorian month.
297
+ # @param month [Integer] Used internally.
298
+ # @return [Integer]
299
+ def last_day_of_month(month=nil)
300
+ month ||= @month
301
+ case month
302
+ when 2
303
+ if (((@year.remainder(4)) == 0) && ((@year.remainder(100)) != 0)) ||
304
+ ((@year.remainder(400)) == 0)
305
+ 29
306
+ else
307
+ 28
308
+ end
309
+ when 4, 6, 9, 11
310
+ 30
311
+ else
312
+ 31
313
+ end
314
+ end
315
+
316
+ # Is this a Hebrew leap year?
317
+ # @param year [Integer] Used internally.
318
+ # @return [Boolean]
319
+ def hebrew_leap_year?(year=nil)
320
+ year ||= @hebrew_year
321
+ (((7 * year) + 1).remainder(19)) < 7
322
+ end
323
+
324
+ # The last month in this Hebrew year (12 or 13).
325
+ # @return [Integer]
326
+ def last_month_of_hebrew_year
327
+ hebrew_leap_year? ? 13 : 12
328
+ end
329
+
330
+ # Last day of the current Hebrew month.
331
+ # @param month [Integer] Used internally.
332
+ # @return [Integer]
333
+ def last_day_of_hebrew_month(month=nil)
334
+ month ||= @hebrew_month
335
+ if month == 2 ||
336
+ month == 4 ||
337
+ month == 6 ||
338
+ (month == 8 && !_cheshvan_long?) ||
339
+ (month == 9 && _kislev_short?) ||
340
+ month == 10 ||
341
+ (month == 12 && !hebrew_leap_year?) ||
342
+ month == 13
343
+ 29
344
+ else
345
+ 30
346
+ end
347
+ end
348
+
349
+ private
350
+
351
+ # Computes the Gregorian date from the absolute date.
352
+ # @return [void]
353
+ def _abs_date_to_date!
354
+ puts "abs_date_to_date #{@abs_date}" if @debug
355
+ # Search forward year by year from approximate year
356
+ @year = (@abs_date / 366.0).to_i
357
+ @year += 1 while @abs_date >= _date_to_abs_date(@year + 1, 1, 1)
358
+
359
+ # Search forward month by month from January
360
+ @month = 1
361
+ @month += 1 while @abs_date >
362
+ _date_to_abs_date(@year, @month, last_day_of_month)
363
+ @date = @abs_date - _date_to_abs_date(@year, @month, 1) + 1
364
+ puts "abs_date_to_date calculated #{@year} #{@month} #{@date}" if @debug
365
+ end
366
+
367
+ # @param month [Integer]
368
+ # @param date [Integer]
369
+ # @param year [Integer]
370
+ # @return [Integer]
371
+ def _date_to_abs_date(year, month, date)
372
+ puts "date_to_abs_date #{year} #{month} #{date}" if @debug
373
+ # loop through days in prior months this year
374
+ (1..month - 1).each do |m|
375
+ date += last_day_of_month(m)
376
+ end
377
+ ret = date + # days this year
378
+ (365 * (year - 1)) + # days in previous years ignoring leap days
379
+ (((year - 1) / 4.0).to_i) - # Julian leap years before this year
380
+ (((year - 1) / 100.0).to_i) + # minus prior century years
381
+ (((year - 1) / 400.0).to_i) # plus prior years divisible by 400
382
+ puts "date_to_abs_date calculated #{ret.to_i}" if @debug
383
+ ret.to_i
384
+ end
385
+
386
+ # @param year [Integer]
387
+ # @return [Integer]
388
+ def _hebrew_calendar_elapsed_days(year)
389
+ puts "hebrew_calendar_elapsed_days #{year}" if @debug
390
+ months_elapsed =
391
+ (235 * ((year - 1) / 19.0).to_i) + # Months in complete cycles so far
392
+ (12 * ((year - 1).remainder(19))) + # Regular months in this cycle
393
+ ((7 * ((year - 1).remainder(19)) + 1) / 19.0).to_i # Leap months this cycle
394
+ parts_elapsed = 204 + 793 * (months_elapsed.remainder(1080))
395
+ hours_elapsed =
396
+ (5 + 12 * months_elapsed + 793 * (months_elapsed / 1080.0).to_i +
397
+ (parts_elapsed/ 1080.0).to_i)
398
+ conjunction_day = (1 + 29 * months_elapsed + (hours_elapsed / 24.0).to_i)
399
+ conjunction_parts = 1080 * (hours_elapsed.remainder(24)) +
400
+ parts_elapsed.remainder(1080)
401
+ alternative_day = conjunction_day
402
+ if (conjunction_parts >= 19440) || # If new moon is at or after midday,
403
+ (((conjunction_day.remainder(7)) == 2) && # ...or is on a Tuesday...
404
+ (conjunction_parts >= 9924) && # at 9 hours, 204 parts or later...
405
+ !hebrew_leap_year?(year)) || # ...of a common year,
406
+ (((conjunction_day.remainder(7)) == 1) && # ...or is on a Monday at...
407
+ (conjunction_parts >= 16789) && # 15 hours, 589 parts or later...
408
+ (hebrew_leap_year?(year - 1))) # at the end of a leap year
409
+ # Then postpone Rosh HaShanah one day
410
+ alternative_day += 1
411
+ end
412
+
413
+ # If Rosh Hashana would occur on Sunday, Wednesday, or Friday
414
+ # then postpone it one more day
415
+ if [0, 3, 5].include?(alternative_day.remainder(7))
416
+ alternative_day += 1
417
+ end
418
+ puts "hebrew_calendar_elapsed_days calculated #{alternative_day}" if @debug
419
+ alternative_day
420
+ end
421
+
422
+ # @return [Integer]
423
+ def _days_in_hebrew_year
424
+ _hebrew_calendar_elapsed_days(@hebrew_year + 1) -
425
+ _hebrew_calendar_elapsed_days(@hebrew_year)
426
+ end
427
+
428
+ # @return [Boolean]
429
+ def _cheshvan_long?
430
+ _days_in_hebrew_year % 10 == 5
431
+ end
432
+
433
+ # @return [Boolean]
434
+ def _kislev_short?
435
+ _days_in_hebrew_year % 10 == 3
436
+ end
437
+
438
+ # @return [void]
439
+ def _abs_date_to_hebrew_date!
440
+ puts "abs_date_to_hebrew_date #{@abs_date}" if @debug
441
+ # Approximation from below.
442
+ @hebrew_year = ((@abs_date + HEBREW_EPOCH) / 366.0).ceil
443
+ # Search forward for year from the approximation.
444
+ while @abs_date >= _hebrew_date_to_abs_date(@hebrew_year + 1, 7, 1) do
445
+ @hebrew_year += 1
446
+ end
447
+ # Search forward for month from either Tishrei or Nissan.
448
+ if @abs_date < _hebrew_date_to_abs_date(@hebrew_year, 1, 1)
449
+ @hebrew_month = 7 # Start at Tishrei
450
+ else
451
+ @hebrew_month = 1 # Start at Nissan
452
+ end
453
+
454
+ while @abs_date > _hebrew_date_to_abs_date(@hebrew_year,
455
+ @hebrew_month,
456
+ last_day_of_hebrew_month) do
457
+ @hebrew_month += 1
458
+ end
459
+ # Calculate the day by subtraction.
460
+ @hebrew_date = @abs_date -
461
+ _hebrew_date_to_abs_date(@hebrew_year, @hebrew_month, 1) + 1
462
+ puts "_abs_date_to_hebrew_date calculated #{@hebrew_year} #{@hebrew_month} #{@hebrew_date}" if @debug
463
+ end
464
+
465
+ # @param month [Integer]
466
+ # @param date [Integer]
467
+ # @param year [Integer]
468
+ # @return [void]
469
+ def _hebrew_date_to_abs_date(year, month, date)
470
+ puts "hebrew_date_to_abs_date #{year} #{month} #{date}" if @debug
471
+ if month < 7 # Before Tishri, so add days in prior months
472
+ # this year before and after Nisan.
473
+ (7..last_month_of_hebrew_year).each do |m|
474
+ date += last_day_of_hebrew_month(m)
475
+ end
476
+
477
+ (1...month).each do |m|
478
+ date += last_day_of_hebrew_month(m)
479
+ end
480
+ else
481
+ # Add days in prior months this year
482
+ (7...month).each do |m|
483
+ date += last_day_of_hebrew_month(m)
484
+ end
485
+ end
486
+ foo = date + _hebrew_calendar_elapsed_days(year) + # Days in prior years.
487
+ HEBREW_EPOCH # Days elapsed before absolute date 1.
488
+ puts "hebrew_date_to_abs_date calculated #{foo}" if @debug
489
+ foo
490
+ end
491
+
492
+ end