--- ISO week-date 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-iso -- @release 0.1 2026-07-19 local M = {} local basic = require("calendrica-basic") local gregorian = require("calendrica-gregorian") -- === Date constructor and accessors === -- ISO date constructor. local function iso_date(year, week, day) return {year, week, day} end -- ISO year accessor. local function iso_year(date) return date[1] end -- ISO week accessor. local function iso_week(date) return date[2] end -- ISO day accessor. local function iso_day(date) return date[3] end -- === Conversion === --- Fixed date equivalent to ISO date `i_date`. -- @tparam table i_date ISO date {year, week, day}. -- @treturn number Fixed date. function M.fixed_from_iso(i_date) local week = iso_week(i_date) local day = iso_day(i_date) local year = iso_year(i_date) -- Add fixed date of Sunday preceding date plus day in week. return gregorian.nth_kday( week, basic.SUNDAY, gregorian.gregorian_date(year - 1, gregorian.DECEMBER, 28) ) + day end local fixed_from_iso = M.fixed_from_iso --- ISO date {year, week, day} corresponding to fixed `date`. -- @tparam number date Fixed date. -- @treturn table {year, week, day} function M.iso_from_fixed(date) -- Year may be one too small. local approx = gregorian.gregorian_year_from_fixed(date - 3) local year if date >= fixed_from_iso(iso_date(approx + 1, 1, 1)) then year = approx + 1 else year = approx end local week = 1 + basic.quotient( date - fixed_from_iso(iso_date(year, 1, 1)), 7 ) local day = basic.amod(date - basic.rd(0), 7) return iso_date(year, week, day) end -- === Long year predicate === -- True if i_year is a long (53-week) ISO year. local function iso_long_year(i_year) local jan1 = basic.day_of_week_from_fixed(gregorian.gregorian_new_year(i_year)) local dec31 = basic.day_of_week_from_fixed(gregorian.gregorian_year_end(i_year)) return jan1 == basic.THURSDAY or dec31 == basic.THURSDAY end --- True if ISO year `i_year` is a long year (53 weeks). -- @function iso_long_year -- @tparam number i_year ISO year. -- @treturn boolean M.iso_long_year = iso_long_year return M