Module:Sandbox/maslen/Next Hebrew Date explained

local p =

-- Absolute dates

-- "Absolute date" means the number of days elapsed since the Gregorian date-- Sunday, December 31, 1 BC. (Since there was no year 0, the year following-- 1 BC is 1 AD.) Thus the Gregorian date January 1, 1 AD is absolute date-- number 1.

-- Gregorian dates

function LastDayOfGregorianMonth(month, year)-- Compute the last date of the month for the Gregorian calendar. if month

2 then if ((((year % 4)

0) and ((year % 100) ~= 0)) or ((year % 400)

0)) then return 29; else return 28; end elseif month

2 or month

4 or month

6 or month

9 or month

11 then return 30; else return 31; endend

GregorianDate = GregorianDate.__index = GregorianDate

function GregorianDate.create(month, day, year) local gregoriandate = -- our new object setmetatable(gregoriandate,GregorianDate) -- make GregorianDate handle lookup -- initialize our object gregoriandate.month = month -- 1

January, ..., 12

December gregoriandate.day = day -- 1..LastDayOfGregorianMonth(month, year) gregoriandate.year = year -- return gregoriandateend

function GregorianDateFromAbsolute(d) -- Computes the Gregorian date from the absolute date. -- Search forward year by year from approximate year local year = math.floor(d/366); while (d >= AbsoluteFromGregorianDate(GregorianDate.create(1,1,year+1))) do year = year + 1; end -- Search forward month by month from January local month = 1; while (d > AbsoluteFromGregorianDate(GregorianDate.create(month, LastDayOfGregorianMonth(month,year), year))) do month = month + 1; end local day = d - AbsoluteFromGregorianDate(GregorianDate.create(month,1,year)) + 1; return GregorianDate.create(month, day, year)end

function AbsoluteFromGregorianDate(gregoriandate)-- Computes the absolute date from the Gregorian date. local N = gregoriandate.day; -- days this month -- days in prior months this year local m = gregoriandate.month - 1; while (m > 0) do N = N + LastDayOfGregorianMonth(m, gregoriandate.year); m = m - 1; end return (N -- days this year + 365 * (gregoriandate.year - 1) -- days in previous years ignoring leap days + math.floor((gregoriandate.year - 1)/4) -- Julian leap days before this year... - math.floor((gregoriandate.year - 1)/100) -- ...minus prior century years... + math.floor((gregoriandate.year - 1)/400)); -- ...plus prior years divisible by 400

end

-- Hebrew dates

HebrewEpoch = -1373429 -- Absolute date of start of Hebrew calendar

function HebrewLeapYear(year)-- True if year is an Hebrew leap year if ((((7 * year) + 1) % 19) < 7) then return 1; else return 0; endend

--Last month of Hebrew year.function LastMonthOfHebrewYear(year) if (HebrewLeapYear(year)

1) then return 13; else return 12; endend

function HebrewCalendarElapsedDays(year)-- Number of days elapsed from the Sunday prior to the start of the-- Hebrew calendar to the mean conjunction of Tishri of Hebrew year. local MonthsElapsed = (235 * math.floor((year - 1) / 19)) -- Months in complete cycles so far. + (12 * ((year - 1) % 19)) -- Regular months in this cycle. + math.floor((7 * ((year - 1) % 19) + 1) / 19); -- Leap months this cycle local PartsElapsed = 204 + 793 * (MonthsElapsed % 1080); local HoursElapsed = 5 + 12 * MonthsElapsed + 793 * math.floor(MonthsElapsed / 1080) + math.floor(PartsElapsed / 1080); local ConjunctionDay = 1 + 29 * MonthsElapsed + math.floor(HoursElapsed / 24); local ConjunctionParts = 1080 * (HoursElapsed % 24) + PartsElapsed % 1080; local AlternativeDay = 0; if ((ConjunctionParts >= 19440) -- If new moon is at or after midday, or (((ConjunctionDay % 7)

2) -- ...or is on a Tuesday... and (ConjunctionParts >= 9924) -- at 9 hours, 204 parts or later... and (HebrewLeapYear(year))

0) -- ...of a common year, or (((ConjunctionDay % 7)

1) -- ...or is on a Monday at... and (ConjunctionParts >= 16789) -- 15 hours, 589 parts or later... and (HebrewLeapYear(year - 1)

1))) then -- at the end of a leap year -- Then postpone Rosh HaShanah one day AlternativeDay = ConjunctionDay + 1; else AlternativeDay = ConjunctionDay; end if (((AlternativeDay % 7)

0)-- If Rosh HaShanah would occur on Sunday, or ((AlternativeDay % 7)

3) -- or Wednesday, or ((AlternativeDay % 7)

5)) -- or Friday -- Then postpone it one (more) day then return (1+ AlternativeDay); else return AlternativeDay; endend

function DaysInHebrewYear(year) -- Number of days in Hebrew year. return ((HebrewCalendarElapsedDays(year + 1)) - (HebrewCalendarElapsedDays(year)));end

function LongHeshvan(year) -- True if Heshvan is long in Hebrew year. if ((DaysInHebrewYear(year) % 10)

5) then return 1; else return 0; endend

function ShortKislev(year) -- True if Kislev is short in Hebrew year. if ((DaysInHebrewYear(year) % 10)

3) then return 1; else return 0; endend function LastDayOfHebrewMonth(month, year)-- Last day of month in Hebrew year. if ((month

2) or (month

4) or (month

6) or ((month

8) and LongHeshvan(year)

0) or ((month

9) and ShortKislev(year)

1) or (month

10) or ((month

12) and (HebrewLeapYear(year)

0)) or (month

13)) then return 29; else return 30; endend

