hebrew_date 1.0.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.
@@ -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