Hebrew Calendar Conversion Sample Code in VB.Net (original) (raw)

Public Module HebCal ' This code demonstrates how to convert a Hebrew date into a ' Gregorian date. The code is written in VB.Net, but I purposely ' used very generic features so it would be easy to translate ' this into other languages. Also, I avoided using many ' optimizations in order to make the logic clearer. ' ' These functions assume that all the current rules of the ' Hebrew calendar were always in existence (which is not true ' since the Hebrew calendar was not always fixed) and all the ' current rules of the Gregorian calendar were always in existence ' (which is not true).

' Here is a very brief description of the Hebrew calendar. ' ' The Hebrew calendar is a lunisolar calendar. This means that ' the months are in sync with the moon and the years stay in sync ' with the sun. A solar year is approximately 365.25 days. A ' lunar month is approximately 29.5 days. Twelve lunar months is ' approximately 354 days (12 * 29.5=354). Thus, a lunar year of ' twelve months is approximately 11.25 days shorter than the solar year. ' To make up for this shortfall, the Hebrew calendar adds a thirteenth ' month to seven years over a nineteen year period. Therefore, over ' a nineteen year period, the Hebrew calendar is approximately the ' same length as a nineteen year solar calendar. ' ' In order to understand this code, you must know the following ' terms: ' Molad - new moon. Hebrew months start around the day of the ' new moon ' Chalakim - 1 / 1080 of an hour or 3 1/3 seconds ' Tishrei - the first month of the Hebrew year (at least for ' these calculations) ' Rosh Hashanah - The Jewish new year which starts on Tishrei 1. ' ' The Hebrew calendar assumes the period of time between one new ' moon to the next is 29 days, 12 hours and 793 chalakim. The first ' molad after creation occurred on Monday, September, 7th -3760 at 5 ' hours and 204 chalakim. Technically, the Gregorian date would be ' in the year 3761 BCE because there was no year 0 in the Gregorian ' calendar, but we will use the year of -3760.

' Sample Usage: ' ' Converts AdarB/7/5765 to 4/6/2005 ' MsgBox(HebCal.HebToGreg(5765, 7, 26)) ' ' Converts 4/6/2005 to AdarB/7/5765. The function will return ' ' a string of "07/26/5765" and nYearH, nMonthH, hDateH will get ' ' set to 5765, 7, 26 ' MsgBox(HebCal.GregToHeb(#04/06/2005#, nYearH, nMonthH, nDateH))

'

' This function returns how many months there has been from the ' first Molad until the beginning of the year nYearH Public Function MonSinceFirstMolad(ByVal nYearH As Integer) _ As Integer Dim nMonSinceFirstMolad As Integer

_' A shortcut to this function can simply be the following formula_
_'   Return Int(((235 * nYearH) - 234) / 19)_
_' This formula is found in Remy Landau's website and he_
_' attributes it to Wolfgang Alexander Shochen. I will use a less_
_' optimized function which I believe shows the underlying logic_
_' better._

_' count how many months there has been in all years up to last_
_' year. The months of this year hasn't happened yet._
nYearH -= 1

_' In the 19 year cycle, there will always be 235 months. That_
_' would be 19 years times 12 months plus 7 extra month for the_
_' leap years. (19 * 12) + 7 = 235._

_' Get how many 19 year cycles there has been and multiply it by_
_' 235_
nMonSinceFirstMolad = Int(nYearH / 19) * 235
_' Get the remaining years after the last complete 19 year cycle_
nYearH = nYearH Mod 19
_' Add 12 months for each of those years_
nMonSinceFirstMolad += 12 * nYearH
_' Add the extra months to account for the leap years_
If nYearH >= 17 Then
  nMonSinceFirstMolad += 6
ElseIf nYearH >= 14 Then
  nMonSinceFirstMolad += 5
ElseIf nYearH >= 11 Then
  nMonSinceFirstMolad += 4
ElseIf nYearH >= 8 Then
  nMonSinceFirstMolad += 3
ElseIf nYearH >= 6 Then
  nMonSinceFirstMolad += 2
ElseIf nYearH >= 3 Then
  nMonSinceFirstMolad += 1
End If
Return nMonSinceFirstMolad

End Function

' This function returns if a given year is a leap year. Public Function IsLeapYear(ByVal nYearH As Integer) As Boolean Dim nYearInCycle As Integer

