--- Icelandic calendar conversions. -- Ported from "Calendrical Calculations" (4th edition) -- by Nachum Dershowitz and Edward M. Reingold. -- Original Lisp code (CALENDRICA 4.0) is Apache 2.0 licensed. -- @module calendrica-icelandic -- @release 0.1 2026-07-19 local M = {} local basic = require("calendrica-basic") local gregorian = require("calendrica-gregorian") local astro = require("calendrica-astro") -- === Epoch === -- Fixed date of start of the Icelandic calendar. local ICELANDIC_EPOCH = gregorian.fixed_from_gregorian( gregorian.gregorian_date(1, gregorian.APRIL, 19) ) -- === Date constructor and accessors === -- Icelandic date constructor. local function icelandic_date(year, season, week, weekday) return {year, season, week, weekday} end -- Icelandic year accessor. local function icelandic_year(i_date) return i_date[1] end -- Icelandic season accessor. local function icelandic_season(i_date) return i_date[2] end -- Icelandic week accessor. local function icelandic_week(i_date) return i_date[3] end -- Icelandic weekday accessor. local function icelandic_weekday(i_date) return i_date[4] end -- === Season boundaries === -- Fixed date of start of summer season in Icelandic year i_year. local function icelandic_summer(i_year) local apr19 = ICELANDIC_EPOCH + 365 * (i_year - 1) + basic.sigma( { basic.to_radix(i_year, {4,25,4}), {97,24,1,0} }, function(y,a) return y*a end) return gregorian.kday_on_or_after(basic.THURSDAY, apr19) end -- Fixed date of start of winter season in Icelandic year i_year. local function icelandic_winter(i_year) return icelandic_summer(i_year + 1) - 180 end -- === Conversion === --- Fixed date equivalent to Icelandic date `i_date`. -- @tparam table i_date Icelandic date {year, season, week, weekday}. -- @treturn number Fixed date. function M.fixed_from_icelandic(i_date) local year = icelandic_year(i_date) local season = icelandic_season(i_date) local week = icelandic_week(i_date) local weekday = icelandic_weekday(i_date) local start -- Start of season. local shift -- First day of week in prior season. if season == astro.SUMMER then start = icelandic_summer(year) shift = basic.THURSDAY else start = icelandic_winter(year) shift = basic.SATURDAY end return start + 7 * (week - 1) -- Elapsed weeks. + (weekday - shift) % 7 end local fixed_from_icelandic = M.fixed_from_icelandic --- Icelandic date {year, season, week, weekday} corresponding to fixed `date`. -- @tparam number date Fixed date. -- @treturn table {year, season, week, weekday} function M.icelandic_from_fixed(date) local approx = basic.quotient( date - ICELANDIC_EPOCH + 369, 146097 / 400 ) local year if date >= icelandic_summer(approx) then year = approx else year = approx - 1 end local season if date < icelandic_winter(year) then season = astro.SUMMER else season = astro.WINTER end local start -- Start of current season. if season == astro.SUMMER then start = icelandic_summer(year) else start = icelandic_winter(year) end local week = 1 + basic.quotient(date - start, 7) local weekday = basic.day_of_week_from_fixed(date) return icelandic_date(year, season, week, weekday) end -- === Predicates and helpers === -- True if Icelandic i_year is a leap year (53 weeks). local function icelandic_leap_year(i_year) return (icelandic_summer(i_year + 1) - icelandic_summer(i_year)) ~= 364 end -- Month of i_date on the Icelandic calendar. local function icelandic_month(i_date) local date = fixed_from_icelandic(i_date) local year = icelandic_year(i_date) local season = icelandic_season(i_date) local midsummer = icelandic_winter(year) - 90 local start if season == astro.WINTER then start = icelandic_winter(year) elseif date >= midsummer then start = midsummer - 90 elseif date < icelandic_summer(year) + 90 then start = icelandic_summer(year) else start = midsummer -- Epagomenae. end return 1 + basic.quotient(date - start, 30) end -- === Exports === --- Fixed date of start of summer season in Icelandic year `i_year`. -- @function icelandic_summer -- @tparam number i_year Icelandic year. -- @treturn number Fixed date. M.icelandic_summer = icelandic_summer --- Fixed date of start of winter season in Icelandic year `i_year`. -- @function icelandic_winter -- @tparam number i_year Icelandic year. -- @treturn number Fixed date. M.icelandic_winter = icelandic_winter --- True if Icelandic year `i_year` is a leap year (53 weeks). -- @function icelandic_leap_year -- @tparam number i_year Icelandic year. -- @treturn boolean M.icelandic_leap_year = icelandic_leap_year --- Month number of Icelandic date `i_date`. -- @function icelandic_month -- @tparam table i_date Icelandic date {year, season, week, weekday}. -- @treturn number Month number. M.icelandic_month = icelandic_month return M