feat(parse): flexible time parsing for @ suffix

Problem: the @HH:MM time suffix required zero-padded 24-hour format,
forcing users to write due:tomorrow@14:00 instead of due:tomorrow@2pm.

Solution: add normalize_time() that accepts bare hours (9, 14),
H:MM (9:30), am/pm (2pm, 9:30am, 12am), and existing HH:MM format,
normalizing all to canonical HH:MM on save.
This commit is contained in:
Barrett Ruth 2026-02-25 20:34:51 -05:00
parent ee2d125846
commit 66eb93a6d1
4 changed files with 157 additions and 4 deletions

View file

@ -323,6 +323,75 @@ describe('parse', function()
end)
end)
describe('resolve_date with time suffix', function()
local today = os.date('*t') --[[@as osdate]]
local tomorrow_str = os.date(
'%Y-%m-%d',
os.time({ year = today.year, month = today.month, day = today.day + 1 })
) --[[@as string]]
it('resolves bare hour to T09:00', function()
local result = parse.resolve_date('tomorrow@9')
assert.are.equal(tomorrow_str .. 'T09:00', result)
end)
it('resolves bare military hour to T14:00', function()
local result = parse.resolve_date('tomorrow@14')
assert.are.equal(tomorrow_str .. 'T14:00', result)
end)
it('resolves H:MM to T09:30', function()
local result = parse.resolve_date('tomorrow@9:30')
assert.are.equal(tomorrow_str .. 'T09:30', result)
end)
it('resolves HH:MM (existing format) to T09:30', function()
local result = parse.resolve_date('tomorrow@09:30')
assert.are.equal(tomorrow_str .. 'T09:30', result)
end)
it('resolves 2pm to T14:00', function()
local result = parse.resolve_date('tomorrow@2pm')
assert.are.equal(tomorrow_str .. 'T14:00', result)
end)
it('resolves 9am to T09:00', function()
local result = parse.resolve_date('tomorrow@9am')
assert.are.equal(tomorrow_str .. 'T09:00', result)
end)
it('resolves 9:30pm to T21:30', function()
local result = parse.resolve_date('tomorrow@9:30pm')
assert.are.equal(tomorrow_str .. 'T21:30', result)
end)
it('resolves 12am to T00:00', function()
local result = parse.resolve_date('tomorrow@12am')
assert.are.equal(tomorrow_str .. 'T00:00', result)
end)
it('resolves 12pm to T12:00', function()
local result = parse.resolve_date('tomorrow@12pm')
assert.are.equal(tomorrow_str .. 'T12:00', result)
end)
it('rejects hour 24', function()
assert.is_nil(parse.resolve_date('tomorrow@24'))
end)
it('rejects 13am', function()
assert.is_nil(parse.resolve_date('tomorrow@13am'))
end)
it('rejects minute 60', function()
assert.is_nil(parse.resolve_date('tomorrow@9:60'))
end)
it('rejects alphabetic garbage', function()
assert.is_nil(parse.resolve_date('tomorrow@abc'))
end)
end)
describe('command_add', function()
it('parses simple text', function()
local desc, meta = parse.command_add('Buy milk')