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()
--[[
@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