Check Sort Dates.fh_lua

--[[
@Title:       Check Sort Dates
@Type:        Standard
@Author:      Mark Draper
@Version:     1.1
@LastUpdated: 23 Jun 2025 
@Licence: This plugin is copyright (c) 2025 Mark Draper, and is licensed under the MIT License
which is hereby incorporated by reference (see https://pluginstore.family-historian.co.uk/fh-plugin-licence)
@Description: Lists and optionally deletes sort dates that are either invalid or unnecessary
]]

fhInitialise(7, 0, 0, 'save_required')
require('iuplua')
require 'luacom'

fh = require('fhUtils')
fh.setIupDefaults()
iup.SetGlobal('UTF8MODE_FILE','YES')

function main()

	local selection

	-- create menu

	local Title = iup.label{title = 'Select option to check project sort dates', padding = '10x10'}
	local btnSortDates = iup.button{title = 'Check sort dates', size = '80x15'} 
	local btnHelp = iup.button{title = 'Help'} 
	local btnClose = iup.button{title = 'Close'}

	local Buttons = iup.vbox{btnSortDates, btnHelp, btnClose;
			normalizesize = 'BOTH', gap = 10}
	local vbox = iup.vbox{Title, Buttons; alignment = 'ACENTER', gap = 10; margin = '20x20'}
	local dialog = iup.dialog{vbox; resize = 'No', minbox = 'No', maxbox = 'No', border = 'NO', 
			 title = 'Check Sort Dates (1.1)'}
	iup.SetAttribute(dialog, 'NATIVEPARENT', fhGetContextInfo('CI_PARENT_HWND'))

	function btnSortDates:action()
		selection = 1
		return iup.CLOSE
	end

	function btnHelp:action()
		selection = 2
		return iup.CLOSE
	end

	function btnClose:action()
		selection = 0
		return iup.CLOSE
	end

	repeat
		selection = 0		

		dialog:show()
		iup.MainLoop()
		dialog:hide()

		if selection == 1 then
			selection = SortDates()
		elseif selection == 2 then
			local Cmd = 'https://pluginstore.family-historian.co.uk/page/help/check-sort-dates'
			fhShellExecute(Cmd)
			fhSleep(1000) 			-- slight pause to suspend immediate redraw
		end 
	until not selection or selection == 0
end

-- ************************************************************************** --

function SortDates()

	-- deletes all invalid sort dates (those that do not resolve as dates) and superfluous sort dates
	-- (equal to actual date or start of range)

	local p = fhNewItemPtr()
	local pR = fhNewItemPtr()
	local tblX = {}
	local CountI, CountS = 0, 0

	for _, Type in ipairs({'INDI', 'FAM'}) do
		pR:MoveToFirstRecord(Type)
		while pR:IsNotNull() do
			p:MoveToFirstChildItem(pR)
			while p:IsNotNull() do
				local pD = fhGetItemPtr(p, '~.DATE')
				local pSD = fhGetItemPtr(p, '~._SDATE')
				if pSD:IsNotNull() then
					local dtSDate = fhGetValueAsDate(pSD)
					if dtSDate:IsNull() then					-- invalid date
						table.insert(tblX, pSD:Clone())
						CountI = CountI + 1
					else
						local dtDate = fhGetValueAsDate(pD)
						local dpDate = dtDate:GetDatePt1()
						local dpSDate = dtSDate:GetDatePt1()
						if dpDate:Compare(dpSDate) == 0 then
							table.insert(tblX, pSD:Clone())
							CountS = CountS + 1
						end
					end
				end					
				p:MoveNext()
			end
			pR:MoveNext()
		end
	end

	if #tblX == 0 then
		MessageBox('No invalid or superfluous sort dates identified.', 'OK', 'INFORMATION')
		return true
	end

	local msg = CountI .. ' invalid and ' .. CountS .. ' superfluous sort dates were identified.\n'
	if CountI + CountS == 1 then msg = msg:gsub('dates were', 'date was') end
	if #tblX == 1 then
		msg = msg .. 'What do you want to do with it?'
	else
		msg = msg .. 'What do you want to do with them?'
	end
	local Action = AlarmForm('Process Sort Dates', msg, 'List', 'Delete', 'Cancel')
	if Action == 3 then
		return true
	elseif Action == 2 then
		for _, p in ipairs(tblX) do fhDeleteItem(p) end
		fhUpdateDisplay()
		return true
	elseif Action == 1 then
		local tblXiE = {}
		local tblXiED = {}
		local tblXiR = {}
		local tblXSD = {}
		for _, p in ipairs(tblX) do
			local pE = fhNewItemPtr()
			pE:MoveToParentItem(p)
			local pED = fhGetItemPtr(pE, '~.DATE')
			local pR = fhNewItemPtr()
			pR:MoveToParentItem(pE)
			table.insert(tblXiE, pE:Clone())
			table.insert(tblXiED, pED:Clone())
			table.insert(tblXiR, pR:Clone())
			table.insert(tblXSD, p:Clone())
		end	
		fhOutputResultSetTitles('Sort Dates')
		fhOutputResultSetColumn('Record', 'item', tblXiR, #tblXiR, 200, 'align_left', 0, true, 'default')
		fhOutputResultSetColumn('Fact', 'item', tblXiE, #tblXiE, 175, 'align_left', 0, true, 'default')
		fhOutputResultSetColumn('Date', 'item', tblXiED, #tblXiED, 160, 'align_left', 0, true, 'date')
		fhOutputResultSetColumn('Sort Date', 'item', tblXSD, #tblXSD, 80, 'align_left', 0, true, 'date')
	end
end

-- ************************************************************************** --

function AlarmForm(formtitle, heading, txt1, txt2, txt3)

	-- create form modelled on iup.alarm (original iup.alarm does not support multi-monitor setup)

	local action = 0

	local btn1 = iup.button{title = txt1, padding = '10x3',
			action = function(self) action = 1 return iup.CLOSE end}
	local btn2 = iup.button{title = txt2, padding = '10x3',
			action = function(self) action = 2 return iup.CLOSE end}
	local btn3 = iup.button{title = txt3, padding = '10x3',
			action = function(self) action = 3 return iup.CLOSE end}
	local buttons = iup.hbox{iup.fill{}, btn1, btn2, btn3, iup.fill{};
			margin = '0x10', normalizesize = 'Both', padding = 10, gap = 20}
	local label = iup.label{title = heading}
	local vbox = iup.vbox{label, buttons; gap = 10, margin = '20x20'}
	local dialog = iup.dialog{vbox; title = formtitle, resize = 'No', minbox = 'No'}
	if fhGetAppVersion() > 6 then
		iup.SetAttribute(dialog, 'NATIVEPARENT', fhGetContextInfo('CI_PARENT_HWND'))
	end
	dialog:popup()

	return action
end

-- ******************************************************************************************************--

function MessageBox(Message, Buttons, Icon, Title, Default)

	-- replaces built-in function with custom version containing more options

	local msgdlg = iup.messagedlg{value = Message, buttons = Buttons, dialogtype = Icon,
			title = Title or 'Check Sort Dates (1.1)', buttondefault = Default}
	msgdlg:popup()
	return tonumber(msgdlg.ButtonResponse)
end

-- ******************************************************************************************************--

main()

Source:Check-Sort-Dates-2.fh_lua