Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend Ionic2-Calender to display recurring events. #682

Open
wants to merge 3 commits into
base: ionic8
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"build": "rm -rf dist && ng build"
},
"dependencies": {
"moment-recur-ts": "^1.3.1",
"tslib": "^2.3.0"
},
"peerDependencies": {
Expand Down
13 changes: 13 additions & 0 deletions src/calendar.interface.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import { EventEmitter, TemplateRef } from '@angular/core';
import 'moment-recur-ts'
import { Rule } from 'moment-recur-ts';

export interface IOccurence {
eventUTCStartTime: number;
eventUTCEndTime: number;
}

export interface IOccurences {

}
export interface IEvent {
allDay: boolean;
endTime: Date;
startTime: Date;
title: string;
category?: string;
rruleFreq?: Rule.MeasureInput;
rruleInterval?: Rule.UnitsInput;
rruleUntil?: Date;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of introducing a separate rruleUntil field on IEvent, could we just leverage the existing endTime field? Basically startTime and endTime field determine the total period of recurring event, rruleFreq and rruleInterval determine what's the pattern of the recurring event.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think it is a good idea. startTime and endTime determinates the start and end time of the event.
By recurring events only the time counts not the date.
If I want to create a recurring event for all thusday 13:00 - 14:00 until 2/11/2025 I need all 3 fields: startTime endTime and rruleUntil also. Only for allDay == true events can you ignore endTime

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if endTime is needed, maybe I didn't get the logic in below code. eventUTCEndTime is only set based on eventUTCStartTime.
eventUTCEndTime: eventUTCStartTime + 86400000

for( let match of moment(event.startTime.getDate()).recur(utcStartTime,utcEndTime).every(event.rruleInterval, event.rruleFreq).all('L') ){
            let matchDate = new Date(match)
            let eventUTCStartTime = Date.UTC(matchDate.getFullYear(), matchDate.getMonth(), matchDate.getDate())
            occurences.push({
                eventUTCStartTime: eventUTCStartTime,
                eventUTCEndTime: eventUTCStartTime + 86400000
            })
        }

Copy link
Author

@petervarkoly petervarkoly Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moment(eventTime).recure(start,end) delivers the list of dates when an event recures.
So eventUTCStartTime is not the start time of the event but the start time of the day on which the event happens. That is why I've selected as eventUTCEndTime the end time of this day. May be adding 86399999 is correct. ( I'm a chemist not a mathematician :-)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense

}