_' Find out which year we are within the cycle.  The 19th year of_
_' the cycle will return 0_
nYearInCycle = nYearH Mod 19
Return nYearInCycle = 3 Or _
       nYearInCycle = 6 Or _
       nYearInCycle = 8 Or _
       nYearInCycle = 11 Or _
       nYearInCycle = 14 Or _
       nYearInCycle = 17 Or _
       nYearInCycle = 0

End Function

' This function figures out the Gregorian Date that corresponds to ' the first day of Tishrei, the first month of the Hebrew ' calendar, for a given Hebrew year. Public Function Tishrei1(ByVal nYearH As Integer) As Date Dim nMonthsSinceFirstMolad As Integer Dim nChalakim As Integer Dim nHours As Integer Dim nDays As Integer Dim nDayOfWeek As Integer Dim dTishrei1 As Date

_' We want to calculate how many days, hours and chalakim it has_
_' been from the time of 0 days, 0 hours and 0 chalakim to the_
_' molad at the beginning of year nYearH._
_'_
_' The period between one new moon to the next is 29 days, 12_
_' hours and 793 chalakim. We must multiply that by the amount_
_' of months that transpired since the first molad. Then we add_
_' the time of the first molad (Monday, 5 hours and 204 chalakim)_
nMonthsSinceFirstMolad = MonSinceFirstMolad(nYearH)
nChalakim = 793 * nMonthsSinceFirstMolad
nChalakim += 204
_' carry the excess Chalakim over to the hours_
nHours = Int(nChalakim / 1080)
nChalakim = nChalakim Mod 1080

nHours += nMonthsSinceFirstMolad * 12
nHours += 5
_' carry the excess hours over to the days_
nDays = Int(nHours / 24)
nHours = nHours Mod 24

nDays += 29 * nMonthsSinceFirstMolad
nDays += 2

_' figure out which day of the week the molad occurs._
_' Sunday = 1, Moday = 2 ..., Shabbos = 0_
nDayOfWeek = nDays Mod 7

_' In a perfect world, Rosh Hashanah would be on the day of the_
_' molad. The Hebrew calendar makes four exceptions where we_
_' push off Rosh Hashanah one or two days. This is done to_
_' prevent three situation. Without explaining why, the three_
_' situations are:_
_'   1) We don't want Rosh Hashanah to come out on Sunday,_
_'      Wednesday or Friday_
_'   2) We don't want Rosh Hashanah to be on the day of the_
_'      molad if the molad occurs after the beginning of 18th_
_'      hour._
_'   3) We want to limit years to specific lengths.  For non-leap_
_'      years, we limit it to either 353, 354 or 355 days.  For_
_'      leap years, we limit it to either 383, 384 or 385 days._
_'      If setting Rosh Hashanah to the day of the molad will_
_'      cause this year, or the previous year to fall outside_
_'      these lengths, we push off Rosh Hashanah to get the year_
_'      back to a valid length._
_' This code handles these exceptions._

If Not IsLeapYear(nYearH) And _
   nDayOfWeek = 3 And _
   (nHours * 1080) + nChalakim >= _
   (9 * 1080) + 204 Then
  _' This prevents the year from being 356 days. We have to push_
  _' Rosh Hashanah off two days because if we pushed it off only_
  _' one day, Rosh Hashanah would comes out on a Wednesday. Check_
  _' the Hebrew year 5745 for an example._
  nDayOfWeek = 5
  nDays += 2
ElseIf IsLeapYear(nYearH - 1) And _
       nDayOfWeek = 2 And _
       (nHours * 1080) + nChalakim >= _
       (15 * 1080) + 589 Then
  _' This prevents the previous year from being 382 days. Check_
  _' the Hebrew Year 5766 for an example. If Rosh Hashanah was not_
  _' pushed off a day then 5765 would be 382 days_
  nDayOfWeek = 3
  nDays += 1
Else
  _' see rule 2 above. Check the Hebrew year 5765 for an example_
  If nHours >= 18 Then
    nDayOfWeek += 1
    nDayOfWeek = nDayOfWeek Mod 7
    nDays += 1
  End If
  _' see rule 1 above. Check the Hebrew year 5765 for an example_
  If nDayOfWeek = 1 Or _
     nDayOfWeek = 4 Or _
     nDayOfWeek = 6 Then
    nDayOfWeek += 1
    nDayOfWeek = nDayOfWeek Mod 7
    nDays += 1
  End If
