fix: remind of a task relative to its start when the reminder says so #83

This commit is contained in:
Linus Rath
2026-10-29 03:14:58 +01:00
parent c6c3d66a9b
commit 6f475c0c52
2 changed files with 38 additions and 3 deletions
@@ -49,8 +49,8 @@ describe('parseAlertOffset / computeFireTime', () => {
);
});
it('fires task alerts relative to due', () => {
const task = { ...event, due: '2026-03-02T09:00:00', alerts: undefined };
it('fires the alerts of a task without a start relative to its due', () => {
const task = { ...event, start: undefined as unknown as string, utcStart: undefined, due: '2026-03-02T09:00:00', alerts: undefined };
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT1H' })).toBe(
new Date('2026-03-02T09:00:00').getTime() - HOUR,
);
@@ -58,11 +58,32 @@ describe('parseAlertOffset / computeFireTime', () => {
});
it('reads a zoned task due in its own time zone', () => {
const task = { ...event, due: '2026-07-01T17:00:00', timeZone: 'Europe/Berlin', alerts: undefined };
const task = { ...event, start: undefined as unknown as string, utcStart: undefined, due: '2026-07-01T17:00:00', timeZone: 'Europe/Berlin', alerts: undefined };
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT1H' })).toBe(
new Date('2026-07-01T14:00:00Z').getTime(),
);
});
// #83 (webmail #1328): start 20:00, due 20:15, five minutes before the start.
it('fires a task alert relative to its start from the start, and one relative to its end from the due', () => {
const task = {
...event, start: '2026-07-01T20:00:00', utcStart: undefined, due: '2026-07-01T20:15:00', timeZone: 'Europe/Berlin', alerts: undefined,
};
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT5M', relativeTo: 'start' })).toBe(
new Date('2026-07-01T17:55:00Z').getTime(),
);
// relativeTo left out means the start.
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT5M' })).toBe(
new Date('2026-07-01T17:55:00Z').getTime(),
);
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT5M', relativeTo: 'end' })).toBe(
new Date('2026-07-01T18:10:00Z').getTime(),
);
// The server's UTC start, when it gave one.
expect(computeTaskFireTime({ ...task, utcStart: '2026-07-01T18:00:00Z' }, { '@type': 'OffsetTrigger', offset: '-PT5M' })).toBe(
new Date('2026-07-01T17:55:00Z').getTime(),
);
});
});
describe('getEffectiveAlerts', () => {
@@ -91,6 +112,8 @@ describe('getUpcomingAlerts', () => {
const task = {
...event,
id: 't1',
start: undefined as unknown as string,
utcStart: undefined,
due: '2026-03-01T12:00:00Z',
alerts: { x: { trigger: { '@type': 'OffsetTrigger', offset: 'PT0S' }, action: 'display' } },
};
@@ -109,6 +132,8 @@ describe('getUpcomingAlerts', () => {
const task = {
...untitled,
id: 't1',
start: undefined as unknown as string,
utcStart: undefined,
due: '2026-03-01T12:00:00Z',
alerts: { x: { trigger: { '@type': 'OffsetTrigger' as const, offset: 'PT0S' }, action: 'display' as const } },
};
+10
View File
@@ -83,6 +83,16 @@ export function computeTaskFireTime(task: CalendarEvent, trigger: Alert['trigger
if (trigger.offset === undefined) return null;
const offsetMs = parseAlertOffset(trigger.offset);
if (offsetMs === null) return null;
// RFC 8984 §4.5.2: relativeTo defaults to "start", which for a Task is its
// start; "end" is its due. A task without a start counts from its due
// (#83, webmail #1328).
if (trigger.relativeTo !== 'end' && task.start) {
const start = task.utcStart
? new Date(task.utcStart)
// The start read like a due: in the task's zone, or a calendar date.
: getTaskDueDate({ due: task.start, timeZone: task.timeZone, showWithoutTime: task.showWithoutTime });
if (start && !Number.isNaN(start.getTime())) return start.getTime() + offsetMs;
}
const due = getTaskDueDate(task);
if (!due) return null;
return due.getTime() + offsetMs;