if not modules then modules = { } end modules ['t-calendar'] = {
    version   = 1.000,
    comment   = "Date calculation collection",
    author    = "Willi Egger",
    copyright = "Willi Egger",
    email     = "w.egger@boede.nl",
    license   = "Public Domain"
}

thirddata                 = thirddata     or { }
thirddata.wegger          = thirddata.wegger  or { }
thirddata.wegger.calendar = { }
local calendar = thirddata.wegger.calendar

-- Lua calculates day 1 = sunday, day 0  = saturday
-- 
-- Calculate the weekday of 1st of january according to S. Babwani
function calendar.janfirst(year)
	local c = math.floor(year/100) -- century
	local y = math.mod(year,100)   -- year without century
    direct = true
	local leap_year = converters.leapyear(year) --returns values 0,1
	direct = false
	if leap_year == 1 then
		janfirst = math.mod((math.floor((5 * y) / 4) + 6 + 1 - (2 * (math.mod(c,4)))),7)
	else
		janfirst = math.mod((math.floor((5 * y) / 4) + 0 + 1 - (2 * (math.mod(c,4)))),7)
	end
	return janfirst
end

-- Calculate the ordinal number of a given MONDAY of the year from the weeknumber
function calendar.wknrordinal(weekdayjanfirst,weeknumber,year)
	--[[ This table contains the offset of the first monday after the 1st of january, 
		as used in the calculation of the ordinal number of a day of the year
	--]]
	local dyearbegin = {1,0,6,5,4,3,2}
	local ordinalday = (weeknumber - 1) * 7 + dyearbegin[weekdayjanfirst]
	return ordinalday
end

--[[Calculate the first day of a week calendar based on the week day of the 1st of janary, 
    date calculations based on the OS-timestamp
--]]
function calendar.weekcalendar(weeknumber,year)
    local weekdayjanfirst = calendar.janfirst(year)	
	if weekdayjanfirst == 0 then weekdayjanfirst = 7 end
	local janfirsttimestamp = os.time({year=year, day=1, month=1})
	local startday = janfirsttimestamp
	local ordinalday = calendar.wknrordinal(weekdayjanfirst,weeknumber,year)
	if weekdayjanfirst > 1 and weekdayjanfirst < 5 and weeknumber == 1 then
		startday = janfirsttimestamp - (weekdayjanfirst-1) * 24 * 60 * 60
	elseif weekdayjanfirst == 5 and weeknumber == 1 then
		startday = janfirsttimestamp - (weekdayjanfirst-1) * 24 * 60 * 60
	else
		startday = janfirsttimestamp + (ordinalday-1) * 24 * 60 * 60
	end
	return startday
end

-- Select a weekday from the week table and return the value to ConTeXt
function calendar.select_dayname(weeknumber,year,day)
	local startday = calendar.weekcalendar(weeknumber,year)
	local d = startday + day * 24 * 60 * 60
	local s = string.lower(os.date("%a",d))
	texio.write_nl("term", "A Dayname: " .. s)
	if s == "sun" then
			-- texio.write_nl("term", "A Dayname red: " .. s)
		return context("\\color[red] %s",context.labeltext(s))

	else
	   return context.labeltext(s)
	end
end

-- Select a date from the week table and return the value to ConTeXt
function calendar.select_fulldate(weeknumber,year,day)
	local startday = calendar.weekcalendar(weeknumber,year)
	local d = startday + day * 24 * 60 * 60
	local s = os.date("%d-%m-%Y",d)
	return context(s)
end

-- Select a ordinal day from the week table and return the value to ConTeXt
function calendar.select_dayofmonth(weeknumber,year,day)
	local startday = calendar.weekcalendar(weeknumber,year)
	local d = startday + day * 24 * 60 * 60
	local s = os.date("%d",d)
	local b = string.lower(os.date("%a",d))
	-- texio.write_nl("term", "B Dayname: " .. s)
	if b == "sun" then
	-- 	-- texio.write_nl("term", "B Dayname red: " .. b)
		return context(s)
	else 
	   return context(s)
	end
end

-- Select the month from the week table and return the value to ConTeXt
function calendar.select_month(weeknumber,year,day)
	local startday = calendar.weekcalendar(weeknumber,year)
	local d = startday + day * 24 * 60 * 60
	local s = os.date("%m",d)
	return context(s)
end

-- Select the month name from the week table and return the value to ConTeXt
function calendar.select_monthname(weeknumber,year,day)
	local startday = calendar.weekcalendar(weeknumber,year)
	local d = startday + day * 24 * 60 * 60
	local s = string.lower(os.date("%B",d))
	return context.labeltext(s)
end

