Module:Next Occurrence of Hebrew Date

From IxWiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:Next Occurrence of Hebrew Date/doc

-- Based on public domain code from: http://emr.cs.iit.edu/~reingold/calendar.C

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 == 4 or month == 6 or month == 9 or month == 11 then
    return 30;
  else 
    return 31;
  end
end

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 gregoriandate
end


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;
  end
end
 

--Last month of Hebrew year.
function LastMonthOfHebrewYear(year)
  if (HebrewLeapYear(year) == 1) then
    return 13;
  else
    return 12;
  end
end



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;
  end
end

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;
  end
end

function ShortKislev(year) 
-- True if Kislev is short in Hebrew year.
  
  if ((DaysInHebrewYear(year) % 10) == 3)
  then
    return 1;
  else
    return 0;
  end
end
  
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;
  end
end

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 hebrewdate
end


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 gregorian
end

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"
    end
end


function HebrewMonthNameToNumber(month)
	month = string.lower(month)
    if month == "nisan" then
        return 1
    elseif month == "iyar" then
        return 2
    elseif month == "sivan" then
        return 3
    elseif month == "tammuz" then
        return 4
    elseif month == "av" then
        return 5
    elseif month == "elul" then
        return 6
    elseif month == "tishrei" then
        return 7
    elseif month == "cheshvan" then
        return 8
    elseif month == "kislev" then
        return 9
    elseif month == "tevet" then
        return 10
    elseif month == "shevat" then
        return 11
    elseif month == "adar" then
        return 12
    else
    	return nil
    end
end

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 = frame.args[4]
	local heb_day = tonumber(frame.args[5])
	-- Validate some of the parameters:
	-- heb_month can either be a month name or number
	heb_month_number = HebrewMonthNameToNumber(heb_month)
	if heb_month_number then
		heb_month = heb_month_number
	else
		heb_month = tonumber(heb_month)
	end
	
    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.year
end

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 = frame.args[1]
	local heb_day = tonumber(frame.args[2])
	-- Validate some of the parameters:
	-- heb_month can either be a month name or number
	heb_month_number = HebrewMonthNameToNumber(heb_month)
	if heb_month_number then
		heb_month = heb_month_number
	else
		heb_month = tonumber(heb_month)
	end
	
    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.year
end

return p