feat(11-01): implement VALARM builders, classifier, extractor, computeAlertInstantUtc

Task 1 — buildTimedValarm, buildAllDayValarm, VALARM emission in buildVeventString:
- buildTimedValarm(leadMinutes): relative DURATION trigger via resetType('duration') +
  ICAL.Duration.fromSeconds to prevent VALUE=TEXT (Pitfall 2)
- buildAllDayValarm(alertInstantUtc): absolute DATE-TIME trigger via resetType('date-time') +
  ICAL.Time.fromJSDate(utc, true); ensures VALUE=DATE-TIME, no DURATION
- NewEventParams extended with reminderLeadMinutes, valarms, allDayAlertInstantUtc
- buildVeventString: preserve path (valarms[] wins) → all-day absolute → timed relative;
  timed 0 = None per D-06; no emission on null/undefined (CAL-13/D-08)

Task 2 — classifyValarms, extractValarms (CAL-14):
- AlarmClassification type: none | preset | offlist | custom
- PRESET_MINUTES set: 0,5,10,15,30,60,120,1440,2880,10080
- classifyValarms: ICAL.parse try/catch → none/custom/preset/offlist via instanceof ICAL.Time
- extractValarms: returns live ICAL.Component[] for re-attachment; safe on parse failure

Task 3 — computeAlertInstantUtc DST-correct 9 AM local→UTC (NOTIF-06):
- Probes UTC offset at 9 AM (not midnight) so spring-forward/fall-back DST transitions
  before 9 AM resolve with the post-transition offset
- Pure Intl.DateTimeFormat arithmetic, no timezone library; verified at 4 DST boundaries
This commit is contained in:
Lucas Berger
2026-06-13 22:04:18 -04:00
parent 860c7419ac
commit d9eb5c1875
2 changed files with 310 additions and 1 deletions
+3 -1
View File
@@ -426,7 +426,9 @@ describe('extractValarms', () => {
// Should be usable as a component — attach it to a new vevent
const vevent = new ICAL.Component('vevent');
expect(() => vevent.addSubcomponent(alarm)).not.toThrow();
expect(vevent.toString()).toContain('TRIGGER:-PT1H');
// ical.js may normalize -PT60M to -PT1H or keep -PT60M — both are RFC-valid
const serialized = vevent.toString();
expect(serialized).toMatch(/TRIGGER:-P(?:T60M|T1H)/);
});
});