HebrewDate = HebrewDate.__index = HebrewDate

function HebrewDate.create(month, day, year) local hebrewdate = -- our new object setmetatable(hebrewdate,HebrewDate) -- make HebrewDate handle lookup -- initialize our object hebrewdate.month = month -- 1... hebrewdate.day = day -- 1..LastMonthOfHebrewYear(year) hebrewdate.year = year -- 1..LastDayOfHebrewMonth(month, year) return hebrewdateend

function HebrewDateFromAbsolute(d) -- Computes the Hebrew date from the absolute date. local year = math.floor((d + HebrewEpoch) / 366); -- Approximation from below. -- Search forward for year from the approximation. while (d >= AbsoluteFromHebrewDate(HebrewDate.create(7,1,year + 1))) do year = year + 1; end -- Search forward for month from either Tishri or Nisan. local month = 0 if (d < AbsoluteFromHebrewDate(HebrewDate.create(1, 1, year))) then month = 7; -- Start at Tishri else month = 1; -- Start at Nisan end while (d > AbsoluteFromHebrewDate(HebrewDate.create(month, (LastDayOfHebrewMonth(month,year)), year))) do month = month + 1; end -- Calculate the day by subtraction. local day = d - AbsoluteFromHebrewDate(HebrewDate.create(month, 1, year)) + 1; return HebrewDate.create(month, day, year) end

function AbsoluteFromHebrewDate(hebrewdate) -- Computes the absolute date of Hebrew date. local DayInYear = hebrewdate.day; -- Days so far this month. if (hebrewdate.month < 7) then -- Before Tishri, so add days in prior months -- this year before and after Nisan. local m = 7; while (m <= (LastMonthOfHebrewYear(hebrewdate.year))) do DayInYear = DayInYear + LastDayOfHebrewMonth(m, hebrewdate.year); m = m + 1; end m = 1; while (m < hebrewdate.month) do DayInYear = DayInYear + LastDayOfHebrewMonth(m, hebrewdate.year); m = m + 1; end else -- Add days in prior months this year local m = 7; while (m < hebrewdate.month) do DayInYear = DayInYear + LastDayOfHebrewMonth(m, hebrewdate.year); m = m + 1; end end return (DayInYear + (HebrewCalendarElapsedDays(hebrewdate.year)-- Days in prior years. + HebrewEpoch)); -- Days elapsed before absolute date 1.end

function find_gregorian_for_next_hebrew_date_occurrence(greg_year, greg_month, greg_day, heb_month, heb_day) local greg_absolute = AbsoluteFromGregorianDate(GregorianDate.create(greg_month, greg_day, greg_year)) local hebrew_date = HebrewDateFromAbsolute(greg_absolute) local heb_year = hebrew_date.year -- Check if we already passed that date this year. If we have, increase the year by 1 local this_years_hebrew_absolute = AbsoluteFromHebrewDate(HebrewDate.create(heb_month, heb_day, heb_year)) if (greg_absolute > this_years_hebrew_absolute) then heb_year = heb_year + 1 end -- Certain months only have 29 days. Advance years until we find a year with 30 days that month. if heb_day

30 then while ((heb_month

8) and not(LongHeshvan(heb_year))) or ((heb_month

9) and ShortKislev(heb_year)) or ((heb_month

12) and (HebrewLeapYear(heb_year)

0)) do heb_year = heb_year + 1 end end

-- The year we're in has the date. Get the absolute and convert it back to a Gregorian date local absolute = AbsoluteFromHebrewDate(HebrewDate.create(heb_month, heb_day, heb_year)) local gregorian = GregorianDateFromAbsolute(absolute) return gregorianend

function GregorianMonthToName(monthNumber) if monthNumber

1 then return "January" elseif monthNumber

2 then return "February" elseif monthNumber

3 then return "March" elseif monthNumber

4 then return "April" elseif monthNumber

5 then return "May" elseif monthNumber

6 then return "June" elseif monthNumber

7 then return "July" elseif monthNumber

8 then return "August" elseif monthNumber

9 then return "September" elseif monthNumber

10 then return "October" elseif monthNumber

11 then return "November" elseif monthNumber

12 then return "December" endend

function p.next_occurrence_of_hebrew_date_from_date(frame) local greg_year = tonumber(frame.args[1]) local greg_month = tonumber(frame.args[2]) local greg_day = tonumber(frame.args[3]) local heb_month = tonumber(frame.args[4]) local heb_day = tonumber(frame.args[5]) gregorian = find_gregorian_for_next_hebrew_date_occurrence(greg_year, greg_month, greg_day, heb_month, heb_day) month_name = GregorianMonthToName(gregorian.month) return month_name.." ".. gregorian.day..", "..gregorian.yearend

function p.next_occurrence_of_hebrew_date(frame) local greg_year = tonumber(os.date("%Y")) local greg_month = tonumber(os.date("%m")) local greg_day = tonumber(os.date("%d")) local heb_month = tonumber(frame.args[1]) local heb_day = tonumber(frame.args[2]) gregorian = find_gregorian_for_next_hebrew_date_occurrence(greg_year, greg_month, greg_day, heb_month, heb_day) month_name = GregorianMonthToName(gregorian.month) return month_name.." ".. gregorian.day..", "..gregorian.yearend

return p