export interface IRange {
Expand Down
45 changes: 42 additions & 3 deletions src/calendar.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import * as moment from 'moment'
import 'moment-recur-ts'

import {ICalendarComponent, IView, CalendarMode, QueryMode} from './calendar.interface';
import { ICalendarComponent, IView, CalendarMode, QueryMode, IEvent, IOccurence } from './calendar.interface';

@Injectable()
export class CalendarService {
Expand Down Expand Up @@ -159,4 +161,41 @@ export class CalendarService {
update() {
this.slideUpdated.next();
}

getEventOccurences(event: IEvent, utcStartTime: number, utcEndTime: number): IOccurence[] {
let occurences: IOccurence[] = []
if( event.startTime > event.endTime) {
return occurences
}
if (!event.rruleFreq) {
let eventUTCStartTime: number
let eventUTCEndTime: number
if (event.allDay) {
eventUTCStartTime = event.startTime.getTime()
eventUTCEndTime = event.endTime.getTime()
} else {
eventUTCStartTime = Date.UTC(event.startTime.getFullYear(), event.startTime.getMonth(), event.startTime.getDate())
eventUTCEndTime = Date.UTC(event.endTime.getFullYear(), event.endTime.getMonth(), event.endTime.getDate() + 1)
}
if( eventUTCEndTime > utcStartTime && eventUTCStartTime < utcEndTime ) {
occurences.push({
eventUTCStartTime: eventUTCStartTime,
eventUTCEndTime: eventUTCEndTime
})
}
return occurences
}
if(event.rruleUntil && event.rruleUntil.getTime() < utcStartTime ){
return occurences;
}
for( let match of moment(event.startTime.getDate()).recur(utcStartTime,utcEndTime).every(event.rruleInterval, event.rruleFreq).all('L') ){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems not correct, the range should be

moment().recur(max(utcStartTime, event.startTime.getDate(), min(utcEndTime, event.rruleUntil.getDate())).every(xxxxx)

Also instead of introducing a separate rruleUntil field on IEvent, could we just leverage the existing endTime field? Basically startTime and endTime field determine the total period of recurring event, rruleFreq and rruleInterval determine what's the pattern of the recurring event.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you fix this logic? utcStartTime and utcEndTime is the range, for example, month start and month end. But the recurring event could start and end in the middle of the month. So it should be like this:

moment().recur(max(utcStartTime, event.startTime.getDate()), min(utcEndTime, event.rruleUntil.getDate())).every(xxxxx)

let matchDate = new Date(match)
let eventUTCStartTime = Date.UTC(matchDate.getFullYear(), matchDate.getMonth(), matchDate.getDate())
occurences.push({
eventUTCStartTime: eventUTCStartTime,
eventUTCEndTime: eventUTCStartTime + 86399999
})
}
return occurences
}
}
166 changes: 75 additions & 91 deletions src/dayview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { CalendarService } from './calendar.service';
encapsulation: ViewEncapsulation.None
})
export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges, OnDestroy, AfterViewInit {
constructor(private calendarService: CalendarService, private elm: ElementRef, private zone: NgZone) {}
constructor(private calendarService: CalendarService, private elm: ElementRef, private zone: NgZone) { }

private slider!: Swiper;
@ViewChild('dayViewSwiper') swiperElement?: ElementRef;
Expand Down Expand Up @@ -413,111 +413,95 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges,

for (let i = 0; i < len; i += 1) {
const event = eventSource[i];
const eventStartTime = event.startTime;
const eventEndTime = event.endTime;
let eventUTCStartTime: number, eventUTCEndTime: number;
for (let occurence of this.calendarService.getEventOccurences(event, utcStartTime, utcEndTime)) {
let eventUTCStartTime = occurence.eventUTCStartTime
let eventUTCEndTime = occurence.eventUTCEndTime

if (event.allDay) {
eventUTCStartTime = eventStartTime.getTime();
eventUTCEndTime = eventEndTime.getTime();
} else {
eventUTCStartTime = Date.UTC(
eventStartTime.getFullYear(),
eventStartTime.getMonth(),
eventStartTime.getDate()
);
eventUTCEndTime = Date.UTC(
eventEndTime.getFullYear(),
eventEndTime.getMonth(),
eventEndTime.getDate() + 1
);
}

if (eventUTCEndTime <= utcStartTime || eventUTCStartTime >= utcEndTime || eventStartTime >= eventEndTime) {
continue;
}

if (event.allDay) {
allDayEvents.push({
event
});
} else {
normalEventInRange = true;

let timeDifferenceStart: number;
if (eventUTCStartTime < utcStartTime) {
timeDifferenceStart = 0;
} else {
timeDifferenceStart =
(eventStartTime.getHours() + eventStartTime.getMinutes() / 60) * this.hourSegments;
}
const eventStartTime = event.startTime;
const eventEndTime = event.endTime;

let timeDifferenceEnd: number;
if (eventUTCEndTime > utcEndTime) {
timeDifferenceEnd = ((utcEndTime - utcStartTime) / oneHour) * this.hourSegments;
if (event.allDay) {
allDayEvents.push({
event
});
} else {
timeDifferenceEnd = (eventEndTime.getHours() + eventEndTime.getMinutes() / 60) * this.hourSegments;
}
normalEventInRange = true;

let startIndex = Math.floor(timeDifferenceStart);
let endIndex = Math.ceil(timeDifferenceEnd - eps);
let startOffset = 0;
let endOffset = 0;
if (this.hourParts !== 1) {
if (startIndex < rangeStartRowIndex) {
startOffset = 0;
let timeDifferenceStart: number;
if (eventUTCStartTime < utcStartTime) {
timeDifferenceStart = 0;
} else {
startOffset = Math.floor((timeDifferenceStart - startIndex) * this.hourParts);
timeDifferenceStart =
(eventStartTime.getHours() + eventStartTime.getMinutes() / 60) * this.hourSegments;
}
if (endIndex > rangeEndRowIndex) {
endOffset = 0;

let timeDifferenceEnd: number;
if (eventUTCEndTime > utcEndTime) {
timeDifferenceEnd = ((utcEndTime - utcStartTime) / oneHour) * this.hourSegments;
} else {
endOffset = Math.floor((endIndex - timeDifferenceEnd) * this.hourParts);
timeDifferenceEnd = (eventEndTime.getHours() + eventEndTime.getMinutes() / 60) * this.hourSegments;
}
}

if (startIndex < rangeStartRowIndex) {
startIndex = 0;
} else {
startIndex -= rangeStartRowIndex;
}
if (endIndex > rangeEndRowIndex) {
endIndex = rangeEndRowIndex;
}
endIndex -= rangeStartRowIndex;

if (startIndex < endIndex) {
const displayEvent: IDisplayEvent = {
event,
startIndex,
endIndex,
startOffset,
endOffset,
position: 0
};

let eventSet = rows[startIndex].events;
if (eventSet) {
eventSet.push(displayEvent);
let startIndex = Math.floor(timeDifferenceStart);
let endIndex = Math.ceil(timeDifferenceEnd - eps);
let startOffset = 0;
let endOffset = 0;
if (this.hourParts !== 1) {
if (startIndex < rangeStartRowIndex) {
startOffset = 0;
} else {
startOffset = Math.floor((timeDifferenceStart - startIndex) * this.hourParts);
}
if (endIndex > rangeEndRowIndex) {
endOffset = 0;
} else {
endOffset = Math.floor((endIndex - timeDifferenceEnd) * this.hourParts);
}
}

if (startIndex < rangeStartRowIndex) {
startIndex = 0;
} else {
eventSet = [];
eventSet.push(displayEvent);
rows[startIndex].events = eventSet;
startIndex -= rangeStartRowIndex;
}
if (endIndex > rangeEndRowIndex) {
endIndex = rangeEndRowIndex;
}
endIndex -= rangeStartRowIndex;

if (startIndex < endIndex) {
const displayEvent: IDisplayEvent = {
event,
startIndex,
endIndex,
startOffset,
endOffset,
position: 0
};

let eventSet = rows[startIndex].events;
if (eventSet) {
eventSet.push(displayEvent);
} else {
eventSet = [];
eventSet.push(displayEvent);
rows[startIndex].events = eventSet;
}

// setup eventsGroupedByCategory
if (this.dayviewShowCategoryView && this.dayviewCategorySource && event.category) {
let groupedEvents = rows[startIndex].eventsGroupByCategory;
if (groupedEvents) {
if (Array.isArray(groupedEvents.get(event.category))) {
groupedEvents.get(event.category)?.push(displayEvent);
// setup eventsGroupedByCategory
if (this.dayviewShowCategoryView && this.dayviewCategorySource && event.category) {
let groupedEvents = rows[startIndex].eventsGroupByCategory;
if (groupedEvents) {
if (Array.isArray(groupedEvents.get(event.category))) {
groupedEvents.get(event.category)?.push(displayEvent);
} else {
groupedEvents.set(event.category, [displayEvent]);
}
} else {
groupedEvents = new Map();
groupedEvents.set(event.category, [displayEvent]);
rows[startIndex].eventsGroupByCategory = groupedEvents;
}
} else {
groupedEvents = new Map();
groupedEvents.set(event.category, [displayEvent]);
rows[startIndex].eventsGroupByCategory = groupedEvents;
}
}
}
Expand Down
Loading