-- Select the fullyear from the week table and return the value to ConTeXt
function calendar.select_fullyear(weeknumber,year,day)
	local startday = calendar.weekcalendar(weeknumber,year)
	local d = startday + day * 24 * 60 * 60
	local s = os.date("%Y",d)
	return context(s)
end

-- Select the year from the week table and return the value to ConTeXt
function calendar.select_year(weeknumber,year,day)
	local startday = calendar.weekcalendar(weeknumber,year)
	local d = startday + day * 24 * 60 * 60
	local s = os.date("%y",d)
	return context(s)
end

--[[
  EASTER DATE CALCULATION FOR YEARS 1583 TO 4099

   y is a 4 digit year 1583 to 4099
   d returns the day of the month of Easter
   m returns the month of Easter
   
   Easter Sunday is the Sunday following the Paschal Full Moon
   (PFM) date for the year
   
   This algorithm is an arithmetic interpretation of the 3 step
   Easter Dating Method developed by Ron Mallen 1985, as a vast
   improvement on the method described in the Common Prayer Book
   
   Because this algorithm is a direct translation of the
   official tables, it can be easily proved to be 100\% correct
   
   This algorithm derives values by sequential inter-dependent
   calculations, so ... DO NOT MODIFY THE ORDER OF CALCULATIONS!
   
   All variables are integer data types
   
   It's free!

	Comment: Translated from a BASIC-source into lua by W. Egger, 11-2010
--]]

function calendar.eastercalculation(year)
	local FirstDig = math.floor(year / 100)   --first 2 digits of year
	local Remain19 = math.mod(year,19)        --remainder of year / 19
    local temp
	local a ={}
	local tA
	local tB
	local tC
	local tD
	local tE
	local m
	local d
	-- calculate PFM (Paschal Full Moon) date
	temp = math.floor((FirstDig - 15) / 2) + 202 - 11 * Remain19
	a = {21, 24, 25, 27, 28, 29, 30, 31, 32, 34, 35, 38}
	for val in pairs(a) do
 		if val == FristDig then
			temp = temp - 1
			break
		end
	end
	
	a = {33, 36, 37, 39, 40}
	for val in pairs(a) do
 		if val == FirstDig then
			temp = temp - 2
			break
		end
	end
		
    temp = math.mod(temp, 30)
    tA = temp + 21
	if temp == 29 then 
		tA = tA - 1
	end
	if (temp == 28 and Remain19 > 10) then 
		tA = tA - 1
    end

	--find the next Sunday
	tB = math.mod((tA - 19), 7)
    tC = math.mod((40 - FirstDig), 4)
	if tC == 3 then 
		tC = tC + 1
	end	
	if tC > 1 then 
		tC = tC + 1
	end
    temp = math.mod(year, 100)
	tD = math.mod(temp + math.floor(temp/ 4), 7)
    tE = math.mod((20 - tB - tC - tD), 7) + 1
	
	d = tA + tE

	--return the date
	if d > 31 then
		d = d - 31
		m = 4
	else
		m = 3
	end
	--return context("Year: " ..year .." month:" ..m .. " day: " ..d)
	local odes = calendar.ordinalday(d,m,year)
	return odes
end

--Calculate the ordinal daynumber from a given date with os.date and os.time
function calendar.ordinalday(day,month,year)
	local t = os.date("*t",os.time{year=year,month=month,day=day})
	local od = t.yday
	return od 
end

--Create a table with the main christian feasts which depend on the easter sunday
function calendar.feasts(ordinaleastersunday)
	local t = {}
	t[1] = {name = "Ashwednesday" , ordinalday = ordinaleastersunday - 46}
	t[2] = {name = "Palmsunday"   , ordinalday = ordinaleastersunday - 7}
	t[3] = {name = "Goodfriday"   , ordinalday = ordinaleastersunday - 2}
	t[4] = {name = "Eastermonday" , ordinalday = ordinaleastersunday + 1}
	t[5] = {name = "Ascensionday" , ordinalday = ordinaleastersunday + 39}
	t[6] = {name = "Pentecost"    , ordinalday = ordinaleastersunday + 49}
end


--Check a date wheather it is a Christian feast
function calendar.checkchristianfeast(day,month,year)
	local odes = calendar.eastercalculation(year)
	local od = calendar.ordinalday(day,month,year)
	local s = nil
	if month == 1 and od == 1 then
			s = "nyd"
	end
	if month < 7 then -- Easter depending feasts are never later than june
		if od == odes - 46 then
			s = "ashw"
		elseif od == odes - 7 then
			s = "palms"
		elseif od == odes - 2 then
			s = "gfri"
		elseif od == odes then
			s = "esun"
		elseif od == odes + 1 then
			s = "esmo"
		elseif od == odes + 39 then
			s = "ascd"
		elseif od == odes + 49 then
			s= "pcst"
		elseif od == odes + 50 then
			s= "pcstmo"
		end
	end
	if month == 12 and day == 25 then
	 	s = "xmas"
    end
	if month == 12 and day == 26 then
	 	s = "bxd"
    end
 	return s
