feat: recurrence expansion.

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-06-29 22:19:38 -04:00
parent cd270e9e99
commit acbe0ad5ca
8 changed files with 478 additions and 72 deletions
+12 -42
View File
@@ -1,46 +1,11 @@
import type { EntityObject } from '@ChronoManager/models/entity'
import type { EventObject } from '@ChronoManager/models/event'
import type {
CalendarOccurrence,
CalendarOccurrenceIndex,
} from '../types/occurrence'
import type { VisibleDateRange } from '../types'
import { shiftLocalDays, startOfLocalDay } from './dateRanges'
const MINIMUM_DURATION_MS = 1
function eventTimestamp(value: string | null | undefined): number | null {
if (!value) return null
const timestamp = Date.parse(value)
return Number.isFinite(timestamp) ? timestamp : null
}
function occurrenceFromEntity(entity: EntityObject): CalendarOccurrence | null {
const event = entity.properties as EventObject
const startMs = eventTimestamp(event.startsOn)
if (startMs === null) return null
const parsedEndMs = eventTimestamp(event.endsOn)
const endMs = parsedEndMs !== null && parsedEndMs > startMs
? parsedEndMs
: startMs + MINIMUM_DURATION_MS
const dayStartMs = startOfLocalDay(new Date(startMs)).getTime()
const lastOccupiedDayMs = startOfLocalDay(new Date(endMs - 1)).getTime()
const entityKey = String(entity.identifier)
return {
key: `${entityKey}:master`,
entity,
occurrenceId: null,
startMs,
endMs,
dayStartMs,
durationMs: endMs - startMs,
timeless: event.timeless === true || lastOccupiedDayMs !== dayStartMs,
multiDay: lastOccupiedDayMs !== dayStartMs,
}
}
import { expandEntityOccurrences } from './recurrence'
function appendToDayBuckets(index: CalendarOccurrenceIndex, occurrence: CalendarOccurrence) {
const lastOccupiedDayMs = startOfLocalDay(new Date(occurrence.endMs - 1)).getTime()
@@ -63,7 +28,10 @@ function sortOccurrences(occurrences: CalendarOccurrence[]) {
))
}
export function buildOccurrenceIndex(entities: EntityObject[]): CalendarOccurrenceIndex {
export function buildOccurrenceIndex(
entities: EntityObject[],
range?: VisibleDateRange,
): CalendarOccurrenceIndex {
const index: CalendarOccurrenceIndex = {
byDay: new Map(),
byEntity: new Map(),
@@ -71,12 +39,14 @@ export function buildOccurrenceIndex(entities: EntityObject[]): CalendarOccurren
}
for (const entity of entities) {
const occurrence = occurrenceFromEntity(entity)
if (!occurrence) continue
const entityOccurrences = expandEntityOccurrences(entity, range)
if (entityOccurrences.length === 0) continue
index.sorted.push(occurrence)
index.byEntity.set(String(entity.identifier), [occurrence])
appendToDayBuckets(index, occurrence)
index.byEntity.set(String(entity.identifier), entityOccurrences)
for (const occurrence of entityOccurrences) {
index.sorted.push(occurrence)
appendToDayBuckets(index, occurrence)
}
}
sortOccurrences(index.sorted)