End If

_' Here we want to add nDays to creation_
_'    dTishrie1 = creation + nDays_
_' Unfortunately, VB.Net doesn't handle negative years very well._
_' I therefore picked a Random date (1/1/1900) and figured out how_
_' many days it is after the creation (2067025). Then I subtracted_
_' 2067025 from nDays._
nDays -= 2067025
dTishrei1 = #1/1/1900# _' 2067025 days after creation_
dTishrei1 = dTishrei1.AddDays(nDays)
Return dTishrei1

End Function

' This function gets the length of a Hebrew year. Public Function LengthOfYear(ByVal nYearH As Integer) As Integer Dim dThisTishrei1 As Date Dim dNextTishrei1 As Date Dim diff As TimeSpan

_' subtract the date of this year from the date of next year_
dThisTishrei1 = Tishrei1(nYearH)
dNextTishrei1 = Tishrei1(nYearH + 1)
diff = dNextTishrei1.Subtract(dThisTishrei1)
Return diff.Days

End Function

' This function converts a Hebrew date into the Gregorian date ' nYearH - is the Hebrew year ' nMonth - Tishrei=1 ' Cheshvan=2 ' Kislev=3 ' Teves=4 ' Shevat=5 ' Adar A=6 (only valid on leap years) ' Adar=7 (Adar B for leap years) ' Nisan=8 ' Iyar=9 ' Sivan=10 ' Tamuz=11 ' Av=12 ' Elul=13 Public Function HebToGreg(ByVal nYearH As Integer, _ ByVal nMonthH As Integer, _ ByVal nDateH As Integer) As Date Dim nLengthOfYear As Integer Dim bLeap As Boolean Dim dGreg As Date Dim nMonth As Integer Dim nMonthLen As Integer Dim bHaser As Boolean Dim bShalem As Boolean

bLeap = IsLeapYear(nYearH)
nLengthOfYear = LengthOfYear(nYearH)

_' The regular length of a non-leap year is 354 days._
_' The regular length of a leap year is 384 days._
_' On regular years, the length of the months are as follows_
_'   Tishrei (1)   30_
_'   Cheshvan(2)   29_
_'   Kislev  (3)   30_
_'   Teves   (4)   29_
_'   Shevat  (5)   30_
_'   Adar A  (6)   30     (only valid on leap years)_
_'   Adar    (7)   29     (Adar B for leap years)_
_'   Nisan   (8)   30_
_'   Iyar    (9)   29_
_'   Sivan   (10)  30_
_'   Tamuz   (11)  29_
_'   Av      (12)  30_
_'   Elul    (13)  29_
_' If the year is shorter by one less day, it is called a haser_
_' year. Kislev on a haser year has 29 days. If the year is longer_
_' by one day, it is called a shalem year. Cheshvan on a shalem_
_' year is 30 days._
bHaser = nLengthOfYear = 353 Or nLengthOfYear = 383
bShalem = nLengthOfYear = 355 Or nLengthOfYear = 385

_' get the date for Tishrei 1_
dGreg = Tishrei1(nYearH)
_' Now count up days within the year_
For nMonth = 1 To nMonthH - 1
  Select Case nMonth
    Case 1, 5, 8, 10, 12 _' 30 day months_
      nMonthLen = 30
    Case 4, 7, 9, 11, 13 _' 29 day months_
      nMonthLen = 29
    Case 6 _' There is only an Adar A on a leap years_
      nMonthLen = IIf(bLeap, 30, 0)
    Case 2 _' Cheshvan, see note above_
      nMonthLen = IIf(bShalem, 30, 29)
    Case 3 _' Kislev, see note above_
      nMonthLen = IIf(bHaser, 29, 30)
  End Select
  dGreg = dGreg.AddDays(nMonthLen)
Next
dGreg = dGreg.AddDays(nDateH - 1)
Return dGreg

End Function