end 

--Get the name of a Christian feast
function calendar.getfeastname(day,month,year)
	local f = calendar.checkchristianfeast(day,month,year)
	if not f then
		f = " "
	end
	return context.labeltext(f)
end

--Create a month table
function calendar.month(month,year)
	monthtable = {}
    local direct = true
    local nofdays = converters.nofdays(year,month,direct)
	direct = false
	local s = os.date("*t", os.time{year=year,month=month,day=1})
	local wday = s.wday - 1  --Correcting the fact that lua starts the week on sunday
	if wday == 0 then 
		wday = 7
	end  
    local beginmonth =  wday-1
	for i = 1, beginmonth do
		monthtable[i] = {day= nil}
	end
	for i = wday, (nofdays + beginmonth) do 
		monthtable[i] = {day = i - wday + 1}
	end
	for i = (nofdays + beginmonth + 1), 42 do --a month table is max. 7 x 6 fields
		monthtable[i] = {day = nil} 
	end
	local wkf = calendar.weeknumber(1,month,year)
	local wkl = calendar.weeknumber(nofdays,month,year)
	-- texio.write_nl("term", "monthtable nofdays "..nofdays)
	-- texio.write_nl("term", "monthtable wkf: "..wkf)
	-- texio.write_nl("term", "monthtable wkl: "..wkl)
	local b = wkl - wkf + 1
	if month > 1 and month < 12 then	
		b = wkl - wkf + 1
	elseif month == 12 and wkl == 1 then
		wkl = 53
		b = wkl - wkf + 1
	elseif month == 1 and wkf > 51 then
		b = wkl + 1
	end
--	texio.write_nl("term", "monthtable B "..b)
    return  b
end

--Select a day from the month table return value to context
function calendar.dayselectofmonth(day)
	local s = monthtable[day].day
    return context(s)
end

--Generate the month name
function calendar.monthname(month,year)
	local month = month
	local year = year
	if  month > 12 then
		month = month - 12
	    year = year + 1
	end
	local s = string.lower(os.date("%B",os.time{year=year,month=month,day=1}))
	return context.labeltext(s)
end

--Generate a Context table containing the days of a given month, topline weekday names
function calendar.monthtableH(month,year)
	local month = month
	local year = year
	if month > 12 then
		month = month - 12
		year = year + 1
	end
context.bTABLE({setups="table:month"})
  context.bTR()
	local w = {"mon","tue","wed","thu","fri","sat","sun"}
     for a,d in ipairs(w) do
       context.bTD({align="middle"}) context.labeltext(d) context.eTD()   
     end
   context.eTR()
	local c = calendar.month(month,year) --returns number of weeks in a given month
  for i= 1, c do
    context.bTR()
    for j=(i-1)*7+1,(i-1)*7+7 do
      context.bTD()context.strut() calendar.dayselectofmonth(j) context.eTD()
    end 
    context.eTR()
  end
context.eTABLE()
end

--Generate a Context table containing the days of a given month, topline weeknumbers 
function calendar.monthtableV(month,year)
	local c = calendar.month(month,year) --returns number of weeks in a given month
	local wkf = calendar.weeknumber(1,month,year)    
    local direct = true
    local nofdays = converters.nofdays(year,month,direct)
	direct = false
	local wkl = calendar.weeknumber(nofdays,month,year)
	context.bTABLE({setups="table:year"})
		context.bTR({align="flushright"})
		 	context.bTD() context.strut() context.eTD()
			if month == 1 and wkf > 51 then
				context.bTD() context(wkf) context.eTD()
	 			wkf = 1
				for i = wkf, c - 1 do
					context.bTD() context(i) context.eTD()
				end 
			else
				for i = wkf, wkf + c -1 do
					context.bTD() context(i) context.eTD()
				end
			end
		context.eTR() 
		local w = {"mon","tue","wed","thu","fri","sat","sun"}
		local row = 0 
		for a,d in ipairs(w) do
			if a == 7 then
				context.bTR({align="flushright",style="red"})
			else
				context.bTR({align="flushright"})
			end 
			context.bTD({align="flushleft"}) context.labeltext(d) context.eTD()
		  	for i=1,c*7,7 do
				local day = i + row
				context.bTD()context.strut() calendar.dayselectofmonth(day) context.eTD()
			end
			row = row + 1
			context.eTR()			
	     end
	context.eTABLE()
end


