Module:Calendrical Calculations

-- This code is from pages 361-439 of Calendrical Calculations by Dershowitz and Reingold, translated from Lisp to Lua. Most comments in the code are verbatim from the book
local getArgs = require('Module:Arguments').getArgs
local p = {}
local M = {} -- This namespace is to get around Lua's local variable limit of 200

----- B.1 Lisp Preliminaries -----

M.SUNDAY = 0
M.MONDAY = 1
M.TUESDAY = 2
M.WEDNESDAY = 3
M.THURSDAY = 4
M.FRIDAY = 5
M.SATURDAY = 6

M.BOGUS = "bogus" -- Used to denote nonexistent dates

local function make_public(func, do_unpack)
    -- This is not in the book, but is necessary to make the functions work with #invoke on Wikipedia
    if do_unpack == nil then do_unpack = true end
    return function(frame)
        local args = getArgs(frame)

        -- Convert arguments from strings to numbers, because it breaks otherwise
        local numArgs = {}
        for i, v in ipairs(args) do
            numArgs[i] = tonumber(v) or v
        end

        local result
        if do_unpack then
            -- Pass arguments individually: func(arg1, arg2, ...)
            result = func(unpack(numArgs))
        else
            -- Pass argumemts as a single table: func({arg1, arg2, ...})
            result = func(numArgs)
        end

        if type(result) == "boolean" then
            -- Return 1 for true and 0 for false
            return result and "1" or "0"
        elseif type(result) == "table" then
            -- Handle functions that return tables
            local idx = args.index
            if idx then
                -- If the user provided an 'index' parameter in the template, like {{#invoke:Calendrical Calculations|clock_from_moment|739737.12|index=1}}, then use it
                local val = result[tonumber(idx) or idx]
                -- Apply boolean check to table elements as well in case they contain booleans
                if type(val) == "boolean" then return val and "1" or "0" end
                return tostring(val)
            else
                -- Otherwise return the whole table joined by a separator. So {2026, 4, 30} would become "2026-4-30"
                return table.concat(result, "-")
            end
        else
            -- Submit normal results as a string
            return tostring(result)
        end
    end
end

-- Identity function for fixed dates/moments. If internal timekeeping is shifted, change epoch to be RD date of origin of internal count. Epoch should be an integer.
function M.rd(tee)
    local epoch = 0
    return tee - epoch
end

function M.day_of_week_from_fixed(date)
    -- The residue class of the day of the week of date
    return (date - M.rd(0) - M.SUNDAY) % 7
end
p.day_of_week_from_fixed = make_public(M.day_of_week_from_fixed)

M.JD_EPOCH = M.rd(-1721424.5) -- Fixed time of start of the julian day number

function M.moment_from_jd(jd)
    -- Moment of julian day number jd
    return jd + M.JD_EPOCH
end
p.moment_from_jd = make_public(M.moment_from_jd)

function M.jd_from_moment(tee)
    -- Julian day number of moment tee
    return tee - M.JD_EPOCH
end
p.jd_from_moment = make_public(M.jd_from_moment)

function M.fixed_from_jd(jd)
    -- Fixed date of julian day number jd
    return math.floor(M.moment_from_jd(jd))
end
p.fixed_from_jd = make_public(M.fixed_from_jd)

function M.jd_from_fixed(date)
    -- Fixed date of julian day number jd
    return M.jd_from_moment(date)
end
p.jd_from_fixed = make_public(M.jd_from_fixed)

M.MJD_EPOCH = M.rd(678576) -- Fixed time of start of the modified julian day number

function M.fixed_from_mjd(mjd)
    -- Fixed date of modified julian day number mjd
    return mjd + M.MJD_EPOCH
end
p.fixed_from_mjd = make_public(M.fixed_from_mjd)

function M.mjd_from_fixed(date)
    -- Modified julian day number of fixed date
    return date - M.MJD_EPOCH
end
p.mjd_from_fixed = make_public(M.mjd_from_fixed)

function M.quotient(m, n)
    -- Whole part of m/n
    return math.floor(m / n)
end

function M.amod(x, y)
    -- The value of (x mod y) with y instead of 0
    return y + (x % (-y))
end
p.amod = make_public(M.amod)

function M.mod3(x, a, b)
    -- The value of x shifted into the range [a..b). Returns x if a=b
    if a == b then
        return x
    else
        return a + ((x - a) % (b - a))
    end
end

function M.sign(y)
    if y < 0 then return -1
    elseif y > 0 then return 1
    else return 0 end
end

function M.sum(f, i, p)
    -- Sum expression for i, i+1, i+2, and so on, as long as condition holds
    local s = 0
    while p(i) do
        s = s + f(i)
        i = i + 1
    end
    return s
end

function M.sigma(list, f)
    -- Sum of body for indices il..in running simultaneously thru lists ll..ln.
    local s = 0
    for _, item in ipairs(list) do
        s = s + f(item)
    end
    return s
end

function M.poly(x, a)
    --  Sum powers of x with coefficients (from order 0 up) in list a
    local sum = 0
    for i = #a, 1, -1 do
        sum = (sum * x) + a[i]
    end
    return sum
end

function M.next(i, p)
    -- First integer greater or equal to initial such that condition holds
    while not p(i) do
        i = i + 1
    end
    return i
end

function M.final(i, p)
    -- Last integer greater or equal to initial such that condition holds
    while p(i) do
        i = i + 1
    end
    return i - 1
end

function M.binary_search(lo, hi, test)
    --  Bisection search for x in [lo, hi] such that end holds. test determines when to go left
    local l = lo
    local h = hi
    local x
    while true do
        x = (l + h) / 2
        local is_left, is_end = test(x, l, h)
        if is_end then
            return x
        elseif is_left then
            h = x
        else
            l = x
        end
    end
end

function M.invert_angular(f, y, a, b)
    --  Use bisection to find inverse of angular function f at y within interval [a,b]
    local varespsilon = 0.00001 -- Desired accuracy
    
    return M.binary_search(a, b, function(x, low, high)
        local is_left = ( (f(x) - y) % 360 ) < 180
        local is_end = (high - low) < varespsilon
        return is_left, is_end
    end)
end

----- B.2 Basic Code -----

function M.fixed_from_moment(tee)
    -- Fixed-date from moment tee
    return math.floor(tee)
end
p.fixed_from_moment = make_public(M.fixed_from_moment)

function M.time_from_moment(tee)
    -- Time from moment tee
    return tee % 1
end
p.time_from_moment = make_public(M.time_from_moment)

function M.in_range(tee, range)
    -- True if tee is in range
    return tee >= range[1] and tee <= range[2]
end

function M.list_range(ell, range)
    -- Those moments in list ell that occur in range
    local results = {}
    for _, tee in ipairs(ell) do
        if M.in_range(tee, range) then
            table.insert(results, tee)
        end
    end
    return results
end

function M.time_from_clock(hms)
    -- Time of day from hms = (hour minute second)
    return (1/24) * (hms[1] + (hms[2] + (hms[3] / 60)) / 60)
end
p.time_from_clock = make_public(M.time_from_clock, false)

function M.clock_from_moment(tee)
    -- Clock time hour:minute: second from moment tee
    local time = M.time_from_moment(tee)
    local hour = math.floor(time * 24)
    local minute = math.floor((time * 24 * 60) % 60)
    local second = (time * 24 * 60 * 60) % 60
    return {hour, minute, second}
end
p.clock_from_moment = make_public(M.clock_from_moment)

function M.angle_from_degrees(alpha)
    -- List of degrees-arcminutes-arcseconds from angle alpha in degrees
    local d = math.floor(alpha)
    local m = math.floor(60 * (alpha % 1))
    local s = (alpha * 60 * 60) % 60
    return {d, m, s}
end
p.angle_from_degrees = make_public(M.angle_from_degrees)

----- B.3 The Egyptian/Armenian Calendars -----

M.EGYPTIAN_EPOCH = M.fixed_from_jd(1448638) -- Fixed date of start of the Egyptian (Nabonasser) calendar. JD 1448638 = February 26, 747 BCE (Julian)

function M.fixed_from_egyptian(e_date)
    -- Fixed date of Egyptian date e-date. Not to be confused with the kind of e-date that happens on Discord
    local year, month, day = e_date[1], e_date[2], e_date[3]

    return M.EGYPTIAN_EPOCH -- Days before start of calendar
        + (365 * (year - 1)) -- Days in prior years
        + (30 * (month - 1)) -- Days in prior months this year
        + (day - 1) -- Days so far this month
end
p.fixed_from_egyptian = make_public(M.fixed_from_egyptian, false)

function M.egyptian_from_fixed(date)
    -- Egyptian equivalent of fixed date
    local days = date - M.EGYPTIAN_EPOCH -- Elapsed days since epoch
    local year = M.quotient(days, 365) + 1 -- Years since epoch
    local month = 1 + M.quotient((days % 365), 30) -- Calculate the month by division
    local day = days - (365 * (year - 1)) - (30 * (month - 1)) + 1 -- Calculate the day by subtraction

    return {year, month, day}
end
p.egyptian_from_fixed = make_public(M.egyptian_from_fixed)

M.ARMENIAN_EPOCH = M.rd(201443) -- Fixed date of start of the Armenian calendar. = July 11, 552 CE (Julian)

function M.fixed_from_armenian(a_date)
    -- Fixed date of Armenian date a-date
    return M.ARMENIAN_EPOCH + M.fixed_from_egyptian(a_date) - M.EGYPTIAN_EPOCH
end
p.fixed_from_armenian = make_public(M.fixed_from_armenian, false)

function M.armenian_from_fixed(date)
    -- Armenian equivalent of fixed date
    return M.egyptian_from_fixed(date + (M.EGYPTIAN_EPOCH - M.ARMENIAN_EPOCH))
end
p.armenian_from_fixed = make_public(M.armenian_from_fixed)

----- B.4 Cycles of Days -----

function M.kday_on_or_before(k, date)
    -- Fixed date of the k-day on or before fixed date
    -- k=0 means M.Sunday, k=1 means M.Monday, and so on
    return date - M.day_of_week_from_fixed(date - k)
end
p.kday_on_or_before = make_public(M.kday_on_or_before)

function M.kday_on_or_after(k, date)
    -- Fixed date of the k-day on or after fixed date
    -- k=0 means M.Sunday, k=1 means M.Monday, and so on
    return M.kday_on_or_before(k, date + 6)
end
p.kday_on_or_after = make_public(M.kday_on_or_after)

function M.kday_nearest(k, date)
    -- Fixed date of the k-day nearest fixed date
    -- k=0 means M.Sunday, k=1 means M.Monday, and so on
    return M.kday_on_or_before(k, date + 3)
end
p.kday_nearest = make_public(M.kday_nearest)

function M.kday_after(k, date)
    -- Fixed date of the k-day after fixed date
    -- k=0 means M.Sunday, k=1 means M.Monday, and so on
    return M.kday_on_or_before(k, date + 7)
end
p.kday_after = make_public(M.kday_after)

function M.kday_before(k, date)
    -- Fixed date of the k-day before fixed date
    -- k=0 means M.Sunday, k=1 means M.Monday, and so on
    return M.kday_on_or_before(k, date - 1)
end
p.kday_before = make_public(M.kday_before)

----- B.5 The Gregorian Calendar -----

M.GREGORIAN_EPOCH = M.rd(1) -- Fixed date of start of the (proleptic) Gregorian calendar

M.JANUARY = 1 -- January on Julian/Gregorian calendar
M.FEBRUARY = 2 -- February on Julian/Gregorian calendar
M.MARCH = 3 -- March on Julian/Gregorian calendar
M.APRIL = 4 -- April on Julian/Gregorian calendar
M.MAY = 5 -- May on Julian/Gregorian calendar
M.JUNE = 6 -- June on Julian/Gregorian calendar
M.JULY = 7 -- July on Julian/Gregorian calendar
M.AUGUST = 8 -- August on Julian/Gregorian calendar
M.SEPTEMBER = 9 -- September on Julian/Gregorian calendar
M.OCTOBER = 10 -- October on Julian/Gregorian calendar
M.NOVEMBER = 11 -- November on Julian/Gregorian calendar
M.DECEMBER = 12 -- December on Julian/Gregorian calendar

function M.gregorian_leap_year_p(g_year)
    -- True if g-year is a leap year on the Gregorian calendar
    local mod400 = g_year % 400
    return (g_year % 4 == 0) and not (mod400 == 100 or mod400 == 200 or mod400 == 300)
end
p.gregorian_leap_year_p = make_public(M.gregorian_leap_year_p)

function M.fixed_from_gregorian(g_date)
    -- Fixed date equivalent to the Gregorian date g-date
    local year, month, day = g_date[1], g_date[2], g_date[3]
    
    local correction
    if month <= 2 then
        correction = 0
    elseif M.gregorian_leap_year_p(year) then
        correction = -1
    else
        correction = -2
    end

    return M.GREGORIAN_EPOCH - 1 -- Days before start of calendar
        + (365 * (year - 1)) -- Ordinary days since epoch
        + M.quotient(year - 1, 4) -- Julian leap days since epoch...
        - M.quotient(year - 1, 100) -- ...minus century years since epoch..
        + M.quotient(year - 1, 400) -- ..plus years since epoch divisible by 400
        + M.quotient(367 * month - 362, 12) -- Days in prior months this year assuming 30-day Feb
        + correction -- Correct for 28- or 29-day Feb
        + day -- Days so far this month
end
p.fixed_from_gregorian = make_public(M.fixed_from_gregorian, false)

function M.gregorian_new_year(g_year)
    -- Fixed date of January 1 in g-year
    return M.fixed_from_gregorian({g_year, M.JANUARY, 1})
end
p.gregorian_new_year = make_public(M.gregorian_new_year)

function M.gregorian_year_end(g_year)
    -- Fixed date of December 31 in g-year
    return M.fixed_from_gregorian({g_year, M.DECEMBER, 31})
end
p.gregorian_year_end = make_public(M.gregorian_year_end)

function M.gregorian_year_from_fixed(date)
    -- Gregorian year corresponding to the fixed date
    local d0 = date - M.GREGORIAN_EPOCH -- Prior days
    local n400 = M.quotient(d0, 146097) -- Completed 400-year cycles
    local d1 = d0 % 146097 -- Prior days not in n400
    local n100 = M.quotient(d1, 36524) -- 100-year cycles not in n400
    local d2 = d1 % 36524 -- Prior days not in n400 or n100
    local n4 = M.quotient(d2, 1461) -- 4-year cycles not in n400 or n100
    local d3 = d2 % 1461 -- Prior days not in n400, n100, or n4
    local n1 = M.quotient(d3, 365) -- Years not in n400, n100, or n4
    
    local year = (400 * n400) + (100 * n100) + (4 * n4) + n1
    
    if (n100 == 4 or n1 == 4) then
        return year -- Date is day 366 in a leap year
    else
        return year + 1 -- Date is ordinal day ((d3 % 365) + 1) in (year + 1)
    end
end
p.gregorian_year_from_fixed = make_public(M.gregorian_year_from_fixed)

function M.gregorian_year_range(g_year)
    -- The range of moments in Gregorian year g-year
    return {M.gregorian_new_year(g_year), M.gregorian_year_end(g_year)}
end

function M.gregorian_from_fixed(date)
    -- Gregorian {year, month, day} corresponding to the fixed date
    local year = M.gregorian_year_from_fixed(date)
    local prior_days = date - M.gregorian_new_year(year) -- This year
    
    local correction -- To simulate a 30-day Feb
    if date < M.fixed_from_gregorian({year, M.MARCH, 1}) then
        correction = 0
    elseif M.gregorian_leap_year_p(year) then
        correction = 1
    else
        correction = 2
    end
    
    local month = M.quotient(12 * (prior_days + correction) + 373, 367) -- Assuming a 30-day Feb
    local day = date - M.fixed_from_gregorian({year, month, 1}) + 1 -- Calculate the day by subtraction
    
    return {year, month, day}
end
p.gregorian_from_fixed = make_public(M.gregorian_from_fixed)

function M.gregorian_date_difference(g_date1, g_date2)
    -- Number of days from Gregorian date g-date1 until g-date2
    return M.fixed_from_gregorian(g_date2) - M.fixed_from_gregorian(g_date1)
end

function M.gregorian_date_difference_for_wikipedia(table)
    -- This isn't in the book, but I have to add it so the function would work from Wikipedia
    return M.gregorian_date_difference({table[1], table[2], table[3]}, {table[4], table[5], table[6]})
end
p.gregorian_date_difference = make_public(M.gregorian_date_difference_for_wikipedia, false)

function M.day_number(g_date)
    -- Day number in year of Gregorian date g-date
    return M.gregorian_date_difference({g_date[1] - 1, M.DECEMBER, 31}, g_date)
end
p.day_number = make_public(M.day_number, false)

function M.days_remaining(g_date)
    -- Days remaining in year after Gregorian date g-date
    return M.gregorian_date_difference(g_date, {g_date[1], M.DECEMBER, 31})
end
p.days_remaining = make_public(M.days_remaining, false)

function M.alt_fixed_from_gregorian(g_date)
    -- Alternative calculation of fixed date equivalent to the Gregorian date g-date
    local year, month, day = g_date[1], g_date[2], g_date[3]
    local m = M.amod(month - 2, 12)
    local y = year + M.quotient(month + 9, 12)
    
    return M.GREGORIAN_EPOCH - 1 - 306 -- Days in March...December
           + (365 * (y - 1)) -- Ordinary days since epoch
           + M.uotient(y - 1, 4) -- Julian leap days since epoch...
           - M.quotient(y - 1, 100) -- ...minus century years since epoch...
           + M.quotient(y - 1, 400) -- ...plus years since epoch divisible by 400
           + M.quotient(3 * m - 1, 5) -- Days in prior months this year
           + (30 * (m - 1))
           + day -- Days so far this month
end
p.alt_fixed_from_gregorian = make_public(M.alt_fixed_from_gregorian, false)

function M.alt_gregorian_from_fixed(date)
    -- Alternative calculation of Gregorian {year, month, day} corresponding to fixed date
    local y = M.gregorian_year_from_fixed((M.GREGORIAN_EPOCH - 1) + date + 306)
    local prior_days = date - M.fixed_from_gregorian({y - 1, M.MARCH, 1})
    local month = M.amod(M.quotient((5 * prior_days) + 2, 153) + 3, 12)
    local year = y - M.quotient(month + 9, 12)
    local day = (date - M.fixed_from_gregorian({year, month, 1})) + 1
    
    return {year, month, day}
end
p.alt_gregorian_from_fixed = make_public(M.alt_gregorian_from_fixed)

function M.alt_gregorian_year_from_fixed(date)
    -- Gregorian year corresponding to the fixed date
    local approx = M.quotient(date - M.GREGORIAN_EPOCH + 2, 146097/400) -- approximate year
    local start = M.GREGORIAN_EPOCH + (365 * approx) + M.quotient(approx, 4) - M.quotient(approx, 100) + M.quotient(approx, 400) -- start of next year
    if date < start then
        return approx
    else
        return approx + 1
    end
end
p.alt_gregorian_year_from_fixed = make_public(M.alt_gregorian_year_from_fixed)

function M.independence_day(g_year)
    -- Fixed date of United States Independence Day in Gregorian year g-yaer [sic]
    return M.fixed_from_gregorian({g_year, M.JULY, 4})
end
p.independence_day = make_public(M.independence_day)

function M.nth_kday(n, k, g_date)
    -- Fixed date of n-th k-day after/before Gregorian date g-date. If n>0, return the n-th k-day on or after g-date. If n<0, return the n-th k-day on or before g-date
    -- A k-day of 0 means M.Sunday, 1 means M.Monday, and so on.
    if n > 0 then
        return (7 * n) + M.kday_before(k, M.fixed_from_gregorian(g_date))
    else
        return (7 * n) + M.kday_after(k, M.fixed_from_gregorian(g_date))
    end
end

function M.nth_kday_for_wikipedia(table)
    -- This isn't in the book, but I have to add it so the function would work from Wikipedia
    return M.nth_kday(table[1], table[2], {table[3], table[4], table[5]})
end
p.nth_kday = make_public(M.nth_kday_for_wikipedia, false)

function M.first_kday(k, g_date)
    -- Fixed date of first k-day on or after Gregorian date g-date
    -- A k-day of 0 means M.Sunday, 1 means M.Monday, and so on
    return M.nth_kday(1, k, g_date)
end

function M.first_kday_for_wikipedia(table)
    -- This isn't in the book, but I have to add it so the function would work from Wikipedia
    return M.first_kday(table[1], {table[2], table[3], table[4]})
end
p.first_kday = make_public(M.first_kday_for_wikipedia, false)

function M.last_kday(k, g_date)
    -- Fixed date of last k-day on or before Gregorian date g-date
    -- A k-day of 0 means M.Sunday, 1 means M.Monday, and so on
    return M.nth_kday(-1, k, g_date)
end

function M.last_kday_for_wikipedia(table)
    -- This isn't in the book, but I have to add it so the function would work from Wikipedia
    return M.last_kday(table[1], {table[2], table[3], table[4]})
end
p.last_kday = make_public(M.last_kday_for_wikipedia, false)

function M.labor_day(g_year)
    -- Fixed date of United States Labor Day in Gregorian year g-year (the first Monday in September)
    return M.first_kday(M.MONDAY, {g_year, M.SEPTEMBER, 1})
end
p.labor_day = make_public(M.labor_day)

function M.memorial_day(g_year)
    -- Fixed date of United States Memorial Day in Gregorian year g-year (the last Monday in May)
    return M.last_kday(M.MONDAY, {g_year, M.MAY, 31})
end
p.memorial_day = make_public(M.memorial_day)

function M.election_day(g_year)
    -- Fixed date of United States Election Day in Gregorian year g-year (the Tuesday after the first Monday in November)
    return M.first_kday(M.TUESDAY, {g_year, M.NOVEMBER, 2})
end
p.election_day = make_public(M.election_day)

function M.daylight_saving_start(g_year)
    -- Fixed date of the start of United States daylight saving time in Gregorian year g-year (the second Sunday in March)
    return M.nth_kday(2, M.SUNDAY, {g_year, M.MARCH, 1})
end
p.daylight_saving_start = make_public(M.daylight_saving_start)

function M.daylight_saving_end(g_year)
    --  Fixed date of of the end of United States daylight saving time in Gregorian year g-year (the first Sunday in November)
    return M.first_kday(M.SUNDAY, {g_year, M.NOVEMBER, 1})
end
p.daylight_saving_end = make_public(M.daylight_saving_end)

function M.christmas(g_year)
    -- Fixed date of Christmas in Gregorian year g-year
    return M.fixed_from_gregorian({g_year, M.DECEMBER, 25})
end
p.christmas = make_public(M.christmas)

function M.advent(g_year)
    -- Fixed date of Advent in Gregorian year g-year (the Sunday closest to November 30)
    return M.kday_nearest(M.SUNDAY, M.fixed_from_gregorian({g_year, M.NOVEMBER, 30}))
end
p.advent = make_public(M.advent)

function M.epiphany(g_year)
    -- Fixed date of Epiphany in U.S. in Gregorian year g-year (the first Sunday after January 1)
    return M.first_kday(M.SUNDAY, {g_year, M.JANUARY, 2})
end
p.epiphany = make_public(M.epiphany)

----- B.6 The Julian Calendar -----

function M.julian_leap_year_p(j_year)
    -- True if j-year is a leap year on the Julian calendar.
    if j_year > 0 then
        return (j_year % 4) == 0
    else
        return (j_year % 4) == 3
    end
end
p.julian_leap_year_p = make_public(M.julian_leap_year_p)

M.JULIAN_EPOCH = M.fixed_from_gregorian({0, M.DECEMBER, 30}) -- Fixed date of start of the Julian calendar

function M.fixed_from_julian(j_date)
    -- Fixed date equivalent to the Julian date j-date
    local year, month, day = j_date[1], j_date[2], j_date[3]
    local y
    if year < 0 then
        y = year + 1 -- No year zero
    else
        y = year
    end

    local correction
    if month <= 2 then
        correction = 0
    elseif M.julian_leap_year_p(year) then
        correction = -1
    else
        correction = -2
    end

    return (M.JULIAN_EPOCH - 1) -- Days before start of calendar
        + (365 * (y - 1)) -- Ordinary days since epoch
        + M.quotient(y - 1, 4) -- Leap days since epoch...
        + M.quotient(367 * month - 362, 12) -- Days in prior months this year assuming 30-day Feb
        + correction -- Correct for 28- or 29-day Feb
        + day -- Days so far this month
end
p.fixed_from_julian = make_public(M.fixed_from_julian, false)

function M.julian_from_fixed(date)
    -- Julian {year, month, day} corresponding to fixed date.
    local approx = M.quotient((4 * (date - M.JULIAN_EPOCH)) + 1464, 1461) -- Nominal year
    local year
    if approx <= 0 then
        year = approx - 1 -- No year 0
    else
        year = approx
    end
    
    local prior_days = date - M.fixed_from_julian({year, M.JANUARY, 1}) -- This year
    
    local correction -- To simulate a 30-day Feb
    if date < M.fixed_from_julian({year, M.MARCH, 1}) then
        correction = 0
    elseif M.julian_leap_year_p(year) then
        correction = 1
    else
        correction = 2
    end
    
    local month = M.quotient((12 * (prior_days + correction)) + 373, 367) -- Assuming a 30-day Feb
    local day = 1 + (date - M.fixed_from_julian({year, month, 1})) -- Calculate the day by subtraction
    
    return {year, month, day}
end
p.julian_from_fixed = make_public(M.julian_from_fixed)

M.YEAR_ROME_FOUNDED = -753 -- Year on the Julian calendar of the founding of Rome.

function M.julian_year_from_auc_year(year)
    -- Julian year equivalent to AUC year
    if 1 <= year and year <= -M.YEAR_ROME_FOUNDED then
        return year + M.YEAR_ROME_FOUNDED - 1
    else
        return year + M.YEAR_ROME_FOUNDED
    end
end
p.julian_year_from_auc_year = make_public(M.julian_year_from_auc_year)

function M.auc_year_from_julian_year(year)
    -- AUC equivalent to Julian year
    if M.YEAR_ROME_FOUNDED <= year and year <= - 1 then
        return year - M.YEAR_ROME_FOUNDED + 1
    else
        return year - M.YEAR_ROME_FOUNDED
    end
end
p.auc_year_from_julian_year = make_public(M.auc_year_from_julian_year)

function M.julian_in_gregorian(j_month, j_day, g_year)
    -- List of the fixed dates of Julian month j-month, day j-day that occur in Gregorian year g-year
    local jan1 = M.gregorian_new_year(g_year)
    local y = M.julian_from_fixed(jan1)[1]
    local y_prime
    if y == -1 then
        y_prime = 1
    else
        y_prime = y + 1
    end

    -- The possible occurrences in one year are
    local date1 = M.fixed_from_julian({y, j_month, j_day})
    local date2 = M.fixed_from_julian({y_prime, j_month, j_day})

    return M.list_range({date1, date2}, M.gregorian_year_range(g_year))
end
p.julian_in_gregorian = make_public(M.julian_in_gregorian)

-- Since we can only receive numerical arguments from Wikipedia, I made Kalends = 1, Nones = 2 and Ides = 3
M.KALENDS = 1 -- Class of Kalends
M.NONES = 2 -- Class of Nones
M.IDES = 3 -- Class of Ides

function M.ides_of_month(month)
    -- Date of Ides in Roman month
    local long_months = { [M.MARCH]=true, [M.MAY]=true, [M.JULY]=true, [M.OCTOBER]=true }
    if long_months[month] then
        return 15
    else
        return 13
    end
end
p.ides_of_month = make_public(M.ides_of_month)

function M.nones_of_month(month)
    -- Date of Nones in Roman month
    return M.ides_of_month(month) - 8
end
p.nones_of_month = make_public(M.nones_of_month)

function M.fixed_from_roman(r_date)
    -- Fixed date for Roman name r-date
    local year = r_date[1]
    local month = r_date[2]
    local event = r_date[3]
    local count = r_date[4]
    local leap = r_date[5]
    
    if event == M.KALENDS then
        local correction1
        local correction2
        if (month == M.MARCH) and (M.julian_leap_year_p(year)) and 16 >= count and count >= 6 then
            correction1 = 0 --  After Ides until leap day
        else
            correction1 = 1 -- Otherwise
        end
        if leap then
            correction2 = 1 --  Leap day
        else
            correction2 = 0 -- Non-leap day
        end
        return (M.fixed_from_julian({year, month, 1}) - count) + correction1 + correction2
    elseif event == M.NONES then
        return (M.fixed_from_julian({year, month, M.nones_of_month(month)}) - count) + 1
    else -- event == M.IDES
        return (M.fixed_from_julian({year, month, M.ides_of_month(month)}) - count) + 1
    end
end

function M.fixed_from_roman_for_wikipedia(table)
    -- This isn't in the book, but I have to add it so the function would work from Wikipedia
    return M.fixed_from_roman({table[1], table[2], table[3], table[4], table[5] == 1})
end
p.fixed_from_roman = make_public(M.fixed_from_roman_for_wikipedia, false)

function M.roman_from_fixed(date)
    -- Roman name for fixed date
    local j_date = M.julian_from_fixed(date)
    local year, month, day = j_date[1], j_date[2], j_date[3]

    local month_prime = M.amod(month + 1, 12)
    local year_prime
    if month_prime ~= 1 then
        year_prime = year
    elseif year ~= -1 then
        year_prime = year + 1
    else
        year_prime = 1
    end

    local kalends1 = M.fixed_from_roman({year_prime, month_prime, M.KALENDS, 1, false})
    
    if day == 1 then
        return {year, month, M.KALENDS, 1, false}
    elseif day <= M.nones_of_month(month) then
        return {year, month, M.NONES, (M.nones_of_month(month) - day) + 1, false}
    elseif day <= M.ides_of_month(month) then
        return {year, month, M.IDES, (M.ides_of_month(month) - day) + 1, false}
    elseif (month ~= M.FEBRUARY) or (not M.julian_leap_year_p(year)) then
        -- After the Ides, in a month that is not February of a leap year
        return {year_prime, month_prime, M.KALENDS, (kalends1 - date) + 1, false}
    elseif day < 25 then -- February of a leap year, before leap day
        return {year_prime, month_prime, M.KALENDS, 30 - day, false}
    else -- February of a leap year, on or after leap day
        local count = 31 - day
        return {year_prime, month_prime, M.KALENDS, count, (day == 25)}
    end
end
p.roman_from_fixed = make_public(M.roman_from_fixed)

----- B.7 The Coptic and Ethiopic Calendars -----

M.COPTIC_EPOCH = M.fixed_from_julian({284, M.AUGUST, 29}) -- Fixed date of start of the Coptic calendar

function M.coptic_leap_year_p(c_year)
    -- True if c-year is a leap year on the Coptic calendar
    return c_year % 4 == 3
end
p.coptic_leap_year_p = make_public(M.coptic_leap_year_p)

function M.fixed_from_coptic(c_date)
    -- Fixed date of Coptic date c-date
    local year, month, day = c_date[1], c_date[2], c_date[3]
    return M.COPTIC_EPOCH - 1 -- Days before start of calendar
        + 365 * (year - 1) -- Ordinary days in prior years
        + M.quotient(year, 4) -- Leap days in prior years
        + 30 * (month - 1) -- Days in prior months this year
        + day -- Days so far this month
end
p.fixed_from_coptic = make_public(M.fixed_from_coptic, false)

function M.coptic_from_fixed(date)
    -- Coptic equivalent of fixed date
    local year = M.quotient(4 * (date - M.COPTIC_EPOCH) + 1463, 1461) -- Calculate the year by cycle-of-years formula
    local month = M.quotient(date - M.fixed_from_coptic({year, 1, 1}), 30) + 1 -- Calculate the month by division
    local day = date + 1 - M.fixed_from_coptic({year, month, 1}) -- Calculate the day by subtraction
    return {year, month, day}
end
p.coptic_from_fixed = make_public(M.coptic_from_fixed)

M.ETHIOPIC_EPOCH = M.fixed_from_julian({8, M.AUGUST, 29}) -- Fixed date of start of the Ethiopic calendar

function M.fixed_from_ethiopic(e_date)
    -- Fixed date of Ethiopic date e-date
    return M.ETHIOPIC_EPOCH + M.fixed_from_coptic(e_date) - M.COPTIC_EPOCH
end
p.fixed_from_ethiopic = make_public(M.fixed_from_ethiopic, false)

function M.ethiopic_from_fixed(date)
    -- Ethiopic equivalent of fixed date
    return M.coptic_from_fixed(date + (M.COPTIC_EPOCH - M.ETHIOPIC_EPOCH))
end
p.ethiopic_from_fixed = make_public(M.ethiopic_from_fixed)

function M.coptic_in_gregorian(c_month, c_day, g_year)
    -- List of the fixed dates of Coptic month c-month, day c-day that occur in Gregorian year g-year
    local jan1 = M.gregorian_new_year(g_year)
    local y = M.coptic_from_fixed(jan1)[1]

    -- The possible occurrences in one year are
    local date0 = M.fixed_from_coptic({y, c_month, c_day})
    local date1 = M.fixed_from_coptic({y + 1, c_month, c_day})
    return M.list_range({date0, date1}, M.gregorian_year_range(g_year))
end
p.coptic_in_gregorian = make_public(M.coptic_in_gregorian)

function M.coptic_christmas(g_year)
    -- List of zero or one fixed dates of Coptic Christmas in Gregorian year g-year
    return M.coptic_in_gregorian(4, 29, g_year)
end
p.coptic_christmas = make_public(M.coptic_christmas)

----- B.8 The ISO Calendar -----

function M.fixed_from_iso(i_date)
    -- Fixed date equivalent to ISO i-date
    local year, week, day = i_date[1], i_date[2], i_date[3]
    -- Add fixed date of Sunday preceding date plus day in week
    return M.nth_kday(week, M.SUNDAY, {year - 1, M.DECEMBER, 28}) + day
end
p.fixed_from_iso = make_public(M.fixed_from_iso, false)

function M.iso_from_fixed(date)
    -- ISO (year week day) corresponding to the fixed date
    local approx = M.gregorian_year_from_fixed(date - 3) -- Year may be one too small
    local year
    if date >= M.fixed_from_iso({approx + 1, 1, 1}) then
        year = approx + 1
    else
        year = approx
    end
    local week = M.quotient(date - M.fixed_from_iso({year, 1, 1}), 7) + 1
    local day  = M.amod(date - M.rd(0), 7)
    return {year, week, day}
end
p.iso_from_fixed = make_public(M.iso_from_fixed)

function M.iso_long_year_p(i_year)
    -- True if i-year is a long (53-week) year
    local jan1  = M.day_of_week_from_fixed(M.gregorian_new_year(i_year))
    local dec31 = M.day_of_week_from_fixed(M.gregorian_year_end(i_year))
    return jan1 == M.THURSDAY or dec31 == M.THURSDAY
end
p.iso_long_year_p = make_public(M.iso_long_year_p)

----- B.9 The Islamic Calendar -----

----- B.10 The Hebrew Calendar -----

----- B.11 The Ecclesiastical Calendars -----

function M.eastern_orthodox_christmas(g_year)
    -- List of zero or one fixed dates of Eastern Orthodox Christmas in Gregorian year g-year
    return M.julian_in_gregorian(M.DECEMBER, 25, g_year)
end
p.eastern_orthodox_christmas = make_public(M.eastern_orthodox_christmas)

function M.orthodox_easter(g_year)
    -- Fixed date of Orthodox Easter in Gregorian year g-year.
    local shifted_epact = (14 + 11 * (g_year % 19)) % 30 -- Age of moon for April 5 
    local j_year -- Julian year number
    if g_year > 0 then j_year = g_year else j_year = g_year - 1 end
    local paschal_moon = M.fixed_from_julian({j_year, M.APRIL, 19}) - shifted_epact -- Day after full moon on or after March 21
    -- Return the Sunday following the Paschal moon
    return M.kday_after(M.SUNDAY, paschal_moon)
end
p.orthodox_easter = make_public(M.orthodox_easter)

function M.alt_orthodox_easter(g_year)
    -- Alternative calculation of fixed date of Orthodox Easter in Gregorian year g-year
    local paschal_moon = 354 * g_year -- Day after full moon on or after March 21
        + 30 * M.quotient(7 * g_year + 8, 19)
        + M.quotient(g_year, 4)
        - M.quotient(g_year, 19)
        - 273
        + M.GREGORIAN_EPOCH
    -- Return the Sunday following the Paschal moon
    return M.kday_after(M.SUNDAY, paschal_moon)
end
p.alt_orthodox_easter = make_public(M.alt_orthodox_easter)

function M.easter(g_year)
    -- Fixed date of Easter in Gregorian year g-year
    local century = M.quotient(g_year, 100) + 1
    local shifted_epact = (14 + 11 * (g_year % 19)  -- Age of moon for April 5 by Nicaean rule
        - M.quotient(3 * century, 4) -- ...corrected for the Gregorian century rule
        + M.quotient(5 + 8 * century, 25)) % 30 -- ...corrected for Metonic cycle inaccuracy
    local adjusted_epact -- Adjust for 29.5 day month
    if shifted_epact == 0 or (shifted_epact == 1 and (g_year % 19) > 10) then
        adjusted_epact = shifted_epact + 1
    else
        adjusted_epact = shifted_epact
    end
    local paschal_moon = M.fixed_from_gregorian({g_year, M.APRIL, 19}) - adjusted_epact -- Day after full moon on or after March 21
    -- Return the Sunday following the Paschal moon
    return M.kday_after(M.SUNDAY, paschal_moon)
end
p.easter = make_public(M.easter)

function M.pentecost(g_year)
    -- Fixed date of Pentecost in Gregorian year g-year
    return M.easter(g_year) + 49
end
p.pentecost = make_public(M.pentecost)

----- B.12 The Old Hindu Calendars -----

M.HINDU_EPOCH = M.fixed_from_julian({-3102, M.FEBRUARY, 18}) -- Fixed date of start of the Hindu calendar (Kali Yuga)

function M.hindu_day_count(date)
    return date - M.HINDU_EPOCH -- Elapsed days (Ahargana) to date since Hindu epoch (KY)
end

M.ARYA_JOVIAN_PERIOD = 1577917500/364224 -- Number of days in one revolution of Jupiter around the Sun

function M.jovian_year(date)
    -- Year of Jupiter cycle at fixed date
    return M.amod(27 + M.quotient(M.hindu_day_count(date), M.ARYA_JOVIAN_PERIOD / 12), 60)
end

M.ARYA_SOLAR_YEAR  = 1577917500/4320000 -- Length of Old Hindu solar year
M.ARYA_SOLAR_MONTH = M.ARYA_SOLAR_YEAR / 12 -- Length of Old Hindu solar month

function M.old_hindu_solar_from_fixed(date)
    -- Old Hindu solar date equivalent to fixed date
    local sun = M.hindu_day_count(date) + M.hr(6) -- Sunrise on Hindu date
    local year = M.quotient(sun, M.ARYA_SOLAR_YEAR) -- Elapsed years
    local month = M.quotient(sun, M.ARYA_SOLAR_MONTH) % 12 + 1
    local day = math.floor(sun % M.ARYA_SOLAR_MONTH) + 1
    return {year, month, day}
end
p.old_hindu_solar_from_fixed = make_public(M.old_hindu_solar_from_fixed)

function M.fixed_from_old_hindu_solar(s_date)
    -- Fixed date corresponding to Old Hindu solar date s-date
    local year, month, day = s_date[1], s_date[2], s_date[3]
    return math.ceil(M.HINDU_EPOCH -- Since start of era
        + year * M.ARYA_SOLAR_YEAR -- Days in elapsed years
        + (month - 1) * M.ARYA_SOLAR_MONTH -- ...in months
        + day + M.hr(-30)) -- Midnight of day
end
p.fixed_from_old_hindu_solar = make_public(M.fixed_from_old_hindu_solar, false)

M.ARYA_LUNAR_MONTH = 1577917500/53433336 -- Length of Old Hindu lunar month
M.ARYA_LUNAR_DAY   = M.ARYA_LUNAR_MONTH / 30 -- Length of Old Hindu lunar day

function M.old_hindu_lunar_leap_year(l_year)
    -- True if l-year is a leap year on the old Hindu calendar
    return ((l_year * M.ARYA_SOLAR_YEAR - M.ARYA_SOLAR_MONTH) % M.ARYA_LUNAR_MONTH) >= 23902504679/1282400064
end

function M.old_hindu_lunar_from_fixed(date)
    -- Old Hindu lunar date equivalent to fixed date
    local sun = M.hindu_day_count(date) + M.hr(6) -- Sunrise on Hindu date
    local new_moon = sun - (sun % M.ARYA_LUNAR_MONTH) -- Beginning of lunar month
    local leap = (M.ARYA_SOLAR_MONTH - M.ARYA_LUNAR_MONTH >= (new_moon % M.ARYA_SOLAR_MONTH))
                 and ((new_moon % M.ARYA_SOLAR_MONTH) > 0) -- If lunar contained in solar
    local month = math.ceil(new_moon / M.ARYA_SOLAR_MONTH) % 12 + 1 -- Next solar month's name
    local day   = M.quotient(sun, M.ARYA_LUNAR_DAY) % 30 + 1 -- Lunar days since beginning of lunar month
    local year  = math.ceil((new_moon + M.ARYA_SOLAR_MONTH) / M.ARYA_SOLAR_YEAR) - 1 -- Solar year at end of lunar month(s)
    return {year, month, leap, day}
end
p.old_hindu_lunar_from_fixed = make_public(M.old_hindu_lunar_from_fixed)

function M.fixed_from_old_hindu_lunar(l_date)
    -- Fixed date corresponding to Old Hindu lunar date l-date
    local year  = M.old_hindu_lunar_year(l_date)
    local month = M.old_hindu_lunar_month(l_date)
    local leap  = M.old_hindu_lunar_leap(l_date)
    local day   = M.old_hindu_lunar_day(l_date)
    local mina = (12 * year - 1) * M.ARYA_SOLAR_MONTH -- One solar month before solar new year
    local lunar_new_year = M.ARYA_LUNAR_MONTH * (M.quotient(mina, M.ARYA_LUNAR_MONTH) + 1) -- New moon after mina
    local m
    if (not leap) and math.ceil((lunar_new_year - mina) / (M.ARYA_SOLAR_MONTH - M.ARYA_LUNAR_MONTH)) <= month then
        m = month
    else
        m = month - 1
    end
    return math.ceil(M.HINDU_EPOCH
        + lunar_new_year
        + M.ARYA_LUNAR_MONTH * m
        + (day - 1) * M.ARYA_LUNAR_DAY -- Lunar days
        + M.hr(-6)) -- Subtract 1 if phase begins before sunrise
end

----- B.13 The Mayan Calendars -----

-- Fixed date of start of the Mayan calendar, according to the Goodman-Martinez-Thompson correlation.
-- That is, August 11, -3113
M.MAYAN_EPOCH = M.fixed_from_jd(584283)

function M.fixed_from_mayan_long_count(count)
    -- Fixed date corresponding to the Mayan long count, which is a list (baktun katun tun uinal kin)
    local baktun, katun, tun, uinal, kin = count[1], count[2], count[3], count[4], count[5]
    return M.MAYAN_EPOCH + ((((baktun * 20) + katun) * 20 + tun) * 18 + uinal) * 20 + kin
end
p.fixed_from_mayan_long_count = make_public(M.fixed_from_mayan_long_count, false)

function M.mayan_long_count_from_fixed(date)
    -- Mayan long count date of fixed date
    local long_count = date - M.MAYAN_EPOCH
    local baktun = M.quotient(long_count, 144000)
    local day_of_baktun = long_count % 144000
    local katun = M.quotient(day_of_baktun, 7200)
    local day_of_katun = day_of_baktun % 7200
    local tun = M.quotient(day_of_katun, 360)
    local day_of_tun = day_of_katun % 360
    local uinal = M.quotient(day_of_tun, 20)
    local kin = day_of_tun % 20
    return {baktun, katun, tun, uinal, kin}
end
p.mayan_long_count_from_fixed = make_public(M.mayan_long_count_from_fixed)

function M.mayan_haab_ordinal(h_date)
    -- Number of days into haab cycle of Mayan haab date h-date
    local day, month = h_date[2], h_date[1]
    return (month - 1) * 20 + day
end

M.MAYAN_HAAB_EPOCH = M.MAYAN_EPOCH - M.mayan_haab_ordinal({18, 8}) -- Fixed date of start of haab cycle

function M.mayan_haab_from_fixed(date)
    -- Mayan haab date of fixed date
    local count = (date - M.MAYAN_HAAB_EPOCH) % 365
    local day = count % 20
    local month = M.quotient(count, 20) + 1
    return {month, day}
end
p.mayan_haab_from_fixed = make_public(M.mayan_haab_from_fixed)

function M.mayan_haab_on_or_before(haab, date)
    -- Fixed date of latest date on or before fixed date that is Mayan haab date haab
    return M.mod3(M.mayan_haab_ordinal(haab) + M.MAYAN_HAAB_EPOCH, date - 365, date)
end

function M.mayan_tzolkin_ordinal(t_date)
    -- Number of days into Mayan tzolkin cycle of t-date
    local number, name = t_date[1], t_date[2]
    return (number - 1 + 39 * (number - name)) % 260
end

M.MAYAN_TZOLKIN_EPOCH = M.MAYAN_EPOCH - M.mayan_tzolkin_ordinal({4, 20}) -- Start of tzolkin date cycle

function M.mayan_tzolkin_from_fixed(date)
    -- Mayan tzolkin date of fixed date
    local count = date - M.MAYAN_TZOLKIN_EPOCH + 1
    local number = M.amod(count, 13)
    local name   = M.amod(count, 20)
    return {number, name}
end
p.mayan_tzolkin_from_fixed = make_public(M.mayan_tzolkin_from_fixed)

function M.mayan_tzolkin_on_or_before(tzolkin, date)
    -- Fixed date of latest date on or before fixed date that is Mayan tzolkin date tzolkin
    return M.mod3(M.mayan_tzolkin_ordinal(tzolkin) + M.MAYAN_TZOLKIN_EPOCH, date - 260, date)
end

function M.mayan_year_bearer_from_fixed(date)
    -- Year bearer of year containing fixed date. Returns BOGUS for uayeb
    local x = M.mayan_haab_on_or_before({1, 0}, date)
    if M.mayan_haab_month(M.mayan_haab_from_fixed(date)) == 19 then
        return M.BOGUS
    else
        return M.mayan_tzolkin_from_fixed(x)[2]
    end
end
p.mayan_year_bearer_from_fixed = make_public(M.mayan_year_bearer_from_fixed)

function M.mayan_calendar_round_on_or_before(haab, tzolkin, date)
    -- Fixed date of latest date on or before date, that is Mayan haab date haab and an tzolkin date tzolkin.
    -- Returns bogus for impossible combinations
    local haab_count = M.mayan_haab_ordinal(haab) + M.MAYAN_HAAB_EPOCH
    local tzolkin_count = M.mayan_tzolkin_ordinal(tzolkin) + M.MAYAN_TZOLKIN_EPOCH
    local diff = tzolkin_count - haab_count
    if diff % 5 == 0 then
        return M.mod3(haab_count + 365 * diff, date - 18980, date)
    else
        return M.BOGUS -- haab-tzolkin combination is impossible
    end
end

-- Aztec calendars

M.AZTEC_CORRELATION = M.fixed_from_julian({1521, M.AUGUST, 13}) -- Known date of Aztec cycles (Caso's correlation)

function M.aztec_xihuitl_ordinal(x_date) -- Number of elapsed days into cycle of Aztec xihuitl x-date
    local day, month = x_date[2], x_date[1]
    return (month - 1) * 20 + (day - 1)
end

M.AZTEC_XIHUITL_CORRELATION = M.AZTEC_CORRELATION - M.aztec_xihuitl_ordinal({11, 2}) -- Start of a xihuitl cycle

function M.aztec_xihuitl_from_fixed(date)
    -- Aztec xihuitl date of fixed date
    local count = (date - M.AZTEC_XIHUITL_CORRELATION) % 365
    local day = (count % 20) + 1
    local month = M.quotient(count, 20) + 1
    return {month, day}
end
p.aztec_xihuitl_from_fixed = make_public(M.aztec_xihuitl_from_fixed)

function M.aztec_xihuitl_on_or_before(xihuitl, date)
    -- Fixed date of latest date on or before date that is Aztec xihuitl xihuitl
    return M.mod3(M.AZTEC_XIHUITL_CORRELATION + M.aztec_xihuitl_ordinal(xihuitl), date - 365, date)
end

function M.aztec_tonalpohualli_ordinal(t_date)
    -- Number of days into Aztec tonalpohualli cycle of t-date
    local number, name = t_date[1], t_date[2]
    return (number - 1 + 39 * (number - name)) % 260
end

M.AZTEC_TONALPOHUALLI_CORRELATION = M.AZTEC_CORRELATION - M.aztec_tonalpohualli_ordinal({1, 5}) -- Start of a tonalpohualli date cycle

function M.aztec_tonalpohualli_from_fixed(date)
    -- Aztec tonalpohualli date of fixed date
    local count  = date - M.AZTEC_TONALPOHUALLI_CORRELATION + 1
    local number = M.amod(count, 13)
    local name   = M.amod(count, 20)
    return {number, name}
end
p.aztec_tonalpohualli_from_fixed = make_public(M.aztec_tonalpohualli_from_fixed)

function M.aztec_tonalpohualli_on_or_before(tonalpohualli, date)
    -- Fixed date of latest date on or before date that is Aztec tonalpohualli tonalpohualli
    return M.mod3(M.AZTEC_TONALPOHUALLI_CORRELATION + M.aztec_tonalpohualli_ordinal(tonalpohualli),
                date - 260, date)
end

function M.aztec_xiuhmolpilli_from_fixed(date)
    -- Designation of year containing fixed date. Returns BOGUS for nemontemi
    local x = M.aztec_xihuitl_on_or_before({18, 20}, date + 364)
    local month = M.aztec_xihuitl_from_fixed(date)[1]
    if month == 19 then return M.BOGUS
    else return M.aztec_tonalpohualli_from_fixed(x) end
end

function M.aztec_xihuitl_tonalpohualli_on_or_before(xihuitl, tonalpohualli, date)
    -- Fixed date of latest xihuitl-tonalpohualli combination on or before date. That is the date on or before date that is Aztec xihuitl date xihuitl and tonalpohualli date tonalpohualli
    -- Returns bogus for impossible combinations.
    local xihuitl_count       = M.aztec_xihuitl_ordinal(xihuitl) + M.AZTEC_XIHUITL_CORRELATION
    local tonalpohualli_count = M.aztec_tonalpohualli_ordinal(tonalpohualli) + M.AZTEC_TONALPOHUALLI_CORRELATION
    local diff = tonalpohualli_count - xihuitl_count
    if diff % 5 == 0 then
        return M.mod3(xihuitl_count + 365 * diff, date - 18980, date)
    else
        return M.BOGUS -- xihuitl-tonalpohualli combination is impossible
    end
end

----- B.14 The Balinese Pawukon Calendar -----

----- B.15 Time and Astronomy -----
-- I had to rearrange some of the functions here, because the code in the book often calls constants and functions before defining them, which is not possible in Lua

function M.degrees(theta)
    -- Normalize angle theta to range [0,360) degrees
    return theta % 360
end
p.degrees = make_public(M.degrees)

function M.radians_from_degrees(theta)
    -- Convert angle theta from degrees to radians
    return M.degrees(theta) * math.pi / 180
end
p.radians_from_degrees = make_public(M.radians_from_degrees)

function M.degrees_from_radians(theta)
    -- Convert angle theta from degrees to radians
    return M.degrees(theta * 180 / math.pi)
end
p.degrees_from_radians = make_public(M.degrees_from_radians)

function M.sin_degrees(theta) return math.sin(M.radians_from_degrees(theta)) end -- Sine of theta (given in degrees)
function M.cosine_degrees(theta) return math.cos(M.radians_from_degrees(theta)) end -- Cosine of theta (given in degrees)
function M.tangent_degrees(theta) return math.tan(M.radians_from_degrees(theta)) end -- Tangent of theta (given in degrees)

function M.arctan_degrees(y, x)
    -- Arctangent of y/x in degrees
    local result
    if x == 0 and y ~= 0 then
        result = M.sign(y) * 90
    else
        local alpha = M.degrees_from_radians(math.atan(y / x))
        if x >= 0 then result = alpha
        else result = alpha + 180 end
    end
    return result % 360
end

function M.arcsin_degrees(x) return M.degrees_from_radians(math.asin(x)) end -- Arcsine of x in degrees
function M.arccos_degrees(x) return M.degrees_from_radians(math.acos(x)) end -- Arccosine of x in degrees

function M.hr(x)  return x / 24 end -- x hours
function M.mn(x)  return x / (24 * 60) end -- x minutes
function M.sec(x) return x / (24 * 60 * 60) end -- x seconds
function M.mins(x) return x / 60 end -- x arcminutes -> degrees
function M.secs(x) return x / 3600 end -- x arcseconds -> degrees

function M.angle(d, m, s)
    -- d degrees, m arcminutes, s arcseconds
    return d + (m + s/60) / 60
end
p.angle = make_public(M.angle)

M.MECCA = {M.angle(21, 25, 24), M.angle(39, 49, 24), 298, M.hr(2)}
M.JERUSALEM = {31.8, 35.2, 298, M.hr(2)}

function M.direction(locale, focus)
    -- Angle (clockwise from North) to face focus when standing in locale. Subject to errors near focus and its antipode
    local phi = locale[1]
    local phi_prime = focus[1]
    local psi = locale[2]
    local psi_prime = focus[2]
    local y = M.sin_degrees(psi_prime - psi)
    local x = M.cosine_degrees(phi) * M.tangent_degrees(phi_prime)
        - M.sin_degrees(phi) * M.cosine_degrees(psi - psi_prime)
    if (x == 0 and y == 0) or phi_prime == 90 then return 0
    elseif phi_prime == -90 then return 180
    else return M.arctan_degrees(y, x) end
end

function M.direction_for_wikipedia(table)
    -- This isn't in the book, but I have to add it so the function would work from Wikipedia
    return M.direction({table[1], table[2], table[3], table[4], table[5]}, {table[6], table[7], table[8], table[9], table[10]})
end
p.direction = make_public(M.direction_for_wikipedia, false)

function M.zone_from_longitude(phi)
    -- Difference between UT and local mean time at longitude phi (fraction of day)
    return phi / 360
end

function M.universal_from_local(tee_ell, locale)
    -- Universal time from local tee_ell at locale
    return tee_ell - M.zone_from_longitude(locale[2])
end

function M.local_from_universal(tee_rom_u, locale)
    -- Local time from universal tee_rom-u at locale
    return tee_rom_u + M.zone_from_longitude(locale[2])
end

function M.standard_from_universal(tee_rom_u, locale)
    --  Standard time from tee_rom-u in universal time at locale
    return tee_rom_u + locale[4]
end

function M.universal_from_standard(tee_rom_s, locale)
    --  Universal time from tee_rom-s in standard time at locale
    return tee_rom_s - locale[4]
end

function M.standard_from_local(tee_ell, locale)
    -- Standard time from local tee_ell at locale
    return M.standard_from_universal(M.universal_from_local(tee_ell, locale), locale)
end

function M.local_from_standard(tee_rom_s, locale)
    -- Local time from standard tee_rom-s at locale
    return M.local_from_universal(M.universal_from_standard(tee_rom_s, locale), locale)
end

M.ephemeris_correction = function(tee)
    -- Dynamical Time minus Universal Time (in days) for moment tee
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 1991
    local year = M.gregorian_year_from_fixed(math.floor(tee))
    local c = M.gregorian_date_difference({1900, M.JANUARY, 1}, {year, M.JULY, 1}) / 36525
    local x = M.hr(12) + M.gregorian_date_difference({1810, M.JANUARY, 1}, {year, M.JANUARY, 1})

    if 1988 <= year and year <= 2019 then
        return (1/86400) * (year - 1933)
    elseif 1900 <= year and year <= 1987 then
        return M.poly(c, {-0.00002, 0.000297, 0.025184, -0.181133, 0.553040, -0.861938, 0.677066, -0.212591})
    elseif 1800 <= year and year <= 1899 then
        return M.poly(c, {-0.000009, 0.003844, 0.083563, 0.865736, 4.867575, 15.845535, 31.332267, 38.291999, 28.316289, 11.636204, 2.043794})
    elseif 1700 <= year and year <= 1799 then
        return (1/86400) * M.poly((year - 1700), {8.118780842, -0.005092142, 0.003336121, -0.0000266484})
    elseif 1620 <= year and year <= 1699 then
        return (1/86400) * M.poly((year - 1600), {196.58333, -4.0675, 0.0219167})
    else
        return (1/86400) * ((x * x) / 41048480 - 15)
    end
end

function M.dynamical_from_universal(tee)
    -- Dynamical time at Universal moment tee
    return tee + M.ephemeris_correction(tee)
end

function M.universal_from_dynamical(tee)
    -- Universal moment from Dynamical time tee
    return tee - M.ephemeris_correction(tee)
end

M.J2000 = M.hr(12) + M.gregorian_new_year(2000) -- Noon at start of Gregorian year 2000

function M.julian_centuries(tee)
    -- Julian centuries since 2000 at moment tee
    return (M.dynamical_from_universal(tee) - M.J2000) / 36525
end
p.julian_centuries = make_public(M.julian_centuries)

function M.obliquity(tee)
    -- Obliquity of ecliptic at moment tee
    local c = M.julian_centuries(tee)
    return M.angle(23, 26, 21.448)
        + M.poly(c, {0, M.angle(0, 0, -46.8150), M.angle(0, 0, -0.00059), M.angle(0, 0, 0.001813)})
end

function M.declination(tee, beta, lambda)
    -- Declination at moment UT tee of object at latitude beta and longitude lambda
    local varespsilon = M.obliquity(tee)
    return M.arcsin_degrees(M.sin_degrees(beta) * M.cosine_degrees(varespsilon)
        + M.cosine_degrees(beta) * M.sin_degrees(varespsilon) * M.sin_degrees(lambda))
end

function M.right_ascension(tee, beta, lambda)
    -- Right ascension at moment UT tee of object at latitude beta and longitude lambda
    -- Note: The comment in the book confusingly says "latitude lambda and longitude beta" here. But this seems to be a typo, as it doesn't match with page 186
    local varespsilon = M.obliquity(tee)
    return M.arctan_degrees(
        M.sin_degrees(lambda) * M.cosine_degrees(varespsilon) - M.tangent_degrees(beta) * M.sin_degrees(varespsilon),
        M.cosine_degrees(lambda))
end

M.equation_of_time = function(tee)
    -- Equation of time (as fraction of day) for moment tee.
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 1991
    local c = M.julian_centuries(tee)
    local lambda  = M.poly(c, {280.46645, 36000.76983, 0.0003032})
    local anomaly = M.poly(c, {357.52910, 35999.05030, -0.0001559, -0.00000048})
    local eccentricity = M.poly(c, {0.016708617, -0.000042037, -0.0000001236})
    local varespsilon = M.obliquity(tee)
    local y = M.tangent_degrees(varespsilon / 2) ^ 2
    local equation = (1 / (2 * math.pi)) * (
        y * M.sin_degrees(2 * lambda)
        - 2 * eccentricity * M.sin_degrees(anomaly)
        + 4 * eccentricity * y * M.sin_degrees(anomaly) * M.cosine_degrees(2 * lambda)
        - 0.5 * y * y * M.sin_degrees(4 * lambda)
        - 1.25 * eccentricity * eccentricity * M.sin_degrees(2 * anomaly))
    return M.sign(equation) * math.min(math.abs(equation), M.hr(12))
end
p.equation_of_time = make_public(M.equation_of_time)

function M.apparent_from_local(tee, locale)
    -- Sundial time at local time tee at locale
    return tee + M.equation_of_time(M.universal_from_local(tee, locale))
end

function M.local_from_apparent(tee, locale)
    -- Local time from sundial time tee at locale
    return tee - M.equation_of_time(M.universal_from_local(tee, locale))
end

function M.midday(date, locale)
    -- Standard time on fixed date of midday at locale
    return M.standard_from_local(M.local_from_apparent(date + M.hr(12), locale), locale)
end

function M.midnight(date, locale)
    --  Standard time on fixed date of true (apparent) midnight at locale
    return M.standard_from_local(M.local_from_apparent(date, locale), locale)
end

function M.sidereal_from_moment(tee)
    -- Mean sidereal time of day from moment tee expressed as hour angle
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 1991
    local c = (tee - M.J2000) / 36525
    return M.poly(c, {280.46061837, 36525 * 360.98564736629, 0.000387933, -1/38710000}) % 360
end

M.nutation = function(tee)
    -- Longitudinal nutation at moment tee
    local c = M.julian_centuries(tee) -- moment in Julian centuries
    local cap_A = M.poly(c, {124.90, -1934.134, 0.002063})
    local cap_B = M.poly(c, {201.11, 72001.5377, 0.00057})
    return -0.004778 * M.sin_degrees(cap_A) - 0.0003667 * M.sin_degrees(cap_B)
end

M.aberration = function(tee)
    -- Aberration at moment tee
    local c = M.julian_centuries(tee) -- moment in Julian centuries
    return 0.0000974 * M.cosine_degrees(177.63 + 35999.01848 * c) - 0.005575
end

M.MEAN_TROPICAL_YEAR_AST = 365.242189
M.MEAN_SIDEREAL_YEAR = 365.25636

M.solar_longitude = function(tee)
    -- Longitude of sun at moment tee
    -- Adapted from "Planetary Programs and Tables from -4000 to +2800" by Pierre Bretagnon and Jean-Louis Simon, Willmann-Bell, Inc., 1986
    local c = M.julian_centuries(tee) -- moment in Julian centuries
    local coefficients = {
        403406, 195207, 119433, 112392, 3891, 2819, 1721,
        660, 350, 334, 314, 268, 242, 234, 158, 132, 129, 114,
        99, 93, 86, 78, 72, 68, 64, 46, 38, 37, 32, 29, 28, 27, 27,
        25, 24, 21, 21, 20, 18, 17, 14, 13, 13, 13, 12, 10, 10, 10, 10
    }
    local multipliers = {
        0.9287892, 35999.1376958, 35999.4089666,
        35998.7287385, 71998.20261, 71998.4403,
        36000.35726, 71997.4812, 32964.4678,
        -19.4410, 445267.1117, 45036.8840, 3.1008,
        22518.4434, -19.9739, 65928.9345,
        9038.0293, 3034.7684, 33718.148, 3034.448,
        -2280.773, 29929.992, 31556.493, 149.588,
        9037.750, 107997.405, -4444.176, 151.771,
        67555.316, 31556.080, -4561.540,
        107996.706, 1221.655, 62894.167,
        31437.369, 14578.298, -31931.757,
        34777.243, 1221.999, 62894.511,
        -4442.039, 107997.909, 119.066, 16859.071,
        -4.578, 26895.292, -39.127, 12297.536, 90073.778
    }
    local addends = {
        270.54861, 340.19128, 63.91854, 331.26220,
        317.843, 86.631, 240.052, 310.26, 247.23,
        260.87, 297.82, 343.14, 166.79, 81.53,
        3.50, 132.75, 182.95, 162.03, 29.8,
        266.4, 249.2, 157.6, 257.8, 185.1, 69.9,
        8.0, 197.1, 250.4, 65.3, 162.7, 341.5,
        291.6, 98.5, 146.7, 110.0, 5.2, 342.6,
        230.9, 256.1, 45.3, 242.9, 115.2, 151.8,
        285.3, 53.3, 126.6, 205.7, 85.9, 146.1
    }
    local s = 0
    for i = 1, #coefficients do
        s = s + coefficients[i] * M.sin_degrees(addends[i] + multipliers[i] * c)
    end
    local lambda = 282.7771834 + 36000.76953744 * c + 0.000005729577951308232 * s
    return (lambda + M.aberration(tee) + M.nutation(tee)) % 360
end

M.SPRING = 0 -- Longitude of sun at vernal equinox
M.SUMMER = 90 -- Longitude of sun at summer solstice
M.AUTUMN = 180 -- Longitude of sun at autumnal equinox
M.WINTER = 270 -- Longitude of sun at winter solstice

function M.solar_longitude_after(lambda, tee)
    -- Moment UT of first time at or after tee when the solar longitude will be lambda degrees
    local rate = M.MEAN_TROPICAL_YEAR_AST / 360  -- Mean days for 1 degree change
    local tau = tee + rate * ((lambda - M.solar_longitude(tee)) % 360) -- Estimate within 5 days
    local a = math.max(tee, tau - 5) -- At or after tee
    local b = tau + 5
    return M.invert_angular(M.solar_longitude, lambda, a, b)
end

function M.precession(tee)
    -- Precession at moment tee using 0,0 as J2000 coordinates
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 1991
    local c = M.julian_centuries(tee)
    local eta = M.poly(c, {0, M.secs(47.0029), M.secs(-0.03302), M.secs(0.000060)}) % 360
    local cap_P = M.poly(c, {174.876384, M.secs(-869.8089), M.secs(0.03536)}) % 360
    local p = M.poly(c, {0, M.secs(5029.0966), M.secs(1.11113), M.secs(0.000006)}) % 360
    local cap_A = M.cosine_degrees(eta) * M.sin_degrees(cap_P)
    local cap_B = M.cosine_degrees(cap_P)
    local arg = M.arctan_degrees(cap_A, cap_B)
    return ((p + cap_P) - arg) % 360
end

-- Skip sidereal_solar_longitude for now, as I need to do the Hindu calendars for it to work

function M.estimate_prior_solar_longitude(lambda, tee)
    -- Approximate moment at or before tee when solar longitude just exceeded lambda degrees
    local rate = M.MEAN_TROPICAL_YEAR_AST / 360 -- Mean change per degree
    local tau = tee - rate * ((M.solar_longitude(tee) - lambda) % 360) -- First approximation
    local cap_Delta = M.mod3(M.solar_longitude(tau) - lambda, -180, 180) -- Difference in longitude
    return math.min(tee, tau - rate * cap_Delta)
end

M.MEAN_SYNODIC_MONTH = 29.530588853

function M.nth_new_moon(n)
    -- Moment of n-th new moon after (or before) the new moon of January 11, 1
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    local n0 = 24724 -- Months from RD 0 until j2000
    local k = n - n0 -- Months since j2000
    local c = k / 1236.85 -- Julian centuries
    local approx = M.J2000 + M.poly(c, {5.09766, M.MEAN_SYNODIC_MONTH * 1236.85,
        0.0001337, -0.000000150, 0.00000000073})
    local cap_E = M.poly(c, {1, -0.002516, -0.0000074})
    local solar_anomaly = M.poly(c, {2.5534, 1236.85 * 29.10535669, -0.0000218, -0.00000011})
    local lunar_anomaly = M.poly(c, {201.5643, 385.81693528 * 1236.85, 0.0107438, 0.00001239, -0.000000058})
    local moon_argument = M.poly(c, {160.7108, 390.67050274 * 1236.85, -0.0016341, -0.00000227, 0.000000011}) -- Moon's argument of latitude
    local cap_omega = M.poly(c, {124.7746, -1.56375588 * 1236.85, 0.0020672, 0.00000215}) -- Longitude of ascending node
    local E_factor = {0, 1, 0, 0, 1, 1, 2, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    local solar_coeff = {0, 1, 0, 0, -1, 1, 2, 0, 0, 1, 0, 1, 1, -1, 2, 0, 3, 1, 0, 1, -1, -1, 1, 0}
    local lunar_coeff = {1, 0, 2, 0, 1, 1, 0, 1, 1, 2, 3, 0, 0, 2, 1, 2, 0, 1, 2, 1, 1, 1, 3, 4}
    local moon_coeff = {0, 0, 0, 2, 0, 0, 0, -2, 2, 0, 0, 2, -2, 0, 0, -2, 0, -2, 2, 2, 2, -2, 0, 0}
    local sine_coeff = {
        -0.40720, 0.17241, 0.01608, 0.01039,
         0.00739, -0.00514, 0.00208,
        -0.00111, -0.00057, 0.00056,
        -0.00042, 0.00042, 0.00038,
        -0.00024, -0.00007, 0.00004,
         0.00004, 0.00003, 0.00003,
        -0.00003, 0.00003, -0.00002,
        -0.00002, 0.00002}
    local correction = -0.00017 * M.sin_degrees(cap_omega)
    for i = 1, #sine_coeff do
        correction = correction + sine_coeff[i] * (cap_E ^ E_factor[i])
            * M.sin_degrees(solar_coeff[i] * solar_anomaly
                        + lunar_coeff[i] * lunar_anomaly
                        + moon_coeff[i]  * moon_argument)
    end
    local add_const  = {251.88, 251.83, 349.42, 84.66, 141.74, 207.14, 154.84,
                        34.52, 207.19, 291.34, 161.72, 239.56, 331.55}
    local add_coeff  = {0.016321, 26.651886, 36.412478, 18.206239, 53.303771,
                        2.453732, 7.306860, 27.261239, 0.121824, 1.844379,
                        24.198154, 25.513099, 3.592518}
    local add_factor = {0.000165, 0.000164, 0.000126, 0.000110, 0.000062,
                        0.000060, 0.000056, 0.000047, 0.000042, 0.000040,
                        0.000037, 0.000035, 0.000023}
    local extra = 0.000325 * M.sin_degrees(M.poly(c, {299.77, 132.8475848, -0.009173}))
    local additional = 0
    for i = 1, #add_const do
        additional = additional + add_factor[i] * M.sin_degrees(add_const[i] + add_coeff[i] * k)
    end
    return M.universal_from_dynamical(approx + correction + extra + additional)
end

function M.mean_lunar_longitude(c)
    -- Mean longitude of moon (in degrees) at moment given in Julian centuries c
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    return M.poly(c, {218.3164477, 481267.88123421, -0.0015786, 1/538841, -1/65194000}) % 360
end

function M.lunar_elongation(c)
    -- Elongation of moon (in degrees) at moment given in Julian centuries c
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    return M.poly(c, {297.8501921, 445267.1114034, -0.0018819, 1/545868, -1/113065000}) % 360
end

function M.solar_anomaly(c)
    -- Mean anomaly of sun (in degrees) at moment given in Julian centuries c
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    return M.poly(c, {357.5291092, 35999.0502909, -0.0001536, 1/24490000}) % 360
end

function M.lunar_anomaly(c)
    -- Mean anomaly of moon (in degrees) at moment given in Julian centuries c
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    return M.poly(c, {134.9633964, 477198.8675055, 0.0087414, 1/69699, -1/14712000}) % 360
end

function M.moon_node(c)
    -- Moon's argument of latitude (in degrees) at moment given in Julian centuries c
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    return M.poly(c, {93.2720950, 483202.0175233, -0.0036539, -1/3526000, 1/863310000}) % 360
end

M.lunar_longitude_func = function(tee)
    -- Longitude of moon (degrees) at moment tee
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    local c = M.julian_centuries(tee)
    local cap_L_prime = M.mean_lunar_longitude(c)
    local cap_D = M.lunar_elongation(c)
    local cap_M = M.solar_anomaly(c)
    local cap_M_prime = M.lunar_anomaly(c)
    local cap_F = M.moon_node(c)
    local cap_E = M.poly(c, {1, -0.002516, -0.0000074})
    local args_lunar_elongation = {
        0, 2, 2, 0, 0, 0, 2, 2, 2, 2, 0, 1, 0, 2, 0, 0, 4, 0, 4, 2, 2, 1,
        1, 2, 2, 4, 2, 0, 2, 2, 1, 2, 0, 0, 2, 2, 2, 4, 0, 3, 2, 4, 0, 2,
        2, 2, 4, 0, 4, 1, 2, 0, 1, 3, 4, 2, 0, 1, 2}
    local args_solar_anomaly = {
        0, 0, 0, 0, 1, 0, 0, -1, 0, -1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1,
        0, 1, -1, 0, 0, 0, 1, 0, -1, 0, -2, 1, 2, -2, 0, 0, -1, 0, 0, 1,
        -1, 2, 2, 1, -1, 0, 0, -1, 0, 1, 0, 1, 0, 0, -1, 2, 1, 0}
    local args_lunar_anomaly = {
        1, -1, 0, 2, 0, 0, -2, -1, 1, 0, -1, 0, 1, 0, 1, 1, -1, 3, -2,
        -1, 0, -1, 0, 1, 2, 0, -3, -2, -1, -2, 1, 0, 2, 0, -1, 1, 0,
        -1, 2, -1, 1, -2, -1, -1, -2, 0, 1, 4, 0, -2, 0, 2, 1, -2, -3,
        2, 1, -1, 3}
    local args_moon_node = {
        0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, -2, 2, -2, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, -2, 2, 0, 2, 0, 0, 0, 0,
        0, 0, -2, 0, 0, 0, 0, -2, -2, 0, 0, 0, 0, 0, 0, 0}
    local sine_coeff = {
        6288774, 1274027, 658314, 213618, -185116, -114332,
        58793, 57066, 53322, 45758, -40923, -34720, -30383,
        15327, -12528, 10980, 10675, 10034, 8548, -7888,
        -6766, -5163, 4987, 4036, 3994, 3861, 3665, -2689,
        -2602, 2390, -2348, 2236, -2120, -2069, 2048, -1773,
        -1595, 1215, -1110, -892, -810, 759, -713, -700, 691,
        596, 549, 537, 520, -487, -399, -381, 351, -340, 330,
        327, -323, 299, 294}
    local correction = 0
    for i = 1, #sine_coeff do
        correction = correction + sine_coeff[i]
            * (cap_E ^ math.abs(args_solar_anomaly[i]))
            * M.sin_degrees(args_lunar_elongation[i] * cap_D
                        + args_solar_anomaly[i] * cap_M
                        + args_lunar_anomaly[i] * cap_M_prime
                        + args_moon_node[i] * cap_F)
    end
    correction = correction / 1000000
    local venus = (3958/1000000) * M.sin_degrees(119.75 + c * 131.849)
    local jupiter = (318/1000000)  * M.sin_degrees(53.09  + c * 479264.29)
    local flat_earth = (1962/1000000) * M.sin_degrees(cap_L_prime - cap_F)
    return (cap_L_prime + correction + venus + jupiter + flat_earth + M.nutation(tee)) % 360
end
function M.lunar_longitude(tee) return M.lunar_longitude_func(tee) end

M.lunar_phase_fn = function(tee)
    -- Lunar phase, as an angle in degrees, at moment tee.
    -- An angle of 0 means a new moon, 90 degrees means the first quarter, 180 means a full moon, and 270 degrees means the last quarter
    local phi = (M.lunar_longitude(tee) - M.solar_longitude(tee)) % 360
    local t0 = M.nth_new_moon(0)
    local n = math.floor(0.5 + (tee - t0) / M.MEAN_SYNODIC_MONTH)
    local phi_prime = 360 * (((tee - M.nth_new_moon(n)) / M.MEAN_SYNODIC_MONTH) % 1)
    if math.abs(phi - phi_prime) > 180 then return phi_prime else return phi end
end
function M.lunar_phase(tee) return M.lunar_phase_fn(tee) end

function M.new_moon_before(tee)
    -- Moment UT of last new moon before tee
    local t0 = M.nth_new_moon(0)
    local phi = M.lunar_phase(tee)
    local n = math.floor(0.5 + (tee - t0) / M.MEAN_SYNODIC_MONTH - phi / 360)
    local k = n - 1
    while M.nth_new_moon(k) < tee do k = k + 1 end
    return M.nth_new_moon(k - 1)
end

function M.new_moon_at_or_after(tee)
    -- Moment UT of first new moon at or after tee
    local t0 = M.nth_new_moon(0)
    local phi = M.lunar_phase(tee)
    local n = math.floor(0.5 + (tee - t0) / M.MEAN_SYNODIC_MONTH - phi / 360)
    local k = n
    while M.nth_new_moon(k) < tee do k = k + 1 end
    return M.nth_new_moon(k)
end

function M.lunar_phase_at_or_before(phi, tee)
    -- Moment UT of the last time at or before tee when the lunar-phase was phi degrees
    local tau = tee - M.MEAN_SYNODIC_MONTH * (1/360) * ((M.lunar_phase(tee) - phi) % 360) -- Estimate
    local a = tau - 2
    local b = math.min(tee, tau + 2) -- At or before tee
    return M.invert_angular(M.lunar_phase, phi, a, b)
end

function M.lunar_phase_at_or_after(phi, tee)
    -- Moment UT of the next time at or after tee when the lunar-phase is phi degrees
    local tau = tee + M.MEAN_SYNODIC_MONTH * (1/360) * ((phi - M.lunar_phase(tee)) % 360) -- Estimate
    local a = math.max(tee, tau - 2) -- At or after tee
    local b = tau + 2
    return M.invert_angular(M.lunar_phase, phi, a, b)
end

M.NEW = 0
M.FULL = 180
M.FIRST_QUARTER = 90
M.LAST_QUARTER_PHASE = 270

function M.lunar_latitude(tee)
    -- Latitude of moon (in degrees) at moment tee
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    local c = M.julian_centuries(tee)
    local cap_L_prime = M.mean_lunar_longitude(c)
    local cap_D = M.lunar_elongation(c)
    local cap_M = M.solar_anomaly(c)
    local cap_M_prime = M.lunar_anomaly(c)
    local cap_F = M.moon_node(c)
    local cap_E = M.poly(c, {1, -0.002516, -0.0000074})
    local args_lunar_elongation = {
        0, 0, 0, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 4, 0, 0, 0,
        1, 0, 0, 0, 1, 0, 4, 4, 0, 4, 2, 2, 2, 2, 0, 2, 2, 2, 2, 4, 2, 2,
        0, 2, 1, 1, 0, 2, 1, 2, 0, 4, 4, 1, 4, 1, 4, 2}
    local args_solar_anomaly = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 1, -1, -1, -1, 1, 0, 1,
        0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 1,
        0, -1, -2, 0, 1, 1, 1, 1, 1, 0, -1, 1, 0, -1, 0, 0, 0, -1, -2}
    local args_lunar_anomaly = {
        0, 1, 1, 0, -1, -1, 0, 2, 1, 2, 0, -2, 1, 0, -1, 0, -1, -1, -1,
        0, 0, -1, 0, 1, 1, 0, 0, 3, 0, -1, 1, -2, 0, 2, 1, -2, 3, 2, -3,
        -1, 0, 0, 1, 0, 1, 1, 0, 0, -2, -1, 1, -2, 2, -2, -1, 1, 1, -1,
        0, 0}
    local args_moon_node = {
        1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1,
        -1, 1, 3, 1, 1, 1, -1, -1, -1, 1, -1, 1, -3, 1, -3, -1, -1, 1,
        -1, 1, -1, 1, 1, 1, 1, -1, 3, -1, -1, 1, -1, -1, 1, -1, 1, -1,
        -1, -1, -1, -1, -1, 1}
    local sine_coeff = {
        5128122, 280602, 277693, 173237, 55413, 46271, 32573,
        17198, 9266, 8822, 8216, 4324, 4200, -3359, 2463, 2211,
        2065, -1870, 1828, -1794, -1749, -1565, -1491, -1475,
        -1410, -1344, -1335, 1107, 1021, 833, 777, 671, 607,
        596, 491, -451, 439, 422, 421, -366, -351, 331, 315,
        302, -283, -229, 223, 223, -220, -220, -185, 181,
        -177, 176, 166, -164, 132, -119, 115, 107}
    local beta = 0
    for i = 1, #sine_coeff do
        beta = beta + sine_coeff[i]
            * (cap_E ^ math.abs(args_solar_anomaly[i]))
            * M.sin_degrees(args_lunar_elongation[i] * cap_D
                        + args_solar_anomaly[i]    * cap_M
                        + args_lunar_anomaly[i]    * cap_M_prime
                        + args_moon_node[i]        * cap_F)
    end
    beta = beta / 1000000
    local venus = (175/1000000) * (
          M.sin_degrees(119.75 + c * 131.849 + cap_F)
        + M.sin_degrees(119.75 + c * 131.849 - cap_F))
    local flat_earth = (-2235/1000000) * M.sin_degrees(cap_L_prime)
                     + (127/1000000)   * M.sin_degrees(cap_L_prime - cap_M_prime)
                     + (-115/1000000)  * M.sin_degrees(cap_L_prime + cap_M_prime)
    local extra = (382/1000000) * M.sin_degrees(313.45 + c * 481266.484)
    return beta + venus + flat_earth + extra
end

function M.lunar_altitude(tee, locale)
    -- Geocentric altitude of moon at tee at locale, as a small positive/negative angle in degrees, ignoring parallax and refraction
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed., 1998
    local phi = locale[1] -- Local latitude
    local psi = locale[2] -- Local longitude
    local lambda = M.lunar_longitude(tee) -- Lunar longitude
    local beta   = M.lunar_latitude(tee) -- Lunar latitude
    local alpha  = M.right_ascension(tee, beta, lambda) -- Lunar right ascension
    local delta  = M.declination(tee, beta, lambda) -- Lunar declination
    local theta0 = M.sidereal_from_moment(tee) -- Sidereal time
    local cap_H = (theta0 + psi - alpha) % 360 -- Local hour angle
    local altitude = M.arcsin_degrees(M.sin_degrees(phi) * M.sin_degrees(delta)
        + M.cosine_degrees(phi) * M.cosine_degrees(delta) * M.cosine_degrees(cap_H))
    return M.mod3(altitude, -180, 180)
end

function M.lunar_distance(tee)
    -- Distance to moon (in meters) at moment tee
    -- Adapted Adapted from from "Astronomical Astronomica Algorithms" by Jean Meeus, Willmann-Bell, Inc., 2nd ed.
    local c = M.julian_centuries(tee)
    local cap_D = M.lunar_elongation(c)
    local cap_M = M.solar_anomaly(c)
    local cap_M_prime = M.lunar_anomaly(c)
    local cap_F = M.moon_node(c)
    local cap_E = M.poly(c, {1, -0.002516, -0.0000074})
    local args_lunar_elongation = {
        0, 2, 2, 0, 0, 0, 2, 2, 2, 2, 0, 1, 0, 2, 0, 0, 4, 0, 4, 2, 2, 1,
        1, 2, 2, 4, 2, 0, 2, 2, 1, 2, 0, 0, 2, 2, 2, 4, 0, 3, 2, 4, 0, 2,
        2, 2, 4, 0, 4, 1, 2, 0, 1, 3, 4, 2, 0, 1, 2, 2}
    local args_solar_anomaly = {
        0, 0, 0, 0, 1, 0, 0, -1, 0, -1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1,
        0, 1, -1, 0, 0, 0, 1, 0, -1, 0, -2, 1, 2, -2, 0, 0, -1, 0, 0, 1,
        -1, 2, 2, 1, -1, 0, 0, -1, 0, 1, 0, 1, 0, 0, -1, 2, 1, 0, 0}
    local args_lunar_anomaly = {
        1, -1, 0, 2, 0, 0, -2, -1, 1, 0, -1, 0, 1, 0, 1, 1, -1, 3, -2,
        -1, 0, -1, 0, 1, 2, 0, -3, -2, -1, -2, 1, 0, 2, 0, -1, 1, 0,
        -1, 2, -1, 1, -2, -1, -1, -2, 0, 1, 4, 0, -2, 0, 2, 1, -2, -3,
        2, 1, -1, 3, -1}
    local args_moon_node = {
        0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, -2, 2, -2, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, -2, 2, 0, 2, 0, 0, 0, 0,
        0, 0, -2, 0, 0, 0, 0, -2, -2, 0, 0, 0, 0, 0, 0, 0, -2}
    local cosine_coeff = {
        -20905355, -3699111, -2955968, -569925, 48888, -3149,
        246158, -152138, -170733, -204586, -129620, 108743,
        104755, 10321, 0, 79661, -34782, -23210, -21636, 24208,
        30824, -8379, -16675, -12831, -10445, -11650, 14403,
        -7003, 0, 10056, 6322, -9884, 5751, 0, -4950, 4130, 0,
        -3958, 0, 3258, 2616, -1897, -2117, 2354, 0, 0, -1423,
        -1117, -1571, -1739, 0, -4421, 0, 0, 0, 0, 1165, 0, 0,
        8752}
    local correction = 0
    for i = 1, #cosine_coeff do
        correction = correction + cosine_coeff[i]
            * (cap_E ^ math.abs(args_solar_anomaly[i]))
            * M.cosine_degrees(args_lunar_elongation[i] * cap_D
                        + args_solar_anomaly[i] * cap_M
                        + args_lunar_anomaly[i] * cap_M_prime
                        + args_moon_node[i] * cap_F)
    end
    return 385000560 + correction
end

function M.lunar_parallax(tee, locale)
    -- Parallax of moon at tee at locale
    -- Adapted from "Astronomical Algorithms" by Jean Meeus, Willmann-Bell, Inc., 1998
    local geo = M.lunar_altitude(tee, locale)
    local cap_Delta = M.lunar_distance(tee)
    local alt = 6378140 / cap_Delta
    return M.arcsin_degrees(alt * M.cosine_degrees(geo))
end

function M.topocentric_lunar_altitude(tee, locale)
    -- Topocentric altitude of moon at tee at locale, as a small positive/negative angle in degrees, ignoring refraction
    return M.lunar_altitude(tee, locale) - M.lunar_parallax(tee, locale)
end

function M.sine_offset(tee, locale, alpha)
    --  Sine of angle between position of sun at local time tee and when its depression is alpha at locale. Out of range when it does not occur
    local phi = locale[1]
    local tee_prime = M.universal_from_local(tee, locale)
    local delta = M.declination(tee_prime, 0, M.solar_longitude(tee_prime)) -- Declination of sun
    return M.tangent_degrees(phi) * M.tangent_degrees(delta)
         + M.sin_degrees(alpha) / (M.cosine_degrees(delta) * M.cosine_degrees(phi))
end

function M.approx_moment_of_depression(tee, locale, alpha, early)
    --  Moment in local time near tee when depression angle of sun is alpha (negative if above horizon) at locale; early? is true when morning event is sought and false for evening. Returns bogus if depression angle is not reached
    local try = M.sine_offset(tee, locale, alpha)
    local date = M.fixed_from_moment(tee)
    local alt
    if alpha >= 0 then
        if early then alt = date else alt = date + 1 end
    else
        alt = date + M.hr(12)
    end
    local value
    if math.abs(try) > 1 then value = M.sine_offset(alt, locale, alpha) else value = try end
    if math.abs(value) <= 1 then -- Event occurs
        local offset = M.mod3(M.arcsin_degrees(value) / 360, M.hr(-12), M.hr(12))
        local t
        if early then t = date + (M.hr(6) - offset) else t = date + (M.hr(18) + offset) end
        return M.local_from_apparent(t, locale)
    else
        return M.BOGUS
    end
end

function M.moment_of_depression(approx, locale, alpha, early)
    --  Moment in local time near approx when depression angle of sun is alpha (negative if above horizon) locale; early? is true when morning event is sought, and false for evening
    -- Returns bogus if depression angle is not reached
    local tee = M.approx_moment_of_depression(approx, locale, alpha, early)
    if tee == M.BOGUS then return M.BOGUS end
    if math.abs(approx - tee) < M.sec(30) then return tee
    else return M.moment_of_depression(tee, locale, alpha, early) end
end

M.MORNING = true -- Signifies morning
M.EVENING = false -- Signifies evening

function M.dawn(date, locale, alpha)
    -- Standard time in morning on fixed date at locale when depression angle of sun is alpha. Returns bogus if there is no dawn on date
    local result = M.moment_of_depression(date + M.hr(6), locale, alpha, M.MORNING)
    if result == M.BOGUS then return M.BOGUS end
    return M.standard_from_local(result, locale)
end

function M.dusk(date, locale, alpha)
    -- Standard time in evening on fixed date at locale when depression angle of sun is alpha. Returns bogus if there is no dusk on date
    local result = M.moment_of_depression(date + M.hr(18), locale, alpha, M.EVENING)
    if result == M.BOGUS then return M.BOGUS end
    return M.standard_from_local(result, locale)
end

function M.refraction(locale)
    -- Refraction angle at locale
    local h = math.max(0, locale[3])
    local cap_R = 6.372e6 -- Radius of Earth
    local dip = M.arccos_degrees(cap_R / (cap_R + h)) -- Depression of visible horizon
    return M.mins(34) + dip + M.secs(19) * math.sqrt(h)
end

function M.sunrise(date, locale)
    -- Standard time of sunrise on fixed date at locale
    local alpha = M.refraction(locale) + M.mins(16)
    return M.dawn(date, locale, alpha)
end

function M.sunset(date, locale)
    -- Standard time of sunset on fixed date at locale
    local alpha = M.refraction(locale) + M.mins(16)
    return M.dusk(date, locale, alpha)
end

function M.jewish_dusk(date, locale)
    -- Standard time of Jewish dusk on fixed date at locale (as per Vilna Gaon)
    return M.dusk(date, locale, M.angle(4, 40, 0))
end

function M.jewish_sabbath_ends(date, locale)
    -- Standard time of end of Jewish sabbath on fixed date at locale (as per Berthold Cohn)
    return M.dusk(date, locale, M.angle(7, 5, 0))
end

function M.daytime_temporal_hour(date, locale)
    -- Length of daytime temporal hour on fixed date at locale
    -- Returns bogus if there no sunrise or sunset on date
    local sunrise, sunset = M.sunrise(date, locale), M.sunset(date, locale)
    if sunrise == M.BOGUS or sunset == M.BOGUS then return M.BOGUS end
    return (sunset - sunrise) / 12
end

function M.nighttime_temporal_hour(date, locale)
    -- Length of nighttime temporal hour on fixed date at locale
    -- Returns bogus if there no sunrise or sunset on date
    local sunrise_next = M.sunrise(date + 1, locale)
    local sunset = M.sunset(date, locale)
    if sunrise_next == M.BOGUS or sunset == M.BOGUS then return M.BOGUS end
    return (sunrise_next - sunset) / 12
end

function M.standard_from_sundial(tee, locale)
    -- Standard time of temporal moment tee at locale
    -- Returns bogus if temporal hour is undefined that day
    local date = M.fixed_from_moment(tee)
    local hour = 24 * (tee % 1)
    local h
    if 6 <= hour and hour <= 18 then h = M.daytime_temporal_hour(date, locale) -- daytime today
    elseif hour < 6 then h = M.nighttime_temporal_hour(date - 1, locale) -- early this morning
    else h = M.nighttime_temporal_hour(date, locale) end -- this evening
    if h == M.BOGUS then return M.BOGUS end
    if 6 <= hour and hour <= 18 then return M.sunrise(date, locale) + (hour - 6) * h -- daytime today
    elseif hour < 6 then return M.sunset(date - 1, locale) + (hour + 6) * h -- early this morning
    else return M.sunset(date, locale) + (hour - 18) * h end -- this evening
end

function M.jewish_morning_end(date, locale)
    -- Standard time on fixed date at locale of end of morning according to Jewish ritual
    return M.standard_from_sundial(date + M.hr(10), locale)
end

function M.asr(date, locale)
    -- Standard time of asr on fixed date at locale
    local noon = M.midday(date, locale) -- Time when sun nearest zenith
    local phi = locale[1]
    local delta = M.declination(noon, 0, M.solar_longitude(noon)) -- Solar declination at noon
    local altitude = M.arcsin_degrees(M.cos_degrees(delta) * M.cos_degrees(phi) -- Solar altitude at noon
                                  + M.sin_degrees(delta) * M.sin_degrees(phi))
    local h = M.mod3(M.arctan_degrees(M.tan_degrees(altitude), -- Sun's altitude when shadow increases by double its length
                                  1 + 2 * M.tan_degrees(altitude)), -90, 90)
                                  -- For Shafii use instead:
                                  -- 1 + M.tan_degrees(altitude)), -90, 90)
    if altitude <= 0 then return M.BOGUS end
    return M.dusk(date, locale, -h)
end

----- B.16 The Persian Calendar -----

----- B.18 The French Revolutionary Calendar -----

M.FRENCH_EPOCH = M.fixed_from_gregorian({1792, M.SEPTEMBER, 22}) -- Fixed date of start of the French Revolutionary calendar
M.PARIS = {M.angle(48, 50, 11), M.angle(2, 20, 15), 27, M.hr(1)} -- Location of Paris Observatory. Longitude corresponds to difference of 9m 21s between Paris time zone and Universal Time

function M.midnight_in_paris(date)
    -- Universal time of true midnight at end of fixed date in Paris
    return M.midnight(date + 1, M.PARIS)
end

function M.french_new_year_on_or_before(date)
    -- Fixed date of French Revolutionary New Year on or before fixed date
    local approx = M.estimate_prior_solar_longitude(M.AUTUMN, M.midnight_in_paris(date)) -- Approximate time of solstice
    local day = math.floor(approx) - 1
    while not (M.AUTUMN <= M.solar_longitude(M.midnight_in_paris(day))) do
        day = day + 1
    end
    return day
end

function M.fixed_from_french(f_date)
    -- Fixed date of French Revolutionary date
    local year, month, day = f_date[1], f_date[2], f_date[3]
    local new_year = M.french_new_year_on_or_before(
        math.floor(M.FRENCH_EPOCH + 180 + M.MEAN_TROPICAL_YEAR_AST * (year - 1)))
    return new_year - 1 + 30 * (month - 1) + day
end
p.fixed_from_french = make_public(M.fixed_from_french, false)

function M.french_from_fixed(date)
    -- French Revolutionary date of fixed date
    local new_year = M.french_new_year_on_or_before(date)
    local year = math.floor(0.5 + (new_year - M.FRENCH_EPOCH) / M.MEAN_TROPICAL_YEAR_AST) + 1
    local month = M.quotient(date - new_year, 30) + 1
    local day = (date - new_year) % 30 + 1
    local decade = M.quotient(day - 1, 10) + 1 -- I added decade and weekday, which are not in the book, for use on Wikipedia
    local weekday = M.amod(day, 10)
    return {year, month, decade, weekday}
end
p.french_from_fixed = make_public(M.french_from_fixed)

function M.french_leap_year(f_year)
    -- True if f-year is a leap year on the French Revolutionary calendar
    return (M.fixed_from_french({f_year + 1, 1, 1}) - M.fixed_from_french({f_year, 1, 1})) > 365
end

function M.arithmetic_french_leap_year(f_year)
    -- True if f-year is a leap year on the Arithmetic French Revolutionary calendar
    local m400 = f_year % 400
    return (f_year % 4 == 0)
        and not (m400 == 100 or m400 == 200 or m400 == 300)
        and not (f_year % 4000 == 0)
end

function M.fixed_from_arithmetic_french(f_date)
    -- Fixed date of Arithmetic French Revolutionary date f-date
    local year, month, day = f_date[1], f_date[2], f_date[3]
    return M.FRENCH_EPOCH - 1 -- Days before start of calendar
        + 365 * (year - 1) -- Ordinary days in prior years
        + M.quotient(year - 1, 4) -- Leap days in prior years
        - M.quotient(year - 1, 100)
        + M.quotient(year - 1, 400)
        - M.quotient(year - 1, 4000)
        + 30 * (month - 1) -- Days in prior months this year
        + day -- Days this month
end
p.fixed_from_arithmetic_french = make_public(M.fixed_from_arithmetic_french, false)

function M.arithmetic_french_from_fixed(date)
    -- Arithmetic French Revolutionary (year month day) of fixed date
    local approx = M.quotient(date - M.FRENCH_EPOCH + 2, 1460969/4000) + 1 -- Approximate year (may be off by 1)
    local year
    if date < M.fixed_from_arithmetic_french({approx, 1, 1}) then year = approx - 1
    else year = approx end
    local month = M.quotient(date - M.fixed_from_arithmetic_french({year, 1, 1}), 30) + 1 -- Calculate the month by division
    local day = date - M.fixed_from_arithmetic_french({year, month, 1}) + 1 -- Calculate the day by subtraction
    return {year, month, day}
end
p.arithmetic_french_from_fixed = make_public(M.arithmetic_french_from_fixed)

----- B.19 The Chinese Calendar -----

function M.chinese_location(tee)
    -- Location of Beijing; time zone varies with tee
    local year = M.gregorian_year_from_fixed(math.floor(tee))
    if year < 1929 then
        return {M.angle(39, 55, 0), M.angle(116, 25, 0), 43.5, M.hr(1397/180)}
    else
        return {M.angle(39, 55, 0), M.angle(116, 25, 0), 43.5, M.hr(8)}
    end
end

function M.midnight_in_china(date)
    --  Universal time of (clock) midnight at start of fixed date in China
    return M.universal_from_standard(date, M.chinese_location(date))
end

function M.chinese_solar_longitude_on_or_after(lambda, date)
    -- Moment (Beijing time) of the first date on or after fixed date (Beijing time) when the solar longitude will be lambda degrees
    local tee = M.solar_longitude_after(lambda, M.universal_from_standard(date, M.chinese_location(date)))
    return M.standard_from_universal(tee, M.chinese_location(tee))
end

function M.chinese_current_major_solar_term(date)
    -- Last Chinese major solar term (zhongqi) before fixed date
    local s = M.solar_longitude(M.universal_from_standard(date, M.chinese_location(date)))
    return M.amod(2 + M.quotient(s, 30), 12)
end

function M.chinese_major_solar_term_on_or_after(date)
    -- Moment (in Beijing) of first Chinese major solar term (zhongqi) on or after fixed date. The major terms begin when the sun's longitude is a multiple of 30 degrees
    local s = M.solar_longitude(M.midnight_in_china(date))
    local l = (30 * math.ceil(s / 30)) % 360
    return M.chinese_solar_longitude_on_or_after(l, date)
end

function M.chinese_current_minor_solar_term(date)
    -- Last Chinese minor solar term (jieqi) before date
    local s = M.solar_longitude(M.universal_from_standard(date, M.chinese_location(date)))
    return M.amod(3 + M.quotient(s - 15, 30), 12)
end

function M.chinese_minor_solar_term_on_or_after(date)
    --  Moment (in Beijing) of the first Chinese minor solar term (jieqi) on or after fixed date. The minor terms begin when the sun's longitude is an odd multiple of 15 degrees
    local s = M.solar_longitude(M.midnight_in_china(date))
    local l = (30 * math.ceil((s - 15) / 30) + 15) % 360
    return M.chinese_solar_longitude_on_or_after(l, date)
end

function M.chinese_solar_term_from_fixed(fixed)
    -- This one is not in the book, but I added it so that Template:Currect Sekki can use it
    local s = M.solar_longitude(M.universal_from_standard(fixed, M.chinese_location(fixed)))
    local l = 15 * (M.quotient(s, 15) % 24) -- Last solar term
    local next_l = (l + 15) % 360 -- Next solar term
    local next_moment = math.floor(M.chinese_solar_longitude_on_or_after(next_l, fixed)) -- Calculate day when the next solar term begins
    local days_until = next_moment - fixed
    return {l, next_l, days_until}
end
p.chinese_solar_term_from_fixed = make_public(M.chinese_solar_term_from_fixed)

function M.chinese_new_moon_before(date)
    -- Fixed date (Beijing) of first new moon before fixed date
    local tee = M.new_moon_before(M.midnight_in_china(date))
    return math.floor(M.standard_from_universal(tee, M.chinese_location(tee)))
end

function M.chinese_new_moon_on_or_after(date)
    -- Fixed date (Beijing) of first new moon on or after fixed date
    local tee = M.new_moon_at_or_after(M.midnight_in_china(date))
    return math.floor(M.standard_from_universal(tee, M.chinese_location(tee)))
end

M.CHINESE_EPOCH = M.fixed_from_gregorian({-2636, M.FEBRUARY, 15}) -- Fixed date of start of the Chinese calendar

function M.chinese_no_major_solar_term(date)
    -- True if Chinese lunar month starting on date has no major solar term
    return M.chinese_current_major_solar_term(date)
        == M.chinese_current_major_solar_term(M.chinese_new_moon_on_or_after(date + 1))
end

function M.chinese_winter_solstice_on_or_before(date)
    -- Fixed date, in the Chinese zone, of winter solstice on or before fixed date
    local approx = M.estimate_prior_solar_longitude(M.WINTER, M.midnight_in_china(date + 1)) -- Approximate time of solstice
    local day = math.floor(approx) - 1
    while not (M.WINTER < M.solar_longitude(M.midnight_in_china(day + 1))) do
        day = day + 1
    end
    return day
end

function M.chinese_new_year_in_sui(date)
    -- Fixed date of Chinese New Year in sui (period from solstice to solstice) containing date
    local s1 = M.chinese_winter_solstice_on_or_before(date) -- prior solstice
    local s2 = M.chinese_winter_solstice_on_or_before(s1 + 370) -- following solstice
    local m12 = M.chinese_new_moon_on_or_after(s1 + 1) -- month after 11th month; either 12 or leaр 11
    local m13 = M.chinese_new_moon_on_or_after(m12 + 1) -- month after m12; either 12 (or leap 12) or 1
    local next_m11 = M.chinese_new_moon_before(s2 + 1) -- next 11th month
    -- Either m12 or m13 is a leap month if there are 13 new moons (12 full lunar months) and either m12 or m13 has no major solar term
    if math.floor(0.5 + (next_m11 - m12) / M.MEAN_SYNODIC_MONTH) == 12
       and (M.chinese_no_major_solar_term(m12) or M.chinese_no_major_solar_term(m13)) then
        return M.chinese_new_moon_on_or_after(m13 + 1)
    else
        return m13
    end
end

function M.chinese_new_year_on_or_before(date)
    -- Fixed date of Chinese New Year on or before fixed date
    local new_year = M.chinese_new_year_in_sui(date)
    if date >= new_year then return new_year
    -- Got the New Year after; this happens if date is after the solstice but before the new year. So, go back half a year
    else return M.chinese_new_year_in_sui(date - 180) end
end

function M.chinese_new_year(g_year)
    -- Fixed date of Chinese New Year in Gregorian year g-year
    return M.chinese_new_year_on_or_before(M.fixed_from_gregorian({g_year, M.JULY, 1}))
end
p.chinese_new_year = make_public(M.chinese_new_year)

M.chinese_prior_leap_month = function(m_prime, m)
    -- True if there is a Chinese leap month on or after lunar month starting m-prime and at or before lunar month starting m
    return m >= m_prime
       and (M.chinese_no_major_solar_term(m)
            or M.chinese_prior_leap_month(m_prime, M.chinese_new_moon_before(m)))
end

function M.chinese_from_fixed(date)
    -- Chinese date (cycle year month leap day) of fixed date
    local s1 = M.chinese_winter_solstice_on_or_before(date) -- Prior solstice
    local s2 = M.chinese_winter_solstice_on_or_before(s1 + 370) -- Following solstice
    local m12 = M.chinese_new_moon_on_or_after(s1 + 1) -- month after last 11th month
    local next_m11 = M.chinese_new_moon_before(s2 + 1) -- next 11th month
    local m = M.chinese_new_moon_before(date + 1) -- start of month containing date
    local leap_year = math.floor(0.5 + (next_m11 - m12) / M.MEAN_SYNODIC_MONTH) == 12 -- if there are 13 new moons (12 full lunar months)
    local prior = (leap_year and M.chinese_prior_leap_month(m12, m)) and 1 or 0 -- subtract 1 from ordinal position of month in year during or after a leap month
    local month = M.amod(math.floor(0.5 + (m - m12) / M.MEAN_SYNODIC_MONTH) - prior, 12) -- month number
    local leap_month = leap_year -- it's a leap month if there are 13 months
        and M.chinese_no_major_solar_term(m) -- no major solar term
        and (not M.chinese_prior_leap_month(m12, M.chinese_new_moon_before(m))) -- and no prior leap month
    local elapsed_years = math.floor(1.5 - month/12 + (date - M.CHINESE_EPOCH) / M.MEAN_TROPICAL_YEAR_AST) -- Approximate since the epoch
    local cycle = M.quotient(elapsed_years - 1, 60) + 1
    local year = M.amod(elapsed_years, 60)
    local day = date - m + 1
    return {cycle, year, month, leap_month, day}
end
p.chinese_from_fixed = make_public(M.chinese_from_fixed)

function M.fixed_from_chinese(c_date)
    -- Fixed date of Chinese date c-date.
    local cycle, year, month, leap, day = c_date[1], c_date[2], c_date[3], c_date[4], c_date[5]
    local mid_year = math.floor(M.CHINESE_EPOCH
        + ((cycle - 1) * 60 + (year - 1) + 1/2) * M.MEAN_TROPICAL_YEAR_AST)
    local new_year = M.chinese_new_year_on_or_before(mid_year)
    local p = M.chinese_new_moon_on_or_after(new_year + (month - 1) * 29)
    local d = M.chinese_from_fixed(p)
    local prior_new_moon
    if month == d[3] and leap == d[4] then
        prior_new_moon = p
    else
        prior_new_moon = M.chinese_new_moon_on_or_after(p + 1)
    end
    return prior_new_moon + day - 1
end

function M.fixed_from_chinese_for_wikipedia(table)
    -- This isn't in the book, but I have to add it so the function would work from Wikipedia
    return M.fixed_from_chinese({table[1], table[2], table[3], table[4] == 1, table[5]})
end
p.fixed_from_chinese = make_public(M.fixed_from_chinese_for_wikipedia, false)

function M.chinese_sexagesimal_name(n)
    -- The n-th name of the Chinese sexagesimal cycle
    return {M.amod(n, 10), M.amod(n, 12)}
end

function M.chinese_name_difference(c_name1, c_name2)
    -- Number of names from Chinese name c-name1 to next occurrence of c-name2
    local stem1, stem2 = c_name1[1], c_name2[2]
    local branch1, branch2 = c_name1[1], c_name2[2]
    local stem_difference = stem2 - stem1
    local branch_difference = branch2 - branch1
    return ((stem_difference - 1 + 25 * (branch_difference - stem_difference)) % 60) + 1
end

function M.chinese_name_of_year(year)
    -- Sexagesimal name for Chinese year of any cycle
    return M.chinese_sexagesimal_name(year)
end

M.CHINESE_MONTH_NAME_EPOCH = 57 -- Elapsed months at start of Chinese sexagesimal month cycle

function M.chinese_name_of_month(month, year)
    -- Sexagesimal name for month of Chinese year
    local elapsed_months = 12 * (year - 1) + (month - 1)
    return M.chinese_sexagesimal_name(elapsed_months - M.CHINESE_MONTH_NAME_EPOCH)
end

M.CHINESE_DAY_NAME_EPOCH = M.rd(45) -- RD date of a start of Chinese sexagesimal day cycle

function M.chinese_name_of_day(date)
    -- Chinese sexagesimal name for date
    return M.chinese_sexagesimal_name(date - M.CHINESE_DAY_NAME_EPOCH)
end

function M.chinese_day_name_on_or_before(name, date)
    -- Fixed date of latest date on or before fixed date that has Chinese name
    return M.mod3(M.chinese_name_difference(M.chinese_day_name(0), name), date - 60, date)
end

function M.dragon_festival(g_year)
    -- Fixed date of the Dragon Festival in Gregorian year g-year
    local elapsed_years = g_year - M.gregorian_year_from_fixed(M.CHINESE_EPOCH) + 1
    local cycle = M.quotient(elapsed_years - 1, 60) + 1
    local year  = M.amod(elapsed_years, 60)
    return M.fixed_from_chinese({cycle, year, 5, false, 5})
end

function M.qing_ming(g_year)
    -- Fixed date of Qingming in Gregorian year g-year
    return math.floor(M.chinese_minor_solar_term_on_or_after(M.fixed_from_gregorian({g_year, M.MARCH, 30})))
end

function M.chinese_age(birthdate, date)
    -- Age at fixed date, given Chinese birthdate, according to the Chinese custom. Returns bogus if date is before birthdate
    local today = M.chinese_from_fixed(date)
    if date >= M.fixed_from_chinese(birthdate) then
        return 60 * (M.chinese_cycle(today) - M.chinese_cycle(birthdate))
             + (M.chinese_year(today) - M.chinese_year(birthdate)) + 1
    else
        return M.BOGUS
    end
end

M.WIDOW_AUGURY = 0 -- Lichun does not occur (double-blind year)
M.BLIND_AUGURY = 1 -- Lichun occurs once at the end
M.BRIGHT_AUGURY = 2 -- Lichun occurs once at the start
M.DOUBLE_BRIGHT_AUGURY = 3 -- Lichun occurs twice (double-happiness)

function M.chinese_year_marriage_augury(cycle, year)
    -- The marriage augury type of Chinese year in cycle
    local new_year = M.fixed_from_chinese({cycle, year, 1, false, 1})
    local c, y
    if year == 60 then c = cycle + 1; y = 1
    else c = cycle; y = year + 1 end
    local next_new_year = M.fixed_from_chinese({c, y, 1, false, 1})
    local first_minor_term      = M.chinese_current_minor_solar_term(new_year)
    local next_first_minor_term = M.chinese_current_minor_solar_term(next_new_year)
    if first_minor_term == 1 and next_first_minor_term == 12 then
        return M.WIDOW_AUGURY
    elseif first_minor_term == 1 and next_first_minor_term ~= 12 then
        return M.BLIND_AUGURY
    elseif first_minor_term ~= 1 and next_first_minor_term == 12 then
        return M.BRIGHT_AUGURY
    else
        return M.DOUBLE_BRIGHT_AUGURY
    end
end

function M.korean_location(tee)
    -- Location for Korean calendar; varies with tee
    -- Seoul city hall at a varying time zone
    local z
    if tee < M.fixed_from_gregorian({1908, M.APRIL, 1}) then z = 3809/450 -- mean  local mean time for longitude 126 deg 58 min
    elseif tee < M.fixed_from_gregorian({1912, M.JANUARY, 1}) then z = 8.5
    elseif tee < M.fixed_from_gregorian({1954, M.MARCH, 21}) then z = 9
    elseif tee < M.fixed_from_gregorian({1961, M.AUGUST, 10}) then z = 8.5
    else z = 9 end
    return M.location(M.angle(37, 34, 0), M.angle(126, 58, 0), 0, M.hr(z))
end

function M.korean_year(cycle, year)
    -- Equivalent Korean year to Chinese cycle and year
    return 60 * cycle + year - 364
end

function M.vietnamese_location(tee)
    -- Location for Vietnamese calendar is Hanoi; varies with tee. Time zone has changed over the years
    local z
    if tee < M.gregorian_new_year(1968) then z = 8 else z = 7 end
    return M.location(M.angle(21, 2, 0), M.angle(105, 51, 0), 0, M.hr(z))
end

----- Japanese calendar -----

function M.japanese_location(tee)
    -- Location for Japanese calendar; varies with tee
    local year = M.gregorian_year_from_fixed(math.floor(tee))
    if year < 1888 then -- Tokyo (139 deg 46 min east) local time
        return {35.7, M.angle(139, 46, 0), 24, M.hr(9 + 143/450)}
    else -- Longitude 135 time zone
        return {35, 135, 0, M.hr(9)}
    end
end

function M.midnight_in_japan(date)
    -- Universal time of (clock) midnight at start of fixed date in Japan
    return M.universal_from_standard(date, M.japanese_location(date))
end

function M.japanese_solar_longitude_on_or_after(lambda, date)
    -- Moment (Tokyo time) of the first date on or after fixed date (Tokyo time) when the solar longitude will be lambda degrees
    local tee = M.solar_longitude_after(lambda, M.universal_from_standard(date, M.japanese_location(date)))
    return M.standard_from_universal(tee, M.japanese_location(tee))
end

function M.japanese_current_major_solar_term(date)
    -- Last Japanese major solar term (chuki) before fixed date
    local s = M.solar_longitude(M.universal_from_standard(date, M.japanese_location(date)))
    return M.amod(2 + M.quotient(s, 30), 12)
end

function M.japanese_major_solar_term_on_or_after(date)
    -- Moment (in Tokyo) of first Japanese major solar term (chuki) on or after fixed date. The major terms begin when the sun's longitude is a multiple of 30 degrees
    local s = M.solar_longitude(M.midnight_in_japan(date))
    local l = (30 * math.ceil(s / 30)) % 360
    return M.japanese_solar_longitude_on_or_after(l, date)
end

function M.japanese_current_minor_solar_term(date)
    -- Last Japanese minor solar term (sekki) before date
    local s = M.solar_longitude(M.universal_from_standard(date, M.japanese_location(date)))
    return M.amod(3 + M.quotient(s - 15, 30), 12)
end

function M.japanese_minor_solar_term_on_or_after(date)
    --  Moment (in Tokyo) of the first Japanese minor solar term (sekki) on or after fixed date. The minor terms begin when the sun's longitude is an odd multiple of 15 degrees
    local s = M.solar_longitude(M.midnight_in_japan(date))
    local l = (30 * math.ceil((s - 15) / 30) + 15) % 360
    return M.japanese_solar_longitude_on_or_after(l, date)
end

function M.japanese_solar_term_from_fixed(fixed)
    -- This one is not in the book, but I added it so that Template:Currect Sekki can use it
    local s = M.solar_longitude(M.universal_from_standard(fixed - M.hr(12), M.japanese_location(fixed)))
    local l = 15 * (M.quotient(s, 15) % 24) -- Last solar term
    local next_l = (l + 15) % 360 -- Next solar term
    local next_moment = math.floor(M.japanese_solar_longitude_on_or_after(next_l, fixed)) -- Calculate day when the next solar term begins
    local days_until = next_moment - fixed
    return {l, next_l, days_until}
end
p.japanese_solar_term_from_fixed = make_public(M.japanese_solar_term_from_fixed)

function M.japanese_new_moon_before(date)
    -- Fixed date (Tokyo) of first new moon before fixed date
    local tee = M.new_moon_before(M.midnight_in_japan(date))
    return math.floor(M.standard_from_universal(tee, M.japanese_location(tee)))
end

function M.japanese_new_moon_on_or_after(date)
    -- Fixed date (Tokyo) of first new moon on or after fixed date
    local tee = M.new_moon_at_or_after(M.midnight_in_japan(date))
    return math.floor(M.standard_from_universal(tee, M.japanese_location(tee)))
end

function M.japanese_no_major_solar_term(date)
    -- True if Japanese lunar month starting on date has no major solar term
    return M.japanese_current_major_solar_term(date)
        == M.japanese_current_major_solar_term(M.japanese_new_moon_on_or_after(date + 1))
end

function M.japanese_winter_solstice_on_or_before(date)
    -- Fixed date, in the Japanese zone, of winter solstice on or before fixed date
    local approx = M.estimate_prior_solar_longitude(M.WINTER, M.midnight_in_japan(date + 1)) -- Approximate time of solstice
    local day = math.floor(approx) - 1
    while not (M.WINTER < M.solar_longitude(M.midnight_in_japan(day + 1))) do
        day = day + 1
    end
    return day
end

function M.japanese_new_year_in_sui(date)
    -- Fixed date of Japanese New Year in sui (period from solstice to solstice) containing date
    local s1 = M.japanese_winter_solstice_on_or_before(date) -- prior solstice
    local s2 = M.japanese_winter_solstice_on_or_before(s1 + 370) -- following solstice
    local m12 = M.japanese_new_moon_on_or_after(s1 + 1) -- month after 11th month; either 12 or leaр 11
    local m13 = M.japanese_new_moon_on_or_after(m12 + 1) -- month after m12; either 12 (or leap 12) or 1
    local next_m11 = M.japanese_new_moon_before(s2 + 1) -- next 11th month
    -- Either m12 or m13 is a leap month if there are 13 new moons (12 full lunar months) and either m12 or m13 has no major solar term
    if math.floor(0.5 + (next_m11 - m12) / M.MEAN_SYNODIC_MONTH) == 12
       and (M.japanese_no_major_solar_term(m12) or M.japanese_no_major_solar_term(m13)) then
        return M.japanese_new_moon_on_or_after(m13 + 1)
    else
        return m13
    end
end

function M.japanese_new_year_on_or_before(date)
    -- Fixed date of Japanese New Year on or before fixed date
    local new_year = M.japanese_new_year_in_sui(date)
    if date >= new_year then return new_year
    -- Got the New Year after; this happens if date is after the solstice but before the new year. So, go back half a year
    else return M.japanese_new_year_in_sui(date - 180) end
end

M.japanese_prior_leap_month = function(m_prime, m)
    -- True if there is a Japanese leap month on or after lunar month starting m-prime and at or before lunar month starting m
    return m >= m_prime
       and (M.japanese_no_major_solar_term(m)
            or M.japanese_prior_leap_month(m_prime, M.japanese_new_moon_before(m)))
end

function M.japanese_from_fixed(date)
    -- Japanese date (cycle year month leap day) of fixed date
    local s1 = M.japanese_winter_solstice_on_or_before(date) -- Prior solstice
    local s2 = M.japanese_winter_solstice_on_or_before(s1 + 370) -- Following solstice
    local m12 = M.japanese_new_moon_on_or_after(s1 + 1) -- month after last 11th month
    local next_m11 = M.japanese_new_moon_before(s2 + 1) -- next 11th month
    local m = M.japanese_new_moon_before(date + 1) -- start of month containing date
    local leap_year = math.floor(0.5 + (next_m11 - m12) / M.MEAN_SYNODIC_MONTH) == 12 -- if there are 13 new moons (12 full lunar months)
    local prior = (leap_year and M.japanese_prior_leap_month(m12, m)) and 1 or 0 -- subtract 1 from ordinal position of month in year during or after a leap month
    local month = M.amod(math.floor(0.5 + (m - m12) / M.MEAN_SYNODIC_MONTH) - prior, 12) -- month number
    local leap_month = leap_year -- it's a leap month if there are 13 months
        and M.japanese_no_major_solar_term(m) -- no major solar term
        and (not M.japanese_prior_leap_month(m12, M.japanese_new_moon_before(m))) -- and no prior leap month
    local elapsed_years = math.floor(1.5 - month/12 + (date - M.CHINESE_EPOCH) / M.MEAN_TROPICAL_YEAR_AST) -- Approximate since the epoch
    local cycle = M.quotient(elapsed_years - 1, 60) + 1
    local year = M.amod(elapsed_years, 60)
    local day = date - m + 1
    return {cycle, year, month, leap_month, day}
end
p.japanese_from_fixed = make_public(M.japanese_from_fixed)

function M.fixed_from_japanese(j_date)
    -- Fixed date of Japanese date j-date.
    local cycle, year, month, leap, day = j_date[1], j_date[2], j_date[3], j_date[4], j_date[5]
    local mid_year = math.floor(M.CHINESE_EPOCH
        + ((cycle - 1) * 60 + (year - 1) + 1/2) * M.MEAN_TROPICAL_YEAR_AST)
    local new_year = M.japanese_new_year_on_or_before(mid_year)
    local p = M.japanese_new_moon_on_or_after(new_year + (month - 1) * 29)
    local d = M.japanese_from_fixed(p)
    local prior_new_moon
    if month == d[3] and leap == d[4] then
        prior_new_moon = p
    else
        prior_new_moon = M.japanese_new_moon_on_or_after(p + 1)
    end
    return prior_new_moon + day - 1
end

function M.fixed_from_japanese_for_wikipedia(table)
    -- This isn't in the book, but I have to add it so the function would work from Wikipedia
    return M.fixed_from_japanese({table[1], table[2], table[3], table[4] == 1, table[5]})
end
p.fixed_from_japanese = make_public(M.fixed_from_japanese_for_wikipedia, false)

----- B.20 The Modern Hindu Calendars -----

----- B.21 The Tibetan Calendar -----

----- B.22 Astronomical Lunar Calendars -----

return p