' This function converts a Gregorian date into the Hebrew date. The ' function returns the hebrew month as a string in the format MM/DD/YYYY. ' Also, the parameters nYearH, nMonthH and hDateH, which are sent by ' reference, will get set the Hebrew year, month and date. See function ' HebToGreg() for the definition of the month numbers. Public Function GregToHeb(ByVal dGreg As Date, _ ByRef nYearH As Integer, _ ByRef nMonthH As Integer, _ ByRef nDateH As Integer) As String Dim nOneMolad As Double Dim nAvrgYear As Double Dim nDays As Integer Dim dTishrei1 As Date Dim nLengthOfYear As Integer Dim bLeap As Boolean Dim bHaser As Boolean Dim bShalem As Boolean Dim nMonthLen As Integer Dim bWhile as Boolean

_' The basic algorythm to get Hebrew date for the Gregorian date dGreg._
_' 1) Find out how many days dGreg is after creation._
_' 2) Based on those days, estimate the Hebrew year_
_' 3) Now that we a good estimate of the Hebrew year, use brute force to_
_'    find the Gregorian date for Tishrei 1 prior to or equal to dGreg_
_' 4) Add to Tishrei 1 the amount of days dGreg is after Tishrei 1_

_' Figure out how many days are in a month._
_' 29 days + 12 hours + 793 chalakim_
nOneMolad = 29 + (12 / 24) + (793 / (1080 * 24))
_' Figure out the average length of a year. The hebrew year has exactly_
_' 235 months over 19 years._
nAvrgYear = nOneMolad * (235 / 19)
_' Get how many days dGreg is after creation. See note as to why I_
_' use 1/1/1900 and add 2067025_
nDays = dGreg.Subtract(#1/1/1900#).Days
nDays += 2067025 _' 2067025 days after creation_

_' Guess the Hebrew year. This should be a pretty accurate guess._
nYearH = Int(CDbl(nDays) / nAvrgYear) + 1
_' Use brute force to find the exact year nYearH. It is the Tishrei 1 in_
_' the year <= dGreg._
dTishrei1 = Tishrei1(nYearH)
If dTishrei1 = dGreg Then
  _' If we got lucky and landed on the exact date, we can stop here_
  nMonthH = 1
  nDateH = 1
Else
  _' Here is the brute force.  Either count up or count down nYearH_
  _' until Tishrei 1 is <= dGreg._
  If dTishrei1 < dGreg Then
    _' If Tishrei 1, nYearH is less than dGreg, count nYearH up._
    Do While Tishrei1(nYearH + 1) <= dGreg
      nYearH += 1
    Loop
  Else
    _' If Tishrei 1, nYearH is greater than dGreg, count nYearH down._
    nYearH -= 1
    Do While Tishrei1(nYearH) > dGreg
      nYearH -= 1
    Loop
  End If

  _' Subtract Tishrei 1, nYearH from dGreg. That should leave us with_
  _' how many days we have to add to Tishrei 1_
  nDays = dGreg.Subtract(Tishrei1(nYearH)).Days

  _' Find out what type of year it is so that we know the length of the_
  _' months_
  nLengthOfYear = LengthOfYear(nYearH)
  bHaser = nLengthOfYear = 353 Or nLengthOfYear = 383
  bShalem = nLengthOfYear = 355 Or nLengthOfYear = 385
  bLeap = IsLeapYear(nYearH)

  _' Add nDays to Tishrei 1._
  nMonthH = 1
  Do
    Select Case nMonthH
      Case 1, 5, 8, 10, 12 _' 30 day months_
        nMonthLen = 30
      Case 4, 7, 9, 11, 13 _' 29 day months_
        nMonthLen = 29
      Case 6 _' Adar A (6) will be skipped on non-leap years_
        nMonthLen = 30
      Case 2 _' Cheshvan, see note above_
        nMonthLen = IIf(bShalem, 30, 29)
      Case 3 _' Kislev, see note above_
        nMonthLen = IIf(bHaser, 29, 30)
    End Select
    If nDays >= nMonthLen Then
      bWhile = True
      If bLeap Or nMonthH <> 5 Then
        nMonthH += 1
      Else
        _' We can skip Adar A (6) if its not a leap year_
        nMonthH += 2
      End If
      nDays -= nMonthLen
    else
      bWhile = False
    End If
  Loop While bWhile
  _' Add the remaining days to Date_
  nDateH = nDays + 1
End If
Return CStr(nMonthH) & "/" & CStr(nDateH) & "/" & CStr(nYearH)

End Function

End Module