function calendar.weektable(mondaytimestamp,year)
	local s = mondaytimestamp
	local d = nil
	local n = nil
	local f = nil
	local m = nil
	context.bTABLE({setups="table:week"}) 
	for i = 1,5 do
		day = s + i * 24 * 60 * 60
		d = tonumber(os.date("%d",day))
		n = string.lower(os.date("%a",day))
		m = tonumber(os.date("%m",day))
		f = calendar.checkchristianfeast(d,m,year)
		if not f then
			f = " "
		end
		context.bTR()
			context.bTD({nx=2}) context(d) context("~") context.labeltext(n) context("~") context.labeltext(f) context.eTD()
		  context.eTR()
	end
		context.bTR()
			day = s + 6 * 24 * 60 * 60
			d = tonumber(os.date("%d",day))
			n = string.lower(os.date("%a",day))
			m = tonumber(os.date("%m",day))
			f = calendar.checkchristianfeast(d,m,year)
			if not f then
				f = " "
			end
			context.bTD() context(d) context("~") context.labeltext(n) context("~") context.labeltext(f) context.eTD()
			day = s + 7 * 24 * 60 * 60
			d = tonumber(os.date("%d",day))
			n = string.lower(os.date("%a",day))
			m = tonumber(os.date("%m",day))
			f = calendar.checkchristianfeast(d,m,year)
			if not f then
				f = " "
			end
			context.bTD({style="red"})context(d) context("~") context.labeltext(n) context("~\\color[black]") context.labeltext(f) context.eTD()
		context.eTR()
   	context.eTABLE()
end

function calendar.thisweek(weeknumber,year)
    local w = weeknumber
	local mondaytimestamp = calendar.weekcalendar(w,year)
    calendar.weektable(mondaytimestamp,year)
end

function calendar.nextweek(weeknumber,year)
    local w = weeknumber + 1
	local mondaytimestamp = calendar.weekcalendar(w,year)
    calendar.weektable(mondaytimestamp,year)
end

function calendar.yearcalendar(year)
--The following line works, but I decided to keep it in the setup-file
--context.setupTABLE({"column"},{"each"},{width=number.todimen(.03*tex.dimen.textwidth),align="flushright",offset="3pt"})
context.startcombination({"4*3"})
	for i= 1,12 do
		local monthname = string.lower(os.date("%B",os.time{year=year,month=i,day=1}))
		context.framedtext({frame="off",
							style="bold",
							width=number.todimen(0.23*tex.dimen.textwidth),
							offset="0.5pt",
							align="middle"},
		   function() context.labeltext(monthname) end,
		   function() calendar.monthtableV(i,year) end
		)
	end
context.stopcombination()
end

-- http://www.irt.org/script/914.htm, Java, Ferry van Schaijk 
-- Get the ISO week date week number from a given date
function calendar.weeknumber(day,month,year)
	    local when = os.time({year=year,month=month,day=day})
	    local modDay = tonumber(os.date("%w",os.time({year=year,month=1,day=1})))
	    if modDay == 0 then 
			modDay = 7
		else
		 	modDay = modDay - 1
		end
	    local daynum = math.round((when - os.time({year=year,month=1,day=1})) /60/60/24) + 1
        local weeknum = 0
	    if modDay < 4 then
	        weeknum = math.floor((daynum+modDay-1)/7)+1
	    else 
	        weeknum = math.floor((daynum+modDay-1)/7)
	        if weeknum == 0 then
	            local prevmodDay = tonumber(os.date("%w",os.time({year=year-1,month=1,day=1})))
            	if prevmodDay == 0 then 
					prevmodDay = 7 
				end
				if prevmodDay < 5 then  --<4
					weeknum = 53 
				else 
					weeknum = 52 
				end    	
			end
	    end
	    return weeknum
end

-- Calculate the week number of a given monday of the year from the ordinal day number
function calendar.ordinaltowknr(ordinalday,year)
	--[[ This table contains the offset of the first monday after the 1st of january, 
		as used in the calculation of the ordinal number of a day of the year
	--]]
	local dyearbegin = {1,0,6,5,4,3,2}
	local weekdayjanfirst = calendar.janfirst(year)
	if weekdayjanfirst == 0 then weekdayjanfirst = 7 end
	local wknumber = math.div(ordinalday - dyearbegin[weekdayjanfirst],7) + 1
	return wknumber
end 

function calendar.nextweeknumberdetermination(weeknumber,year)
	local wk = weeknumber
	local wkbegin = calendar.weekcalendar(wk,year) --timestamp of begin of this week
	local nxtwkbegin = wkbegin + 7 * 24 * 60 * 60
	local d = os.date("*t",nxtwkbegin)
	local s = calendar.ordinaltowknr(d.yday+1,year) --correct for starting week on monday instead of sunday
	return context("%s {\\quad} %s", s,d.year)
end