Refactor to use no of days instead of enddate on events. Other minor cleanup, larger date limits.

This commit is contained in:
Leo Vasanko 2025-08-25 22:40:03 -06:00
parent 579d01dfd8
commit cd4ce6f69e
4 changed files with 63 additions and 57 deletions

View File

@ -244,10 +244,16 @@ function openCreateDialog(selectionData = null) {
recurrenceWeekdays.value[startingDay] = true recurrenceWeekdays.value[startingDay] = true
initialWeekday.value = startingDay initialWeekday.value = startingDay
let days = 1
if (start && end && start <= end) {
const s = fromLocalString(start, DEFAULT_TZ)
const e = fromLocalString(end, DEFAULT_TZ)
days = Math.max(1, (e - s) / 86400000 + 1)
}
editingEventId.value = calendarStore.createEvent({ editingEventId.value = calendarStore.createEvent({
title: '', title: '',
startDate: start, startDate: start,
endDate: end, days,
colorId: colorId.value, colorId: colorId.value,
recur: recur:
recurrenceEnabled.value && repeat.value !== 'none' recurrenceEnabled.value && repeat.value !== 'none'
@ -306,7 +312,8 @@ function openEditDialog(payload) {
if (event.recur.freq === 'weeks' && occurrenceIndex >= 0) { if (event.recur.freq === 'weeks' && occurrenceIndex >= 0) {
const pattern = event.recur.weekdays || [] const pattern = event.recur.weekdays || []
const baseStart = fromLocalString(event.startDate, DEFAULT_TZ) const baseStart = fromLocalString(event.startDate, DEFAULT_TZ)
const baseEnd = fromLocalString(event.endDate, DEFAULT_TZ) const baseEnd = new Date(fromLocalString(event.startDate, DEFAULT_TZ))
baseEnd.setDate(baseEnd.getDate() + (event.days || 1) - 1)
if (occurrenceIndex === 0) { if (occurrenceIndex === 0) {
occurrenceDate = baseStart occurrenceDate = baseStart
weekday = baseStart.getDay() weekday = baseStart.getDay()

View File

@ -1,5 +1,5 @@
import { ref } from 'vue' import { ref } from 'vue'
import { addDays, differenceInWeeks, differenceInCalendarDays } from 'date-fns' import { addDays, differenceInWeeks } from 'date-fns'
import { import {
toLocalString, toLocalString,
fromLocalString, fromLocalString,
@ -72,19 +72,27 @@ export function createVirtualWeekManager({
const collectEventsForDate = (dateStr, curDateObj) => { const collectEventsForDate = (dateStr, curDateObj) => {
const storedEvents = [] const storedEvents = []
for (const ev of calendarStore.events.values()) { for (const ev of calendarStore.events.values()) {
if (!ev.recur && dateStr >= ev.startDate && dateStr <= ev.endDate) { if (!ev.recur) {
storedEvents.push(ev) const evEnd = toLocalString(
addDays(fromLocalString(ev.startDate, DEFAULT_TZ), (ev.days || 1) - 1),
DEFAULT_TZ,
)
if (dateStr >= ev.startDate && dateStr <= evEnd) {
storedEvents.push({ ...ev, endDate: evEnd })
}
} }
} }
const dayEvents = [...storedEvents] const dayEvents = [...storedEvents]
for (const base of repeatingBases) { for (const base of repeatingBases) {
if (dateStr >= base.startDate && dateStr <= base.endDate) { const baseEnd = toLocalString(
dayEvents.push({ ...base, _recurrenceIndex: 0, _baseId: base.id }) addDays(fromLocalString(base.startDate, DEFAULT_TZ), (base.days || 1) - 1),
DEFAULT_TZ,
)
if (dateStr >= base.startDate && dateStr <= baseEnd) {
dayEvents.push({ ...base, endDate: baseEnd, _recurrenceIndex: 0, _baseId: base.id })
continue continue
} }
const baseStart = fromLocalString(base.startDate, DEFAULT_TZ) const spanDays = (base.days || 1) - 1
const baseEnd = fromLocalString(base.endDate, DEFAULT_TZ)
const spanDays = Math.max(0, differenceInCalendarDays(baseEnd, baseStart))
const currentDate = curDateObj const currentDate = curDateObj
let occurrenceFound = false let occurrenceFound = false
for (let offset = 0; offset <= spanDays && !occurrenceFound; offset++) { for (let offset = 0; offset <= spanDays && !occurrenceFound; offset++) {
@ -303,19 +311,27 @@ export function createVirtualWeekManager({
// Rebuild events list for this day // Rebuild events list for this day
const storedEvents = [] const storedEvents = []
for (const ev of calendarStore.events.values()) { for (const ev of calendarStore.events.values()) {
if (!ev.recur && dateStr >= ev.startDate && dateStr <= ev.endDate) { if (!ev.recur) {
storedEvents.push(ev) const evEnd = toLocalString(
addDays(fromLocalString(ev.startDate, DEFAULT_TZ), (ev.days || 1) - 1),
DEFAULT_TZ,
)
if (dateStr >= ev.startDate && dateStr <= evEnd) {
storedEvents.push({ ...ev, endDate: evEnd })
}
} }
} }
const dayEvents = [...storedEvents] const dayEvents = [...storedEvents]
for (const base of repeatingBases) { for (const base of repeatingBases) {
if (dateStr >= base.startDate && dateStr <= base.endDate) { const baseEndStr = toLocalString(
dayEvents.push({ ...base, _recurrenceIndex: 0, _baseId: base.id }) addDays(fromLocalString(base.startDate, DEFAULT_TZ), (base.days || 1) - 1),
DEFAULT_TZ,
)
if (dateStr >= base.startDate && dateStr <= baseEndStr) {
dayEvents.push({ ...base, endDate: baseEndStr, _recurrenceIndex: 0, _baseId: base.id })
continue continue
} }
const baseStart = fromLocalString(base.startDate, DEFAULT_TZ) const spanDays = (base.days || 1) - 1
const baseEnd = fromLocalString(base.endDate, DEFAULT_TZ)
const spanDays = Math.max(0, differenceInCalendarDays(baseEnd, baseStart))
const currentDate = fromLocalString(dateStr, DEFAULT_TZ) const currentDate = fromLocalString(dateStr, DEFAULT_TZ)
let occurrenceFound = false let occurrenceFound = false
for (let offset = 0; offset <= spanDays && !occurrenceFound; offset++) { for (let offset = 0; offset <= spanDays && !occurrenceFound; offset++) {

View File

@ -127,14 +127,18 @@ export const useCalendarStore = defineStore('calendar', {
}, },
createEvent(eventData) { createEvent(eventData) {
const singleDay = eventData.startDate === eventData.endDate let days = 1
if (typeof eventData.days === 'number') {
days = Math.max(1, Math.floor(eventData.days))
}
const singleDay = days === 1
const event = { const event = {
id: this.generateId(), id: this.generateId(),
title: eventData.title, title: eventData.title,
startDate: eventData.startDate, startDate: eventData.startDate,
endDate: eventData.endDate, days,
colorId: colorId:
eventData.colorId ?? this.selectEventColorId(eventData.startDate, eventData.endDate), eventData.colorId ?? this.selectEventColorId(eventData.startDate, eventData.startDate),
startTime: singleDay ? eventData.startTime || '09:00' : null, startTime: singleDay ? eventData.startTime || '09:00' : null,
durationMinutes: singleDay ? eventData.durationMinutes || 60 : null, durationMinutes: singleDay ? eventData.durationMinutes || 60 : null,
recur: recur:
@ -149,7 +153,7 @@ export const useCalendarStore = defineStore('calendar', {
} }
: null, : null,
} }
this.events.set(event.id, { ...event, isSpanning: event.startDate < event.endDate }) this.events.set(event.id, { ...event, isSpanning: event.days > 1 })
this.notifyEventsChanged() this.notifyEventsChanged()
return event.id return event.id
}, },
@ -164,7 +168,7 @@ export const useCalendarStore = defineStore('calendar', {
const endDate = fromLocalString(endDateStr, DEFAULT_TZ) const endDate = fromLocalString(endDateStr, DEFAULT_TZ)
for (const ev of this.events.values()) { for (const ev of this.events.values()) {
const evStart = fromLocalString(ev.startDate) const evStart = fromLocalString(ev.startDate)
const evEnd = fromLocalString(ev.endDate) const evEnd = addDays(evStart, (ev.days || 1) - 1)
if (evEnd < startDate || evStart > endDate) continue if (evEnd < startDate || evStart > endDate) continue
if (ev.colorId >= 0 && ev.colorId < 8) colorCounts[ev.colorId]++ if (ev.colorId >= 0 && ev.colorId < 8) colorCounts[ev.colorId]++
} }
@ -202,17 +206,10 @@ export const useCalendarStore = defineStore('calendar', {
this.deleteEvent(baseId) this.deleteEvent(baseId)
return return
} }
const oldStart = fromLocalString(base.startDate, DEFAULT_TZ)
const oldEnd = fromLocalString(base.endDate, DEFAULT_TZ)
const durationDays = Math.max(0, differenceInCalendarDays(oldEnd, oldStart))
const newEndStr = toLocalString(
addDays(fromLocalString(nextStartStr, DEFAULT_TZ), durationDays),
DEFAULT_TZ,
)
base.startDate = nextStartStr base.startDate = nextStartStr
base.endDate = newEndStr // keep same days length
if (numericCount !== Infinity) base.recur.count = String(Math.max(1, numericCount - 1)) if (numericCount !== Infinity) base.recur.count = String(Math.max(1, numericCount - 1))
this.events.set(baseId, { ...base, isSpanning: base.startDate < base.endDate }) this.events.set(baseId, { ...base, isSpanning: base.days > 1 })
this.notifyEventsChanged() this.notifyEventsChanged()
}, },
@ -234,14 +231,6 @@ export const useCalendarStore = defineStore('calendar', {
base.recur.count = occurrenceIndex base.recur.count = occurrenceIndex
const nextStartStr = getOccurrenceDate(snapshot, occurrenceIndex + 1, DEFAULT_TZ) const nextStartStr = getOccurrenceDate(snapshot, occurrenceIndex + 1, DEFAULT_TZ)
if (!nextStartStr) return if (!nextStartStr) return
const durationDays = Math.max(
0,
differenceInCalendarDays(
fromLocalString(snapshot.endDate),
fromLocalString(snapshot.startDate),
),
)
const newEndStr = toLocalString(addDays(fromLocalString(nextStartStr), durationDays))
const originalNumeric = const originalNumeric =
snapshot.recur.count === 'unlimited' ? Infinity : parseInt(snapshot.recur.count, 10) snapshot.recur.count === 'unlimited' ? Infinity : parseInt(snapshot.recur.count, 10)
let remainingCount = 'unlimited' let remainingCount = 'unlimited'
@ -253,7 +242,7 @@ export const useCalendarStore = defineStore('calendar', {
this.createEvent({ this.createEvent({
title: snapshot.title, title: snapshot.title,
startDate: nextStartStr, startDate: nextStartStr,
endDate: newEndStr, days: snapshot.days,
colorId: snapshot.colorId, colorId: snapshot.colorId,
recur: snapshot.recur recur: snapshot.recur
? { ? {
@ -283,8 +272,7 @@ export const useCalendarStore = defineStore('calendar', {
const snapshot = this.events.get(eventId) const snapshot = this.events.get(eventId)
if (!snapshot) return if (!snapshot) return
const prevStart = fromLocalString(snapshot.startDate, DEFAULT_TZ) const prevStart = fromLocalString(snapshot.startDate, DEFAULT_TZ)
const prevEnd = fromLocalString(snapshot.endDate, DEFAULT_TZ) const prevDurationDays = (snapshot.days || 1) - 1
const prevDurationDays = Math.max(0, differenceInCalendarDays(prevEnd, prevStart))
const newStart = fromLocalString(newStartStr, DEFAULT_TZ) const newStart = fromLocalString(newStartStr, DEFAULT_TZ)
const newEnd = fromLocalString(newEndStr, DEFAULT_TZ) const newEnd = fromLocalString(newEndStr, DEFAULT_TZ)
const proposedDurationDays = Math.max(0, differenceInCalendarDays(newEnd, newStart)) const proposedDurationDays = Math.max(0, differenceInCalendarDays(newEnd, newStart))
@ -292,10 +280,7 @@ export const useCalendarStore = defineStore('calendar', {
if (mode === 'resize-left' || mode === 'resize-right') if (mode === 'resize-left' || mode === 'resize-right')
finalDurationDays = proposedDurationDays finalDurationDays = proposedDurationDays
snapshot.startDate = newStartStr snapshot.startDate = newStartStr
snapshot.endDate = toLocalString( snapshot.days = finalDurationDays + 1
addDays(fromLocalString(newStartStr, DEFAULT_TZ), finalDurationDays),
DEFAULT_TZ,
)
if ( if (
rotatePattern && rotatePattern &&
(mode === 'move' || mode === 'resize-left') && (mode === 'move' || mode === 'resize-left') &&
@ -310,7 +295,7 @@ export const useCalendarStore = defineStore('calendar', {
snapshot.recur.weekdays = this._rotateWeekdayPattern(snapshot.recur.weekdays, shift) snapshot.recur.weekdays = this._rotateWeekdayPattern(snapshot.recur.weekdays, shift)
} }
} }
this.events.set(eventId, { ...snapshot, isSpanning: snapshot.startDate < snapshot.endDate }) this.events.set(eventId, { ...snapshot, isSpanning: snapshot.days > 1 })
this.notifyEventsChanged() this.notifyEventsChanged()
}, },
@ -398,7 +383,7 @@ export const useCalendarStore = defineStore('calendar', {
const newId = this.createEvent({ const newId = this.createEvent({
title: base.title, title: base.title,
startDate: newStartStr, startDate: newStartStr,
endDate: newEndStr, days: base.days,
colorId: base.colorId, colorId: base.colorId,
recur: { recur: {
freq: base.recur.freq, freq: base.recur.freq,
@ -430,7 +415,7 @@ export const useCalendarStore = defineStore('calendar', {
return newId return newId
}, },
splitRepeatSeries(baseId, occurrenceIndex, newStartStr, newEndStr) { splitRepeatSeries(baseId, occurrenceIndex, newStartStr, _newEndStr) {
const base = this.events.get(baseId) const base = this.events.get(baseId)
if (!base || !base.recur) return null if (!base || !base.recur) return null
const originalCountRaw = base.recur.count const originalCountRaw = base.recur.count
@ -446,7 +431,7 @@ export const useCalendarStore = defineStore('calendar', {
return this.createEvent({ return this.createEvent({
title: base.title, title: base.title,
startDate: newStartStr, startDate: newStartStr,
endDate: newEndStr, days: base.days,
colorId: base.colorId, colorId: base.colorId,
recur: base.recur recur: base.recur
? { ? {
@ -484,11 +469,12 @@ export const useCalendarStore = defineStore('calendar', {
}) })
}, },
deserialize(value) { deserialize(value) {
return JSON.parse(value, (_k, v) => { const revived = JSON.parse(value, (_k, v) => {
if (v && v.__map) return new Map(v.data) if (v && v.__map) return new Map(v.data)
if (v && v.__set) return new Set(v.data) if (v && v.__set) return new Set(v.data)
return v return v
}) })
return revived
}, },
}, },
}, },

View File

@ -23,9 +23,8 @@ const monthAbbr = [
'nov', 'nov',
'dec', 'dec',
] ]
// Calendar year bounds (used instead of config.min_year / config.max_year) const MIN_YEAR = 100 // less than 100 is interpreted as 19xx
const MIN_YEAR = 1901 const MAX_YEAR = 9999
const MAX_YEAR = 2100
// Core helpers ------------------------------------------------------------ // Core helpers ------------------------------------------------------------
/** /**
@ -230,9 +229,7 @@ function getOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ) {
} }
function getVirtualOccurrenceEndDate(event, occurrenceStartDate, timeZone = DEFAULT_TZ) { function getVirtualOccurrenceEndDate(event, occurrenceStartDate, timeZone = DEFAULT_TZ) {
const baseStart = fromLocalString(event.startDate, timeZone) const spanDays = Math.max(0, (event.days || 1) - 1)
const baseEnd = fromLocalString(event.endDate, timeZone)
const spanDays = Math.max(0, dateFns.differenceInCalendarDays(baseEnd, baseStart))
const occurrenceStart = fromLocalString(occurrenceStartDate, timeZone) const occurrenceStart = fromLocalString(occurrenceStartDate, timeZone)
return toLocalString(dateFns.addDays(occurrenceStart, spanDays), timeZone) return toLocalString(dateFns.addDays(occurrenceStart, spanDays), timeZone)
} }